-
Notifications
You must be signed in to change notification settings - Fork 1.8k
new lint: use_crate_prefix_for_self_imports #13662
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
7215150
2f103ff
db08e51
56b0e8e
02f0e66
8fc4cce
83c9271
0c0b1de
3512129
1d361a4
40cf7c0
f2856b6
8fc104a
93bd82c
2722eac
ba35482
1d80a38
6400f00
65043bf
78c1c70
fd03d91
b5ac4a4
fcf45ca
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,156 @@ | ||
use clippy_utils::diagnostics::span_lint_and_sugg; | ||
use clippy_utils::source::SpanRangeExt; | ||
use clippy_utils::tokenize_with_text; | ||
use def_id::LOCAL_CRATE; | ||
use rustc_data_structures::fx::FxHashSet; | ||
use rustc_errors::Applicability; | ||
use rustc_hir::def::Res; | ||
use rustc_hir::{Item, ItemKind, UsePath, def_id}; | ||
use rustc_lint::{LateContext, LateLintPass, LintContext}; | ||
use rustc_session::impl_lint_pass; | ||
use rustc_span::{FileName, RealFileName, Span, Symbol, kw}; | ||
|
||
declare_clippy_lint! { | ||
/// ### What it does | ||
/// This lint checks for imports from the current crate that do not use the `crate::` prefix. | ||
/// It suggests using `crate::` to make it clear that the item is from the same crate. | ||
/// | ||
/// ### Why is this bad? | ||
/// When imports from the current crate lack the `crate::` prefix, it can make the code less readable | ||
/// because it’s not immediately clear if the imported item is from the current crate or an external dependency. | ||
/// Using `crate::` for self-imports provides a consistent style, making the origin of each import clear. | ||
/// This helps reduce confusion and maintain a uniform codebase. | ||
/// | ||
/// ### Example | ||
/// ```rust,ignore | ||
/// // lib.rs | ||
/// mod foo; | ||
/// use foo::bar; | ||
/// ``` | ||
/// | ||
/// ```rust,ignore | ||
/// // foo.rs | ||
/// #[path = "./foo.rs"] | ||
/// pub fn bar() {} | ||
/// ``` | ||
/// | ||
/// Use instead: | ||
/// ```rust,ignore | ||
/// // lib.rs | ||
/// mod foo; | ||
/// use crate::foo::bar; | ||
/// ``` | ||
/// | ||
/// ```rust,ignore | ||
/// // foo.rs | ||
/// #[path = "./foo.rs"] | ||
/// pub fn bar() {} | ||
/// ``` | ||
#[clippy::version = "1.92.0"] | ||
pub USE_CRATE_PREFIX_FOR_SELF_IMPORTS, | ||
nursery, | ||
"checks that imports from the current crate use the `crate::` prefix" | ||
} | ||
|
||
#[derive(Clone, Default)] | ||
pub struct UseCratePrefixForSelfImports<'a, 'tcx> { | ||
/// collect `use` in current block | ||
use_block: Vec<&'a UsePath<'tcx>>, | ||
/// collect `mod` in current block | ||
mod_names: FxHashSet<Symbol>, | ||
latest_span: Option<Span>, | ||
} | ||
|
||
impl_lint_pass!(UseCratePrefixForSelfImports<'_, '_> => [USE_CRATE_PREFIX_FOR_SELF_IMPORTS]); | ||
|
||
impl<'a, 'tcx> LateLintPass<'tcx> for UseCratePrefixForSelfImports<'a, 'tcx> { | ||
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'a Item<'tcx>) { | ||
let FileName::Real(RealFileName::LocalPath(p)) = cx.sess().source_map().span_to_filename(item.span) else { | ||
self.clear(); | ||
return; | ||
}; | ||
let Some(file_name) = p.file_name() else { | ||
self.clear(); | ||
return; | ||
}; | ||
// only check `main.rs` and `lib.rs` | ||
if !(file_name == "main.rs" || file_name == "lib.rs") { | ||
return; | ||
} | ||
|
||
self.insert_item(cx, item); | ||
} | ||
} | ||
|
||
impl<'tcx> UseCratePrefixForSelfImports<'_, 'tcx> { | ||
fn in_same_block(&self, cx: &LateContext<'tcx>, span: Span) -> bool { | ||
match self.latest_span { | ||
Some(latest_span) => { | ||
if latest_span.contains(span) { | ||
return true; | ||
} | ||
let gap_span = latest_span.between(span); | ||
let gap_snippet = gap_span.get_source_text(cx).unwrap(); | ||
for (token, source, _) in tokenize_with_text(&gap_snippet) { | ||
if token == rustc_lexer::TokenKind::Whitespace && source.chars().filter(|c| *c == '\n').count() > 1 | ||
{ | ||
return false; | ||
} | ||
} | ||
true | ||
}, | ||
None => true, | ||
} | ||
} | ||
|
||
fn insert_item(&mut self, cx: &LateContext<'tcx>, item: &Item<'tcx>) { | ||
if item.span.from_expansion() { | ||
return; | ||
} | ||
if !self.in_same_block(cx, item.span) { | ||
self.try_lint(cx); | ||
self.clear(); | ||
} | ||
match item.kind { | ||
ItemKind::Mod(ident, _) => { | ||
self.mod_names.insert(ident.name); | ||
}, | ||
ItemKind::Use(use_tree, _) => { | ||
self.use_block.push(use_tree); | ||
}, | ||
_ => {}, | ||
} | ||
self.latest_span = match self.latest_span { | ||
Some(latest_span) => Some(latest_span.with_hi(item.span.hi())), | ||
None => Some(item.span), | ||
}; | ||
} | ||
|
||
fn try_lint(&self, cx: &LateContext<'tcx>) { | ||
for use_path in &self.use_block { | ||
if let [segment, ..] = &use_path.segments | ||
&& let Res::Def(_, def_id) = segment.res | ||
&& def_id.krate == LOCAL_CRATE | ||
{ | ||
let root = segment.ident.name; | ||
if !matches!(root, kw::Crate | kw::Super | kw::SelfLower) && !self.mod_names.contains(&root) { | ||
span_lint_and_sugg( | ||
cx, | ||
USE_CRATE_PREFIX_FOR_SELF_IMPORTS, | ||
segment.ident.span, | ||
"this import is not clear", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This message is not clear. Why is the import "not clear"? (I don't have a better suggestion yet) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I can't infer whether it's from crate or from my local codebase |
||
"prefix with `crate::`", | ||
format!("crate::{root}"), | ||
Applicability::MachineApplicable, | ||
); | ||
} | ||
} | ||
} | ||
} | ||
|
||
fn clear(&mut self) { | ||
self.use_block.clear(); | ||
self.mod_names.clear(); | ||
self.latest_span = None; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
error: this import is not clear | ||
--> src/main.rs:3:5 | ||
| | ||
3 | use foo::Foo; | ||
| ^^^ help: prefix with `crate::`: `crate::foo` | ||
| | ||
= note: `-D clippy::use-crate-prefix-for-self-imports` implied by `-D warnings` | ||
= help: to override `-D warnings` add `#[allow(clippy::use_crate_prefix_for_self_imports)]` | ||
|
||
error: could not compile `fail` (bin "fail") due to 1 previous error |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
[package] | ||
name = "fail" | ||
version = "0.1.0" | ||
edition = "2024" | ||
publish = false | ||
|
||
[dependencies] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
pub struct Foo; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
#![warn(clippy::use_crate_prefix_for_self_imports)] | ||
|
||
use foo::Foo; | ||
|
||
mod foo; | ||
|
||
fn main() { | ||
let _foo = Foo; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
[package] | ||
name = "pass" | ||
version = "0.1.0" | ||
edition = "2024" | ||
publish = false | ||
|
||
[dependencies] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
pub struct Foo; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
#![warn(clippy::use_crate_prefix_for_self_imports)] | ||
|
||
use crate::foo::Foo; | ||
|
||
mod foo; | ||
|
||
fn main() { | ||
let _foo = Foo; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
[package] | ||
name = "pass_attribute" | ||
version = "0.1.0" | ||
edition = "2024" | ||
publish = false | ||
|
||
[dependencies] | ||
|
||
[features] | ||
default = ["bar"] | ||
bar = [] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
pub struct Foo; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
#![warn(clippy::use_crate_prefix_for_self_imports)] | ||
|
||
#[cfg(feature = "bar")] | ||
mod foo; | ||
#[cfg(feature = "bar")] | ||
pub use foo::Foo; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
[package] | ||
name = "pass_attribute_2" | ||
version = "0.1.0" | ||
edition = "2024" | ||
publish = false | ||
|
||
[dependencies] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
pub struct Foo; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
#![warn(clippy::use_crate_prefix_for_self_imports)] | ||
|
||
mod foo; | ||
#[doc(hidden)] | ||
pub use foo::Foo; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
[package] | ||
name = "pass_sibling_2" | ||
version = "0.1.0" | ||
edition = "2024" | ||
publish = false | ||
|
||
[dependencies] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
pub struct Foo; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
#![warn(clippy::use_crate_prefix_for_self_imports)] | ||
|
||
use foo::Foo; | ||
mod foo; | ||
|
||
fn main() { | ||
let _foo = Foo; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
[package] | ||
name = "pass_sibling_3" | ||
version = "0.1.0" | ||
edition = "2024" | ||
publish = false | ||
|
||
[dependencies] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
pub struct Foo; | ||
pub struct Bar; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
#![warn(clippy::use_crate_prefix_for_self_imports)] | ||
|
||
mod foo; | ||
pub use foo::{Bar, Foo}; | ||
|
||
fn main() { | ||
let _foo = Foo; | ||
let _bar = Bar; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
[package] | ||
name = "pass_sibling_4" | ||
version = "0.1.0" | ||
edition = "2024" | ||
publish = false | ||
|
||
[dependencies] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
pub struct Foo; | ||
pub struct Bar; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
#![warn(clippy::use_crate_prefix_for_self_imports)] | ||
|
||
pub use foo::{Bar, Foo}; | ||
mod foo; | ||
|
||
fn main() { | ||
let _foo = Foo; | ||
let _bar = Bar; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
[package] | ||
name = "pass_sibling_comment" | ||
version = "0.1.0" | ||
edition = "2024" | ||
publish = false | ||
|
||
[dependencies] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
pub struct Foo; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
#![warn(clippy::use_crate_prefix_for_self_imports)] | ||
|
||
mod foo; | ||
// some comments here | ||
use foo::Foo; | ||
|
||
fn main() { | ||
let _foo = Foo; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why check only those two files?
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we want to lint on
path/to/mod.rs
, we have to rewriteuse bar
touse crate::foo1::foo2::bar
Such a long list of segments is formidable