Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6940,6 +6940,7 @@ Released 2018-09-13
[`unwrap_or_else_default`]: https://rust-lang.github.io/rust-clippy/master/index.html#unwrap_or_else_default
[`unwrap_used`]: https://rust-lang.github.io/rust-clippy/master/index.html#unwrap_used
[`upper_case_acronyms`]: https://rust-lang.github.io/rust-clippy/master/index.html#upper_case_acronyms
[`use_crate_prefix_for_self_imports`]: https://rust-lang.github.io/rust-clippy/master/index.html#use_crate_prefix_for_self_imports
[`use_debug`]: https://rust-lang.github.io/rust-clippy/master/index.html#use_debug
[`use_self`]: https://rust-lang.github.io/rust-clippy/master/index.html#use_self
[`used_underscore_binding`]: https://rust-lang.github.io/rust-clippy/master/index.html#used_underscore_binding
Expand Down
1 change: 1 addition & 0 deletions clippy_lints/src/declared_lints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -774,6 +774,7 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[
crate::unwrap::UNNECESSARY_UNWRAP_INFO,
crate::unwrap_in_result::UNWRAP_IN_RESULT_INFO,
crate::upper_case_acronyms::UPPER_CASE_ACRONYMS_INFO,
crate::use_crate_prefix_for_self_imports::USE_CRATE_PREFIX_FOR_SELF_IMPORTS_INFO,
crate::use_self::USE_SELF_INFO,
crate::useless_concat::USELESS_CONCAT_INFO,
crate::useless_conversion::USELESS_CONVERSION_INFO,
Expand Down
4 changes: 4 additions & 0 deletions clippy_lints/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,7 @@ mod unused_unit;
mod unwrap;
mod unwrap_in_result;
mod upper_case_acronyms;
mod use_crate_prefix_for_self_imports;
mod use_self;
mod useless_concat;
mod useless_conversion;
Expand Down Expand Up @@ -834,5 +835,8 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co
store.register_late_pass(|_| Box::new(toplevel_ref_arg::ToplevelRefArg));
store.register_late_pass(|_| Box::new(volatile_composites::VolatileComposites));
store.register_late_pass(|_| Box::new(replace_box::ReplaceBox));
store.register_late_pass(|_| {
Box::<use_crate_prefix_for_self_imports::UseCratePrefixForSelfImports<'_, '_>>::default()
});
// add lints here, do not remove this comment, it's used in `new_lint`
}
156 changes: 156 additions & 0 deletions clippy_lints/src/use_crate_prefix_for_self_imports.rs
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") {
Copy link
Member

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?

Copy link
Contributor Author

@lengyijun lengyijun Sep 19, 2025

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 rewrite use bar to use crate::foo1::foo2::bar
Such a long list of segments is formidable

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",
Copy link
Member

Choose a reason for hiding this comment

The 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)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is the import "not clear"?

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;
}
}
10 changes: 10 additions & 0 deletions tests/ui-cargo/use_crate_prefix_for_self_imports/fail/Cargo.stderr
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;
}