diff --git a/clippy_lints/src/attrs.rs b/clippy_lints/src/attrs.rs index 235ab4977ce4..ecf8e83375db 100644 --- a/clippy_lints/src/attrs.rs +++ b/clippy_lints/src/attrs.rs @@ -378,7 +378,9 @@ impl<'tcx> LateLintPass<'tcx> for Attributes { | "enum_glob_use" | "redundant_pub_crate" | "macro_use_imports" - | "unsafe_removed_from_name", + | "unsafe_removed_from_name" + | "module_name_repetitions" + | "single_component_path_imports" ) }) { diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 4e640d7ebf65..3ab5031696d5 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -792,7 +792,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_late_pass(|_| Box::new(floating_point_arithmetic::FloatingPointArithmetic)); store.register_early_pass(|| Box::new(as_conversions::AsConversions)); store.register_late_pass(|_| Box::new(let_underscore::LetUnderscore)); - store.register_early_pass(|| Box::new(single_component_path_imports::SingleComponentPathImports)); + store.register_early_pass(|| Box::::default()); let max_fn_params_bools = conf.max_fn_params_bools; let max_struct_bools = conf.max_struct_bools; store.register_late_pass(move |_| { diff --git a/clippy_lints/src/single_component_path_imports.rs b/clippy_lints/src/single_component_path_imports.rs index 66b79513032f..2036e85db7e8 100644 --- a/clippy_lints/src/single_component_path_imports.rs +++ b/clippy_lints/src/single_component_path_imports.rs @@ -1,8 +1,9 @@ use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_sugg}; +use rustc_ast::node_id::{NodeId, NodeMap}; use rustc_ast::{ptr::P, Crate, Item, ItemKind, MacroDef, ModKind, UseTreeKind}; use rustc_errors::Applicability; use rustc_lint::{EarlyContext, EarlyLintPass, LintContext}; -use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_session::{declare_tool_lint, impl_lint_pass}; use rustc_span::{edition::Edition, symbol::kw, Span, Symbol}; declare_clippy_lint! { @@ -33,51 +34,32 @@ declare_clippy_lint! { "imports with single component path are redundant" } -declare_lint_pass!(SingleComponentPathImports => [SINGLE_COMPONENT_PATH_IMPORTS]); +#[derive(Default)] +pub struct SingleComponentPathImports { + /// Buffer found usages to emit when visiting that item so that `#[allow]` works as expected + found: NodeMap>, +} + +struct SingleUse { + name: Symbol, + span: Span, + item_id: NodeId, + can_suggest: bool, +} + +impl_lint_pass!(SingleComponentPathImports => [SINGLE_COMPONENT_PATH_IMPORTS]); impl EarlyLintPass for SingleComponentPathImports { fn check_crate(&mut self, cx: &EarlyContext<'_>, krate: &Crate) { if cx.sess().opts.edition < Edition::Edition2018 { return; } - check_mod(cx, &krate.items); - } -} -fn check_mod(cx: &EarlyContext<'_>, items: &[P]) { - // keep track of imports reused with `self` keyword, - // such as `self::crypto_hash` in the example below - // ```rust,ignore - // use self::crypto_hash::{Algorithm, Hasher}; - // ``` - let mut imports_reused_with_self = Vec::new(); - - // keep track of single use statements - // such as `crypto_hash` in the example below - // ```rust,ignore - // use crypto_hash; - // ``` - let mut single_use_usages = Vec::new(); - - // keep track of macros defined in the module as we don't want it to trigger on this (#7106) - // ```rust,ignore - // macro_rules! foo { () => {} }; - // pub(crate) use foo; - // ``` - let mut macros = Vec::new(); - - for item in items { - track_uses( - cx, - item, - &mut imports_reused_with_self, - &mut single_use_usages, - &mut macros, - ); + self.check_mod(cx, &krate.items); } - for (name, span, can_suggest) in single_use_usages { - if !imports_reused_with_self.contains(&name) { + fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) { + for SingleUse { span, can_suggest, .. } in self.found.remove(&item.id).into_iter().flatten() { if can_suggest { span_lint_and_sugg( cx, @@ -102,74 +84,127 @@ fn check_mod(cx: &EarlyContext<'_>, items: &[P]) { } } -fn track_uses( - cx: &EarlyContext<'_>, - item: &Item, - imports_reused_with_self: &mut Vec, - single_use_usages: &mut Vec<(Symbol, Span, bool)>, - macros: &mut Vec, -) { - if item.span.from_expansion() || item.vis.kind.is_pub() { - return; +impl SingleComponentPathImports { + fn check_mod(&mut self, cx: &EarlyContext<'_>, items: &[P]) { + // keep track of imports reused with `self` keyword, such as `self::crypto_hash` in the example + // below. Removing the `use crypto_hash;` would make this a compile error + // ``` + // use crypto_hash; + // + // use self::crypto_hash::{Algorithm, Hasher}; + // ``` + let mut imports_reused_with_self = Vec::new(); + + // keep track of single use statements such as `crypto_hash` in the example below + // ``` + // use crypto_hash; + // ``` + let mut single_use_usages = Vec::new(); + + // keep track of macros defined in the module as we don't want it to trigger on this (#7106) + // ``` + // macro_rules! foo { () => {} }; + // pub(crate) use foo; + // ``` + let mut macros = Vec::new(); + + for item in items { + self.track_uses( + cx, + item, + &mut imports_reused_with_self, + &mut single_use_usages, + &mut macros, + ); + } + + for usage in single_use_usages { + if !imports_reused_with_self.contains(&usage.name) { + self.found.entry(usage.item_id).or_default().push(usage); + } + } } - match &item.kind { - ItemKind::Mod(_, ModKind::Loaded(ref items, ..)) => { - check_mod(cx, items); - }, - ItemKind::MacroDef(MacroDef { macro_rules: true, .. }) => { - macros.push(item.ident.name); - }, - ItemKind::Use(use_tree) => { - let segments = &use_tree.prefix.segments; + fn track_uses( + &mut self, + cx: &EarlyContext<'_>, + item: &Item, + imports_reused_with_self: &mut Vec, + single_use_usages: &mut Vec, + macros: &mut Vec, + ) { + if item.span.from_expansion() || item.vis.kind.is_pub() { + return; + } - // keep track of `use some_module;` usages - if segments.len() == 1 { - if let UseTreeKind::Simple(None, _, _) = use_tree.kind { - let name = segments[0].ident.name; - if !macros.contains(&name) { - single_use_usages.push((name, item.span, true)); + match &item.kind { + ItemKind::Mod(_, ModKind::Loaded(ref items, ..)) => { + self.check_mod(cx, items); + }, + ItemKind::MacroDef(MacroDef { macro_rules: true, .. }) => { + macros.push(item.ident.name); + }, + ItemKind::Use(use_tree) => { + let segments = &use_tree.prefix.segments; + + // keep track of `use some_module;` usages + if segments.len() == 1 { + if let UseTreeKind::Simple(None, _, _) = use_tree.kind { + let name = segments[0].ident.name; + if !macros.contains(&name) { + single_use_usages.push(SingleUse { + name, + span: item.span, + item_id: item.id, + can_suggest: true, + }); + } } + return; } - return; - } - if segments.is_empty() { - // keep track of `use {some_module, some_other_module};` usages - if let UseTreeKind::Nested(trees) = &use_tree.kind { - for tree in trees { - let segments = &tree.0.prefix.segments; - if segments.len() == 1 { - if let UseTreeKind::Simple(None, _, _) = tree.0.kind { - let name = segments[0].ident.name; - if !macros.contains(&name) { - single_use_usages.push((name, tree.0.span, false)); + if segments.is_empty() { + // keep track of `use {some_module, some_other_module};` usages + if let UseTreeKind::Nested(trees) = &use_tree.kind { + for tree in trees { + let segments = &tree.0.prefix.segments; + if segments.len() == 1 { + if let UseTreeKind::Simple(None, _, _) = tree.0.kind { + let name = segments[0].ident.name; + if !macros.contains(&name) { + single_use_usages.push(SingleUse { + name, + span: tree.0.span, + item_id: item.id, + can_suggest: false, + }); + } + } + } + } + } + } else { + // keep track of `use self::some_module` usages + if segments[0].ident.name == kw::SelfLower { + // simple case such as `use self::module::SomeStruct` + if segments.len() > 1 { + imports_reused_with_self.push(segments[1].ident.name); + return; + } + + // nested case such as `use self::{module1::Struct1, module2::Struct2}` + if let UseTreeKind::Nested(trees) = &use_tree.kind { + for tree in trees { + let segments = &tree.0.prefix.segments; + if !segments.is_empty() { + imports_reused_with_self.push(segments[0].ident.name); } } } } } - } else { - // keep track of `use self::some_module` usages - if segments[0].ident.name == kw::SelfLower { - // simple case such as `use self::module::SomeStruct` - if segments.len() > 1 { - imports_reused_with_self.push(segments[1].ident.name); - return; - } - - // nested case such as `use self::{module1::Struct1, module2::Struct2}` - if let UseTreeKind::Nested(trees) = &use_tree.kind { - for tree in trees { - let segments = &tree.0.prefix.segments; - if !segments.is_empty() { - imports_reused_with_self.push(segments[0].ident.name); - } - } - } - } - } - }, - _ => {}, + }, + _ => {}, + } } } diff --git a/tests/ui/single_component_path_imports.stderr b/tests/ui/single_component_path_imports.stderr index 509c88ac256a..71dcc25d6e5b 100644 --- a/tests/ui/single_component_path_imports.stderr +++ b/tests/ui/single_component_path_imports.stderr @@ -1,16 +1,16 @@ -error: this import is redundant - --> $DIR/single_component_path_imports.rs:23:5 - | -LL | use regex; - | ^^^^^^^^^^ help: remove it entirely - | - = note: `-D clippy::single-component-path-imports` implied by `-D warnings` - error: this import is redundant --> $DIR/single_component_path_imports.rs:5:1 | LL | use regex; | ^^^^^^^^^^ help: remove it entirely + | + = note: `-D clippy::single-component-path-imports` implied by `-D warnings` + +error: this import is redundant + --> $DIR/single_component_path_imports.rs:23:5 + | +LL | use regex; + | ^^^^^^^^^^ help: remove it entirely error: aborting due to 2 previous errors diff --git a/tests/ui/single_component_path_imports_nested_first.stderr b/tests/ui/single_component_path_imports_nested_first.stderr index 633546f6419a..330f285202d0 100644 --- a/tests/ui/single_component_path_imports_nested_first.stderr +++ b/tests/ui/single_component_path_imports_nested_first.stderr @@ -1,3 +1,11 @@ +error: this import is redundant + --> $DIR/single_component_path_imports_nested_first.rs:4:1 + | +LL | use regex; + | ^^^^^^^^^^ help: remove it entirely + | + = note: `-D clippy::single-component-path-imports` implied by `-D warnings` + error: this import is redundant --> $DIR/single_component_path_imports_nested_first.rs:13:10 | @@ -5,7 +13,6 @@ LL | use {regex, serde}; | ^^^^^ | = help: remove this import - = note: `-D clippy::single-component-path-imports` implied by `-D warnings` error: this import is redundant --> $DIR/single_component_path_imports_nested_first.rs:13:17 @@ -15,11 +22,5 @@ LL | use {regex, serde}; | = help: remove this import -error: this import is redundant - --> $DIR/single_component_path_imports_nested_first.rs:4:1 - | -LL | use regex; - | ^^^^^^^^^^ help: remove it entirely - error: aborting due to 3 previous errors diff --git a/tests/ui/useless_attribute.fixed b/tests/ui/useless_attribute.fixed index c23231a99e9f..871e4fb5c3a9 100644 --- a/tests/ui/useless_attribute.fixed +++ b/tests/ui/useless_attribute.fixed @@ -1,6 +1,7 @@ // run-rustfix // aux-build:proc_macro_derive.rs +#![allow(unused)] #![warn(clippy::useless_attribute)] #![warn(unreachable_pub)] #![feature(rustc_private)] @@ -16,6 +17,13 @@ extern crate rustc_middle; #[macro_use] extern crate proc_macro_derive; +fn test_indented_attr() { + #![allow(clippy::almost_swapped)] + use std::collections::HashSet; + + let _ = HashSet::::default(); +} + // don't lint on unused_import for `use` items #[allow(unused_imports)] use std::collections; @@ -63,13 +71,16 @@ mod c { pub(crate) struct S; } -fn test_indented_attr() { - #![allow(clippy::almost_swapped)] - use std::collections::HashSet; - - let _ = HashSet::::default(); +// https://github.com/rust-lang/rust-clippy/issues/7511 +pub mod split { + #[allow(clippy::module_name_repetitions)] + pub use regex::SplitN; } +// https://github.com/rust-lang/rust-clippy/issues/8768 +#[allow(clippy::single_component_path_imports)] +use regex; + fn main() { test_indented_attr(); } diff --git a/tests/ui/useless_attribute.rs b/tests/ui/useless_attribute.rs index 7a7b198ea607..cb50736ba395 100644 --- a/tests/ui/useless_attribute.rs +++ b/tests/ui/useless_attribute.rs @@ -1,6 +1,7 @@ // run-rustfix // aux-build:proc_macro_derive.rs +#![allow(unused)] #![warn(clippy::useless_attribute)] #![warn(unreachable_pub)] #![feature(rustc_private)] @@ -16,6 +17,13 @@ extern crate rustc_middle; #[macro_use] extern crate proc_macro_derive; +fn test_indented_attr() { + #[allow(clippy::almost_swapped)] + use std::collections::HashSet; + + let _ = HashSet::::default(); +} + // don't lint on unused_import for `use` items #[allow(unused_imports)] use std::collections; @@ -63,13 +71,16 @@ mod c { pub(crate) struct S; } -fn test_indented_attr() { - #[allow(clippy::almost_swapped)] - use std::collections::HashSet; - - let _ = HashSet::::default(); +// https://github.com/rust-lang/rust-clippy/issues/7511 +pub mod split { + #[allow(clippy::module_name_repetitions)] + pub use regex::SplitN; } +// https://github.com/rust-lang/rust-clippy/issues/8768 +#[allow(clippy::single_component_path_imports)] +use regex; + fn main() { test_indented_attr(); } diff --git a/tests/ui/useless_attribute.stderr b/tests/ui/useless_attribute.stderr index 255d28763553..a7ea0df22945 100644 --- a/tests/ui/useless_attribute.stderr +++ b/tests/ui/useless_attribute.stderr @@ -1,5 +1,5 @@ error: useless lint attribute - --> $DIR/useless_attribute.rs:8:1 + --> $DIR/useless_attribute.rs:9:1 | LL | #[allow(dead_code)] | ^^^^^^^^^^^^^^^^^^^ help: if you just forgot a `!`, use: `#![allow(dead_code)]` @@ -7,13 +7,13 @@ LL | #[allow(dead_code)] = note: `-D clippy::useless-attribute` implied by `-D warnings` error: useless lint attribute - --> $DIR/useless_attribute.rs:9:1 + --> $DIR/useless_attribute.rs:10:1 | LL | #[cfg_attr(feature = "cargo-clippy", allow(dead_code))] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: if you just forgot a `!`, use: `#![cfg_attr(feature = "cargo-clippy", allow(dead_code)` error: useless lint attribute - --> $DIR/useless_attribute.rs:67:5 + --> $DIR/useless_attribute.rs:21:5 | LL | #[allow(clippy::almost_swapped)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: if you just forgot a `!`, use: `#![allow(clippy::almost_swapped)]`