| 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::{declare_tool_lint, 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<Vec<SingleUse>>, |
| } |
| |
| 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<Symbol>, |
| } |
| |
| 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<Item>]) { |
| // 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<Symbol>, |
| single_use_usages: &mut Vec<SingleUse>, |
| macros: &mut Vec<Symbol>, |
| ) { |
| 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(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); |
| } |
| } |
| } |
| } |
| } |
| }, |
| _ => {}, |
| } |
| } |
| } |