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