1 use clippy_utils::diagnostics::span_lint_and_sugg;
2 use itertools::Itertools;
3 use rustc_errors::Applicability;
4 use rustc_hir::{Item, ItemKind};
5 use rustc_lint::{LateContext, LateLintPass, LintContext};
6 use rustc_middle::lint::in_external_macro;
7 use rustc_session::{declare_tool_lint, impl_lint_pass};
8 use rustc_span::symbol::Ident;
10 declare_clippy_lint! {
12 /// Checks for fully capitalized names and optionally names containing a capitalized acronym.
14 /// ### Why is this bad?
15 /// In CamelCase, acronyms count as one word.
16 /// See [naming conventions](https://rust-lang.github.io/api-guidelines/naming.html#casing-conforms-to-rfc-430-c-case)
19 /// By default, the lint only triggers on fully-capitalized names.
20 /// You can use the `upper-case-acronyms-aggressive: true` config option to enable linting
21 /// on all camel case names
23 /// ### Known problems
24 /// When two acronyms are contiguous, the lint can't tell where
25 /// the first acronym ends and the second starts, so it suggests to lowercase all of
26 /// the letters in the second acronym.
30 /// struct HTTPResponse;
34 /// struct HttpResponse;
36 pub UPPER_CASE_ACRONYMS,
38 "capitalized acronyms are against the naming convention"
42 pub struct UpperCaseAcronyms {
43 avoid_breaking_exported_api: bool,
44 upper_case_acronyms_aggressive: bool,
47 impl UpperCaseAcronyms {
48 pub fn new(avoid_breaking_exported_api: bool, aggressive: bool) -> Self {
50 avoid_breaking_exported_api,
51 upper_case_acronyms_aggressive: aggressive,
56 impl_lint_pass!(UpperCaseAcronyms => [UPPER_CASE_ACRONYMS]);
58 fn correct_ident(ident: &str) -> String {
59 let ident = ident.chars().rev().collect::<String>();
61 .split_inclusive(|x: char| !x.is_ascii_lowercase())
63 .map(|x| x.chars().rev().collect::<String>());
65 let mut ident = fragments.clone().next().unwrap();
66 for (ref prev, ref curr) in fragments.tuple_windows() {
69 .all(|s| s.len() == 1 && s.chars().next().unwrap().is_ascii_uppercase())
71 ident.push_str(&curr.to_ascii_lowercase());
79 fn check_ident(cx: &LateContext<'_>, ident: &Ident, be_aggressive: bool) {
80 let span = ident.span;
81 let ident = &ident.as_str();
82 let corrected = correct_ident(ident);
83 // warn if we have pure-uppercase idents
84 // assume that two-letter words are some kind of valid abbreviation like FP for false positive
86 if (ident.chars().all(|c| c.is_ascii_uppercase()) && ident.len() > 2)
87 // otherwise, warn if we have SOmeTHING lIKE THIs but only warn with the aggressive
88 // upper-case-acronyms-aggressive config option enabled
89 || (be_aggressive && ident != &corrected)
95 &format!("name `{}` contains a capitalized acronym", ident),
96 "consider making the acronym lowercase, except the initial letter",
98 Applicability::MaybeIncorrect,
103 impl LateLintPass<'_> for UpperCaseAcronyms {
104 fn check_item(&mut self, cx: &LateContext<'_>, it: &Item<'_>) {
105 // do not lint public items or in macros
106 if in_external_macro(cx.sess(), it.span)
107 || (self.avoid_breaking_exported_api && cx.access_levels.is_exported(it.hir_id()))
112 ItemKind::TyAlias(..) | ItemKind::Struct(..) | ItemKind::Trait(..) => {
113 check_ident(cx, &it.ident, self.upper_case_acronyms_aggressive);
115 ItemKind::Enum(ref enumdef, _) => {
116 // check enum variants seperately because again we only want to lint on private enums and
117 // the fn check_variant does not know about the vis of the enum of its variants
121 .for_each(|variant| check_ident(cx, &variant.ident, self.upper_case_acronyms_aggressive));