]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/crate_in_macro_def.rs
Add `crate_in_macro_def` lint
[rust.git] / clippy_lints / src / crate_in_macro_def.rs
1 use clippy_utils::diagnostics::span_lint_and_sugg;
2 use rustc_ast::ast::MacroDef;
3 use rustc_ast::node_id::NodeId;
4 use rustc_ast::token::{Token, TokenKind};
5 use rustc_ast::tokenstream::{TokenStream, TokenTree};
6 use rustc_errors::Applicability;
7 use rustc_lint::{EarlyContext, EarlyLintPass};
8 use rustc_session::{declare_lint_pass, declare_tool_lint};
9 use rustc_span::Span;
10
11 declare_clippy_lint! {
12     /// ### What it does
13     /// Checks for use of `crate` as opposed to `$crate` in a macro definition.
14     ///
15     /// ### Why is this bad?
16     /// `crate` refers to macro call's crate, whereas `$crate` refers to the macro
17     /// definition's crate. Rarely is the former intended. See:
18     /// https://doc.rust-lang.org/reference/macros-by-example.html#hygiene
19     ///
20     /// ### Example
21     /// ```rust
22     /// macro_rules! print_message {
23     ///     () => {
24     ///         println!("{}", crate::MESSAGE);
25     ///     };
26     /// }
27     /// pub const MESSAGE: &str = "Hello!";
28     /// ```
29     /// Use instead:
30     /// ```rust
31     /// macro_rules! print_message {
32     ///     () => {
33     ///         println!("{}", $crate::MESSAGE);
34     ///     };
35     /// }
36     /// pub const MESSAGE: &str = "Hello!";
37     /// ```
38     #[clippy::version = "1.61.0"]
39     pub CRATE_IN_MACRO_DEF,
40     correctness,
41     "using `crate` in a macro definition"
42 }
43 declare_lint_pass!(CrateInMacroDef => [CRATE_IN_MACRO_DEF]);
44
45 impl EarlyLintPass for CrateInMacroDef {
46     fn check_mac_def(&mut self, cx: &EarlyContext<'_>, macro_def: &MacroDef, _: NodeId) {
47         let tts = macro_def.body.inner_tokens();
48         if let Some(span) = contains_unhygienic_crate_reference(&tts) {
49             span_lint_and_sugg(
50                 cx,
51                 CRATE_IN_MACRO_DEF,
52                 span,
53                 "reference to the macro call's crate, which is rarely intended",
54                 "if reference to the macro definition's crate is intended, use",
55                 String::from("$crate"),
56                 Applicability::MachineApplicable,
57             );
58         }
59     }
60 }
61
62 fn contains_unhygienic_crate_reference(tts: &TokenStream) -> Option<Span> {
63     let mut prev_is_dollar = false;
64     let mut cursor = tts.trees();
65     while let Some(curr) = cursor.next() {
66         if_chain! {
67             if !prev_is_dollar;
68             if let Some(span) = is_crate_keyword(&curr);
69             if let Some(next) = cursor.look_ahead(0);
70             if is_token(next, &TokenKind::ModSep);
71             then {
72                 return Some(span);
73             }
74         }
75         if let TokenTree::Delimited(_, _, tts) = &curr {
76             let span = contains_unhygienic_crate_reference(tts);
77             if span.is_some() {
78                 return span;
79             }
80         }
81         prev_is_dollar = is_token(&curr, &TokenKind::Dollar);
82     }
83     None
84 }
85
86 fn is_crate_keyword(tt: &TokenTree) -> Option<Span> {
87     if_chain! {
88         if let TokenTree::Token(Token { kind: TokenKind::Ident(symbol, _), span }) = tt;
89         if symbol.as_str() == "crate";
90         then { Some(*span) } else { None }
91     }
92 }
93
94 fn is_token(tt: &TokenTree, kind: &TokenKind) -> bool {
95     if let TokenTree::Token(Token { kind: other, .. }) = tt {
96         kind == other
97     } else {
98         false
99     }
100 }