|
1 | 1 | use clippy_utils::diagnostics::span_lint_hir_and_then; |
2 | | -use itertools::Itertools; |
| 2 | +use core::mem::replace; |
3 | 3 | use rustc_errors::Applicability; |
4 | 4 | use rustc_hir::{HirId, Item, ItemKind}; |
5 | 5 | use rustc_lint::{LateContext, LateLintPass, LintContext}; |
@@ -56,55 +56,71 @@ impl UpperCaseAcronyms { |
56 | 56 |
|
57 | 57 | impl_lint_pass!(UpperCaseAcronyms => [UPPER_CASE_ACRONYMS]); |
58 | 58 |
|
59 | | -fn correct_ident(ident: &str) -> String { |
60 | | - let ident = ident.chars().rev().collect::<String>(); |
61 | | - let fragments = ident |
62 | | - .split_inclusive(|x: char| !x.is_ascii_lowercase()) |
63 | | - .rev() |
64 | | - .map(|x| x.chars().rev().collect::<String>()); |
65 | | - |
66 | | - let mut ident = fragments.clone().next().unwrap(); |
67 | | - for (ref prev, ref curr) in fragments.tuple_windows() { |
68 | | - if <[&String; 2]>::from((prev, curr)) |
69 | | - .iter() |
70 | | - .all(|s| s.len() == 1 && s.chars().next().unwrap().is_ascii_uppercase()) |
71 | | - { |
72 | | - ident.push_str(&curr.to_ascii_lowercase()); |
| 59 | +fn contains_acronym(s: &str) -> bool { |
| 60 | + let mut count = 0; |
| 61 | + for c in s.chars() { |
| 62 | + if c.is_ascii_uppercase() { |
| 63 | + count += 1; |
| 64 | + if count == 3 { |
| 65 | + return true; |
| 66 | + } |
73 | 67 | } else { |
74 | | - ident.push_str(curr); |
| 68 | + count = 0; |
75 | 69 | } |
76 | 70 | } |
77 | | - ident |
| 71 | + count == 2 |
78 | 72 | } |
79 | 73 |
|
80 | 74 | fn check_ident(cx: &LateContext<'_>, ident: &Ident, hir_id: HirId, be_aggressive: bool) { |
81 | | - let span = ident.span; |
82 | | - let ident = ident.as_str(); |
83 | | - let corrected = correct_ident(ident); |
84 | | - // warn if we have pure-uppercase idents |
85 | | - // assume that two-letter words are some kind of valid abbreviation like FP for false positive |
86 | | - // (and don't warn) |
87 | | - if (ident.chars().all(|c| c.is_ascii_uppercase()) && ident.len() > 2) |
88 | | - // otherwise, warn if we have SOmeTHING lIKE THIs but only warn with the aggressive |
89 | | - // upper-case-acronyms-aggressive config option enabled |
90 | | - || (be_aggressive && ident != corrected) |
| 75 | + let s = ident.as_str(); |
| 76 | + |
| 77 | + // By default, only warn for upper case identifiers with at least 3 characters. |
| 78 | + let replacement = if s.len() > 2 && s.bytes().all(|c| c.is_ascii_uppercase()) { |
| 79 | + let mut r = String::with_capacity(s.len()); |
| 80 | + let mut s = s.chars(); |
| 81 | + r.push(s.next().unwrap()); |
| 82 | + r.extend(s.map(|c| c.to_ascii_lowercase())); |
| 83 | + r |
| 84 | + } else if be_aggressive |
| 85 | + // Only lint if the ident starts with an upper case character. |
| 86 | + && let unprefixed = s.trim_start_matches('_') |
| 87 | + && unprefixed.starts_with(|c: char| c.is_ascii_uppercase()) |
| 88 | + && contains_acronym(unprefixed) |
91 | 89 | { |
92 | | - span_lint_hir_and_then( |
93 | | - cx, |
94 | | - UPPER_CASE_ACRONYMS, |
95 | | - hir_id, |
96 | | - span, |
97 | | - format!("name `{ident}` contains a capitalized acronym"), |
98 | | - |diag| { |
99 | | - diag.span_suggestion( |
100 | | - span, |
101 | | - "consider making the acronym lowercase, except the initial letter", |
102 | | - corrected, |
103 | | - Applicability::MaybeIncorrect, |
104 | | - ); |
105 | | - }, |
106 | | - ); |
107 | | - } |
| 90 | + let mut r = String::with_capacity(s.len()); |
| 91 | + let mut s = s.chars(); |
| 92 | + let mut prev_upper = false; |
| 93 | + while let Some(c) = s.next() { |
| 94 | + r.push( |
| 95 | + if replace(&mut prev_upper, c.is_ascii_uppercase()) |
| 96 | + && s.clone().next().map_or(true, |c| c.is_ascii_uppercase()) |
| 97 | + { |
| 98 | + c.to_ascii_lowercase() |
| 99 | + } else { |
| 100 | + c |
| 101 | + }, |
| 102 | + ); |
| 103 | + } |
| 104 | + r |
| 105 | + } else { |
| 106 | + return; |
| 107 | + }; |
| 108 | + |
| 109 | + span_lint_hir_and_then( |
| 110 | + cx, |
| 111 | + UPPER_CASE_ACRONYMS, |
| 112 | + hir_id, |
| 113 | + ident.span, |
| 114 | + format!("name `{ident}` contains a capitalized acronym"), |
| 115 | + |diag| { |
| 116 | + diag.span_suggestion( |
| 117 | + ident.span, |
| 118 | + "consider making the acronym lowercase, except the initial letter", |
| 119 | + replacement, |
| 120 | + Applicability::MaybeIncorrect, |
| 121 | + ); |
| 122 | + }, |
| 123 | + ); |
108 | 124 | } |
109 | 125 |
|
110 | 126 | impl LateLintPass<'_> for UpperCaseAcronyms { |
|
0 commit comments