Skip to content

Commit c240f3a

Browse files
authored
feat: add skip_macro_invocations option (#5347)
* feat: add skip_macro_names option * [review] update configuration documentation * [review] fix docstring * [feat] implement wildcard macro invocation skip * commit missed files * [review] test override skip macro names * [review] skip_macro_names -> skip_macro_invocations * [review] expand doc configuration * [review] add lots more tests * [review] add use alias test examples * [review] add link to standard macro behaviour
1 parent 2403f82 commit c240f3a

27 files changed

+459
-4
lines changed

Diff for: Configurations.md

+56
Original file line numberDiff line numberDiff line change
@@ -1014,6 +1014,62 @@ macro_rules! foo {
10141014

10151015
See also [`format_macro_matchers`](#format_macro_matchers).
10161016

1017+
## `skip_macro_invocations`
1018+
1019+
Skip formatting the bodies of macro invocations with the following names.
1020+
1021+
rustfmt will not format any macro invocation for macros with names set in this list.
1022+
Including the special value "*" will prevent any macro invocations from being formatted.
1023+
1024+
Note: This option does not have any impact on how rustfmt formats macro definitions.
1025+
1026+
- **Default value**: `[]`
1027+
- **Possible values**: a list of macro name idents, `["name_0", "name_1", ..., "*"]`
1028+
- **Stable**: No (tracking issue: [#5346](https://github.com/rust-lang/rustfmt/issues/5346))
1029+
1030+
#### `[]` (default):
1031+
1032+
rustfmt will follow its standard approach to formatting macro invocations.
1033+
1034+
No macro invocations will be skipped based on their name. More information about rustfmt's standard macro invocation formatting behavior can be found in [#5437](https://github.com/rust-lang/rustfmt/discussions/5437).
1035+
1036+
```rust
1037+
lorem!(
1038+
const _: u8 = 0;
1039+
);
1040+
1041+
ipsum!(
1042+
const _: u8 = 0;
1043+
);
1044+
```
1045+
1046+
#### `["lorem"]`:
1047+
1048+
The named macro invocations will be skipped.
1049+
1050+
```rust
1051+
lorem!(
1052+
const _: u8 = 0;
1053+
);
1054+
1055+
ipsum!(
1056+
const _: u8 = 0;
1057+
);
1058+
```
1059+
1060+
#### `["*"]`:
1061+
1062+
The special selector `*` will skip all macro invocations.
1063+
1064+
```rust
1065+
lorem!(
1066+
const _: u8 = 0;
1067+
);
1068+
1069+
ipsum!(
1070+
const _: u8 = 0;
1071+
);
1072+
```
10171073

10181074
## `format_strings`
10191075

Diff for: src/config/config_type.rs

+7
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use crate::config::file_lines::FileLines;
2+
use crate::config::macro_names::MacroSelectors;
23
use crate::config::options::{IgnoreList, WidthHeuristics};
34

45
/// Trait for types that can be used in `Config`.
@@ -46,6 +47,12 @@ impl ConfigType for FileLines {
4647
}
4748
}
4849

50+
impl ConfigType for MacroSelectors {
51+
fn doc_hint() -> String {
52+
String::from("[<string>, ...]")
53+
}
54+
}
55+
4956
impl ConfigType for WidthHeuristics {
5057
fn doc_hint() -> String {
5158
String::new()

Diff for: src/config/macro_names.rs

+118
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
//! This module contains types and functions to support formatting specific macros.
2+
3+
use itertools::Itertools;
4+
use std::{fmt, str};
5+
6+
use serde::{Deserialize, Serialize};
7+
use serde_json as json;
8+
use thiserror::Error;
9+
10+
/// Defines the name of a macro.
11+
#[derive(Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd, Deserialize, Serialize)]
12+
pub struct MacroName(String);
13+
14+
impl MacroName {
15+
pub fn new(other: String) -> Self {
16+
Self(other)
17+
}
18+
}
19+
20+
impl fmt::Display for MacroName {
21+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
22+
self.0.fmt(f)
23+
}
24+
}
25+
26+
impl From<MacroName> for String {
27+
fn from(other: MacroName) -> Self {
28+
other.0
29+
}
30+
}
31+
32+
/// Defines a selector to match against a macro.
33+
#[derive(Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd, Deserialize, Serialize)]
34+
pub enum MacroSelector {
35+
Name(MacroName),
36+
All,
37+
}
38+
39+
impl fmt::Display for MacroSelector {
40+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
41+
match self {
42+
Self::Name(name) => name.fmt(f),
43+
Self::All => write!(f, "*"),
44+
}
45+
}
46+
}
47+
48+
impl str::FromStr for MacroSelector {
49+
type Err = std::convert::Infallible;
50+
51+
fn from_str(s: &str) -> Result<Self, Self::Err> {
52+
Ok(match s {
53+
"*" => MacroSelector::All,
54+
name => MacroSelector::Name(MacroName(name.to_owned())),
55+
})
56+
}
57+
}
58+
59+
/// A set of macro selectors.
60+
#[derive(Clone, Debug, Default, PartialEq, Deserialize, Serialize)]
61+
pub struct MacroSelectors(pub Vec<MacroSelector>);
62+
63+
impl fmt::Display for MacroSelectors {
64+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
65+
write!(f, "{}", self.0.iter().format(", "))
66+
}
67+
}
68+
69+
#[derive(Error, Debug)]
70+
pub enum MacroSelectorsError {
71+
#[error("{0}")]
72+
Json(json::Error),
73+
}
74+
75+
// This impl is needed for `Config::override_value` to work for use in tests.
76+
impl str::FromStr for MacroSelectors {
77+
type Err = MacroSelectorsError;
78+
79+
fn from_str(s: &str) -> Result<Self, Self::Err> {
80+
let raw: Vec<&str> = json::from_str(s).map_err(MacroSelectorsError::Json)?;
81+
Ok(Self(
82+
raw.into_iter()
83+
.map(|raw| {
84+
MacroSelector::from_str(raw).expect("MacroSelector from_str is infallible")
85+
})
86+
.collect(),
87+
))
88+
}
89+
}
90+
91+
#[cfg(test)]
92+
mod test {
93+
use super::*;
94+
use std::str::FromStr;
95+
96+
#[test]
97+
fn macro_names_from_str() {
98+
let macro_names = MacroSelectors::from_str(r#"["foo", "*", "bar"]"#).unwrap();
99+
assert_eq!(
100+
macro_names,
101+
MacroSelectors(
102+
[
103+
MacroSelector::Name(MacroName("foo".to_owned())),
104+
MacroSelector::All,
105+
MacroSelector::Name(MacroName("bar".to_owned()))
106+
]
107+
.into_iter()
108+
.collect()
109+
)
110+
);
111+
}
112+
113+
#[test]
114+
fn macro_names_display() {
115+
let macro_names = MacroSelectors::from_str(r#"["foo", "*", "bar"]"#).unwrap();
116+
assert_eq!(format!("{}", macro_names), "foo, *, bar");
117+
}
118+
}

Diff for: src/config/mod.rs

+20
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ pub use crate::config::file_lines::{FileLines, FileName, Range};
1313
#[allow(unreachable_pub)]
1414
pub use crate::config::lists::*;
1515
#[allow(unreachable_pub)]
16+
pub use crate::config::macro_names::{MacroSelector, MacroSelectors};
17+
#[allow(unreachable_pub)]
1618
pub use crate::config::options::*;
1719

1820
#[macro_use]
@@ -22,6 +24,7 @@ pub(crate) mod options;
2224

2325
pub(crate) mod file_lines;
2426
pub(crate) mod lists;
27+
pub(crate) mod macro_names;
2528

2629
// This macro defines configuration options used in rustfmt. Each option
2730
// is defined as follows:
@@ -67,6 +70,8 @@ create_config! {
6770
format_macro_matchers: bool, false, false,
6871
"Format the metavariable matching patterns in macros";
6972
format_macro_bodies: bool, true, false, "Format the bodies of macros";
73+
skip_macro_invocations: MacroSelectors, MacroSelectors::default(), false,
74+
"Skip formatting the bodies of macros invoked with the following names.";
7075
hex_literal_case: HexLiteralCase, HexLiteralCase::Preserve, false,
7176
"Format hexadecimal integer literals";
7277

@@ -403,6 +408,7 @@ mod test {
403408
use super::*;
404409
use std::str;
405410

411+
use crate::config::macro_names::MacroName;
406412
use rustfmt_config_proc_macro::{nightly_only_test, stable_only_test};
407413

408414
#[allow(dead_code)]
@@ -611,6 +617,7 @@ normalize_doc_attributes = false
611617
format_strings = false
612618
format_macro_matchers = false
613619
format_macro_bodies = true
620+
skip_macro_invocations = []
614621
hex_literal_case = "Preserve"
615622
empty_item_single_line = true
616623
struct_lit_single_line = true
@@ -1019,4 +1026,17 @@ make_backup = false
10191026
);
10201027
}
10211028
}
1029+
1030+
#[test]
1031+
fn test_override_skip_macro_invocations() {
1032+
let mut config = Config::default();
1033+
config.override_value("skip_macro_invocations", r#"["*", "println"]"#);
1034+
assert_eq!(
1035+
config.skip_macro_invocations(),
1036+
MacroSelectors(vec![
1037+
MacroSelector::All,
1038+
MacroSelector::Name(MacroName::new("println".to_owned()))
1039+
])
1040+
);
1041+
}
10221042
}

Diff for: src/skip.rs

+9-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use rustc_ast_pretty::pprust;
77
/// by other context. Query this context to know if you need skip a block.
88
#[derive(Default, Clone)]
99
pub(crate) struct SkipContext {
10+
pub(crate) all_macros: bool,
1011
macros: Vec<String>,
1112
attributes: Vec<String>,
1213
}
@@ -23,8 +24,15 @@ impl SkipContext {
2324
self.attributes.append(&mut other.attributes);
2425
}
2526

27+
pub(crate) fn update_macros<T>(&mut self, other: T)
28+
where
29+
T: IntoIterator<Item = String>,
30+
{
31+
self.macros.extend(other.into_iter());
32+
}
33+
2634
pub(crate) fn skip_macro(&self, name: &str) -> bool {
27-
self.macros.iter().any(|n| n == name)
35+
self.all_macros || self.macros.iter().any(|n| n == name)
2836
}
2937

3038
pub(crate) fn skip_attribute(&self, name: &str) -> bool {

Diff for: src/test/configuration_snippet.rs

+6-1
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,13 @@ impl ConfigurationSection {
2727
lazy_static! {
2828
static ref CONFIG_NAME_REGEX: regex::Regex =
2929
regex::Regex::new(r"^## `([^`]+)`").expect("failed creating configuration pattern");
30+
// Configuration values, which will be passed to `from_str`:
31+
//
32+
// - must be prefixed with `####`
33+
// - must be wrapped in backticks
34+
// - may by wrapped in double quotes (which will be stripped)
3035
static ref CONFIG_VALUE_REGEX: regex::Regex =
31-
regex::Regex::new(r#"^#### `"?([^`"]+)"?`"#)
36+
regex::Regex::new(r#"^#### `"?([^`]+?)"?`"#)
3237
.expect("failed creating configuration value pattern");
3338
}
3439

Diff for: src/visitor.rs

+11-2
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use rustc_span::{symbol, BytePos, Pos, Span};
88
use crate::attr::*;
99
use crate::comment::{contains_comment, rewrite_comment, CodeCharKind, CommentCodeSlices};
1010
use crate::config::Version;
11-
use crate::config::{BraceStyle, Config};
11+
use crate::config::{BraceStyle, Config, MacroSelector};
1212
use crate::coverage::transform_missing_snippet;
1313
use crate::items::{
1414
format_impl, format_trait, format_trait_alias, is_mod_decl, is_use_item, rewrite_extern_crate,
@@ -770,6 +770,15 @@ impl<'b, 'a: 'b> FmtVisitor<'a> {
770770
snippet_provider: &'a SnippetProvider,
771771
report: FormatReport,
772772
) -> FmtVisitor<'a> {
773+
let mut skip_context = SkipContext::default();
774+
let mut macro_names = Vec::new();
775+
for macro_selector in config.skip_macro_invocations().0 {
776+
match macro_selector {
777+
MacroSelector::Name(name) => macro_names.push(name.to_string()),
778+
MacroSelector::All => skip_context.all_macros = true,
779+
}
780+
}
781+
skip_context.update_macros(macro_names);
773782
FmtVisitor {
774783
parent_context: None,
775784
parse_sess: parse_session,
@@ -784,7 +793,7 @@ impl<'b, 'a: 'b> FmtVisitor<'a> {
784793
is_macro_def: false,
785794
macro_rewrite_failure: false,
786795
report,
787-
skip_context: Default::default(),
796+
skip_context,
788797
}
789798
}
790799

Diff for: tests/source/skip_macro_invocations/all.rs

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// rustfmt-skip_macro_invocations: ["*"]
2+
3+
// Should skip this invocation
4+
items!(
5+
const _: u8 = 0;
6+
);
7+
8+
// Should skip this invocation
9+
renamed_items!(
10+
const _: u8 = 0;
11+
);

Diff for: tests/source/skip_macro_invocations/all_and_name.rs

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// rustfmt-skip_macro_invocations: ["*","items"]
2+
3+
// Should skip this invocation
4+
items!(
5+
const _: u8 = 0;
6+
);
7+
8+
// Should also skip this invocation, as the wildcard covers it
9+
renamed_items!(
10+
const _: u8 = 0;
11+
);

Diff for: tests/source/skip_macro_invocations/empty.rs

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// rustfmt-skip_macro_invocations: []
2+
3+
// Should not skip this invocation
4+
items!(
5+
const _: u8 = 0;
6+
);
7+
8+
// Should not skip this invocation
9+
renamed_items!(
10+
const _: u8 = 0;
11+
);

Diff for: tests/source/skip_macro_invocations/name.rs

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// rustfmt-skip_macro_invocations: ["items"]
2+
3+
// Should skip this invocation
4+
items!(
5+
const _: u8 = 0;
6+
);
7+
8+
// Should not skip this invocation
9+
renamed_items!(
10+
const _: u8 = 0;
11+
);

Diff for: tests/source/skip_macro_invocations/name_unknown.rs

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// rustfmt-skip_macro_invocations: ["unknown"]
2+
3+
// Should not skip this invocation
4+
items!(
5+
const _: u8 = 0;
6+
);

0 commit comments

Comments
 (0)