-
Notifications
You must be signed in to change notification settings - Fork 1.6k
/
upper_case_acronyms.rs
149 lines (140 loc) · 4.95 KB
/
upper_case_acronyms.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
use clippy_config::Conf;
use clippy_utils::diagnostics::span_lint_hir_and_then;
use core::mem::replace;
use rustc_errors::Applicability;
use rustc_hir::{HirId, Item, ItemKind};
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::lint::in_external_macro;
use rustc_session::impl_lint_pass;
use rustc_span::symbol::Ident;
declare_clippy_lint! {
/// ### What it does
/// Checks for fully capitalized names and optionally names containing a capitalized acronym.
///
/// ### Why is this bad?
/// In CamelCase, acronyms count as one word.
/// See [naming conventions](https://rust-lang.github.io/api-guidelines/naming.html#casing-conforms-to-rfc-430-c-case)
/// for more.
///
/// By default, the lint only triggers on fully-capitalized names.
/// You can use the `upper-case-acronyms-aggressive: true` config option to enable linting
/// on all camel case names
///
/// ### Known problems
/// When two acronyms are contiguous, the lint can't tell where
/// the first acronym ends and the second starts, so it suggests to lowercase all of
/// the letters in the second acronym.
///
/// ### Example
/// ```no_run
/// struct HTTPResponse;
/// ```
/// Use instead:
/// ```no_run
/// struct HttpResponse;
/// ```
#[clippy::version = "1.51.0"]
pub UPPER_CASE_ACRONYMS,
style,
"capitalized acronyms are against the naming convention"
}
pub struct UpperCaseAcronyms {
avoid_breaking_exported_api: bool,
upper_case_acronyms_aggressive: bool,
}
impl UpperCaseAcronyms {
pub fn new(conf: &'static Conf) -> Self {
Self {
avoid_breaking_exported_api: conf.avoid_breaking_exported_api,
upper_case_acronyms_aggressive: conf.upper_case_acronyms_aggressive,
}
}
}
impl_lint_pass!(UpperCaseAcronyms => [UPPER_CASE_ACRONYMS]);
fn contains_acronym(s: &str) -> bool {
let mut count = 0;
for c in s.chars() {
if c.is_ascii_uppercase() {
count += 1;
if count == 3 {
return true;
}
} else {
count = 0;
}
}
count == 2
}
fn check_ident(cx: &LateContext<'_>, ident: &Ident, hir_id: HirId, be_aggressive: bool) {
let s = ident.as_str();
// By default, only warn for upper case identifiers with at least 3 characters.
let replacement = if s.len() > 2 && s.bytes().all(|c| c.is_ascii_uppercase()) {
let mut r = String::with_capacity(s.len());
let mut s = s.chars();
r.push(s.next().unwrap());
r.extend(s.map(|c| c.to_ascii_lowercase()));
r
} else if be_aggressive
// Only lint if the ident starts with an upper case character.
&& let unprefixed = s.trim_start_matches('_')
&& unprefixed.starts_with(|c: char| c.is_ascii_uppercase())
&& contains_acronym(unprefixed)
{
let mut r = String::with_capacity(s.len());
let mut s = s.chars();
let mut prev_upper = false;
while let Some(c) = s.next() {
r.push(
if replace(&mut prev_upper, c.is_ascii_uppercase())
&& s.clone().next().is_none_or(|c| c.is_ascii_uppercase())
{
c.to_ascii_lowercase()
} else {
c
},
);
}
r
} else {
return;
};
span_lint_hir_and_then(
cx,
UPPER_CASE_ACRONYMS,
hir_id,
ident.span,
format!("name `{ident}` contains a capitalized acronym"),
|diag| {
diag.span_suggestion(
ident.span,
"consider making the acronym lowercase, except the initial letter",
replacement,
Applicability::MaybeIncorrect,
);
},
);
}
impl LateLintPass<'_> for UpperCaseAcronyms {
fn check_item(&mut self, cx: &LateContext<'_>, it: &Item<'_>) {
// do not lint public items or in macros
if in_external_macro(cx.sess(), it.span)
|| (self.avoid_breaking_exported_api && cx.effective_visibilities.is_exported(it.owner_id.def_id))
{
return;
}
match it.kind {
ItemKind::TyAlias(..) | ItemKind::Struct(..) | ItemKind::Trait(..) => {
check_ident(cx, &it.ident, it.hir_id(), self.upper_case_acronyms_aggressive);
},
ItemKind::Enum(ref enumdef, _) => {
check_ident(cx, &it.ident, it.hir_id(), self.upper_case_acronyms_aggressive);
// check enum variants separately because again we only want to lint on private enums and
// the fn check_variant does not know about the vis of the enum of its variants
enumdef.variants.iter().for_each(|variant| {
check_ident(cx, &variant.ident, variant.hir_id, self.upper_case_acronyms_aggressive);
});
},
_ => {},
}
}
}