]>
Commit | Line | Data |
---|---|---|
cdc7bbd5 | 1 | use clippy_utils::diagnostics::span_lint_and_sugg; |
cdc7bbd5 | 2 | use clippy_utils::source::{snippet, snippet_with_applicability}; |
136023e0 | 3 | use clippy_utils::{in_macro, is_test_module_or_function}; |
f20569fa XL |
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<'_>) { | |
136023e0 | 109 | if is_test_module_or_function(cx.tcx, item) { |
f20569fa XL |
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 | ||
136023e0 XL |
186 | fn check_item_post(&mut self, cx: &LateContext<'_>, item: &Item<'_>) { |
187 | if is_test_module_or_function(cx.tcx, item) { | |
f20569fa XL |
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 | } |