use clippy_utils::diagnostics::span_lint_and_sugg; use rustc_ast::ast::{AttrKind, Attribute, Item, ItemKind}; use rustc_ast::token::{Token, TokenKind}; use rustc_ast::tokenstream::{TokenStream, TokenTree}; use rustc_errors::Applicability; use rustc_lint::{EarlyContext, EarlyLintPass}; use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_span::symbol::sym; use rustc_span::Span; declare_clippy_lint! { /// ### What it does /// Checks for usage of `crate` as opposed to `$crate` in a macro definition. /// /// ### Why is this bad? /// `crate` refers to the macro call's crate, whereas `$crate` refers to the macro definition's /// crate. Rarely is the former intended. See: /// https://doc.rust-lang.org/reference/macros-by-example.html#hygiene /// /// ### Example /// ```no_run /// #[macro_export] /// macro_rules! print_message { /// () => { /// println!("{}", crate::MESSAGE); /// }; /// } /// pub const MESSAGE: &str = "Hello!"; /// ``` /// Use instead: /// ```no_run /// #[macro_export] /// macro_rules! print_message { /// () => { /// println!("{}", $crate::MESSAGE); /// }; /// } /// pub const MESSAGE: &str = "Hello!"; /// ``` /// /// Note that if the use of `crate` is intentional, an `allow` attribute can be applied to the /// macro definition, e.g.: /// ```rust,ignore /// #[allow(clippy::crate_in_macro_def)] /// macro_rules! ok { ... crate::foo ... } /// ``` #[clippy::version = "1.62.0"] pub CRATE_IN_MACRO_DEF, suspicious, "using `crate` in a macro definition" } declare_lint_pass!(CrateInMacroDef => [CRATE_IN_MACRO_DEF]); impl EarlyLintPass for CrateInMacroDef { fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) { if_chain! { if item.attrs.iter().any(is_macro_export); if let ItemKind::MacroDef(macro_def) = &item.kind; let tts = macro_def.body.tokens.clone(); if let Some(span) = contains_unhygienic_crate_reference(&tts); then { span_lint_and_sugg( cx, CRATE_IN_MACRO_DEF, span, "`crate` references the macro call's crate", "to reference the macro definition's crate, use", String::from("$crate"), Applicability::MachineApplicable, ); } } } } fn is_macro_export(attr: &Attribute) -> bool { if_chain! { if let AttrKind::Normal(normal) = &attr.kind; if let [segment] = normal.item.path.segments.as_slice(); then { segment.ident.name == sym::macro_export } else { false } } } fn contains_unhygienic_crate_reference(tts: &TokenStream) -> Option { let mut prev_is_dollar = false; let mut cursor = tts.trees(); while let Some(curr) = cursor.next() { if_chain! { if !prev_is_dollar; if let Some(span) = is_crate_keyword(curr); if let Some(next) = cursor.look_ahead(0); if is_token(next, &TokenKind::ModSep); then { return Some(span); } } if let TokenTree::Delimited(_, _, tts) = &curr { let span = contains_unhygienic_crate_reference(tts); if span.is_some() { return span; } } prev_is_dollar = is_token(curr, &TokenKind::Dollar); } None } fn is_crate_keyword(tt: &TokenTree) -> Option { if_chain! { if let TokenTree::Token(Token { kind: TokenKind::Ident(symbol, _), span }, _) = tt; if symbol.as_str() == "crate"; then { Some(*span) } else { None } } } fn is_token(tt: &TokenTree, kind: &TokenKind) -> bool { if let TokenTree::Token(Token { kind: other, .. }, _) = tt { kind == other } else { false } }