-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add macro_braces lint to check for irregular brace use in certain macros
- Loading branch information
Showing
5 changed files
with
187 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,146 @@ | ||
use clippy_utils::{diagnostics::span_lint_and_help, in_macro, is_direct_expn_of, source::snippet_opt}; | ||
use if_chain::if_chain; | ||
use rustc_ast::ast; | ||
use rustc_lint::{EarlyContext, EarlyLintPass}; | ||
use rustc_session::{declare_tool_lint, impl_lint_pass}; | ||
use rustc_span::Span; | ||
|
||
declare_clippy_lint! { | ||
/// **What it does:** Checks that common macros are used with consistent bracing. | ||
/// | ||
/// **Why is this bad?** This is mostly a consistency lint although using () or [] | ||
/// doesn't give you a semicolon in item position, which can be unexpected. | ||
/// | ||
/// **Known problems:** | ||
/// None | ||
/// | ||
/// **Example:** | ||
/// | ||
/// ```rust | ||
/// vec!{1, 2, 3}; | ||
/// ``` | ||
/// Use instead: | ||
/// ```rust | ||
/// vec![1, 2, 3]; | ||
/// ``` | ||
pub UNCONVENTIONAL_MACRO_BRACES, | ||
nursery, | ||
"check consistent use of braces in macro" | ||
} | ||
|
||
#[derive(Clone, Copy, Debug)] | ||
struct MacroMatcher { | ||
name: &'static str, | ||
braces: (&'static str, &'static str), | ||
} | ||
|
||
const BRACES: &[(&str, &str)] = &[("(", ")"), ("{", "}"), ("[", "]")]; | ||
|
||
const MACROS_TO_BRACES: &[MacroMatcher] = &[ | ||
MacroMatcher { | ||
name: "print", | ||
braces: ("(", ")"), | ||
}, | ||
MacroMatcher { | ||
name: "println", | ||
braces: ("(", ")"), | ||
}, | ||
MacroMatcher { | ||
name: "eprint", | ||
braces: ("(", ")"), | ||
}, | ||
MacroMatcher { | ||
name: "eprintln", | ||
braces: ("(", ")"), | ||
}, | ||
MacroMatcher { | ||
name: "write", | ||
braces: ("(", ")"), | ||
}, | ||
MacroMatcher { | ||
name: "format", | ||
braces: ("(", ")"), | ||
}, | ||
MacroMatcher { | ||
name: "format_args", | ||
braces: ("(", ")"), | ||
}, | ||
MacroMatcher { | ||
name: "vec", | ||
braces: ("[", "]"), | ||
}, | ||
MacroMatcher { | ||
name: "quote", | ||
braces: ("{", "}"), | ||
}, | ||
]; | ||
|
||
type MacroInfo = (&'static str, (&'static str, &'static str), String); | ||
|
||
#[derive(Clone, Debug, Default)] | ||
pub struct MacroBraces { | ||
done: Vec<&'static str>, | ||
} | ||
|
||
impl_lint_pass!(MacroBraces => [UNCONVENTIONAL_MACRO_BRACES]); | ||
|
||
impl EarlyLintPass for MacroBraces { | ||
fn check_item(&mut self, cx: &EarlyContext<'_>, item: &ast::Item) { | ||
if let Some((name, braces, snip)) = is_offending_macro(cx, item.span, &self.done) { | ||
emit_help(cx, snip, braces, name, item.span); | ||
self.done.push(name); | ||
} | ||
} | ||
|
||
fn check_stmt(&mut self, cx: &EarlyContext<'_>, stmt: &ast::Stmt) { | ||
if let Some((name, braces, snip)) = is_offending_macro(cx, stmt.span, &self.done) { | ||
emit_help(cx, snip, braces, name, stmt.span); | ||
self.done.push(name); | ||
} | ||
} | ||
|
||
fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &ast::Expr) { | ||
if let Some((name, braces, snip)) = is_offending_macro(cx, expr.span, &self.done) { | ||
emit_help(cx, snip, braces, name, expr.span); | ||
self.done.push(name); | ||
} | ||
} | ||
} | ||
|
||
fn is_offending_macro(cx: &EarlyContext<'_>, span: Span, done: &[&str]) -> Option<MacroInfo> { | ||
if_chain! { | ||
if in_macro(span); | ||
if let Some(MacroMatcher { name, braces }) = find_matching_macro(span); | ||
if let Some(snip) = snippet_opt(cx, span.ctxt().outer_expn_data().call_site); | ||
let c = snip.replace(" ", ""); // make formatting consistent | ||
if !c.starts_with(&format!("{}!{}", name, braces.0)); | ||
if !done.contains(&name); | ||
then { | ||
Some((name, braces, snip)) | ||
} else { | ||
None | ||
} | ||
} | ||
} | ||
|
||
fn emit_help(cx: &EarlyContext<'_>, snip: String, braces: (&str, &str), name: &str, span: Span) { | ||
let mut help = snip; | ||
for b in BRACES.iter().filter(|b| b.0 != braces.0) { | ||
help = help.replace(b.0, braces.0).replace(b.1, braces.1); | ||
} | ||
span_lint_and_help( | ||
cx, | ||
UNCONVENTIONAL_MACRO_BRACES, | ||
span, | ||
&format!("use of irregular braces for `{}!` macro", name), | ||
Some(span.ctxt().outer_expn_data().call_site), | ||
&format!("consider writing `{}`", help), | ||
); | ||
} | ||
|
||
fn find_matching_macro(span: Span) -> Option<MacroMatcher> { | ||
MACROS_TO_BRACES | ||
.iter() | ||
.copied() | ||
.find(|mac| is_direct_expn_of(span, mac.name).is_some()) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
#![warn(clippy::unconventional_macro_braces)] | ||
|
||
#[rustfmt::skip] | ||
fn main() { | ||
let x = vec! {1, 2, 3}; | ||
let y = format!["ugh {} stop being such a good compiler", "hello"]; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
error: use of irregular braces for `vec!` macro | ||
--> $DIR/unconventional_macro_braces.rs:5:13 | ||
| | ||
LL | let x = vec! {1, 2, 3}; | ||
| ^^^^^^^^^^^^^^ | ||
| | ||
= note: `-D clippy::unconventional-macro-braces` implied by `-D warnings` | ||
help: consider writing `vec! [1, 2, 3]` | ||
--> $DIR/unconventional_macro_braces.rs:5:13 | ||
| | ||
LL | let x = vec! {1, 2, 3}; | ||
| ^^^^^^^^^^^^^^ | ||
= note: this error originates in the macro `vec` (in Nightly builds, run with -Z macro-backtrace for more info) | ||
|
||
error: use of irregular braces for `format!` macro | ||
--> $DIR/unconventional_macro_braces.rs:6:13 | ||
| | ||
LL | let y = format!["ugh {} stop being such a good compiler", "hello"]; | ||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | ||
| | ||
help: consider writing `format!("ugh () stop being such a good compiler", "hello")` | ||
--> $DIR/unconventional_macro_braces.rs:6:13 | ||
| | ||
LL | let y = format!["ugh {} stop being such a good compiler", "hello"]; | ||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | ||
= note: this error originates in the macro `format` (in Nightly builds, run with -Z macro-backtrace for more info) | ||
|
||
error: aborting due to 2 previous errors | ||
|