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