Auto merge of #8576 - smoelius:crate_in_macro_def, r=llogiq
Add `crate_in_macro_def` lint This PR adds a lint to check for `crate` as opposed to `$crate` used in a macro definition. I think this can close #4798. That issue focused on the case where the macro author "imports something into said macro." But I think use of `crate` is likely to be a bug whether it appears in a `use` statement or not. There could be some use case I am failing to see, though. (cc: `@nilscript` `@flip1995)` changelog: `crate_in_macro_def`
This commit is contained in:
commit
fe7254ff6f
@ -3229,6 +3229,7 @@ Released 2018-09-13
|
||||
[`comparison_chain`]: https://rust-lang.github.io/rust-clippy/master/index.html#comparison_chain
|
||||
[`comparison_to_empty`]: https://rust-lang.github.io/rust-clippy/master/index.html#comparison_to_empty
|
||||
[`copy_iterator`]: https://rust-lang.github.io/rust-clippy/master/index.html#copy_iterator
|
||||
[`crate_in_macro_def`]: https://rust-lang.github.io/rust-clippy/master/index.html#crate_in_macro_def
|
||||
[`create_dir`]: https://rust-lang.github.io/rust-clippy/master/index.html#create_dir
|
||||
[`crosspointer_transmute`]: https://rust-lang.github.io/rust-clippy/master/index.html#crosspointer_transmute
|
||||
[`dbg_macro`]: https://rust-lang.github.io/rust-clippy/master/index.html#dbg_macro
|
||||
|
125
clippy_lints/src/crate_in_macro_def.rs
Normal file
125
clippy_lints/src/crate_in_macro_def.rs
Normal file
@ -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.61.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
|
||||
}
|
||||
}
|
@ -37,6 +37,7 @@
|
||||
LintId::of(comparison_chain::COMPARISON_CHAIN),
|
||||
LintId::of(copies::IFS_SAME_COND),
|
||||
LintId::of(copies::IF_SAME_THEN_ELSE),
|
||||
LintId::of(crate_in_macro_def::CRATE_IN_MACRO_DEF),
|
||||
LintId::of(default::FIELD_REASSIGN_WITH_DEFAULT),
|
||||
LintId::of(dereference::NEEDLESS_BORROW),
|
||||
LintId::of(derivable_impls::DERIVABLE_IMPLS),
|
||||
|
@ -97,6 +97,7 @@
|
||||
copies::IF_SAME_THEN_ELSE,
|
||||
copies::SAME_FUNCTIONS_IN_IF_CONDITION,
|
||||
copy_iterator::COPY_ITERATOR,
|
||||
crate_in_macro_def::CRATE_IN_MACRO_DEF,
|
||||
create_dir::CREATE_DIR,
|
||||
dbg_macro::DBG_MACRO,
|
||||
default::DEFAULT_TRAIT_ACCESS,
|
||||
|
@ -9,6 +9,7 @@
|
||||
LintId::of(await_holding_invalid::AWAIT_HOLDING_REFCELL_REF),
|
||||
LintId::of(casts::CAST_ENUM_CONSTRUCTOR),
|
||||
LintId::of(casts::CAST_ENUM_TRUNCATION),
|
||||
LintId::of(crate_in_macro_def::CRATE_IN_MACRO_DEF),
|
||||
LintId::of(eval_order_dependence::EVAL_ORDER_DEPENDENCE),
|
||||
LintId::of(float_equality_without_abs::FLOAT_EQUALITY_WITHOUT_ABS),
|
||||
LintId::of(format_impl::PRINT_IN_FORMAT_IMPL),
|
||||
|
@ -190,6 +190,7 @@ macro_rules! declare_clippy_lint {
|
||||
mod comparison_chain;
|
||||
mod copies;
|
||||
mod copy_iterator;
|
||||
mod crate_in_macro_def;
|
||||
mod create_dir;
|
||||
mod dbg_macro;
|
||||
mod default;
|
||||
@ -867,6 +868,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
|
||||
ignore_publish: cargo_ignore_publish,
|
||||
})
|
||||
});
|
||||
store.register_early_pass(|| Box::new(crate_in_macro_def::CrateInMacroDef));
|
||||
// add lints here, do not remove this comment, it's used in `new_lint`
|
||||
}
|
||||
|
||||
|
56
tests/ui/crate_in_macro_def.fixed
Normal file
56
tests/ui/crate_in_macro_def.fixed
Normal file
@ -0,0 +1,56 @@
|
||||
// run-rustfix
|
||||
#![warn(clippy::crate_in_macro_def)]
|
||||
|
||||
mod hygienic {
|
||||
#[macro_export]
|
||||
macro_rules! print_message_hygienic {
|
||||
() => {
|
||||
println!("{}", $crate::hygienic::MESSAGE);
|
||||
};
|
||||
}
|
||||
|
||||
pub const MESSAGE: &str = "Hello!";
|
||||
}
|
||||
|
||||
mod unhygienic {
|
||||
#[macro_export]
|
||||
macro_rules! print_message_unhygienic {
|
||||
() => {
|
||||
println!("{}", $crate::unhygienic::MESSAGE);
|
||||
};
|
||||
}
|
||||
|
||||
pub const MESSAGE: &str = "Hello!";
|
||||
}
|
||||
|
||||
mod unhygienic_intentionally {
|
||||
// For cases where the use of `crate` is intentional, applying `allow` to the macro definition
|
||||
// should suppress the lint.
|
||||
#[allow(clippy::crate_in_macro_def)]
|
||||
#[macro_export]
|
||||
macro_rules! print_message_unhygienic_intentionally {
|
||||
() => {
|
||||
println!("{}", crate::CALLER_PROVIDED_MESSAGE);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_use]
|
||||
mod not_exported {
|
||||
macro_rules! print_message_not_exported {
|
||||
() => {
|
||||
println!("{}", crate::not_exported::MESSAGE);
|
||||
};
|
||||
}
|
||||
|
||||
pub const MESSAGE: &str = "Hello!";
|
||||
}
|
||||
|
||||
fn main() {
|
||||
print_message_hygienic!();
|
||||
print_message_unhygienic!();
|
||||
print_message_unhygienic_intentionally!();
|
||||
print_message_not_exported!();
|
||||
}
|
||||
|
||||
pub const CALLER_PROVIDED_MESSAGE: &str = "Hello!";
|
56
tests/ui/crate_in_macro_def.rs
Normal file
56
tests/ui/crate_in_macro_def.rs
Normal file
@ -0,0 +1,56 @@
|
||||
// run-rustfix
|
||||
#![warn(clippy::crate_in_macro_def)]
|
||||
|
||||
mod hygienic {
|
||||
#[macro_export]
|
||||
macro_rules! print_message_hygienic {
|
||||
() => {
|
||||
println!("{}", $crate::hygienic::MESSAGE);
|
||||
};
|
||||
}
|
||||
|
||||
pub const MESSAGE: &str = "Hello!";
|
||||
}
|
||||
|
||||
mod unhygienic {
|
||||
#[macro_export]
|
||||
macro_rules! print_message_unhygienic {
|
||||
() => {
|
||||
println!("{}", crate::unhygienic::MESSAGE);
|
||||
};
|
||||
}
|
||||
|
||||
pub const MESSAGE: &str = "Hello!";
|
||||
}
|
||||
|
||||
mod unhygienic_intentionally {
|
||||
// For cases where the use of `crate` is intentional, applying `allow` to the macro definition
|
||||
// should suppress the lint.
|
||||
#[allow(clippy::crate_in_macro_def)]
|
||||
#[macro_export]
|
||||
macro_rules! print_message_unhygienic_intentionally {
|
||||
() => {
|
||||
println!("{}", crate::CALLER_PROVIDED_MESSAGE);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_use]
|
||||
mod not_exported {
|
||||
macro_rules! print_message_not_exported {
|
||||
() => {
|
||||
println!("{}", crate::not_exported::MESSAGE);
|
||||
};
|
||||
}
|
||||
|
||||
pub const MESSAGE: &str = "Hello!";
|
||||
}
|
||||
|
||||
fn main() {
|
||||
print_message_hygienic!();
|
||||
print_message_unhygienic!();
|
||||
print_message_unhygienic_intentionally!();
|
||||
print_message_not_exported!();
|
||||
}
|
||||
|
||||
pub const CALLER_PROVIDED_MESSAGE: &str = "Hello!";
|
10
tests/ui/crate_in_macro_def.stderr
Normal file
10
tests/ui/crate_in_macro_def.stderr
Normal file
@ -0,0 +1,10 @@
|
||||
error: `crate` references the macro call's crate
|
||||
--> $DIR/crate_in_macro_def.rs:19:28
|
||||
|
|
||||
LL | println!("{}", crate::unhygienic::MESSAGE);
|
||||
| ^^^^^ help: to reference the macro definition's crate, use: `$crate`
|
||||
|
|
||||
= note: `-D clippy::crate-in-macro-def` implied by `-D warnings`
|
||||
|
||||
error: aborting due to previous error
|
||||
|
Loading…
Reference in New Issue
Block a user