]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/upper_case_acronyms.rs
Simplify `rustc_ast::visit::Visitor::visit_poly_trait_ref`.
[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_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;
9
10 declare_clippy_lint! {
11     /// ### What it does
12     /// Checks for fully capitalized names and optionally names containing a capitalized acronym.
13     ///
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)
17     /// for more.
18     ///
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
22     ///
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.
27     ///
28     /// ### Example
29     /// ```rust
30     /// struct HTTPResponse;
31     /// ```
32     /// Use instead:
33     /// ```rust
34     /// struct HttpResponse;
35     /// ```
36     #[clippy::version = "1.51.0"]
37     pub UPPER_CASE_ACRONYMS,
38     style,
39     "capitalized acronyms are against the naming convention"
40 }
41
42 #[derive(Default)]
43 pub struct UpperCaseAcronyms {
44     avoid_breaking_exported_api: bool,
45     upper_case_acronyms_aggressive: bool,
46 }
47
48 impl UpperCaseAcronyms {
49     pub fn new(avoid_breaking_exported_api: bool, aggressive: bool) -> Self {
50         Self {
51             avoid_breaking_exported_api,
52             upper_case_acronyms_aggressive: aggressive,
53         }
54     }
55 }
56
57 impl_lint_pass!(UpperCaseAcronyms => [UPPER_CASE_ACRONYMS]);
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 [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());
73         } else {
74             ident.push_str(curr);
75         }
76     }
77     ident
78 }
79
80 fn check_ident(cx: &LateContext<'_>, ident: &Ident, 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)
91     {
92         span_lint_and_sugg(
93             cx,
94             UPPER_CASE_ACRONYMS,
95             span,
96             &format!("name `{}` contains a capitalized acronym", ident),
97             "consider making the acronym lowercase, except the initial letter",
98             corrected,
99             Applicability::MaybeIncorrect,
100         );
101     }
102 }
103
104 impl LateLintPass<'_> for UpperCaseAcronyms {
105     fn check_item(&mut self, cx: &LateContext<'_>, it: &Item<'_>) {
106         // do not lint public items or in macros
107         if in_external_macro(cx.sess(), it.span)
108             || (self.avoid_breaking_exported_api && cx.access_levels.is_exported(it.def_id))
109         {
110             return;
111         }
112         match it.kind {
113             ItemKind::TyAlias(..) | ItemKind::Struct(..) | ItemKind::Trait(..) => {
114                 check_ident(cx, &it.ident, self.upper_case_acronyms_aggressive);
115             },
116             ItemKind::Enum(ref enumdef, _) => {
117                 // check enum variants separately because again we only want to lint on private enums and
118                 // the fn check_variant does not know about the vis of the enum of its variants
119                 enumdef
120                     .variants
121                     .iter()
122                     .for_each(|variant| check_ident(cx, &variant.ident, self.upper_case_acronyms_aggressive));
123             },
124             _ => {},
125         }
126     }
127 }