|
| 1 | +use crate::utils::{in_macro, snippet_with_applicability, span_lint_and_sugg}; |
| 2 | +use if_chain::if_chain; |
| 3 | +use rustc::ty::DefIdTree; |
| 4 | +use rustc_data_structures::fx::FxHashSet; |
| 5 | +use rustc_errors::Applicability; |
| 6 | +use rustc_hir::def_id::DefId; |
| 7 | +use rustc_hir::intravisit::{walk_item, NestedVisitorMap, Visitor}; |
| 8 | +use rustc_hir::*; |
| 9 | +use rustc_lint::{LateContext, LateLintPass}; |
| 10 | +use rustc_session::{declare_lint_pass, declare_tool_lint}; |
| 11 | +use rustc_span::{symbol::Symbol, BytePos}; |
| 12 | + |
| 13 | +declare_clippy_lint! { |
| 14 | + /// **What it does:** Checks for wildcard imports `use _::*`. |
| 15 | + /// |
| 16 | + /// **Why is this bad?** wildcard imports can polute the namespace. This is especially bad if |
| 17 | + /// you try to import something through a wildcard, that already has been imported by name from |
| 18 | + /// a different source: |
| 19 | + /// |
| 20 | + /// ```rust,ignore |
| 21 | + /// use crate1::foo; // Imports a function named foo |
| 22 | + /// use crate2::*; // Has a function named foo |
| 23 | + /// |
| 24 | + /// foo(); // Calls crate1::foo |
| 25 | + /// ``` |
| 26 | + /// |
| 27 | + /// This can lead to confusing error messages at best and to unexpected behavior at worst. |
| 28 | + /// |
| 29 | + /// **Known problems:** If macros are imported through the wildcard, this macro is not included |
| 30 | + /// by the suggestion and has to be added by hand. |
| 31 | + /// |
| 32 | + /// **Example:** |
| 33 | + /// |
| 34 | + /// Bad: |
| 35 | + /// ```rust,ignore |
| 36 | + /// use crate1::*; |
| 37 | + /// |
| 38 | + /// foo(); |
| 39 | + /// ``` |
| 40 | + /// |
| 41 | + /// Good: |
| 42 | + /// ```rust,ignore |
| 43 | + /// use crate1::foo; |
| 44 | + /// |
| 45 | + /// foo(); |
| 46 | + /// ``` |
| 47 | + pub WILDCARD_IMPORTS, |
| 48 | + pedantic, |
| 49 | + "lint `use _::*` statements" |
| 50 | +} |
| 51 | + |
| 52 | +declare_lint_pass!(WildcardImports => [WILDCARD_IMPORTS]); |
| 53 | + |
| 54 | +impl LateLintPass<'_, '_> for WildcardImports { |
| 55 | + fn check_item(&mut self, cx: &LateContext<'_, '_>, item: &Item<'_>) { |
| 56 | + if item.vis.node.is_pub() || item.vis.node.is_pub_restricted() { |
| 57 | + return; |
| 58 | + } |
| 59 | + if_chain! { |
| 60 | + if !in_macro(item.span); |
| 61 | + if let ItemKind::Use(use_path, UseKind::Glob) = &item.kind; |
| 62 | + if let Some(def_id) = use_path.res.opt_def_id(); |
| 63 | + then { |
| 64 | + let hir = cx.tcx.hir(); |
| 65 | + let parent_id = hir.get_parent_item(item.hir_id); |
| 66 | + let (items, in_module) = if parent_id == CRATE_HIR_ID { |
| 67 | + let items = hir |
| 68 | + .krate() |
| 69 | + .module |
| 70 | + .item_ids |
| 71 | + .iter() |
| 72 | + .map(|item_id| hir.get(item_id.id)) |
| 73 | + .filter_map(|node| { |
| 74 | + if let Node::Item(item) = node { |
| 75 | + Some(item) |
| 76 | + } else { |
| 77 | + None |
| 78 | + } |
| 79 | + }) |
| 80 | + .collect(); |
| 81 | + (items, true) |
| 82 | + } else if let Node::Item(item) = hir.get(parent_id) { |
| 83 | + (vec![item], false) |
| 84 | + } else { |
| 85 | + (vec![], false) |
| 86 | + }; |
| 87 | + |
| 88 | + let mut import_used_visitor = ImportsUsedVisitor { |
| 89 | + cx, |
| 90 | + wildcard_def_id: def_id, |
| 91 | + in_module, |
| 92 | + used_imports: FxHashSet::default(), |
| 93 | + }; |
| 94 | + for item in items { |
| 95 | + import_used_visitor.visit_item(item); |
| 96 | + } |
| 97 | + |
| 98 | + if !import_used_visitor.used_imports.is_empty() { |
| 99 | + let module_name = use_path |
| 100 | + .segments |
| 101 | + .iter() |
| 102 | + .last() |
| 103 | + .expect("path has at least one segment") |
| 104 | + .ident |
| 105 | + .name; |
| 106 | + |
| 107 | + let mut applicability = Applicability::MachineApplicable; |
| 108 | + let import_source = snippet_with_applicability(cx, use_path.span, "..", &mut applicability); |
| 109 | + let (span, braced_glob) = if import_source.is_empty() { |
| 110 | + // This is a `_::{_, *}` import |
| 111 | + // Probably it's `_::{self, *}`, in that case we don't want to suggest to |
| 112 | + // import `self`. |
| 113 | + // If it is something else, we also don't want to include `self` in the |
| 114 | + // suggestion, since either it was imported through another use statement: |
| 115 | + // ``` |
| 116 | + // use foo::bar; |
| 117 | + // use foo::bar::{baz, *}; |
| 118 | + // ``` |
| 119 | + // or it couldn't be used anywhere. |
| 120 | + ( |
| 121 | + use_path.span.with_hi(use_path.span.hi() + BytePos(1)), |
| 122 | + true, |
| 123 | + ) |
| 124 | + } else { |
| 125 | + ( |
| 126 | + use_path.span.with_hi(use_path.span.hi() + BytePos(3)), |
| 127 | + false, |
| 128 | + ) |
| 129 | + }; |
| 130 | + |
| 131 | + let imports_string = if import_used_visitor.used_imports.len() == 1 { |
| 132 | + // We don't need to check for accidental suggesting the module name instead |
| 133 | + // of `self` here, since if `used_imports.len() == 1`, and the only usage |
| 134 | + // is `self`, then it's not through a `*` and if there is a `*`, it gets |
| 135 | + // already linted by `unused_imports` of rustc. |
| 136 | + import_used_visitor.used_imports.iter().next().unwrap().to_string() |
| 137 | + } else { |
| 138 | + let mut imports = import_used_visitor |
| 139 | + .used_imports |
| 140 | + .iter() |
| 141 | + .filter_map(|import_name| { |
| 142 | + if braced_glob && *import_name == module_name { |
| 143 | + None |
| 144 | + } else if *import_name == module_name { |
| 145 | + Some("self".to_string()) |
| 146 | + } else { |
| 147 | + Some(import_name.to_string()) |
| 148 | + } |
| 149 | + }) |
| 150 | + .collect::<Vec<_>>(); |
| 151 | + imports.sort(); |
| 152 | + if braced_glob { |
| 153 | + imports.join(", ") |
| 154 | + } else { |
| 155 | + format!("{{{}}}", imports.join(", ")) |
| 156 | + } |
| 157 | + }; |
| 158 | + |
| 159 | + let sugg = if import_source.is_empty() { |
| 160 | + imports_string |
| 161 | + } else { |
| 162 | + format!("{}::{}", import_source, imports_string) |
| 163 | + }; |
| 164 | + |
| 165 | + span_lint_and_sugg( |
| 166 | + cx, |
| 167 | + WILDCARD_IMPORTS, |
| 168 | + span, |
| 169 | + "usage of wildcard import", |
| 170 | + "try", |
| 171 | + sugg, |
| 172 | + applicability, |
| 173 | + ); |
| 174 | + } |
| 175 | + } |
| 176 | + } |
| 177 | + } |
| 178 | +} |
| 179 | + |
| 180 | +struct ImportsUsedVisitor<'a, 'tcx> { |
| 181 | + cx: &'a LateContext<'a, 'tcx>, |
| 182 | + wildcard_def_id: def_id::DefId, |
| 183 | + in_module: bool, |
| 184 | + used_imports: FxHashSet<Symbol>, |
| 185 | +} |
| 186 | + |
| 187 | +impl<'a, 'tcx> Visitor<'tcx> for ImportsUsedVisitor<'a, 'tcx> { |
| 188 | + type Map = Map<'tcx>; |
| 189 | + |
| 190 | + fn visit_item(&mut self, item: &'tcx Item<'_>) { |
| 191 | + match item.kind { |
| 192 | + ItemKind::Use(..) => {}, |
| 193 | + ItemKind::Mod(..) if self.in_module => {}, |
| 194 | + ItemKind::Mod(..) => self.in_module = true, |
| 195 | + _ => walk_item(self, item), |
| 196 | + } |
| 197 | + } |
| 198 | + |
| 199 | + fn visit_path(&mut self, path: &Path<'_>, _: HirId) { |
| 200 | + if let Some(def_id) = self.first_path_segment_def_id(path) { |
| 201 | + // Check if the function/enum/... was exported |
| 202 | + if let Some(exports) = self.cx.tcx.module_exports(self.wildcard_def_id) { |
| 203 | + for export in exports { |
| 204 | + if let Some(export_def_id) = export.res.opt_def_id() { |
| 205 | + if export_def_id == def_id { |
| 206 | + self.used_imports.insert( |
| 207 | + path.segments |
| 208 | + .iter() |
| 209 | + .next() |
| 210 | + .expect("path has at least one segment") |
| 211 | + .ident |
| 212 | + .name, |
| 213 | + ); |
| 214 | + return; |
| 215 | + } |
| 216 | + } |
| 217 | + } |
| 218 | + } |
| 219 | + |
| 220 | + // Check if it is directly in the module |
| 221 | + if let Some(parent_def_id) = self.cx.tcx.parent(def_id) { |
| 222 | + if self.wildcard_def_id == parent_def_id { |
| 223 | + self.used_imports.insert( |
| 224 | + path.segments |
| 225 | + .iter() |
| 226 | + .next() |
| 227 | + .expect("path has at least one segment") |
| 228 | + .ident |
| 229 | + .name, |
| 230 | + ); |
| 231 | + } |
| 232 | + } |
| 233 | + } |
| 234 | + } |
| 235 | + |
| 236 | + fn nested_visit_map(&mut self) -> NestedVisitorMap<'_, Self::Map> { |
| 237 | + NestedVisitorMap::All(&self.cx.tcx.hir()) |
| 238 | + } |
| 239 | +} |
| 240 | + |
| 241 | +impl ImportsUsedVisitor<'_, '_> { |
| 242 | + fn skip_def_id(&self, def_id: DefId) -> DefId { |
| 243 | + let def_key = self.cx.tcx.def_key(def_id); |
| 244 | + match def_key.disambiguated_data.data { |
| 245 | + DefPathData::Ctor => { |
| 246 | + if let Some(def_id) = self.cx.tcx.parent(def_id) { |
| 247 | + self.skip_def_id(def_id) |
| 248 | + } else { |
| 249 | + def_id |
| 250 | + } |
| 251 | + }, |
| 252 | + _ => def_id, |
| 253 | + } |
| 254 | + } |
| 255 | + |
| 256 | + fn first_path_segment_def_id(&self, path: &Path<'_>) -> Option<DefId> { |
| 257 | + path.res.opt_def_id().and_then(|mut def_id| { |
| 258 | + def_id = self.skip_def_id(def_id); |
| 259 | + for _ in path.segments.iter().skip(1) { |
| 260 | + def_id = self.skip_def_id(def_id); |
| 261 | + if let Some(parent_def_id) = self.cx.tcx.parent(def_id) { |
| 262 | + def_id = parent_def_id; |
| 263 | + } else { |
| 264 | + return None; |
| 265 | + } |
| 266 | + } |
| 267 | + |
| 268 | + Some(def_id) |
| 269 | + }) |
| 270 | + } |
| 271 | +} |
0 commit comments