diff --git a/CHANGELOG.md b/CHANGELOG.md index b2e9f6d1dd3e..f0a6af0e0a3c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs index 375d179681da..c4512a073e46 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -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, diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 149785c59447..f3dd9732a6f6 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -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; @@ -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::>::default() + }); // add lints here, do not remove this comment, it's used in `new_lint` } diff --git a/clippy_lints/src/use_crate_prefix_for_self_imports.rs b/clippy_lints/src/use_crate_prefix_for_self_imports.rs new file mode 100644 index 000000000000..6c92770dbe9e --- /dev/null +++ b/clippy_lints/src/use_crate_prefix_for_self_imports.rs @@ -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, + latest_span: Option, +} + +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", + "prefix with `crate::`", + format!("crate::{root}"), + Applicability::MachineApplicable, + ); + } + } + } + } + + fn clear(&mut self) { + self.use_block.clear(); + self.mod_names.clear(); + self.latest_span = None; + } +} diff --git a/tests/ui-cargo/use_crate_prefix_for_self_imports/fail/Cargo.stderr b/tests/ui-cargo/use_crate_prefix_for_self_imports/fail/Cargo.stderr new file mode 100644 index 000000000000..e559b590f5f7 --- /dev/null +++ b/tests/ui-cargo/use_crate_prefix_for_self_imports/fail/Cargo.stderr @@ -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 diff --git a/tests/ui-cargo/use_crate_prefix_for_self_imports/fail/Cargo.toml b/tests/ui-cargo/use_crate_prefix_for_self_imports/fail/Cargo.toml new file mode 100644 index 000000000000..4fd6d77abc86 --- /dev/null +++ b/tests/ui-cargo/use_crate_prefix_for_self_imports/fail/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "fail" +version = "0.1.0" +edition = "2024" +publish = false + +[dependencies] diff --git a/tests/ui-cargo/use_crate_prefix_for_self_imports/fail/src/foo.rs b/tests/ui-cargo/use_crate_prefix_for_self_imports/fail/src/foo.rs new file mode 100644 index 000000000000..4a835673a596 --- /dev/null +++ b/tests/ui-cargo/use_crate_prefix_for_self_imports/fail/src/foo.rs @@ -0,0 +1 @@ +pub struct Foo; diff --git a/tests/ui-cargo/use_crate_prefix_for_self_imports/fail/src/main.rs b/tests/ui-cargo/use_crate_prefix_for_self_imports/fail/src/main.rs new file mode 100644 index 000000000000..7e75c10f1b5b --- /dev/null +++ b/tests/ui-cargo/use_crate_prefix_for_self_imports/fail/src/main.rs @@ -0,0 +1,9 @@ +#![warn(clippy::use_crate_prefix_for_self_imports)] + +use foo::Foo; + +mod foo; + +fn main() { + let _foo = Foo; +} diff --git a/tests/ui-cargo/use_crate_prefix_for_self_imports/pass/Cargo.toml b/tests/ui-cargo/use_crate_prefix_for_self_imports/pass/Cargo.toml new file mode 100644 index 000000000000..2400bd010748 --- /dev/null +++ b/tests/ui-cargo/use_crate_prefix_for_self_imports/pass/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "pass" +version = "0.1.0" +edition = "2024" +publish = false + +[dependencies] diff --git a/tests/ui-cargo/use_crate_prefix_for_self_imports/pass/src/foo.rs b/tests/ui-cargo/use_crate_prefix_for_self_imports/pass/src/foo.rs new file mode 100644 index 000000000000..4a835673a596 --- /dev/null +++ b/tests/ui-cargo/use_crate_prefix_for_self_imports/pass/src/foo.rs @@ -0,0 +1 @@ +pub struct Foo; diff --git a/tests/ui-cargo/use_crate_prefix_for_self_imports/pass/src/main.rs b/tests/ui-cargo/use_crate_prefix_for_self_imports/pass/src/main.rs new file mode 100644 index 000000000000..820faee159b7 --- /dev/null +++ b/tests/ui-cargo/use_crate_prefix_for_self_imports/pass/src/main.rs @@ -0,0 +1,9 @@ +#![warn(clippy::use_crate_prefix_for_self_imports)] + +use crate::foo::Foo; + +mod foo; + +fn main() { + let _foo = Foo; +} diff --git a/tests/ui-cargo/use_crate_prefix_for_self_imports/pass_attribute/Cargo.toml b/tests/ui-cargo/use_crate_prefix_for_self_imports/pass_attribute/Cargo.toml new file mode 100644 index 000000000000..793f14562d91 --- /dev/null +++ b/tests/ui-cargo/use_crate_prefix_for_self_imports/pass_attribute/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "pass_attribute" +version = "0.1.0" +edition = "2024" +publish = false + +[dependencies] + +[features] +default = ["bar"] +bar = [] diff --git a/tests/ui-cargo/use_crate_prefix_for_self_imports/pass_attribute/src/foo.rs b/tests/ui-cargo/use_crate_prefix_for_self_imports/pass_attribute/src/foo.rs new file mode 100644 index 000000000000..4a835673a596 --- /dev/null +++ b/tests/ui-cargo/use_crate_prefix_for_self_imports/pass_attribute/src/foo.rs @@ -0,0 +1 @@ +pub struct Foo; diff --git a/tests/ui-cargo/use_crate_prefix_for_self_imports/pass_attribute/src/lib.rs b/tests/ui-cargo/use_crate_prefix_for_self_imports/pass_attribute/src/lib.rs new file mode 100644 index 000000000000..1ebe7984e084 --- /dev/null +++ b/tests/ui-cargo/use_crate_prefix_for_self_imports/pass_attribute/src/lib.rs @@ -0,0 +1,6 @@ +#![warn(clippy::use_crate_prefix_for_self_imports)] + +#[cfg(feature = "bar")] +mod foo; +#[cfg(feature = "bar")] +pub use foo::Foo; diff --git a/tests/ui-cargo/use_crate_prefix_for_self_imports/pass_attribute_2/Cargo.toml b/tests/ui-cargo/use_crate_prefix_for_self_imports/pass_attribute_2/Cargo.toml new file mode 100644 index 000000000000..ceb763d04aa9 --- /dev/null +++ b/tests/ui-cargo/use_crate_prefix_for_self_imports/pass_attribute_2/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "pass_attribute_2" +version = "0.1.0" +edition = "2024" +publish = false + +[dependencies] diff --git a/tests/ui-cargo/use_crate_prefix_for_self_imports/pass_attribute_2/src/foo.rs b/tests/ui-cargo/use_crate_prefix_for_self_imports/pass_attribute_2/src/foo.rs new file mode 100644 index 000000000000..4a835673a596 --- /dev/null +++ b/tests/ui-cargo/use_crate_prefix_for_self_imports/pass_attribute_2/src/foo.rs @@ -0,0 +1 @@ +pub struct Foo; diff --git a/tests/ui-cargo/use_crate_prefix_for_self_imports/pass_attribute_2/src/lib.rs b/tests/ui-cargo/use_crate_prefix_for_self_imports/pass_attribute_2/src/lib.rs new file mode 100644 index 000000000000..0633d3a88fc9 --- /dev/null +++ b/tests/ui-cargo/use_crate_prefix_for_self_imports/pass_attribute_2/src/lib.rs @@ -0,0 +1,5 @@ +#![warn(clippy::use_crate_prefix_for_self_imports)] + +mod foo; +#[doc(hidden)] +pub use foo::Foo; diff --git a/tests/ui-cargo/use_crate_prefix_for_self_imports/pass_sibling_2/Cargo.toml b/tests/ui-cargo/use_crate_prefix_for_self_imports/pass_sibling_2/Cargo.toml new file mode 100644 index 000000000000..3e7b3ad1343b --- /dev/null +++ b/tests/ui-cargo/use_crate_prefix_for_self_imports/pass_sibling_2/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "pass_sibling_2" +version = "0.1.0" +edition = "2024" +publish = false + +[dependencies] diff --git a/tests/ui-cargo/use_crate_prefix_for_self_imports/pass_sibling_2/src/foo.rs b/tests/ui-cargo/use_crate_prefix_for_self_imports/pass_sibling_2/src/foo.rs new file mode 100644 index 000000000000..4a835673a596 --- /dev/null +++ b/tests/ui-cargo/use_crate_prefix_for_self_imports/pass_sibling_2/src/foo.rs @@ -0,0 +1 @@ +pub struct Foo; diff --git a/tests/ui-cargo/use_crate_prefix_for_self_imports/pass_sibling_2/src/main.rs b/tests/ui-cargo/use_crate_prefix_for_self_imports/pass_sibling_2/src/main.rs new file mode 100644 index 000000000000..9502ca88022f --- /dev/null +++ b/tests/ui-cargo/use_crate_prefix_for_self_imports/pass_sibling_2/src/main.rs @@ -0,0 +1,8 @@ +#![warn(clippy::use_crate_prefix_for_self_imports)] + +use foo::Foo; +mod foo; + +fn main() { + let _foo = Foo; +} diff --git a/tests/ui-cargo/use_crate_prefix_for_self_imports/pass_sibling_3/Cargo.toml b/tests/ui-cargo/use_crate_prefix_for_self_imports/pass_sibling_3/Cargo.toml new file mode 100644 index 000000000000..f0889780811b --- /dev/null +++ b/tests/ui-cargo/use_crate_prefix_for_self_imports/pass_sibling_3/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "pass_sibling_3" +version = "0.1.0" +edition = "2024" +publish = false + +[dependencies] diff --git a/tests/ui-cargo/use_crate_prefix_for_self_imports/pass_sibling_3/src/foo.rs b/tests/ui-cargo/use_crate_prefix_for_self_imports/pass_sibling_3/src/foo.rs new file mode 100644 index 000000000000..3e1446bda001 --- /dev/null +++ b/tests/ui-cargo/use_crate_prefix_for_self_imports/pass_sibling_3/src/foo.rs @@ -0,0 +1,2 @@ +pub struct Foo; +pub struct Bar; diff --git a/tests/ui-cargo/use_crate_prefix_for_self_imports/pass_sibling_3/src/main.rs b/tests/ui-cargo/use_crate_prefix_for_self_imports/pass_sibling_3/src/main.rs new file mode 100644 index 000000000000..0f62eabb5fef --- /dev/null +++ b/tests/ui-cargo/use_crate_prefix_for_self_imports/pass_sibling_3/src/main.rs @@ -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; +} diff --git a/tests/ui-cargo/use_crate_prefix_for_self_imports/pass_sibling_4/Cargo.toml b/tests/ui-cargo/use_crate_prefix_for_self_imports/pass_sibling_4/Cargo.toml new file mode 100644 index 000000000000..5be4b1276615 --- /dev/null +++ b/tests/ui-cargo/use_crate_prefix_for_self_imports/pass_sibling_4/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "pass_sibling_4" +version = "0.1.0" +edition = "2024" +publish = false + +[dependencies] diff --git a/tests/ui-cargo/use_crate_prefix_for_self_imports/pass_sibling_4/src/foo.rs b/tests/ui-cargo/use_crate_prefix_for_self_imports/pass_sibling_4/src/foo.rs new file mode 100644 index 000000000000..3e1446bda001 --- /dev/null +++ b/tests/ui-cargo/use_crate_prefix_for_self_imports/pass_sibling_4/src/foo.rs @@ -0,0 +1,2 @@ +pub struct Foo; +pub struct Bar; diff --git a/tests/ui-cargo/use_crate_prefix_for_self_imports/pass_sibling_4/src/main.rs b/tests/ui-cargo/use_crate_prefix_for_self_imports/pass_sibling_4/src/main.rs new file mode 100644 index 000000000000..22fc2fafb4df --- /dev/null +++ b/tests/ui-cargo/use_crate_prefix_for_self_imports/pass_sibling_4/src/main.rs @@ -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; +} diff --git a/tests/ui-cargo/use_crate_prefix_for_self_imports/pass_sibling_comment/Cargo.toml b/tests/ui-cargo/use_crate_prefix_for_self_imports/pass_sibling_comment/Cargo.toml new file mode 100644 index 000000000000..e6b8b5732c0a --- /dev/null +++ b/tests/ui-cargo/use_crate_prefix_for_self_imports/pass_sibling_comment/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "pass_sibling_comment" +version = "0.1.0" +edition = "2024" +publish = false + +[dependencies] diff --git a/tests/ui-cargo/use_crate_prefix_for_self_imports/pass_sibling_comment/src/foo.rs b/tests/ui-cargo/use_crate_prefix_for_self_imports/pass_sibling_comment/src/foo.rs new file mode 100644 index 000000000000..4a835673a596 --- /dev/null +++ b/tests/ui-cargo/use_crate_prefix_for_self_imports/pass_sibling_comment/src/foo.rs @@ -0,0 +1 @@ +pub struct Foo; diff --git a/tests/ui-cargo/use_crate_prefix_for_self_imports/pass_sibling_comment/src/main.rs b/tests/ui-cargo/use_crate_prefix_for_self_imports/pass_sibling_comment/src/main.rs new file mode 100644 index 000000000000..27a166b0f09b --- /dev/null +++ b/tests/ui-cargo/use_crate_prefix_for_self_imports/pass_sibling_comment/src/main.rs @@ -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; +}