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
This commit is contained in:
parent
2403f827bf
commit
c240f3a6b3
@ -1014,6 +1014,62 @@ macro_rules! foo {
|
||||
|
||||
See also [`format_macro_matchers`](#format_macro_matchers).
|
||||
|
||||
## `skip_macro_invocations`
|
||||
|
||||
Skip formatting the bodies of macro invocations with the following names.
|
||||
|
||||
rustfmt will not format any macro invocation for macros with names set in this list.
|
||||
Including the special value "*" will prevent any macro invocations from being formatted.
|
||||
|
||||
Note: This option does not have any impact on how rustfmt formats macro definitions.
|
||||
|
||||
- **Default value**: `[]`
|
||||
- **Possible values**: a list of macro name idents, `["name_0", "name_1", ..., "*"]`
|
||||
- **Stable**: No (tracking issue: [#5346](https://github.com/rust-lang/rustfmt/issues/5346))
|
||||
|
||||
#### `[]` (default):
|
||||
|
||||
rustfmt will follow its standard approach to formatting macro invocations.
|
||||
|
||||
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).
|
||||
|
||||
```rust
|
||||
lorem!(
|
||||
const _: u8 = 0;
|
||||
);
|
||||
|
||||
ipsum!(
|
||||
const _: u8 = 0;
|
||||
);
|
||||
```
|
||||
|
||||
#### `["lorem"]`:
|
||||
|
||||
The named macro invocations will be skipped.
|
||||
|
||||
```rust
|
||||
lorem!(
|
||||
const _: u8 = 0;
|
||||
);
|
||||
|
||||
ipsum!(
|
||||
const _: u8 = 0;
|
||||
);
|
||||
```
|
||||
|
||||
#### `["*"]`:
|
||||
|
||||
The special selector `*` will skip all macro invocations.
|
||||
|
||||
```rust
|
||||
lorem!(
|
||||
const _: u8 = 0;
|
||||
);
|
||||
|
||||
ipsum!(
|
||||
const _: u8 = 0;
|
||||
);
|
||||
```
|
||||
|
||||
## `format_strings`
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
use crate::config::file_lines::FileLines;
|
||||
use crate::config::macro_names::MacroSelectors;
|
||||
use crate::config::options::{IgnoreList, WidthHeuristics};
|
||||
|
||||
/// Trait for types that can be used in `Config`.
|
||||
@ -46,6 +47,12 @@ impl ConfigType for FileLines {
|
||||
}
|
||||
}
|
||||
|
||||
impl ConfigType for MacroSelectors {
|
||||
fn doc_hint() -> String {
|
||||
String::from("[<string>, ...]")
|
||||
}
|
||||
}
|
||||
|
||||
impl ConfigType for WidthHeuristics {
|
||||
fn doc_hint() -> String {
|
||||
String::new()
|
||||
|
118
src/config/macro_names.rs
Normal file
118
src/config/macro_names.rs
Normal file
@ -0,0 +1,118 @@
|
||||
//! This module contains types and functions to support formatting specific macros.
|
||||
|
||||
use itertools::Itertools;
|
||||
use std::{fmt, str};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json as json;
|
||||
use thiserror::Error;
|
||||
|
||||
/// Defines the name of a macro.
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd, Deserialize, Serialize)]
|
||||
pub struct MacroName(String);
|
||||
|
||||
impl MacroName {
|
||||
pub fn new(other: String) -> Self {
|
||||
Self(other)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for MacroName {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
self.0.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<MacroName> for String {
|
||||
fn from(other: MacroName) -> Self {
|
||||
other.0
|
||||
}
|
||||
}
|
||||
|
||||
/// Defines a selector to match against a macro.
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd, Deserialize, Serialize)]
|
||||
pub enum MacroSelector {
|
||||
Name(MacroName),
|
||||
All,
|
||||
}
|
||||
|
||||
impl fmt::Display for MacroSelector {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Name(name) => name.fmt(f),
|
||||
Self::All => write!(f, "*"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl str::FromStr for MacroSelector {
|
||||
type Err = std::convert::Infallible;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
Ok(match s {
|
||||
"*" => MacroSelector::All,
|
||||
name => MacroSelector::Name(MacroName(name.to_owned())),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// A set of macro selectors.
|
||||
#[derive(Clone, Debug, Default, PartialEq, Deserialize, Serialize)]
|
||||
pub struct MacroSelectors(pub Vec<MacroSelector>);
|
||||
|
||||
impl fmt::Display for MacroSelectors {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self.0.iter().format(", "))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum MacroSelectorsError {
|
||||
#[error("{0}")]
|
||||
Json(json::Error),
|
||||
}
|
||||
|
||||
// This impl is needed for `Config::override_value` to work for use in tests.
|
||||
impl str::FromStr for MacroSelectors {
|
||||
type Err = MacroSelectorsError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let raw: Vec<&str> = json::from_str(s).map_err(MacroSelectorsError::Json)?;
|
||||
Ok(Self(
|
||||
raw.into_iter()
|
||||
.map(|raw| {
|
||||
MacroSelector::from_str(raw).expect("MacroSelector from_str is infallible")
|
||||
})
|
||||
.collect(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use std::str::FromStr;
|
||||
|
||||
#[test]
|
||||
fn macro_names_from_str() {
|
||||
let macro_names = MacroSelectors::from_str(r#"["foo", "*", "bar"]"#).unwrap();
|
||||
assert_eq!(
|
||||
macro_names,
|
||||
MacroSelectors(
|
||||
[
|
||||
MacroSelector::Name(MacroName("foo".to_owned())),
|
||||
MacroSelector::All,
|
||||
MacroSelector::Name(MacroName("bar".to_owned()))
|
||||
]
|
||||
.into_iter()
|
||||
.collect()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn macro_names_display() {
|
||||
let macro_names = MacroSelectors::from_str(r#"["foo", "*", "bar"]"#).unwrap();
|
||||
assert_eq!(format!("{}", macro_names), "foo, *, bar");
|
||||
}
|
||||
}
|
@ -13,6 +13,8 @@ pub use crate::config::file_lines::{FileLines, FileName, Range};
|
||||
#[allow(unreachable_pub)]
|
||||
pub use crate::config::lists::*;
|
||||
#[allow(unreachable_pub)]
|
||||
pub use crate::config::macro_names::{MacroSelector, MacroSelectors};
|
||||
#[allow(unreachable_pub)]
|
||||
pub use crate::config::options::*;
|
||||
|
||||
#[macro_use]
|
||||
@ -22,6 +24,7 @@ pub(crate) mod options;
|
||||
|
||||
pub(crate) mod file_lines;
|
||||
pub(crate) mod lists;
|
||||
pub(crate) mod macro_names;
|
||||
|
||||
// This macro defines configuration options used in rustfmt. Each option
|
||||
// is defined as follows:
|
||||
@ -67,6 +70,8 @@ create_config! {
|
||||
format_macro_matchers: bool, false, false,
|
||||
"Format the metavariable matching patterns in macros";
|
||||
format_macro_bodies: bool, true, false, "Format the bodies of macros";
|
||||
skip_macro_invocations: MacroSelectors, MacroSelectors::default(), false,
|
||||
"Skip formatting the bodies of macros invoked with the following names.";
|
||||
hex_literal_case: HexLiteralCase, HexLiteralCase::Preserve, false,
|
||||
"Format hexadecimal integer literals";
|
||||
|
||||
@ -403,6 +408,7 @@ mod test {
|
||||
use super::*;
|
||||
use std::str;
|
||||
|
||||
use crate::config::macro_names::MacroName;
|
||||
use rustfmt_config_proc_macro::{nightly_only_test, stable_only_test};
|
||||
|
||||
#[allow(dead_code)]
|
||||
@ -611,6 +617,7 @@ normalize_doc_attributes = false
|
||||
format_strings = false
|
||||
format_macro_matchers = false
|
||||
format_macro_bodies = true
|
||||
skip_macro_invocations = []
|
||||
hex_literal_case = "Preserve"
|
||||
empty_item_single_line = true
|
||||
struct_lit_single_line = true
|
||||
@ -1019,4 +1026,17 @@ make_backup = false
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_override_skip_macro_invocations() {
|
||||
let mut config = Config::default();
|
||||
config.override_value("skip_macro_invocations", r#"["*", "println"]"#);
|
||||
assert_eq!(
|
||||
config.skip_macro_invocations(),
|
||||
MacroSelectors(vec![
|
||||
MacroSelector::All,
|
||||
MacroSelector::Name(MacroName::new("println".to_owned()))
|
||||
])
|
||||
);
|
||||
}
|
||||
}
|
||||
|
10
src/skip.rs
10
src/skip.rs
@ -7,6 +7,7 @@ use rustc_ast_pretty::pprust;
|
||||
/// by other context. Query this context to know if you need skip a block.
|
||||
#[derive(Default, Clone)]
|
||||
pub(crate) struct SkipContext {
|
||||
pub(crate) all_macros: bool,
|
||||
macros: Vec<String>,
|
||||
attributes: Vec<String>,
|
||||
}
|
||||
@ -23,8 +24,15 @@ impl SkipContext {
|
||||
self.attributes.append(&mut other.attributes);
|
||||
}
|
||||
|
||||
pub(crate) fn update_macros<T>(&mut self, other: T)
|
||||
where
|
||||
T: IntoIterator<Item = String>,
|
||||
{
|
||||
self.macros.extend(other.into_iter());
|
||||
}
|
||||
|
||||
pub(crate) fn skip_macro(&self, name: &str) -> bool {
|
||||
self.macros.iter().any(|n| n == name)
|
||||
self.all_macros || self.macros.iter().any(|n| n == name)
|
||||
}
|
||||
|
||||
pub(crate) fn skip_attribute(&self, name: &str) -> bool {
|
||||
|
@ -27,8 +27,13 @@ impl ConfigurationSection {
|
||||
lazy_static! {
|
||||
static ref CONFIG_NAME_REGEX: regex::Regex =
|
||||
regex::Regex::new(r"^## `([^`]+)`").expect("failed creating configuration pattern");
|
||||
// Configuration values, which will be passed to `from_str`:
|
||||
//
|
||||
// - must be prefixed with `####`
|
||||
// - must be wrapped in backticks
|
||||
// - may by wrapped in double quotes (which will be stripped)
|
||||
static ref CONFIG_VALUE_REGEX: regex::Regex =
|
||||
regex::Regex::new(r#"^#### `"?([^`"]+)"?`"#)
|
||||
regex::Regex::new(r#"^#### `"?([^`]+?)"?`"#)
|
||||
.expect("failed creating configuration value pattern");
|
||||
}
|
||||
|
||||
|
@ -8,7 +8,7 @@ use rustc_span::{symbol, BytePos, Pos, Span};
|
||||
use crate::attr::*;
|
||||
use crate::comment::{contains_comment, rewrite_comment, CodeCharKind, CommentCodeSlices};
|
||||
use crate::config::Version;
|
||||
use crate::config::{BraceStyle, Config};
|
||||
use crate::config::{BraceStyle, Config, MacroSelector};
|
||||
use crate::coverage::transform_missing_snippet;
|
||||
use crate::items::{
|
||||
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> {
|
||||
snippet_provider: &'a SnippetProvider,
|
||||
report: FormatReport,
|
||||
) -> FmtVisitor<'a> {
|
||||
let mut skip_context = SkipContext::default();
|
||||
let mut macro_names = Vec::new();
|
||||
for macro_selector in config.skip_macro_invocations().0 {
|
||||
match macro_selector {
|
||||
MacroSelector::Name(name) => macro_names.push(name.to_string()),
|
||||
MacroSelector::All => skip_context.all_macros = true,
|
||||
}
|
||||
}
|
||||
skip_context.update_macros(macro_names);
|
||||
FmtVisitor {
|
||||
parent_context: None,
|
||||
parse_sess: parse_session,
|
||||
@ -784,7 +793,7 @@ impl<'b, 'a: 'b> FmtVisitor<'a> {
|
||||
is_macro_def: false,
|
||||
macro_rewrite_failure: false,
|
||||
report,
|
||||
skip_context: Default::default(),
|
||||
skip_context,
|
||||
}
|
||||
}
|
||||
|
||||
|
11
tests/source/skip_macro_invocations/all.rs
Normal file
11
tests/source/skip_macro_invocations/all.rs
Normal file
@ -0,0 +1,11 @@
|
||||
// rustfmt-skip_macro_invocations: ["*"]
|
||||
|
||||
// Should skip this invocation
|
||||
items!(
|
||||
const _: u8 = 0;
|
||||
);
|
||||
|
||||
// Should skip this invocation
|
||||
renamed_items!(
|
||||
const _: u8 = 0;
|
||||
);
|
11
tests/source/skip_macro_invocations/all_and_name.rs
Normal file
11
tests/source/skip_macro_invocations/all_and_name.rs
Normal file
@ -0,0 +1,11 @@
|
||||
// rustfmt-skip_macro_invocations: ["*","items"]
|
||||
|
||||
// Should skip this invocation
|
||||
items!(
|
||||
const _: u8 = 0;
|
||||
);
|
||||
|
||||
// Should also skip this invocation, as the wildcard covers it
|
||||
renamed_items!(
|
||||
const _: u8 = 0;
|
||||
);
|
11
tests/source/skip_macro_invocations/empty.rs
Normal file
11
tests/source/skip_macro_invocations/empty.rs
Normal file
@ -0,0 +1,11 @@
|
||||
// rustfmt-skip_macro_invocations: []
|
||||
|
||||
// Should not skip this invocation
|
||||
items!(
|
||||
const _: u8 = 0;
|
||||
);
|
||||
|
||||
// Should not skip this invocation
|
||||
renamed_items!(
|
||||
const _: u8 = 0;
|
||||
);
|
11
tests/source/skip_macro_invocations/name.rs
Normal file
11
tests/source/skip_macro_invocations/name.rs
Normal file
@ -0,0 +1,11 @@
|
||||
// rustfmt-skip_macro_invocations: ["items"]
|
||||
|
||||
// Should skip this invocation
|
||||
items!(
|
||||
const _: u8 = 0;
|
||||
);
|
||||
|
||||
// Should not skip this invocation
|
||||
renamed_items!(
|
||||
const _: u8 = 0;
|
||||
);
|
6
tests/source/skip_macro_invocations/name_unknown.rs
Normal file
6
tests/source/skip_macro_invocations/name_unknown.rs
Normal file
@ -0,0 +1,6 @@
|
||||
// rustfmt-skip_macro_invocations: ["unknown"]
|
||||
|
||||
// Should not skip this invocation
|
||||
items!(
|
||||
const _: u8 = 0;
|
||||
);
|
16
tests/source/skip_macro_invocations/names.rs
Normal file
16
tests/source/skip_macro_invocations/names.rs
Normal file
@ -0,0 +1,16 @@
|
||||
// rustfmt-skip_macro_invocations: ["foo","bar"]
|
||||
|
||||
// Should skip this invocation
|
||||
foo!(
|
||||
const _: u8 = 0;
|
||||
);
|
||||
|
||||
// Should skip this invocation
|
||||
bar!(
|
||||
const _: u8 = 0;
|
||||
);
|
||||
|
||||
// Should not skip this invocation
|
||||
baz!(
|
||||
const _: u8 = 0;
|
||||
);
|
@ -0,0 +1,6 @@
|
||||
// rustfmt-skip_macro_invocations: ["items"]
|
||||
|
||||
// Should not skip this invocation
|
||||
self::items!(
|
||||
const _: u8 = 0;
|
||||
);
|
@ -0,0 +1,6 @@
|
||||
// rustfmt-skip_macro_invocations: ["self::items"]
|
||||
|
||||
// Should skip this invocation
|
||||
self::items!(
|
||||
const _: u8 = 0;
|
||||
);
|
@ -0,0 +1,6 @@
|
||||
// rustfmt-skip_macro_invocations: ["self::items"]
|
||||
|
||||
// Should not skip this invocation
|
||||
items!(
|
||||
const _: u8 = 0;
|
||||
);
|
32
tests/source/skip_macro_invocations/use_alias_examples.rs
Normal file
32
tests/source/skip_macro_invocations/use_alias_examples.rs
Normal file
@ -0,0 +1,32 @@
|
||||
// rustfmt-skip_macro_invocations: ["aaa","ccc"]
|
||||
|
||||
// These tests demonstrate a realistic use case with use aliases.
|
||||
// The use statements should not impact functionality in any way.
|
||||
|
||||
use crate::{aaa, bbb, ddd};
|
||||
|
||||
// No use alias, invocation in list
|
||||
// Should skip this invocation
|
||||
aaa!(
|
||||
const _: u8 = 0;
|
||||
);
|
||||
|
||||
// Use alias, invocation in list
|
||||
// Should skip this invocation
|
||||
use crate::bbb as ccc;
|
||||
ccc!(
|
||||
const _: u8 = 0;
|
||||
);
|
||||
|
||||
// Use alias, invocation not in list
|
||||
// Should not skip this invocation
|
||||
use crate::ddd as eee;
|
||||
eee!(
|
||||
const _: u8 = 0;
|
||||
);
|
||||
|
||||
// No use alias, invocation not in list
|
||||
// Should not skip this invocation
|
||||
fff!(
|
||||
const _: u8 = 0;
|
||||
);
|
11
tests/target/skip_macro_invocations/all.rs
Normal file
11
tests/target/skip_macro_invocations/all.rs
Normal file
@ -0,0 +1,11 @@
|
||||
// rustfmt-skip_macro_invocations: ["*"]
|
||||
|
||||
// Should skip this invocation
|
||||
items!(
|
||||
const _: u8 = 0;
|
||||
);
|
||||
|
||||
// Should skip this invocation
|
||||
renamed_items!(
|
||||
const _: u8 = 0;
|
||||
);
|
11
tests/target/skip_macro_invocations/all_and_name.rs
Normal file
11
tests/target/skip_macro_invocations/all_and_name.rs
Normal file
@ -0,0 +1,11 @@
|
||||
// rustfmt-skip_macro_invocations: ["*","items"]
|
||||
|
||||
// Should skip this invocation
|
||||
items!(
|
||||
const _: u8 = 0;
|
||||
);
|
||||
|
||||
// Should also skip this invocation, as the wildcard covers it
|
||||
renamed_items!(
|
||||
const _: u8 = 0;
|
||||
);
|
11
tests/target/skip_macro_invocations/empty.rs
Normal file
11
tests/target/skip_macro_invocations/empty.rs
Normal file
@ -0,0 +1,11 @@
|
||||
// rustfmt-skip_macro_invocations: []
|
||||
|
||||
// Should not skip this invocation
|
||||
items!(
|
||||
const _: u8 = 0;
|
||||
);
|
||||
|
||||
// Should not skip this invocation
|
||||
renamed_items!(
|
||||
const _: u8 = 0;
|
||||
);
|
11
tests/target/skip_macro_invocations/name.rs
Normal file
11
tests/target/skip_macro_invocations/name.rs
Normal file
@ -0,0 +1,11 @@
|
||||
// rustfmt-skip_macro_invocations: ["items"]
|
||||
|
||||
// Should skip this invocation
|
||||
items!(
|
||||
const _: u8 = 0;
|
||||
);
|
||||
|
||||
// Should not skip this invocation
|
||||
renamed_items!(
|
||||
const _: u8 = 0;
|
||||
);
|
6
tests/target/skip_macro_invocations/name_unknown.rs
Normal file
6
tests/target/skip_macro_invocations/name_unknown.rs
Normal file
@ -0,0 +1,6 @@
|
||||
// rustfmt-skip_macro_invocations: ["unknown"]
|
||||
|
||||
// Should not skip this invocation
|
||||
items!(
|
||||
const _: u8 = 0;
|
||||
);
|
16
tests/target/skip_macro_invocations/names.rs
Normal file
16
tests/target/skip_macro_invocations/names.rs
Normal file
@ -0,0 +1,16 @@
|
||||
// rustfmt-skip_macro_invocations: ["foo","bar"]
|
||||
|
||||
// Should skip this invocation
|
||||
foo!(
|
||||
const _: u8 = 0;
|
||||
);
|
||||
|
||||
// Should skip this invocation
|
||||
bar!(
|
||||
const _: u8 = 0;
|
||||
);
|
||||
|
||||
// Should not skip this invocation
|
||||
baz!(
|
||||
const _: u8 = 0;
|
||||
);
|
@ -0,0 +1,6 @@
|
||||
// rustfmt-skip_macro_invocations: ["items"]
|
||||
|
||||
// Should not skip this invocation
|
||||
self::items!(
|
||||
const _: u8 = 0;
|
||||
);
|
@ -0,0 +1,6 @@
|
||||
// rustfmt-skip_macro_invocations: ["self::items"]
|
||||
|
||||
// Should skip this invocation
|
||||
self::items!(
|
||||
const _: u8 = 0;
|
||||
);
|
@ -0,0 +1,6 @@
|
||||
// rustfmt-skip_macro_invocations: ["self::items"]
|
||||
|
||||
// Should not skip this invocation
|
||||
items!(
|
||||
const _: u8 = 0;
|
||||
);
|
32
tests/target/skip_macro_invocations/use_alias_examples.rs
Normal file
32
tests/target/skip_macro_invocations/use_alias_examples.rs
Normal file
@ -0,0 +1,32 @@
|
||||
// rustfmt-skip_macro_invocations: ["aaa","ccc"]
|
||||
|
||||
// These tests demonstrate a realistic use case with use aliases.
|
||||
// The use statements should not impact functionality in any way.
|
||||
|
||||
use crate::{aaa, bbb, ddd};
|
||||
|
||||
// No use alias, invocation in list
|
||||
// Should skip this invocation
|
||||
aaa!(
|
||||
const _: u8 = 0;
|
||||
);
|
||||
|
||||
// Use alias, invocation in list
|
||||
// Should skip this invocation
|
||||
use crate::bbb as ccc;
|
||||
ccc!(
|
||||
const _: u8 = 0;
|
||||
);
|
||||
|
||||
// Use alias, invocation not in list
|
||||
// Should not skip this invocation
|
||||
use crate::ddd as eee;
|
||||
eee!(
|
||||
const _: u8 = 0;
|
||||
);
|
||||
|
||||
// No use alias, invocation not in list
|
||||
// Should not skip this invocation
|
||||
fff!(
|
||||
const _: u8 = 0;
|
||||
);
|
Loading…
x
Reference in New Issue
Block a user