]>
Commit | Line | Data |
---|---|---|
04454e1e FG |
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.61.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.inner_tokens(); | |
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(attr_item, _) = &attr.kind; | |
78 | if let [segment] = attr_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; | |
923072b8 | 93 | if let Some(span) = is_crate_keyword(curr); |
04454e1e FG |
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 | } | |
923072b8 | 106 | prev_is_dollar = is_token(curr, &TokenKind::Dollar); |
04454e1e FG |
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 | } |