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