]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/crate_in_macro_def.rs
Split `MacArgs` in two.
[rust.git] / clippy_lints / src / crate_in_macro_def.rs
1 use clippy_utils::diagnostics::span_lint_and_sugg;
2 use rustc_ast::ast::{AttrKind, Attribute, Item, ItemKind};
3 use rustc_ast::token::{Token, TokenKind};
4 use rustc_ast::tokenstream::{TokenStream, TokenTree};
5 use rustc_errors::Applicability;
6 use rustc_lint::{EarlyContext, EarlyLintPass};
7 use rustc_session::{declare_lint_pass, declare_tool_lint};
8 use rustc_span::{symbol::sym, Span};
9
10 declare_clippy_lint! {
11     /// ### What it does
12     /// Checks for use of `crate` as opposed to `$crate` in a macro definition.
13     ///
14     /// ### Why is this bad?
15     /// `crate` refers to the macro call's crate, whereas `$crate` refers to the macro definition's
16     /// crate. Rarely is the former intended. See:
17     /// https://doc.rust-lang.org/reference/macros-by-example.html#hygiene
18     ///
19     /// ### Example
20     /// ```rust
21     /// #[macro_export]
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_export]
32     /// macro_rules! print_message {
33     ///     () => {
34     ///         println!("{}", $crate::MESSAGE);
35     ///     };
36     /// }
37     /// pub const MESSAGE: &str = "Hello!";
38     /// ```
39     ///
40     /// Note that if the use of `crate` is intentional, an `allow` attribute can be applied to the
41     /// macro definition, e.g.:
42     /// ```rust,ignore
43     /// #[allow(clippy::crate_in_macro_def)]
44     /// macro_rules! ok { ... crate::foo ... }
45     /// ```
46     #[clippy::version = "1.62.0"]
47     pub CRATE_IN_MACRO_DEF,
48     suspicious,
49     "using `crate` in a macro definition"
50 }
51 declare_lint_pass!(CrateInMacroDef => [CRATE_IN_MACRO_DEF]);
52
53 impl EarlyLintPass for CrateInMacroDef {
54     fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) {
55         if_chain! {
56             if item.attrs.iter().any(is_macro_export);
57             if let ItemKind::MacroDef(macro_def) = &item.kind;
58             let tts = macro_def.body.tokens.clone();
59             if let Some(span) = contains_unhygienic_crate_reference(&tts);
60             then {
61                 span_lint_and_sugg(
62                     cx,
63                     CRATE_IN_MACRO_DEF,
64                     span,
65                     "`crate` references the macro call's crate",
66                     "to reference the macro definition's crate, use",
67                     String::from("$crate"),
68                     Applicability::MachineApplicable,
69                 );
70             }
71         }
72     }
73 }
74
75 fn is_macro_export(attr: &Attribute) -> bool {
76     if_chain! {
77         if let AttrKind::Normal(normal) = &attr.kind;
78         if let [segment] = normal.item.path.segments.as_slice();
79         then {
80             segment.ident.name == sym::macro_export
81         } else {
82             false
83         }
84     }
85 }
86
87 fn contains_unhygienic_crate_reference(tts: &TokenStream) -> Option<Span> {
88     let mut prev_is_dollar = false;
89     let mut cursor = tts.trees();
90     while let Some(curr) = cursor.next() {
91         if_chain! {
92             if !prev_is_dollar;
93             if let Some(span) = is_crate_keyword(curr);
94             if let Some(next) = cursor.look_ahead(0);
95             if is_token(next, &TokenKind::ModSep);
96             then {
97                 return Some(span);
98             }
99         }
100         if let TokenTree::Delimited(_, _, tts) = &curr {
101             let span = contains_unhygienic_crate_reference(tts);
102             if span.is_some() {
103                 return span;
104             }
105         }
106         prev_is_dollar = is_token(curr, &TokenKind::Dollar);
107     }
108     None
109 }
110
111 fn is_crate_keyword(tt: &TokenTree) -> Option<Span> {
112     if_chain! {
113         if let TokenTree::Token(Token { kind: TokenKind::Ident(symbol, _), span }, _) = tt;
114         if symbol.as_str() == "crate";
115         then { Some(*span) } else { None }
116     }
117 }
118
119 fn is_token(tt: &TokenTree, kind: &TokenKind) -> bool {
120     if let TokenTree::Token(Token { kind: other, .. }, _) = tt {
121         kind == other
122     } else {
123         false
124     }
125 }