]> git.proxmox.com Git - rustc.git/blame - src/tools/clippy/clippy_lints/src/upper_case_acronyms.rs
New upstream version 1.52.1+dfsg1
[rustc.git] / src / tools / clippy / clippy_lints / src / upper_case_acronyms.rs
CommitLineData
f20569fa
XL
1use crate::utils::span_lint_and_sugg;
2use if_chain::if_chain;
3use itertools::Itertools;
4use rustc_ast::ast::{Item, ItemKind, Variant};
5use rustc_errors::Applicability;
6use rustc_lint::{EarlyContext, EarlyLintPass, LintContext};
7use rustc_middle::lint::in_external_macro;
8use rustc_session::{declare_tool_lint, impl_lint_pass};
9use rustc_span::symbol::Ident;
10
11declare_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)]
41pub struct UpperCaseAcronyms {
42 upper_case_acronyms_aggressive: bool,
43}
44
45impl UpperCaseAcronyms {
46 pub fn new(aggressive: bool) -> Self {
47 Self {
48 upper_case_acronyms_aggressive: aggressive,
49 }
50 }
51}
52
53impl_lint_pass!(UpperCaseAcronyms => [UPPER_CASE_ACRONYMS]);
54
55fn 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
76fn 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
100impl 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}