1 use clippy_utils
::diagnostics
::{span_lint, span_lint_and_then}
;
3 Arm
, AssocItem
, AssocItemKind
, Attribute
, Block
, FnDecl
, FnKind
, Item
, ItemKind
, Local
, Pat
,
6 use rustc_ast
::visit
::{walk_block, walk_expr, walk_pat, Visitor}
;
7 use rustc_lint
::{EarlyContext, EarlyLintPass}
;
8 use rustc_middle
::lint
::in_external_macro
;
9 use rustc_session
::{declare_tool_lint, impl_lint_pass}
;
10 use rustc_span
::source_map
::Span
;
12 use rustc_span
::symbol
::{Ident, Symbol}
;
13 use std
::cmp
::Ordering
;
15 declare_clippy_lint
! {
16 /// **What it does:** Checks for names that are very similar and thus confusing.
18 /// **Why is this bad?** It's hard to distinguish between names that differ only
19 /// by a single character.
21 /// **Known problems:** None?
25 /// let checked_exp = something;
26 /// let checked_expr = something_else;
30 "similarly named items and bindings"
33 declare_clippy_lint
! {
34 /// **What it does:** Checks for too many variables whose name consists of a
37 /// **Why is this bad?** It's hard to memorize what a variable means without a
40 /// **Known problems:** None?
44 /// let (a, b, c, d, e, f, g) = (...);
46 pub MANY_SINGLE_CHAR_NAMES
,
48 "too many single character bindings"
51 declare_clippy_lint
! {
52 /// **What it does:** Checks if you have variables whose name consists of just
53 /// underscores and digits.
55 /// **Why is this bad?** It's hard to memorize what a variable means without a
58 /// **Known problems:** None?
66 pub JUST_UNDERSCORES_AND_DIGITS
,
71 #[derive(Copy, Clone)]
72 pub struct NonExpressiveNames
{
73 pub single_char_binding_names_threshold
: u64,
76 impl_lint_pass
!(NonExpressiveNames
=> [SIMILAR_NAMES
, MANY_SINGLE_CHAR_NAMES
, JUST_UNDERSCORES_AND_DIGITS
]);
82 exemptions
: &'
static [&'
static str],
85 struct SimilarNamesLocalVisitor
<'a
, 'tcx
> {
86 names
: Vec
<ExistingName
>,
87 cx
: &'a EarlyContext
<'tcx
>,
88 lint
: &'a NonExpressiveNames
,
90 /// A stack of scopes containing the single-character bindings in each scope.
91 single_char_names
: Vec
<Vec
<Ident
>>,
94 impl<'a
, 'tcx
> SimilarNamesLocalVisitor
<'a
, 'tcx
> {
95 fn check_single_char_names(&self) {
96 let num_single_char_names
= self.single_char_names
.iter().flatten().count();
97 let threshold
= self.lint
.single_char_binding_names_threshold
;
98 if num_single_char_names
as u64 > threshold
{
103 .map(|ident
| ident
.span
)
104 .collect
::<Vec
<_
>>();
107 MANY_SINGLE_CHAR_NAMES
,
110 "{} bindings with single-character names in scope",
111 num_single_char_names
118 // this list contains lists of names that are allowed to be similar
119 // the assumption is that no name is ever contained in multiple lists.
121 const ALLOWED_TO_BE_SIMILAR
: &[&[&str]] = &[
122 &["parsed", "parser"],
129 &["wparam", "lparam"],
132 struct SimilarNamesNameVisitor
<'a
, 'tcx
, 'b
>(&'b
mut SimilarNamesLocalVisitor
<'a
, 'tcx
>);
134 impl<'a
, 'tcx
, 'b
> Visitor
<'tcx
> for SimilarNamesNameVisitor
<'a
, 'tcx
, 'b
> {
135 fn visit_pat(&mut self, pat
: &'tcx Pat
) {
137 PatKind
::Ident(_
, ident
, _
) => {
138 if !pat
.span
.from_expansion() {
139 self.check_ident(ident
);
142 PatKind
::Struct(_
, _
, ref fields
, _
) => {
143 for field
in fields
{
144 if !field
.is_shorthand
{
145 self.visit_pat(&field
.pat
);
149 // just go through the first pattern, as either all patterns
150 // bind the same bindings or rustc would have errored much earlier
151 PatKind
::Or(ref pats
) => self.visit_pat(&pats
[0]),
152 _
=> walk_pat(self, pat
),
158 fn get_exemptions(interned_name
: &str) -> Option
<&'
static [&'
static str]> {
159 for &list
in ALLOWED_TO_BE_SIMILAR
{
160 if allowed_to_be_similar(interned_name
, list
) {
168 fn allowed_to_be_similar(interned_name
: &str, list
: &[&str]) -> bool
{
170 .any(|&name
| interned_name
.starts_with(name
) || interned_name
.ends_with(name
))
173 impl<'a
, 'tcx
, 'b
> SimilarNamesNameVisitor
<'a
, 'tcx
, 'b
> {
174 fn check_short_ident(&mut self, ident
: Ident
) {
181 .any(|id
| id
.name
== ident
.name
)
186 if let Some(scope
) = &mut self.0.single_char_names
.last_mut() {
191 #[allow(clippy::too_many_lines)]
192 fn check_ident(&mut self, ident
: Ident
) {
193 let interned_name
= ident
.name
.as_str();
194 if interned_name
.chars().any(char::is_uppercase
) {
197 if interned_name
.chars().all(|c
| c
.is_digit(10) || c
== '_'
) {
200 JUST_UNDERSCORES_AND_DIGITS
,
202 "consider choosing a more descriptive name",
206 if interned_name
.starts_with('_'
) {
207 // these bindings are typically unused or represent an ignored portion of a destructuring pattern
210 let count
= interned_name
.chars().count();
213 self.check_short_ident(ident
);
217 for existing_name
in &self.0.names
{
218 if allowed_to_be_similar(&interned_name
, existing_name
.exemptions
) {
221 let mut split_at
= None
;
222 match existing_name
.len
.cmp(&count
) {
223 Ordering
::Greater
=> {
224 if existing_name
.len
- count
!= 1
225 || levenstein_not_1(&interned_name
, &existing_name
.interned
.as_str())
231 if count
- existing_name
.len
!= 1
232 || levenstein_not_1(&existing_name
.interned
.as_str(), &interned_name
)
238 let mut interned_chars
= interned_name
.chars();
239 let interned_str
= existing_name
.interned
.as_str();
240 let mut existing_chars
= interned_str
.chars();
241 let first_i
= interned_chars
.next().expect("we know we have at least one char");
242 let first_e
= existing_chars
.next().expect("we know we have at least one char");
243 let eq_or_numeric
= |(a
, b
): (char, char)| a
== b
|| a
.is_numeric() && b
.is_numeric();
245 if eq_or_numeric((first_i
, first_e
)) {
246 let last_i
= interned_chars
.next_back().expect("we know we have at least two chars");
247 let last_e
= existing_chars
.next_back().expect("we know we have at least two chars");
248 if eq_or_numeric((last_i
, last_e
)) {
251 .filter(|&ie
| !eq_or_numeric(ie
))
258 let second_last_i
= interned_chars
260 .expect("we know we have at least three chars");
261 let second_last_e
= existing_chars
263 .expect("we know we have at least three chars");
264 if !eq_or_numeric((second_last_i
, second_last_e
))
265 || second_last_i
== '_'
266 || !interned_chars
.zip(existing_chars
).all(eq_or_numeric
)
268 // allowed similarity foo_x, foo_y
269 // or too many chars differ (foo_x, boo_y) or (foox, booy)
272 split_at
= interned_name
.char_indices().rev().next().map(|(i
, _
)| i
);
275 let second_i
= interned_chars
.next().expect("we know we have at least two chars");
276 let second_e
= existing_chars
.next().expect("we know we have at least two chars");
277 if !eq_or_numeric((second_i
, second_e
))
279 || !interned_chars
.zip(existing_chars
).all(eq_or_numeric
)
281 // allowed similarity x_foo, y_foo
282 // or too many chars differ (x_foo, y_boo) or (xfoo, yboo)
285 split_at
= interned_name
.chars().next().map(char::len_utf8
);
293 "binding's name is too similar to existing binding",
295 diag
.span_note(existing_name
.span
, "existing binding defined here");
296 if let Some(split
) = split_at
{
300 "separate the discriminating character by an \
301 underscore like: `{}_{}`",
302 &interned_name
[..split
],
303 &interned_name
[split
..]
311 self.0.names
.push(ExistingName
{
312 exemptions
: get_exemptions(&interned_name
).unwrap_or(&[]),
313 interned
: ident
.name
,
320 impl<'a
, 'b
> SimilarNamesLocalVisitor
<'a
, 'b
> {
321 /// ensure scoping rules work
322 fn apply
<F
: for<'c
> Fn(&'c
mut Self)>(&mut self, f
: F
) {
323 let n
= self.names
.len();
324 let single_char_count
= self.single_char_names
.len();
326 self.names
.truncate(n
);
327 self.single_char_names
.truncate(single_char_count
);
331 impl<'a
, 'tcx
> Visitor
<'tcx
> for SimilarNamesLocalVisitor
<'a
, 'tcx
> {
332 fn visit_local(&mut self, local
: &'tcx Local
) {
333 if let Some(ref init
) = local
.init
{
334 self.apply(|this
| walk_expr(this
, &**init
));
336 // add the pattern after the expression because the bindings aren't available
339 SimilarNamesNameVisitor(self).visit_pat(&*local
.pat
);
341 fn visit_block(&mut self, blk
: &'tcx Block
) {
342 self.single_char_names
.push(vec
![]);
344 self.apply(|this
| walk_block(this
, blk
));
346 self.check_single_char_names();
347 self.single_char_names
.pop();
349 fn visit_arm(&mut self, arm
: &'tcx Arm
) {
350 self.single_char_names
.push(vec
![]);
353 SimilarNamesNameVisitor(this
).visit_pat(&arm
.pat
);
354 this
.apply(|this
| walk_expr(this
, &arm
.body
));
357 self.check_single_char_names();
358 self.single_char_names
.pop();
360 fn visit_item(&mut self, _
: &Item
) {
361 // do not recurse into inner items
365 impl EarlyLintPass
for NonExpressiveNames
{
366 fn check_item(&mut self, cx
: &EarlyContext
<'_
>, item
: &Item
) {
367 if in_external_macro(cx
.sess
, item
.span
) {
371 if let ItemKind
::Fn(box FnKind(_
, ref sig
, _
, Some(ref blk
))) = item
.kind
{
372 do_check(self, cx
, &item
.attrs
, &sig
.decl
, blk
);
376 fn check_impl_item(&mut self, cx
: &EarlyContext
<'_
>, item
: &AssocItem
) {
377 if in_external_macro(cx
.sess
, item
.span
) {
381 if let AssocItemKind
::Fn(box FnKind(_
, ref sig
, _
, Some(ref blk
))) = item
.kind
{
382 do_check(self, cx
, &item
.attrs
, &sig
.decl
, blk
);
387 fn do_check(lint
: &mut NonExpressiveNames
, cx
: &EarlyContext
<'_
>, attrs
: &[Attribute
], decl
: &FnDecl
, blk
: &Block
) {
388 if !attrs
.iter().any(|attr
| attr
.has_name(sym
::test
)) {
389 let mut visitor
= SimilarNamesLocalVisitor
{
393 single_char_names
: vec
![vec
![]],
396 // initialize with function arguments
397 for arg
in &decl
.inputs
{
398 SimilarNamesNameVisitor(&mut visitor
).visit_pat(&arg
.pat
);
400 // walk all other bindings
401 walk_block(&mut visitor
, blk
);
403 visitor
.check_single_char_names();
407 /// Precondition: `a_name.chars().count() < b_name.chars().count()`.
409 fn levenstein_not_1(a_name
: &str, b_name
: &str) -> bool
{
410 debug_assert
!(a_name
.chars().count() < b_name
.chars().count());
411 let mut a_chars
= a_name
.chars();
412 let mut b_chars
= b_name
.chars();
413 while let (Some(a
), Some(b
)) = (a_chars
.next(), b_chars
.next()) {
417 if let Some(b2
) = b_chars
.next() {
418 // check if there's just one character inserted
419 return a
!= b2
|| a_chars
.ne(b_chars
);