]> git.lizzy.rs Git - rust.git/blobdiff - clippy_lints/src/wildcard_imports.rs
Merge remote-tracking branch 'upstream/master' into rustup
[rust.git] / clippy_lints / src / wildcard_imports.rs
index 70098f31aef7b16c5b091925bce8fcb5391f7831..10005a7fc81ed1381abdada242234bcbbcb802de 100644 (file)
@@ -1,19 +1,43 @@
-use crate::utils::{in_macro, snippet_with_applicability, span_lint_and_sugg};
+use crate::utils::{in_macro, snippet, snippet_with_applicability, span_lint_and_sugg};
 use if_chain::if_chain;
-use rustc::ty::DefIdTree;
-use rustc_data_structures::fx::FxHashSet;
 use rustc_errors::Applicability;
-use rustc_hir::def_id::DefId;
-use rustc_hir::intravisit::{walk_item, NestedVisitorMap, Visitor};
-use rustc_hir::*;
+use rustc_hir::{
+    def::{DefKind, Res},
+    Item, ItemKind, PathSegment, UseKind,
+};
 use rustc_lint::{LateContext, LateLintPass};
-use rustc_session::{declare_lint_pass, declare_tool_lint};
-use rustc_span::{symbol::Symbol, BytePos};
+use rustc_session::{declare_tool_lint, 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:**
+    /// ```rust,ignore
+    /// // Bad
+    /// use std::cmp::Ordering::*;
+    /// foo(Less);
+    ///
+    /// // Good
+    /// use std::cmp::Ordering;
+    /// foo(Ordering::Less)
+    /// ```
+    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 polute the namespace. This is especially bad if
+    /// **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:
     ///
     ///
     /// This can lead to confusing error messages at best and to unexpected behavior at worst.
     ///
+    /// **Exceptions:**
+    ///
+    /// Wildcard imports are allowed from modules named `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:**
     ///
-    /// Bad:
     /// ```rust,ignore
+    /// // Bad
     /// use crate1::*;
     ///
     /// foo();
     /// ```
     ///
-    /// Good:
     /// ```rust,ignore
+    /// // Good
     /// use crate1::foo;
     ///
     /// foo();
     "lint `use _::*` statements"
 }
 
-declare_lint_pass!(WildcardImports => [WILDCARD_IMPORTS]);
+#[derive(Default)]
+pub struct WildcardImports {
+    warn_on_all: bool,
+    test_modules_deep: u32,
+}
+
+impl WildcardImports {
+    pub fn new(warn_on_all: bool) -> Self {
+        Self {
+            warn_on_all,
+            test_modules_deep: 0,
+        }
+    }
+}
+
+impl_lint_pass!(WildcardImports => [ENUM_GLOB_USE, WILDCARD_IMPORTS]);
 
