]>
Commit | Line | Data |
---|---|---|
f20569fa XL |
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 | } |