| use clippy_utils::diagnostics::span_lint_and_sugg; |
| use clippy_utils::is_test_module_or_function; |
| use clippy_utils::source::{snippet, snippet_with_applicability}; |
| use rustc_data_structures::fx::FxHashSet; |
| use rustc_errors::Applicability; |
| use rustc_hir::def::{DefKind, Res}; |
| use rustc_hir::{Item, ItemKind, PathSegment, UseKind}; |
| use rustc_lint::{LateContext, LateLintPass, LintContext}; |
| use rustc_middle::ty; |
| use rustc_session::impl_lint_pass; |
| use rustc_span::symbol::kw; |
| use rustc_span::{sym, BytePos}; |
| |
| declare_clippy_lint! { |
| /// ### What it does |
| /// Checks for `use Enum::*`. |
| /// |
| /// ### Why is this bad? |
| /// It is usually better style to use the prefixed name of |
| /// an enumeration variant, rather than importing variants. |
| /// |
| /// ### Known problems |
| /// Old-style enumerations that prefix the variants are |
| /// still around. |
| /// |
| /// ### Example |
| /// ```no_run |
| /// use std::cmp::Ordering::*; |
| /// |
| /// # fn foo(_: std::cmp::Ordering) {} |
| /// foo(Less); |
| /// ``` |
| /// |
| /// Use instead: |
| /// ```no_run |
| /// use std::cmp::Ordering; |
| /// |
| /// # fn foo(_: Ordering) {} |
| /// foo(Ordering::Less) |
| /// ``` |
| #[clippy::version = "pre 1.29.0"] |
| pub ENUM_GLOB_USE, |
| pedantic, |
| "use items that import all variants of an enum" |
| } |
| |
| declare_clippy_lint! { |
| /// ### What it does |
| /// Checks for wildcard imports `use _::*`. |
| /// |
| /// ### Why is this bad? |
| /// wildcard imports can pollute the namespace. This is especially bad if |
| /// you try to import something through a wildcard, that already has been imported by name from |
| /// a different source: |
| /// |
| /// ```rust,ignore |
| /// use crate1::foo; // Imports a function named foo |
| /// use crate2::*; // Has a function named foo |
| /// |
| /// foo(); // Calls crate1::foo |
| /// ``` |
| /// |
| /// This can lead to confusing error messages at best and to unexpected behavior at worst. |
| /// |
| /// ### Exceptions |
| /// Wildcard imports are allowed from modules that their name contains `prelude`. Many crates |
| /// (including the standard library) provide modules named "prelude" specifically designed |
| /// for wildcard import. |
| /// |
| /// `use super::*` is allowed in test modules. This is defined as any module with "test" in the name. |
| /// |
| /// These exceptions can be disabled using the `warn-on-all-wildcard-imports` configuration flag. |
| /// |
| /// ### Known problems |
| /// If macros are imported through the wildcard, this macro is not included |
| /// by the suggestion and has to be added by hand. |
| /// |
| /// Applying the suggestion when explicit imports of the things imported with a glob import |
| /// exist, may result in `unused_imports` warnings. |
| /// |
| /// ### Example |
| /// ```rust,ignore |
| /// use crate1::*; |
| /// |
| /// foo(); |
| /// ``` |
| /// |
| /// Use instead: |
| /// ```rust,ignore |
| /// use crate1::foo; |
| /// |
| /// foo(); |
| /// ``` |
| #[clippy::version = "1.43.0"] |
| pub WILDCARD_IMPORTS, |
| pedantic, |
| "lint `use _::*` statements" |
| } |
| |
| #[derive(Default)] |
| pub struct WildcardImports { |
| warn_on_all: bool, |
| test_modules_deep: u32, |
| allowed_segments: FxHashSet<String>, |
| } |
| |
| impl WildcardImports { |
| pub fn new(warn_on_all: bool, allowed_wildcard_imports: FxHashSet<String>) -> Self { |
| Self { |
| warn_on_all, |
| test_modules_deep: 0, |
| allowed_segments: allowed_wildcard_imports, |
| } |
| } |
| } |
| |
| impl_lint_pass!(WildcardImports => [ENUM_GLOB_USE, WILDCARD_IMPORTS]); |
| |
| impl LateLintPass<'_> for WildcardImports { |
| fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) { |
| if cx.sess().is_test_crate() { |
| return; |
| } |
| |
| if is_test_module_or_function(cx.tcx, item) { |
| self.test_modules_deep = self.test_modules_deep.saturating_add(1); |
| } |
| let module = cx.tcx.parent_module_from_def_id(item.owner_id.def_id); |
| if cx.tcx.visibility(item.owner_id.def_id) != ty::Visibility::Restricted(module.to_def_id()) { |
| return; |
| } |
| if let ItemKind::Use(use_path, UseKind::Glob) = &item.kind |
| && (self.warn_on_all || !self.check_exceptions(item, use_path.segments)) |
| && let used_imports = cx.tcx.names_imported_by_glob_use(item.owner_id.def_id) |
| && !used_imports.is_empty() // Already handled by `unused_imports` |
| && !used_imports.contains(&kw::Underscore) |
| { |
| let mut applicability = Applicability::MachineApplicable; |
| let import_source_snippet = snippet_with_applicability(cx, use_path.span, "..", &mut applicability); |
| let (span, braced_glob) = if import_source_snippet.is_empty() { |
| // This is a `_::{_, *}` import |
| // In this case `use_path.span` is empty and ends directly in front of the `*`, |
| // so we need to extend it by one byte. |
| (use_path.span.with_hi(use_path.span.hi() + BytePos(1)), true) |
| } else { |
| // In this case, the `use_path.span` ends right before the `::*`, so we need to |
| // extend it up to the `*`. Since it is hard to find the `*` in weird |
| // formatting like `use _ :: *;`, we extend it up to, but not including the |
| // `;`. In nested imports, like `use _::{inner::*, _}` there is no `;` and we |
| // can just use the end of the item span |
| let mut span = use_path.span.with_hi(item.span.hi()); |
| if snippet(cx, span, "").ends_with(';') { |
| span = use_path.span.with_hi(item.span.hi() - BytePos(1)); |
| } |
| (span, false) |
| }; |
| |
| let mut imports = used_imports.items().map(ToString::to_string).into_sorted_stable_ord(); |
| let imports_string = if imports.len() == 1 { |
| imports.pop().unwrap() |
| } else if braced_glob { |
| imports.join(", ") |
| } else { |
| format!("{{{}}}", imports.join(", ")) |
| }; |
| |
| let sugg = if braced_glob { |
| imports_string |
| } else { |
| format!("{import_source_snippet}::{imports_string}") |
| }; |
| |
| // Glob imports always have a single resolution. |
| let (lint, message) = if let Res::Def(DefKind::Enum, _) = use_path.res[0] { |
| (ENUM_GLOB_USE, "usage of wildcard import for enum variants") |
| } else { |
| (WILDCARD_IMPORTS, "usage of wildcard import") |
| }; |
| |
| span_lint_and_sugg(cx, lint, span, message, "try", sugg, applicability); |
| } |
| } |
| |
| fn check_item_post(&mut self, cx: &LateContext<'_>, item: &Item<'_>) { |
| if is_test_module_or_function(cx.tcx, item) { |
| self.test_modules_deep = self.test_modules_deep.saturating_sub(1); |
| } |
| } |
| } |
| |
| impl WildcardImports { |
| fn check_exceptions(&self, item: &Item<'_>, segments: &[PathSegment<'_>]) -> bool { |
| item.span.from_expansion() |
| || is_prelude_import(segments) |
| || (is_super_only_import(segments) && self.test_modules_deep > 0) |
| || is_allowed_via_config(segments, &self.allowed_segments) |
| } |
| } |
| |
| // Allow "...prelude::..::*" imports. |
| // Many crates have a prelude, and it is imported as a glob by design. |
| fn is_prelude_import(segments: &[PathSegment<'_>]) -> bool { |
| segments |
| .iter() |
| .any(|ps| ps.ident.as_str().contains(sym::prelude.as_str())) |
| } |
| |
| // Allow "super::*" imports in tests. |
| fn is_super_only_import(segments: &[PathSegment<'_>]) -> bool { |
| segments.len() == 1 && segments[0].ident.name == kw::Super |
| } |
| |
| // Allow skipping imports containing user configured segments, |
| // i.e. "...::utils::...::*" if user put `allowed-wildcard-imports = ["utils"]` in `Clippy.toml` |
| fn is_allowed_via_config(segments: &[PathSegment<'_>], allowed_segments: &FxHashSet<String>) -> bool { |
| // segment matching need to be exact instead of using 'contains', in case user unintentionaly put |
| // a single character in the config thus skipping most of the warnings. |
| segments.iter().any(|seg| allowed_segments.contains(seg.ident.as_str())) |
| } |