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