summaryrefslogtreecommitdiffstats
path: root/src/tools/clippy/clippy_lints/src/crate_in_macro_def.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/tools/clippy/clippy_lints/src/crate_in_macro_def.rs')
-rw-r--r--src/tools/clippy/clippy_lints/src/crate_in_macro_def.rs125
1 files changed, 125 insertions, 0 deletions
diff --git a/src/tools/clippy/clippy_lints/src/crate_in_macro_def.rs b/src/tools/clippy/clippy_lints/src/crate_in_macro_def.rs
new file mode 100644
index 000000000..454ec2338
--- /dev/null
+++ b/src/tools/clippy/clippy_lints/src/crate_in_macro_def.rs
@@ -0,0 +1,125 @@
+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, Span};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for use 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
+ /// ```rust
+ /// #[macro_export]
+ /// macro_rules! print_message {
+ /// () => {
+ /// println!("{}", crate::MESSAGE);
+ /// };
+ /// }
+ /// pub const MESSAGE: &str = "Hello!";
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// #[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.inner_tokens();
+ 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(attr_item, _) = &attr.kind;
+ if let [segment] = attr_item.path.segments.as_slice();
+ then {
+ segment.ident.name == sym::macro_export
+ } else {
+ false
+ }
+ }
+}
+
+fn contains_unhygienic_crate_reference(tts: &TokenStream) -> Option<Span> {
+ 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<Span> {
+ 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
+ }
+}