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