-impl LateLintPass<'_, '_> for WildcardImports {
-    fn check_item(&mut self, cx: &LateContext<'_, '_>, item: &Item<'_>) {
+impl LateLintPass<'_> for WildcardImports {
+    fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) {
+        if is_test_module_or_function(item) {
+            self.test_modules_deep = self.test_modules_deep.saturating_add(1);
+        }
         if item.vis.node.is_pub() || item.vis.node.is_pub_restricted() {
             return;
         }
         if_chain! {
-            if !in_macro(item.span);
             if let ItemKind::Use(use_path, UseKind::Glob) = &item.kind;
-            if let Some(def_id) = use_path.res.opt_def_id();
+            if self.warn_on_all || !self.check_exceptions(item, use_path.segments);
+            let used_imports = cx.tcx.names_imported_by_glob_use(item.hir_id.owner);
+            if !used_imports.is_empty(); // Already handled by `unused_imports`
             then {
-                let hir = cx.tcx.hir();
-                let parent_id = hir.get_parent_item(item.hir_id);
-                let (items, in_module) = if parent_id == CRATE_HIR_ID {
-                    let items = hir
-                        .krate()
-                        .module
-                        .item_ids
-                        .iter()
-                        .map(|item_id| hir.get(item_id.id))
-                        .filter_map(|node| {
-                            if let Node::Item(item) = node {
-                                Some(item)
-                            } else {
-                                None
-                            }
-                        })
-                        .collect();
-                    (items, true)
-                } else if let Node::Item(item) = hir.get(parent_id) {
-                    (vec![item], false)
+                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 {
-                    (vec![], false)
-                };
-
-                let mut import_used_visitor = ImportsUsedVisitor {
-                    cx,
-                    wildcard_def_id: def_id,
-                    in_module,
-                    used_imports: FxHashSet::default(),
+                    // 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
+                    // formattings 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,
+                    )
                 };
-                for item in items {
-                    import_used_visitor.visit_item(item);
-                }
 
-                if !import_used_visitor.used_imports.is_empty() {
-                    let module_name = use_path
-                        .segments
+                let imports_string = if used_imports.len() == 1 {
+                    used_imports.iter().next().unwrap().to_string()
+                } else {
+                    let mut imports = used_imports
                         .iter()
-                        .last()
-                        .expect("path has at least one segment")
-                        .ident
-                        .name;
-
-                    let mut applicability = Applicability::MachineApplicable;
-                    let import_source = snippet_with_applicability(cx, use_path.span, "..", &mut applicability);
-                    let (span, braced_glob) = if import_source.is_empty() {
-                        // This is a `_::{_, *}` import
-                        // Probably it's `_::{self, *}`, in that case we don't want to suggest to
-                        // import `self`.
-                        // If it is something else, we also don't want to include `self` in the
-                        // suggestion, since either it was imported through another use statement:
-                        // ```
-                        // use foo::bar;
-                        // use foo::bar::{baz, *};
-                        // ```
-                        // or it couldn't be used anywhere.
-                        (
-                            use_path.span.with_hi(use_path.span.hi() + BytePos(1)),
-                            true,
-                        )
+                        .map(ToString::to_string)
+                        .collect::<Vec<_>>();
+                    imports.sort();
+                    if braced_glob {
+                        imports.join(", ")
                     } else {
-                        (
-                            use_path.span.with_hi(use_path.span.hi() + BytePos(3)),
-                            false,
-                        )
-                    };
+                        format!("{{{}}}", imports.join(", "))
+                    }
+                };
 
-                    let imports_string = if import_used_visitor.used_imports.len() == 1 {
-                        // We don't need to check for accidental suggesting the module name instead
-                        // of `self` here, since if `used_imports.len() == 1`, and the only usage
-                        // is `self`, then it's not through a `*` and if there is a `*`, it gets
-                        // already linted by `unused_imports` of rustc.
-                        import_used_visitor.used_imports.iter().next().unwrap().to_string()
-                    } else {
-                        let mut imports = import_used_visitor
-                            .used_imports
-                            .iter()
-                            .filter_map(|import_name| {
-                                if braced_glob && *import_name == module_name {
-                                    None
-                                } else if *import_name == module_name {
-                                    Some("self".to_string())
-                                } else {
-                                    Some(import_name.to_string())
-                                }
-                            })
-                            .collect::<Vec<_>>();
-                        imports.sort();
-                        if braced_glob {
-                            imports.join(", ")
-                        } else {
-                            format!("{{{}}}", imports.join(", "))
-                        }
-                    };
+                let sugg = if braced_glob {
+                    imports_string
+                } else {
+                    format!("{}::{}", import_source_snippet, imports_string)
+                };
 
-                    let sugg = if import_source.is_empty() {
-                        imports_string
-                    } else {
-                        format!("{}::{}", import_source, imports_string)
-                    };
+                let (lint, message) = if let Res::Def(DefKind::Enum, _) = use_path.res {
+                    (ENUM_GLOB_USE, "usage of wildcard import for enum variants")
+                } else {
+                    (WILDCARD_IMPORTS, "usage of wildcard import")
+                };
 
-                    span_lint_and_sugg(
-                        cx,
-                        WILDCARD_IMPORTS,
-                        span,
-                        "usage of wildcard import",
-                        "try",
-                        sugg,
-                        applicability,
-                    );
-                }
+                span_lint_and_sugg(
+                    cx,
+                    lint,
+                    span,
+                    message,
+                    "try",
+                    sugg,
+                    applicability,
+                );
             }
         }
     }
-}
-
-struct ImportsUsedVisitor<'a, 'tcx> {
-    cx: &'a LateContext<'a, 'tcx>,
-    wildcard_def_id: def_id::DefId,
-    in_module: bool,
-    used_imports: FxHashSet<Symbol>,
-}
 
