Skip to content
This repository was archived by the owner on May 28, 2025. It is now read-only.

Commit 52cfc99

Browse files
committed
Add lint single_letter_idents
1 parent 903fe3b commit 52cfc99

File tree

9 files changed

+246
-6
lines changed

9 files changed

+246
-6
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5160,6 +5160,7 @@ Released 2018-09-13
51605160
[`single_char_push_str`]: https://rust-lang.github.io/rust-clippy/master/index.html#single_char_push_str
51615161
[`single_component_path_imports`]: https://rust-lang.github.io/rust-clippy/master/index.html#single_component_path_imports
51625162
[`single_element_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#single_element_loop
5163+
[`single_letter_idents`]: https://rust-lang.github.io/rust-clippy/master/index.html#single_letter_idents
51635164
[`single_match`]: https://rust-lang.github.io/rust-clippy/master/index.html#single_match
51645165
[`single_match_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#single_match_else
51655166
[`size_of_in_element_count`]: https://rust-lang.github.io/rust-clippy/master/index.html#size_of_in_element_count

clippy_lints/src/declared_lints.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -567,6 +567,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
567567
crate::significant_drop_tightening::SIGNIFICANT_DROP_TIGHTENING_INFO,
568568
crate::single_char_lifetime_names::SINGLE_CHAR_LIFETIME_NAMES_INFO,
569569
crate::single_component_path_imports::SINGLE_COMPONENT_PATH_IMPORTS_INFO,
570+
crate::single_letter_idents::SINGLE_LETTER_IDENTS_INFO,
570571
crate::size_of_in_element_count::SIZE_OF_IN_ELEMENT_COUNT_INFO,
571572
crate::size_of_ref::SIZE_OF_REF_INFO,
572573
crate::slow_vector_initialization::SLOW_VECTOR_INITIALIZATION_INFO,

clippy_lints/src/lib.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,7 @@ mod shadow;
286286
mod significant_drop_tightening;
287287
mod single_char_lifetime_names;
288288
mod single_component_path_imports;
289+
mod single_letter_idents;
289290
mod size_of_in_element_count;
290291
mod size_of_ref;
291292
mod slow_vector_initialization;
@@ -1033,6 +1034,12 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
10331034
store.register_late_pass(|_| Box::new(redundant_type_annotations::RedundantTypeAnnotations));
10341035
store.register_late_pass(|_| Box::new(arc_with_non_send_sync::ArcWithNonSendSync));
10351036
store.register_late_pass(|_| Box::new(needless_if::NeedlessIf));
1037+
let allowed_idents = conf.allowed_idents.clone();
1038+
store.register_early_pass(move || {
1039+
Box::new(single_letter_idents::SingleLetterIdents {
1040+
allowed_idents: allowed_idents.clone(),
1041+
})
1042+
});
10361043
// add lints here, do not remove this comment, it's used in `new_lint`
10371044
}
10381045

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
use clippy_utils::{diagnostics::span_lint, source::snippet};
2+
use itertools::Itertools;
3+
use rustc_data_structures::fx::FxHashSet;
4+
use rustc_lint::{EarlyContext, EarlyLintPass, LintContext};
5+
use rustc_middle::lint::in_external_macro;
6+
use rustc_session::{declare_tool_lint, impl_lint_pass};
7+
use rustc_span::symbol::Ident;
8+
9+
declare_clippy_lint! {
10+
/// ### What it does
11+
/// Checks for idents which comprise of a single letter.
12+
///
13+
/// Note: This lint can be very noisy when enabled; it even lints generics! it may be desirable
14+
/// to only enable it temporarily.
15+
///
16+
/// ### Why is this bad?
17+
/// In many cases it's not, but at times it can severely hinder readability. Some codebases may
18+
/// wish to disallow this to improve readability.
19+
///
20+
/// ### Example
21+
/// ```rust,ignore
22+
/// for i in collection {
23+
/// let x = i.x;
24+
/// }
25+
/// ```
26+
#[clippy::version = "1.72.0"]
27+
pub SINGLE_LETTER_IDENTS,
28+
restriction,
29+
"disallows idents that can be represented as a char"
30+
}
31+
impl_lint_pass!(SingleLetterIdents => [SINGLE_LETTER_IDENTS]);
32+
33+
#[derive(Clone)]
34+
pub struct SingleLetterIdents {
35+
pub allowed_idents: FxHashSet<char>,
36+
}
37+
38+
impl EarlyLintPass for SingleLetterIdents {
39+
fn check_ident(&mut self, cx: &EarlyContext<'_>, ident: Ident) {
40+
let str = ident.name.as_str();
41+
let chars = str.chars();
42+
if let [char, rest @ ..] = chars.collect_vec().as_slice()
43+
&& rest.is_empty()
44+
&& self.allowed_idents.get(char).is_none()
45+
&& !in_external_macro(cx.sess(), ident.span)
46+
// Ignore proc macros. Let's implement `WithSearchPat` for early lints someday :)
47+
&& snippet(cx, ident.span, str) == str
48+
{
49+
span_lint(cx, SINGLE_LETTER_IDENTS, ident.span, "this ident comprises of a single letter");
50+
}
51+
}
52+
}

