|
1 | | -use clippy_utils::diagnostics::span_lint_and_sugg; |
| 1 | +use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_sugg}; |
2 | 2 | use clippy_utils::in_macro; |
3 | | -use if_chain::if_chain; |
4 | | -use rustc_ast::{Item, ItemKind, UseTreeKind}; |
| 3 | +use rustc_ast::{ptr::P, Crate, Item, ItemKind, ModKind, UseTreeKind}; |
5 | 4 | use rustc_errors::Applicability; |
6 | 5 | use rustc_lint::{EarlyContext, EarlyLintPass}; |
7 | 6 | use rustc_session::{declare_lint_pass, declare_tool_lint}; |
8 | | -use rustc_span::edition::Edition; |
| 7 | +use rustc_span::{edition::Edition, symbol::kw, Span, Symbol}; |
9 | 8 |
|
10 | 9 | declare_clippy_lint! { |
11 | 10 | /// **What it does:** Checking for imports with single component use path. |
@@ -38,26 +37,120 @@ declare_clippy_lint! { |
38 | 37 | declare_lint_pass!(SingleComponentPathImports => [SINGLE_COMPONENT_PATH_IMPORTS]); |
39 | 38 |
|
40 | 39 | impl EarlyLintPass for SingleComponentPathImports { |
41 | | - fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) { |
42 | | - if_chain! { |
43 | | - if !in_macro(item.span); |
44 | | - if cx.sess.opts.edition >= Edition::Edition2018; |
45 | | - if !item.vis.kind.is_pub(); |
46 | | - if let ItemKind::Use(use_tree) = &item.kind; |
47 | | - if let segments = &use_tree.prefix.segments; |
48 | | - if segments.len() == 1; |
49 | | - if let UseTreeKind::Simple(None, _, _) = use_tree.kind; |
50 | | - then { |
| 40 | + fn check_crate(&mut self, cx: &EarlyContext<'_>, krate: &Crate) { |
| 41 | + if cx.sess.opts.edition < Edition::Edition2018 { |
| 42 | + return; |
| 43 | + } |
| 44 | + check_mod(cx, &krate.items); |
| 45 | + } |
| 46 | +} |
| 47 | + |
| 48 | +fn check_mod(cx: &EarlyContext<'_>, items: &[P<Item>]) { |
| 49 | + // keep track of imports reused with `self` keyword, |
| 50 | + // such as `self::crypto_hash` in the example below |
| 51 | + // ```rust,ignore |
| 52 | + // use self::crypto_hash::{Algorithm, Hasher}; |
| 53 | + // ``` |
| 54 | + let mut imports_reused_with_self = Vec::new(); |
| 55 | + |
| 56 | + // keep track of single use statements |
| 57 | + // such as `crypto_hash` in the example below |
| 58 | + // ```rust,ignore |
| 59 | + // use crypto_hash; |
| 60 | + // ``` |
| 61 | + let mut single_use_usages = Vec::new(); |
| 62 | + |
| 63 | + for item in items { |
| 64 | + track_uses(cx, &item, &mut imports_reused_with_self, &mut single_use_usages); |
| 65 | + } |
| 66 | + |
| 67 | + for single_use in &single_use_usages { |
| 68 | + if !imports_reused_with_self.contains(&single_use.0) { |
| 69 | + let can_suggest = single_use.2; |
| 70 | + if can_suggest { |
51 | 71 | span_lint_and_sugg( |
52 | 72 | cx, |
53 | 73 | SINGLE_COMPONENT_PATH_IMPORTS, |
54 | | - item.span, |
| 74 | + single_use.1, |
55 | 75 | "this import is redundant", |
56 | 76 | "remove it entirely", |
57 | 77 | String::new(), |
58 | | - Applicability::MachineApplicable |
| 78 | + Applicability::MachineApplicable, |
| 79 | + ); |
| 80 | + } else { |
| 81 | + span_lint_and_help( |
| 82 | + cx, |
| 83 | + SINGLE_COMPONENT_PATH_IMPORTS, |
| 84 | + single_use.1, |
| 85 | + "this import is redundant", |
| 86 | + None, |
| 87 | + "remove this import", |
59 | 88 | ); |
60 | 89 | } |
61 | 90 | } |
62 | 91 | } |
63 | 92 | } |
| 93 | + |
| 94 | +fn track_uses( |
| 95 | + cx: &EarlyContext<'_>, |
| 96 | + item: &Item, |
| 97 | + imports_reused_with_self: &mut Vec<Symbol>, |
| 98 | + single_use_usages: &mut Vec<(Symbol, Span, bool)>, |
| 99 | +) { |
| 100 | + if in_macro(item.span) || item.vis.kind.is_pub() { |
| 101 | + return; |
| 102 | + } |
| 103 | + |
| 104 | + match &item.kind { |
| 105 | + ItemKind::Mod(_, ModKind::Loaded(ref items, ..)) => { |
| 106 | + check_mod(cx, &items); |
| 107 | + }, |
| 108 | + ItemKind::Use(use_tree) => { |
| 109 | + let segments = &use_tree.prefix.segments; |
| 110 | + |
| 111 | + // keep track of `use some_module;` usages |
| 112 | + if segments.len() == 1 { |
| 113 | + if let UseTreeKind::Simple(None, _, _) = use_tree.kind { |
| 114 | + let ident = &segments[0].ident; |
| 115 | + single_use_usages.push((ident.name, item.span, true)); |
| 116 | + } |
| 117 | + return; |
| 118 | + } |
| 119 | + |
| 120 | + if segments.is_empty() { |
| 121 | + // keep track of `use {some_module, some_other_module};` usages |
| 122 | + if let UseTreeKind::Nested(trees) = &use_tree.kind { |
| 123 | + for tree in trees { |
| 124 | + let segments = &tree.0.prefix.segments; |
| 125 | + if segments.len() == 1 { |
| 126 | + if let UseTreeKind::Simple(None, _, _) = tree.0.kind { |
| 127 | + let ident = &segments[0].ident; |
| 128 | + single_use_usages.push((ident.name, tree.0.span, false)); |
| 129 | + } |
| 130 | + } |
| 131 | + } |
| 132 | + } |
| 133 | + } else { |
| 134 | + // keep track of `use self::some_module` usages |
| 135 | + if segments[0].ident.name == kw::SelfLower { |
| 136 | + // simple case such as `use self::module::SomeStruct` |
| 137 | + if segments.len() > 1 { |
| 138 | + imports_reused_with_self.push(segments[1].ident.name); |
| 139 | + return; |
| 140 | + } |
| 141 | + |
| 142 | + // nested case such as `use self::{module1::Struct1, module2::Struct2}` |
| 143 | + if let UseTreeKind::Nested(trees) = &use_tree.kind { |
| 144 | + for tree in trees { |
| 145 | + let segments = &tree.0.prefix.segments; |
| 146 | + if !segments.is_empty() { |
| 147 | + imports_reused_with_self.push(segments[0].ident.name); |
| 148 | + } |
| 149 | + } |
| 150 | + } |
| 151 | + } |
| 152 | + } |
| 153 | + }, |
| 154 | + _ => {}, |
| 155 | + } |
| 156 | +} |
0 commit comments