154 lines
5.0 KiB
Rust
154 lines
5.0 KiB
Rust
use clippy_config::types::MacroMatcher;
|
|
use clippy_utils::diagnostics::span_lint_and_sugg;
|
|
use clippy_utils::source::snippet_opt;
|
|
use rustc_ast::ast;
|
|
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
|
|
use rustc_errors::Applicability;
|
|
use rustc_hir::def_id::DefId;
|
|
use rustc_lint::{EarlyContext, EarlyLintPass};
|
|
use rustc_session::impl_lint_pass;
|
|
use rustc_span::hygiene::{ExpnKind, MacroKind};
|
|
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.
|
|
///
|
|
/// ### Example
|
|
/// ```no_run
|
|
/// vec!{1, 2, 3};
|
|
/// ```
|
|
/// Use instead:
|
|
/// ```no_run
|
|
/// vec![1, 2, 3];
|
|
/// ```
|
|
#[clippy::version = "1.55.0"]
|
|
pub NONSTANDARD_MACRO_BRACES,
|
|
nursery,
|
|
"check consistent use of braces in macro"
|
|
}
|
|
|
|
/// The (callsite span, (open brace, close brace), source snippet)
|
|
type MacroInfo = (Span, (char, char), String);
|
|
|
|
#[derive(Debug)]
|
|
pub struct MacroBraces {
|
|
macro_braces: FxHashMap<String, (char, char)>,
|
|
done: FxHashSet<Span>,
|
|
}
|
|
|
|
impl MacroBraces {
|
|
pub fn new(conf: &[MacroMatcher]) -> Self {
|
|
let macro_braces = macro_braces(conf);
|
|
Self {
|
|
macro_braces,
|
|
done: FxHashSet::default(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl_lint_pass!(MacroBraces => [NONSTANDARD_MACRO_BRACES]);
|
|
|
|
impl EarlyLintPass for MacroBraces {
|
|
fn check_item(&mut self, cx: &EarlyContext<'_>, item: &ast::Item) {
|
|
if let Some((span, braces, snip)) = is_offending_macro(cx, item.span, self) {
|
|
emit_help(cx, &snip, braces, span);
|
|
self.done.insert(span);
|
|
}
|
|
}
|
|
|
|
fn check_stmt(&mut self, cx: &EarlyContext<'_>, stmt: &ast::Stmt) {
|
|
if let Some((span, braces, snip)) = is_offending_macro(cx, stmt.span, self) {
|
|
emit_help(cx, &snip, braces, span);
|
|
self.done.insert(span);
|
|
}
|
|
}
|
|
|
|
fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &ast::Expr) {
|
|
if let Some((span, braces, snip)) = is_offending_macro(cx, expr.span, self) {
|
|
emit_help(cx, &snip, braces, span);
|
|
self.done.insert(span);
|
|
}
|
|
}
|
|
|
|
fn check_ty(&mut self, cx: &EarlyContext<'_>, ty: &ast::Ty) {
|
|
if let Some((span, braces, snip)) = is_offending_macro(cx, ty.span, self) {
|
|
emit_help(cx, &snip, braces, span);
|
|
self.done.insert(span);
|
|
}
|
|
}
|
|
}
|
|
|
|
fn is_offending_macro(cx: &EarlyContext<'_>, span: Span, mac_braces: &MacroBraces) -> Option<MacroInfo> {
|
|
let unnested_or_local = || {
|
|
!span.ctxt().outer_expn_data().call_site.from_expansion()
|
|
|| span
|
|
.macro_backtrace()
|
|
.last()
|
|
.map_or(false, |e| e.macro_def_id.map_or(false, DefId::is_local))
|
|
};
|
|
let span_call_site = span.ctxt().outer_expn_data().call_site;
|
|
if let ExpnKind::Macro(MacroKind::Bang, mac_name) = span.ctxt().outer_expn_data().kind
|
|
&& let name = mac_name.as_str()
|
|
&& let Some(&braces) = mac_braces.macro_braces.get(name)
|
|
&& let Some(snip) = snippet_opt(cx, span_call_site)
|
|
// we must check only invocation sites
|
|
// https://github.com/rust-lang/rust-clippy/issues/7422
|
|
&& snip.starts_with(&format!("{name}!"))
|
|
&& unnested_or_local()
|
|
// make formatting consistent
|
|
&& let c = snip.replace(' ', "")
|
|
&& !c.starts_with(&format!("{name}!{}", braces.0))
|
|
&& !mac_braces.done.contains(&span_call_site)
|
|
{
|
|
Some((span_call_site, braces, snip))
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
fn emit_help(cx: &EarlyContext<'_>, snip: &str, (open, close): (char, char), span: Span) {
|
|
if let Some((macro_name, macro_args_str)) = snip.split_once('!') {
|
|
let mut macro_args = macro_args_str.trim().to_string();
|
|
// now remove the wrong braces
|
|
macro_args.remove(0);
|
|
macro_args.pop();
|
|
span_lint_and_sugg(
|
|
cx,
|
|
NONSTANDARD_MACRO_BRACES,
|
|
span,
|
|
&format!("use of irregular braces for `{macro_name}!` macro"),
|
|
"consider writing",
|
|
format!("{macro_name}!{open}{macro_args}{close}"),
|
|
Applicability::MachineApplicable,
|
|
);
|
|
}
|
|
}
|
|
|
|
fn macro_braces(conf: &[MacroMatcher]) -> FxHashMap<String, (char, char)> {
|
|
let mut braces = FxHashMap::from_iter(
|
|
[
|
|
("print", ('(', ')')),
|
|
("println", ('(', ')')),
|
|
("eprint", ('(', ')')),
|
|
("eprintln", ('(', ')')),
|
|
("write", ('(', ')')),
|
|
("writeln", ('(', ')')),
|
|
("format", ('(', ')')),
|
|
("format_args", ('(', ')')),
|
|
("vec", ('[', ']')),
|
|
("matches", ('(', ')')),
|
|
]
|
|
.map(|(k, v)| (k.to_string(), v)),
|
|
);
|
|
// We want users items to override any existing items
|
|
for it in conf {
|
|
braces.insert(it.name.clone(), it.braces);
|
|
}
|
|
braces
|
|
}
|