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