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