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