summaryrefslogtreecommitdiffstats
path: root/src/tools/clippy/clippy_lints/src/upper_case_acronyms.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/tools/clippy/clippy_lints/src/upper_case_acronyms.rs')
-rw-r--r--src/tools/clippy/clippy_lints/src/upper_case_acronyms.rs127
1 files changed, 127 insertions, 0 deletions
diff --git a/src/tools/clippy/clippy_lints/src/upper_case_acronyms.rs b/src/tools/clippy/clippy_lints/src/upper_case_acronyms.rs
new file mode 100644
index 000000000..02bf09ed5
--- /dev/null
+++ b/src/tools/clippy/clippy_lints/src/upper_case_acronyms.rs
@@ -0,0 +1,127 @@
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use itertools::Itertools;
+use rustc_errors::Applicability;
+use rustc_hir::{Item, ItemKind};
+use rustc_lint::{LateContext, LateLintPass, LintContext};
+use rustc_middle::lint::in_external_macro;
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::symbol::Ident;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for fully capitalized names and optionally names containing a capitalized acronym.
+ ///
+ /// ### Why is this bad?
+ /// In CamelCase, acronyms count as one word.
+ /// See [naming conventions](https://rust-lang.github.io/api-guidelines/naming.html#casing-conforms-to-rfc-430-c-case)
+ /// for more.
+ ///
+ /// By default, the lint only triggers on fully-capitalized names.
+ /// You can use the `upper-case-acronyms-aggressive: true` config option to enable linting
+ /// on all camel case names
+ ///
+ /// ### Known problems
+ /// When two acronyms are contiguous, the lint can't tell where
+ /// the first acronym ends and the second starts, so it suggests to lowercase all of
+ /// the letters in the second acronym.
+ ///
+ /// ### Example
+ /// ```rust
+ /// struct HTTPResponse;
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// struct HttpResponse;
+ /// ```
+ #[clippy::version = "1.51.0"]
+ pub UPPER_CASE_ACRONYMS,
+ style,
+ "capitalized acronyms are against the naming convention"
+}
+
+#[derive(Default)]
+pub struct UpperCaseAcronyms {
+ avoid_breaking_exported_api: bool,
+ upper_case_acronyms_aggressive: bool,
+}
+
+impl UpperCaseAcronyms {
+ pub fn new(avoid_breaking_exported_api: bool, aggressive: bool) -> Self {
+ Self {
+ avoid_breaking_exported_api,
+ upper_case_acronyms_aggressive: aggressive,
+ }
+ }
+}
+
+impl_lint_pass!(UpperCaseAcronyms => [UPPER_CASE_ACRONYMS]);
+
+fn correct_ident(ident: &str) -> String {
+ let ident = ident.chars().rev().collect::<String>();
+ let fragments = ident
+ .split_inclusive(|x: char| !x.is_ascii_lowercase())
+ .rev()
+ .map(|x| x.chars().rev().collect::<String>());
+
+ let mut ident = fragments.clone().next().unwrap();
+ for (ref prev, ref curr) in fragments.tuple_windows() {
+ if [prev, curr]
+ .iter()
+ .all(|s| s.len() == 1 && s.chars().next().unwrap().is_ascii_uppercase())
+ {
+ ident.push_str(&curr.to_ascii_lowercase());
+ } else {
+ ident.push_str(curr);
+ }
+ }
+ ident
+}
+
+fn check_ident(cx: &LateContext<'_>, ident: &Ident, be_aggressive: bool) {
+ let span = ident.span;
+ let ident = ident.as_str();
+ let corrected = correct_ident(ident);
+ // warn if we have pure-uppercase idents
+ // assume that two-letter words are some kind of valid abbreviation like FP for false positive
+ // (and don't warn)
+ if (ident.chars().all(|c| c.is_ascii_uppercase()) && ident.len() > 2)
+ // otherwise, warn if we have SOmeTHING lIKE THIs but only warn with the aggressive
+ // upper-case-acronyms-aggressive config option enabled
+ || (be_aggressive && ident != corrected)
+ {
+ span_lint_and_sugg(
+ cx,
+ UPPER_CASE_ACRONYMS,
+ span,
+ &format!("name `{}` contains a capitalized acronym", ident),
+ "consider making the acronym lowercase, except the initial letter",
+ corrected,
+ Applicability::MaybeIncorrect,
+ );
+ }
+}
+
+impl LateLintPass<'_> for UpperCaseAcronyms {
+ fn check_item(&mut self, cx: &LateContext<'_>, it: &Item<'_>) {
+ // do not lint public items or in macros
+ if in_external_macro(cx.sess(), it.span)
+ || (self.avoid_breaking_exported_api && cx.access_levels.is_exported(it.def_id))
+ {
+ return;
+ }
+ match it.kind {
+ ItemKind::TyAlias(..) | ItemKind::Struct(..) | ItemKind::Trait(..) => {
+ check_ident(cx, &it.ident, self.upper_case_acronyms_aggressive);
+ },
+ ItemKind::Enum(ref enumdef, _) => {
+ // check enum variants separately because again we only want to lint on private enums and
+ // the fn check_variant does not know about the vis of the enum of its variants
+ enumdef
+ .variants
+ .iter()
+ .for_each(|variant| check_ident(cx, &variant.ident, self.upper_case_acronyms_aggressive));
+ },
+ _ => {},
+ }
+ }
+}