Skip to content

Commit 65a2669

Browse files
committed
Add crate_in_macro_def lint
1 parent f07ee8a commit 65a2669

12 files changed

+198
-1
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3097,6 +3097,7 @@ Released 2018-09-13
30973097
[`comparison_chain`]: https://rust-lang.github.io/rust-clippy/master/index.html#comparison_chain
30983098
[`comparison_to_empty`]: https://rust-lang.github.io/rust-clippy/master/index.html#comparison_to_empty
30993099
[`copy_iterator`]: https://rust-lang.github.io/rust-clippy/master/index.html#copy_iterator
3100+
[`crate_in_macro_def`]: https://rust-lang.github.io/rust-clippy/master/index.html#crate_in_macro_def
31003101
[`create_dir`]: https://rust-lang.github.io/rust-clippy/master/index.html#create_dir
31013102
[`crosspointer_transmute`]: https://rust-lang.github.io/rust-clippy/master/index.html#crosspointer_transmute
31023103
[`dbg_macro`]: https://rust-lang.github.io/rust-clippy/master/index.html#dbg_macro
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
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+
}

clippy_lints/src/lib.register_all.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
3737
LintId::of(comparison_chain::COMPARISON_CHAIN),
3838
LintId::of(copies::IFS_SAME_COND),
3939
LintId::of(copies::IF_SAME_THEN_ELSE),
40+
LintId::of(crate_in_macro_def::CRATE_IN_MACRO_DEF),
4041
LintId::of(default::FIELD_REASSIGN_WITH_DEFAULT),
4142
LintId::of(dereference::NEEDLESS_BORROW),
4243
LintId::of(derivable_impls::DERIVABLE_IMPLS),

clippy_lints/src/lib.register_correctness.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ store.register_group(true, "clippy::correctness", Some("clippy_correctness"), ve
1616
LintId::of(casts::CAST_SLICE_DIFFERENT_SIZES),
1717
LintId::of(copies::IFS_SAME_COND),
1818
LintId::of(copies::IF_SAME_THEN_ELSE),
19+
LintId::of(crate_in_macro_def::CRATE_IN_MACRO_DEF),
1920
LintId::of(derive::DERIVE_HASH_XOR_EQ),
2021
LintId::of(derive::DERIVE_ORD_XOR_PARTIAL_ORD),
2122
LintId::of(drop_forget_ref::DROP_COPY),

clippy_lints/src/lib.register_lints.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ store.register_lints(&[
9797
copies::IF_SAME_THEN_ELSE,
9898
copies::SAME_FUNCTIONS_IN_IF_CONDITION,
9999
copy_iterator::COPY_ITERATOR,
100+
crate_in_macro_def::CRATE_IN_MACRO_DEF,
100101
create_dir::CREATE_DIR,
101102
dbg_macro::DBG_MACRO,
102103
default::DEFAULT_TRAIT_ACCESS,

clippy_lints/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,7 @@ mod collapsible_match;
190190
mod comparison_chain;
191191
mod copies;
192192
mod copy_iterator;
193+
mod crate_in_macro_def;
193194
mod create_dir;
194195
mod dbg_macro;
195196
mod default;
@@ -867,6 +868,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
867868
ignore_publish: cargo_ignore_publish,
868869
})
869870
});
871+
store.register_early_pass(|| Box::new(crate_in_macro_def::CrateInMacroDef));
870872
// add lints here, do not remove this comment, it's used in `new_lint`
871873
}
872874

clippy_lints/src/utils/conf.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ macro_rules! define_Conf {
123123

124124
#[cfg(feature = "internal")]
125125
pub mod metadata {
126-
use crate::utils::internal_lints::metadata_collector::ClippyConfiguration;
126+
use $crate::utils::internal_lints::metadata_collector::ClippyConfiguration;
127127

128128
macro_rules! wrap_option {
129129
() => (None);

tests/ui/crate_in_macro_def.fixed

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// run-rustfix
2+
#![warn(clippy::crate_in_macro_def)]
3+
4+
#[macro_use]
5+
mod hygienic {
6+
macro_rules! print_message_hygienic {
7+
() => {
8+
println!("{}", $crate::hygienic::MESSAGE);
9+
};
10+
}
11+
12+
pub const MESSAGE: &str = "Hello!";
13+
}
14+
15+
#[macro_use]
16+
mod unhygienic {
17+
macro_rules! print_message_unhygienic {
18+
() => {
19+
println!("{}", $crate::unhygienic::MESSAGE);
20+
};
21+
}
22+
23+
pub const MESSAGE: &str = "Hello!";
24+
}
25+
26+
fn main() {
27+
print_message_hygienic!();
28+
print_message_unhygienic!();
29+
}

tests/ui/crate_in_macro_def.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// run-rustfix
2+
#![warn(clippy::crate_in_macro_def)]
3+
4+
#[macro_use]
5+
mod hygienic {
6+
macro_rules! print_message_hygienic {
7+
() => {
8+
println!("{}", $crate::hygienic::MESSAGE);
9+
};
10+
}
11+
12+
pub const MESSAGE: &str = "Hello!";
13+
}
14+
15+
#[macro_use]
16+
mod unhygienic {
17+
macro_rules! print_message_unhygienic {
18+
() => {
19+
println!("{}", crate::unhygienic::MESSAGE);
20+
};
21+
}
22+
23+
pub const MESSAGE: &str = "Hello!";
24+
}
25+
26+
fn main() {
27+
print_message_hygienic!();
28+
print_message_unhygienic!();
29+
}

tests/ui/crate_in_macro_def.stderr

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
error: reference to the macro call's crate, which is rarely intended
2+
--> $DIR/crate_in_macro_def.rs:19:28
3+
|
4+
LL | println!("{}", crate::unhygienic::MESSAGE);
5+
| ^^^^^
6+
|
7+
= note: `-D clippy::crate-in-macro-def` implied by `-D warnings`
8+
help: if reference to the macro definition's crate is intended, use
9+
|
10+
LL | println!("{}", $crate::unhygienic::MESSAGE);
11+
| ~~~~~~
12+
13+
error: aborting due to previous error
14+

0 commit comments

Comments
 (0)