]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/upper_case_acronyms.rs
Implement unnecessary-async and UI test
[rust.git] / clippy_lints / src / upper_case_acronyms.rs
1 use clippy_utils::diagnostics::span_lint_and_sugg;
2 use itertools::Itertools;
3 use rustc_ast::ast::{Item, ItemKind, VisibilityKind};
4 use rustc_errors::Applicability;
5 use rustc_lint::{EarlyContext, EarlyLintPass, 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;
9
10 declare_clippy_lint! {
11     /// **What it does:** Checks for fully capitalized names and optionally names containing a capitalized acronym.
12     ///
13     /// **Why is this bad?** In CamelCase, acronyms count as one word.
14     /// See [naming conventions](https://rust-lang.github.io/api-guidelines/naming.html#casing-conforms-to-rfc-430-c-case)
15     /// for more.
16     ///
17     /// By default, the lint only triggers on fully-capitalized names.
18     /// You can use the `upper-case-acronyms-aggressive: true` config option to enable linting
19     /// on all camel case names
20     ///
21     /// **Known problems:** When two acronyms are contiguous, the lint can't tell where
22     /// the first acronym ends and the second starts, so it suggests to lowercase all of
23     /// the letters in the second acronym.
24     ///
25     /// **Example:**
26     ///
27     /// ```rust
28     /// struct HTTPResponse;
29     /// ```
30     /// Use instead:
31     /// ```rust
32     /// struct HttpResponse;
33     /// ```
34     pub UPPER_CASE_ACRONYMS,
35     style,
36     "capitalized acronyms are against the naming convention"
37 }
38
39 #[derive(Default)]
40 pub struct UpperCaseAcronyms {
41     upper_case_acronyms_aggressive: bool,
42 }
43
44 impl UpperCaseAcronyms {
45     pub fn new(aggressive: bool) -> Self {
46         Self {
47             upper_case_acronyms_aggressive: aggressive,
48         }
49     }
50 }
51
52 impl_lint_pass!(UpperCaseAcronyms => [UPPER_CASE_ACRONYMS]);
53
54 fn correct_ident(ident: &str) -> String {
55     let ident = ident.chars().rev().collect::<String>();
56     let fragments = ident
57         .split_inclusive(|x: char| !x.is_ascii_lowercase())
58         .rev()
59         .map(|x| x.chars().rev().collect::<String>());
60
61     let mut ident = fragments.clone().next().unwrap();
62     for (ref prev, ref curr) in fragments.tuple_windows() {
63         if [prev, curr]
64             .iter()
65             .all(|s| s.len() == 1 && s.chars().next().unwrap().is_ascii_uppercase())
66         {
67             ident.push_str(&curr.to_ascii_lowercase());
68         } else {
69             ident.push_str(curr);
70         }
71     }
72     ident
73 }
74
75 fn check_ident(cx: &EarlyContext<'_>, ident: &Ident, be_aggressive: bool) {
76     let span = ident.span;
77     let ident = &ident.as_str();
78     let corrected = correct_ident(ident);
79     // warn if we have pure-uppercase idents
80     // assume that two-letter words are some kind of valid abbreviation like FP for false positive
81     // (and don't warn)
82     if (ident.chars().all(|c| c.is_ascii_uppercase()) && ident.len() > 2)
83     // otherwise, warn if we have SOmeTHING lIKE THIs but only warn with the aggressive
84     // upper-case-acronyms-aggressive config option enabled
85     || (be_aggressive && ident != &corrected)
86     {
87         span_lint_and_sugg(
88             cx,
89             UPPER_CASE_ACRONYMS,
90             span,
91             &format!("name `{}` contains a capitalized acronym", ident),
92             "consider making the acronym lowercase, except the initial letter",
93             corrected,
94             Applicability::MaybeIncorrect,
95         )
96     }
97 }
98
99 impl EarlyLintPass for UpperCaseAcronyms {
100     fn check_item(&mut self, cx: &EarlyContext<'_>, it: &Item) {
101         // do not lint public items or in macros
102         if !in_external_macro(cx.sess(), it.span) && !matches!(it.vis.kind, VisibilityKind::Public) {
103             if matches!(
104                 it.kind,
105                 ItemKind::TyAlias(..) | ItemKind::Struct(..) | ItemKind::Trait(..)
106             ) {
107                 check_ident(cx, &it.ident, self.upper_case_acronyms_aggressive);
108             } else if let ItemKind::Enum(ref enumdef, _) = it.kind {
109                 // check enum variants seperately because again we only want to lint on private enums and
110                 // the fn check_variant does not know about the vis of the enum of its variants
111                 enumdef
112                     .variants
113                     .iter()
114                     .for_each(|variant| check_ident(cx, &variant.ident, self.upper_case_acronyms_aggressive));
115             }
116         }
117     }
118 }