-impl<'a, 'tcx> Visitor<'tcx> for ImportsUsedVisitor<'a, 'tcx> {
-    type Map = Map<'tcx>;
-
-    fn visit_item(&mut self, item: &'tcx Item<'_>) {
-        match item.kind {
-            ItemKind::Use(..) => {},
-            ItemKind::Mod(..) if self.in_module => {},
-            ItemKind::Mod(..) => self.in_module = true,
-            _ => walk_item(self, item),
-        }
-    }
-
-    fn visit_path(&mut self, path: &Path<'_>, _: HirId) {
-        if let Some(def_id) = self.first_path_segment_def_id(path) {
-            // Check if the function/enum/... was exported
-            if let Some(exports) = self.cx.tcx.module_exports(self.wildcard_def_id) {
-                for export in exports {
-                    if let Some(export_def_id) = export.res.opt_def_id() {
-                        if export_def_id == def_id {
-                            self.used_imports.insert(
-                                path.segments
-                                    .iter()
-                                    .next()
-                                    .expect("path has at least one segment")
-                                    .ident
-                                    .name,
-                            );
-                            return;
-                        }
-                    }
-                }
-            }
-
-            // Check if it is directly in the module
-            if let Some(parent_def_id) = self.cx.tcx.parent(def_id) {
-                if self.wildcard_def_id == parent_def_id {
-                    self.used_imports.insert(
-                        path.segments
-                            .iter()
-                            .next()
-                            .expect("path has at least one segment")
-                            .ident
-                            .name,
-                    );
-                }
-            }
+    fn check_item_post(&mut self, _: &LateContext<'_>, item: &Item<'_>) {
+        if is_test_module_or_function(item) {
+            self.test_modules_deep = self.test_modules_deep.saturating_sub(1);
         }
     }
+}
 
-    fn nested_visit_map(&mut self) -> NestedVisitorMap<'_, Self::Map> {
-        NestedVisitorMap::All(&self.cx.tcx.hir())
+impl WildcardImports {
+    fn check_exceptions(&self, item: &Item<'_>, segments: &[PathSegment<'_>]) -> bool {
+        in_macro(item.span)
+            || is_prelude_import(segments)
+            || (is_super_only_import(segments) && self.test_modules_deep > 0)
     }
 }
 
-impl ImportsUsedVisitor<'_, '_> {
-    fn skip_def_id(&self, def_id: DefId) -> DefId {
-        let def_key = self.cx.tcx.def_key(def_id);
-        match def_key.disambiguated_data.data {
-            DefPathData::Ctor => {
-                if let Some(def_id) = self.cx.tcx.parent(def_id) {
-                    self.skip_def_id(def_id)
-                } else {
-                    def_id
-                }
-            },
-            _ => def_id,
-        }
-    }
+// 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.name == sym::prelude)
+}
 
-    fn first_path_segment_def_id(&self, path: &Path<'_>) -> Option<DefId> {
-        path.res.opt_def_id().and_then(|mut def_id| {
-            def_id = self.skip_def_id(def_id);
-            for _ in path.segments.iter().skip(1) {
-                def_id = self.skip_def_id(def_id);
-                if let Some(parent_def_id) = self.cx.tcx.parent(def_id) {
-                    def_id = parent_def_id;
-                } else {
-                    return None;
-                }
-            }
+// Allow "super::*" imports in tests.
+fn is_super_only_import(segments: &[PathSegment<'_>]) -> bool {
+    segments.len() == 1 && segments[0].ident.name == kw::Super
+}
 
-            Some(def_id)
-        })
-    }
+fn is_test_module_or_function(item: &Item<'_>) -> bool {
+    matches!(item.kind, ItemKind::Mod(..)) && item.ident.name.as_str().contains("test")
 }