use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_sugg}; use rustc_ast::node_id::{NodeId, NodeMap}; use rustc_ast::ptr::P; use rustc_ast::visit::{walk_expr, Visitor}; use rustc_ast::{Crate, Expr, ExprKind, Item, ItemKind, MacroDef, ModKind, Ty, TyKind, UseTreeKind}; use rustc_errors::Applicability; use rustc_lint::{EarlyContext, EarlyLintPass, LintContext}; use rustc_session::impl_lint_pass; use rustc_span::edition::Edition; use rustc_span::symbol::kw; use rustc_span::{Span, Symbol}; declare_clippy_lint! { /// ### What it does /// Checking for imports with single component use path. /// /// ### Why is this bad? /// Import with single component use path such as `use cratename;` /// is not necessary, and thus should be removed. /// /// ### Example /// ```rust,ignore /// use regex; /// /// fn main() { /// regex::Regex::new(r"^\d{4}-\d{2}-\d{2}$").unwrap(); /// } /// ``` /// Better as /// ```rust,ignore /// fn main() { /// regex::Regex::new(r"^\d{4}-\d{2}-\d{2}$").unwrap(); /// } /// ``` #[clippy::version = "1.43.0"] pub SINGLE_COMPONENT_PATH_IMPORTS, style, "imports with single component path are redundant" } #[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; } self.check_mod(&krate.items); } 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, SINGLE_COMPONENT_PATH_IMPORTS, span, "this import is redundant", "remove it entirely", String::new(), Applicability::MachineApplicable, ); } else { span_lint_and_help( cx, SINGLE_COMPONENT_PATH_IMPORTS, span, "this import is redundant", None, "remove this import", ); } } } } #[derive(Default)] struct ImportUsageVisitor { // keep track of imports reused with `self` keyword, such as `self::std` in the example below. // Removing the `use std;` would make this a compile error (#10549) // ``` // use std; // // fn main() { // let _ = self::std::io::stdout(); // } // ``` imports_referenced_with_self: Vec, } impl<'tcx> Visitor<'tcx> for ImportUsageVisitor { fn visit_expr(&mut self, expr: &Expr) { if let ExprKind::Path(_, path) = &expr.kind && path.segments.len() > 1 && path.segments[0].ident.name == kw::SelfLower { self.imports_referenced_with_self.push(path.segments[1].ident.name); } walk_expr(self, expr); } fn visit_ty(&mut self, ty: &Ty) { if let TyKind::Path(_, path) = &ty.kind && path.segments.len() > 1 && path.segments[0].ident.name == kw::SelfLower { self.imports_referenced_with_self.push(path.segments[1].ident.name); } } } impl SingleComponentPathImports { fn check_mod(&mut self, 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(); let mut import_usage_visitor = ImportUsageVisitor::default(); for item in items { self.track_uses(item, &mut imports_reused_with_self, &mut single_use_usages, &mut macros); import_usage_visitor.visit_item(item); } for usage in single_use_usages { if !imports_reused_with_self.contains(&usage.name) && !import_usage_visitor.imports_referenced_with_self.contains(&usage.name) { self.found.entry(usage.item_id).or_default().push(usage); } } } fn track_uses( &mut self, 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; } match &item.kind { ItemKind::Mod(_, ModKind::Loaded(ref items, ..)) => { self.check_mod(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; } if segments.is_empty() { // keep track of `use {some_module, some_other_module};` usages if let UseTreeKind::Nested { items, .. } = &use_tree.kind { for tree in items { 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 { items, .. } = &use_tree.kind { for tree in items { let segments = &tree.0.prefix.segments; if !segments.is_empty() { imports_reused_with_self.push(segments[0].ident.name); } } } } } }, _ => {}, } } }