clippy_lints/src/utils/conf.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ const DEFAULT_DOC_VALID_IDENTS: &[&str] = &[
3434
"CamelCase",
3535
];
3636
const DEFAULT_DISALLOWED_NAMES: &[&str] = &["foo", "baz", "quux"];
37+
const DEFAULT_ALLOWED_IDENTS: &[char] = &['i', 'j', 'x', 'y', 'z', 'n'];
3738

3839
/// Holds information used by `MISSING_ENFORCED_IMPORT_RENAMES` lint.
3940
#[derive(Clone, Debug, Deserialize)]

clippy_utils/src/check_proc_macro.rs

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ use rustc_hir::{
2525
use rustc_lint::{LateContext, LintContext};
2626
use rustc_middle::ty::TyCtxt;
2727
use rustc_session::Session;
28-
use rustc_span::{Span, Symbol};
28+
use rustc_span::{symbol::Ident, Span, Symbol};
2929
use rustc_target::spec::abi::Abi;
3030

3131
/// The search pattern to look for. Used by `span_matches_pat`
@@ -344,14 +344,18 @@ fn ty_search_pat(ty: &Ty<'_>) -> (Pat, Pat) {
344344
}
345345
}
346346

347-
pub trait WithSearchPat {
347+
fn ident_search_pat(ident: Ident) -> (Pat, Pat) {
348+
(Pat::OwnedStr(ident.name.as_str().to_owned()), Pat::Str(""))
349+
}
350+
351+
pub trait WithSearchPat<'cx> {
348352
type Context: LintContext;
349353
fn search_pat(&self, cx: &Self::Context) -> (Pat, Pat);
350354
fn span(&self) -> Span;
351355
}
352356
macro_rules! impl_with_search_pat {
353357
($cx:ident: $ty:ident with $fn:ident $(($tcx:ident))?) => {
354-
impl<'cx> WithSearchPat for $ty<'cx> {
358+
impl<'cx> WithSearchPat<'cx> for $ty<'cx> {
355359
type Context = $cx<'cx>;
356360
#[allow(unused_variables)]
357361
fn search_pat(&self, cx: &Self::Context) -> (Pat, Pat) {
@@ -372,7 +376,7 @@ impl_with_search_pat!(LateContext: FieldDef with field_def_search_pat);
372376
impl_with_search_pat!(LateContext: Variant with variant_search_pat);
373377
impl_with_search_pat!(LateContext: Ty with ty_search_pat);
374378

375-
impl<'cx> WithSearchPat for (&FnKind<'cx>, &Body<'cx>, HirId, Span) {
379+
impl<'cx> WithSearchPat<'cx> for (&FnKind<'cx>, &Body<'cx>, HirId, Span) {
376380
type Context = LateContext<'cx>;
377381

378382
fn search_pat(&self, cx: &Self::Context) -> (Pat, Pat) {
@@ -385,7 +389,7 @@ impl<'cx> WithSearchPat for (&FnKind<'cx>, &Body<'cx>, HirId, Span) {
385389
}
386390

387391
// `Attribute` does not have the `hir` associated lifetime, so we cannot use the macro
388-
impl<'cx> WithSearchPat for &'cx Attribute {
392+
impl<'cx> WithSearchPat<'cx> for &'cx Attribute {
389393
type Context = LateContext<'cx>;
390394

391395
fn search_pat(&self, _cx: &Self::Context) -> (Pat, Pat) {
@@ -397,11 +401,24 @@ impl<'cx> WithSearchPat for &'cx Attribute {
397401
}
398402
}
399403

404+
// `Ident` does not have the `hir` associated lifetime, so we cannot use the macro
405+
impl<'cx> WithSearchPat<'cx> for Ident {
406+
type Context = LateContext<'cx>;
407+
408+
fn search_pat(&self, _cx: &Self::Context) -> (Pat, Pat) {
409+
ident_search_pat(*self)
410+
}
411+
412+
fn span(&self) -> Span {
413+
self.span
414+
}
415+
}
416+
400417
/// Checks if the item likely came from a proc-macro.
401418
///
402419
/// This should be called after `in_external_macro` and the initial pattern matching of the ast as
403420
/// it is significantly slower than both of those.
404-
pub fn is_from_proc_macro<T: WithSearchPat>(cx: &T::Context, item: &T) -> bool {
421+
pub fn is_from_proc_macro<'cx, T: WithSearchPat<'cx>>(cx: &T::Context, item: &T) -> bool {
405422
let (start_pat, end_pat) = item.search_pat(cx);
406423
!span_matches_pat(cx.sess(), item.span(), start_pat, end_pat)
407424
}

tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ error: error reading Clippy's configuration file: unknown field `foobar`, expect
55
allow-print-in-tests
66
allow-private-module-inception
77
allow-unwrap-in-tests
8+
allowed-idents
89
allowed-scripts
910
arithmetic-side-effects-allowed
1011
arithmetic-side-effects-allowed-binary
@@ -68,6 +69,7 @@ error: error reading Clippy's configuration file: unknown field `barfoo`, expect
6869
allow-print-in-tests
6970
allow-private-module-inception
7071
allow-unwrap-in-tests
72+
allowed-idents
7173
allowed-scripts
7274
arithmetic-side-effects-allowed
7375
arithmetic-side-effects-allowed-binary

tests/ui/single_letter_idents.rs

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
//@aux-build:proc_macros.rs
2+
#![allow(nonstandard_style, unused)]
3+
#![warn(clippy::single_letter_idents)]
4+
5+
extern crate proc_macros;
6+
use proc_macros::external;
7+
use proc_macros::with_span;
8+
9+
struct A {
10+
a: u32,
11+
i: u32,
12+
A: u32,
13+
I: u32,
14+
}
15+
16+
struct B(u32);
17+
18+
struct i;
19+
20+
enum C {
21+
D,
22+
E,
23+
F,
24+
j,
25+
}
26+
27+
struct Vec4 {
28+
x: u32,
29+
y: u32,
30+
z: u32,
31+
w: u32,
32+
}
33+
34+
struct AA<T, E>(T, E);
35+
36+
fn main() {
37+
// Allowed idents
38+
let w = 1;
39+
// Ok, not this one
40+
// let i = 1;
41+
let j = 1;
42+
let n = 1;
43+
let x = 1;
44+
let y = 1;
45+
let z = 1;
46+
47+
for j in 0..1000 {}
48+
49+
// Do not lint code from external macros
50+
external! { for j in 0..1000 {} }
51+
// Do not lint code from procedural macros
52+
with_span! {
53+
span
54+
for j in 0..1000 {}
55+
}
56+
}
57+
58+
fn b() {}
59+
fn owo() {}

tests/ui/single_letter_idents.stderr

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
error: this ident comprises of a single letter
2+
--> $DIR/single_letter_idents.rs:9:8
3+
|
4+
LL | struct A {
5+
| ^
6+
|
7+
= note: `-D clippy::single-letter-idents` implied by `-D warnings`
8+
9+
error: this ident comprises of a single letter
10+
--> $DIR/single_letter_idents.rs:10:5
11+
|
12+
LL | a: u32,
13+
| ^
14+
15+
error: this ident comprises of a single letter
16+
--> $DIR/single_letter_idents.rs:12:5
17+
|
18+
LL | A: u32,
19+
| ^
20+
21+
error: this ident comprises of a single letter
22+
--> $DIR/single_letter_idents.rs:13:5
23+
|
24+
LL | I: u32,
25+
| ^
26+
27+
error: this ident comprises of a single letter
28+
--> $DIR/single_letter_idents.rs:16:8
29+
|
30+
LL | struct B(u32);
31+
| ^
32+
33+
error: this ident comprises of a single letter
34+
--> $DIR/single_letter_idents.rs:20:6
35+
|
36+
LL | enum C {
37+
| ^
38+
39+
error: this ident comprises of a single letter
40+
--> $DIR/single_letter_idents.rs:21:5
41+
|
42+
LL | D,
43+
| ^
44+
45+
error: this ident comprises of a single letter
46+
--> $DIR/single_letter_idents.rs:22:5
47+
|
48+
LL | E,
49+
| ^
50+
51+
error: this ident comprises of a single letter
52+
--> $DIR/single_letter_idents.rs:23:5
53+
|
54+
LL | F,
55+
| ^
56+
57+
error: this ident comprises of a single letter
58+
--> $DIR/single_letter_idents.rs:31:5
59+
|
60+
LL | w: u32,
61+
| ^
62+
63+
error: this ident comprises of a single letter
64+
--> $DIR/single_letter_idents.rs:34:11
65+
|
66+
LL | struct AA<T, E>(T, E);
67+
| ^
68+
69+
error: this ident comprises of a single letter
70+
--> $DIR/single_letter_idents.rs:34:14
71+
|
72+
LL | struct AA<T, E>(T, E);
73+
| ^
74+
75+
error: this ident comprises of a single letter
76+
--> $DIR/single_letter_idents.rs:34:17
77+
|
78+
LL | struct AA<T, E>(T, E);
79+
| ^
80+
81+
error: this ident comprises of a single letter
82+
--> $DIR/single_letter_idents.rs:34:20
83+
|
84+
LL | struct AA<T, E>(T, E);
85+
| ^
86+
87+
error: this ident comprises of a single letter
88+
--> $DIR/single_letter_idents.rs:38:9
89+
|
90+
LL | let w = 1;
91+
| ^
92+
93+
error: this ident comprises of a single letter
94+
--> $DIR/single_letter_idents.rs:58:4
95+
|
96+
LL | fn b() {}
97+
| ^
98+
99+
error: aborting due to 16 previous errors
100+

0 commit comments

Comments
 (0)