]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/wildcard_imports.rs
Add missing diagnostic item Symbols
[rust.git] / clippy_lints / src / wildcard_imports.rs
1 use crate::utils::{in_macro, snippet, snippet_with_applicability, span_lint_and_sugg};
2 use if_chain::if_chain;
3 use rustc_errors::Applicability;
4 use rustc_hir::{
5     def::{DefKind, Res},
6     Item, ItemKind, PathSegment, UseKind,
7 };
8 use rustc_lint::{LateContext, LateLintPass};
9 use rustc_session::{declare_tool_lint, impl_lint_pass};
10 use rustc_span::symbol::kw;
11 use rustc_span::{sym, BytePos};
12
13 declare_clippy_lint! {
14     /// **What it does:** Checks for `use Enum::*`.
15     ///
16     /// **Why is this bad?** It is usually better style to use the prefixed name of
17     /// an enumeration variant, rather than importing variants.
18     ///
19     /// **Known problems:** Old-style enumerations that prefix the variants are
20     /// still around.
21     ///
22     /// **Example:**
23     /// ```rust,ignore
24     /// // Bad
25     /// use std::cmp::Ordering::*;
26     /// foo(Less);
27     ///
28     /// // Good
29     /// use std::cmp::Ordering;
30     /// foo(Ordering::Less)
31     /// ```
32     pub ENUM_GLOB_USE,
33     pedantic,
34     "use items that import all variants of an enum"
35 }
36
37 declare_clippy_lint! {
38     /// **What it does:** Checks for wildcard imports `use _::*`.
39     ///
40     /// **Why is this bad?** wildcard imports can pollute the namespace. This is especially bad if
41     /// you try to import something through a wildcard, that already has been imported by name from
42     /// a different source:
43     ///
44     /// ```rust,ignore
45     /// use crate1::foo; // Imports a function named foo
46     /// use crate2::*; // Has a function named foo
47     ///
48     /// foo(); // Calls crate1::foo
49     /// ```
50     ///
51     /// This can lead to confusing error messages at best and to unexpected behavior at worst.
52     ///
53     /// **Exceptions:**
54     ///
55     /// Wildcard imports are allowed from modules named `prelude`. Many crates (including the standard library)
56     /// provide modules named "prelude" specifically designed for wildcard import.
57     ///
58     /// `use super::*` is allowed in test modules. This is defined as any module with "test" in the name.
59     ///
60     /// These exceptions can be disabled using the `warn-on-all-wildcard-imports` configuration flag.
61     ///
62     /// **Known problems:** If macros are imported through the wildcard, this macro is not included
63     /// by the suggestion and has to be added by hand.
64     ///
65     /// Applying the suggestion when explicit imports of the things imported with a glob import
66     /// exist, may result in `unused_imports` warnings.
67     ///
68     /// **Example:**
69     ///
70     /// ```rust,ignore
71     /// // Bad
72     /// use crate1::*;
73     ///
74     /// foo();
75     /// ```
76     ///
77     /// ```rust,ignore
78     /// // Good
79     /// use crate1::foo;
80     ///
81     /// foo();
82     /// ```
83     pub WILDCARD_IMPORTS,
84     pedantic,
85     "lint `use _::*` statements"
86 }
87
88 #[derive(Default)]
89 pub struct WildcardImports {
90     warn_on_all: bool,
91     test_modules_deep: u32,
92 }
93
94 impl WildcardImports {
95     pub fn new(warn_on_all: bool) -> Self {
96         Self {
97             warn_on_all,
98             test_modules_deep: 0,
99         }
100     }
101 }
102
103 impl_lint_pass!(WildcardImports => [ENUM_GLOB_USE, WILDCARD_IMPORTS]);
104
105 impl LateLintPass<'_> for WildcardImports {
106     fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) {
107         if is_test_module_or_function(item) {
108             self.test_modules_deep = self.test_modules_deep.saturating_add(1);
109         }
110         if item.vis.node.is_pub() || item.vis.node.is_pub_restricted() {
111             return;
112         }
113         if_chain! {
114             if let ItemKind::Use(use_path, UseKind::Glob) = &item.kind;
115             if self.warn_on_all || !self.check_exceptions(item, use_path.segments);
116             let used_imports = cx.tcx.names_imported_by_glob_use(item.def_id);
117             if !used_imports.is_empty(); // Already handled by `unused_imports`
118             then {
119                 let mut applicability = Applicability::MachineApplicable;
120                 let import_source_snippet = snippet_with_applicability(cx, use_path.span, "..", &mut applicability);
121                 let (span, braced_glob) = if import_source_snippet.is_empty() {
122                     // This is a `_::{_, *}` import
123                     // In this case `use_path.span` is empty and ends directly in front of the `*`,
124                     // so we need to extend it by one byte.
125                     (
126                         use_path.span.with_hi(use_path.span.hi() + BytePos(1)),
127                         true,
128                     )
129                 } else {
130                     // In this case, the `use_path.span` ends right before the `::*`, so we need to
131                     // extend it up to the `*`. Since it is hard to find the `*` in weird
132                     // formattings like `use _ ::  *;`, we extend it up to, but not including the
133                     // `;`. In nested imports, like `use _::{inner::*, _}` there is no `;` and we
134                     // can just use the end of the item span
135                     let mut span = use_path.span.with_hi(item.span.hi());
136                     if snippet(cx, span, "").ends_with(';') {
137                         span = use_path.span.with_hi(item.span.hi() - BytePos(1));
138                     }
139                     (
140                         span, false,
141                     )
142                 };
143
144                 let imports_string = if used_imports.len() == 1 {
145                     used_imports.iter().next().unwrap().to_string()
146                 } else {
147                     let mut imports = used_imports
148                         .iter()
149                         .map(ToString::to_string)
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 braced_glob {
160                     imports_string
161                 } else {
162                     format!("{}::{}", import_source_snippet, imports_string)
163                 };
164
165                 let (lint, message) = if let Res::Def(DefKind::Enum, _) = use_path.res {
166                     (ENUM_GLOB_USE, "usage of wildcard import for enum variants")
167                 } else {
168                     (WILDCARD_IMPORTS, "usage of wildcard import")
169                 };
170
171                 span_lint_and_sugg(
172                     cx,
173                     lint,
174                     span,
175                     message,
176                     "try",
177                     sugg,
178                     applicability,
179                 );
180             }
181         }
182     }
183
184     fn check_item_post(&mut self, _: &LateContext<'_>, item: &Item<'_>) {
185         if is_test_module_or_function(item) {
186             self.test_modules_deep = self.test_modules_deep.saturating_sub(1);
187         }
188     }
189 }
190
191 impl WildcardImports {
192     fn check_exceptions(&self, item: &Item<'_>, segments: &[PathSegment<'_>]) -> bool {
193         in_macro(item.span)
194             || is_prelude_import(segments)
195             || (is_super_only_import(segments) && self.test_modules_deep > 0)
196     }
197 }
198
199 // Allow "...prelude::..::*" imports.
200 // Many crates have a prelude, and it is imported as a glob by design.
201 fn is_prelude_import(segments: &[PathSegment<'_>]) -> bool {
202     segments.iter().any(|ps| ps.ident.name == sym::prelude)
203 }
204
205 // Allow "super::*" imports in tests.
206 fn is_super_only_import(segments: &[PathSegment<'_>]) -> bool {
207     segments.len() == 1 && segments[0].ident.name == kw::Super
208 }
209
210 fn is_test_module_or_function(item: &Item<'_>) -> bool {
211     matches!(item.kind, ItemKind::Mod(..)) && item.ident.name.as_str().contains("test")
212 }