Skip to content

Commit 1a6e9f5

Browse files
committed
Add lint single_letter_idents
1 parent 05de787 commit 1a6e9f5

File tree

9 files changed

+251
-6
lines changed

9 files changed

+251
-6
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5158,6 +5158,7 @@ Released 2018-09-13
51585158
[`single_char_push_str`]: https://rust-lang.github.io/rust-clippy/master/index.html#single_char_push_str
51595159
[`single_component_path_imports`]: https://rust-lang.github.io/rust-clippy/master/index.html#single_component_path_imports
51605160
[`single_element_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#single_element_loop
5161+
[`single_letter_idents`]: https://rust-lang.github.io/rust-clippy/master/index.html#single_letter_idents
51615162
[`single_match`]: https://rust-lang.github.io/rust-clippy/master/index.html#single_match
51625163
[`single_match_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#single_match_else
51635164
[`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
@@ -565,6 +565,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
565565
crate::significant_drop_tightening::SIGNIFICANT_DROP_TIGHTENING_INFO,
566566
crate::single_char_lifetime_names::SINGLE_CHAR_LIFETIME_NAMES_INFO,
567567
crate::single_component_path_imports::SINGLE_COMPONENT_PATH_IMPORTS_INFO,
568+
crate::single_letter_idents::SINGLE_LETTER_IDENTS_INFO,
568569
crate::size_of_in_element_count::SIZE_OF_IN_ELEMENT_COUNT_INFO,
569570
crate::size_of_ref::SIZE_OF_REF_INFO,
570571
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
@@ -284,6 +284,7 @@ mod shadow;
284284
mod significant_drop_tightening;
285285
mod single_char_lifetime_names;
286286
mod single_component_path_imports;
287+
mod single_letter_idents;
287288
mod size_of_in_element_count;
288289
mod size_of_ref;
289290
mod slow_vector_initialization;
@@ -1021,6 +1022,12 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
10211022
store.register_late_pass(|_| Box::new(endian_bytes::EndianBytes));
10221023
store.register_late_pass(|_| Box::new(redundant_type_annotations::RedundantTypeAnnotations));
10231024
store.register_late_pass(|_| Box::new(arc_with_non_send_sync::ArcWithNonSendSync));
1025+
let allowed_idents = conf.allowed_idents.clone();
1026+
store.register_early_pass(move || {
1027+
Box::new(single_letter_idents::SingleLetterIdents {
1028+
allowed_idents: allowed_idents.clone(),
1029+
})
1030+
});
10241031
// add lints here, do not remove this comment, it's used in `new_lint`
10251032
}
10261033

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
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: 6 additions & 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)]
@@ -514,6 +515,11 @@ define_Conf! {
514515
///
515516
/// The byte size a `T` in `Box<T>` can have, below which it triggers the `clippy::unnecessary_box` lint
516517
(unnecessary_box_size: u64 = 128),
518+
/// Lint: SINGLE_LETTER_IDENTS.
519+
///
520+
/// Allowed single letter idents.
521+
(allowed_idents: rustc_data_structures::fx::FxHashSet<char>
522+
= super::DEFAULT_ALLOWED_IDENTS.iter().copied().collect()),
517523
}
518524

519525
/// Search for the configuration file.

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`
@@ -319,14 +319,18 @@ fn attr_search_pat(attr: &Attribute) -> (Pat, Pat) {
319319
}
320320
}
321321

322-
pub trait WithSearchPat {
322+
fn ident_search_pat(ident: Ident) -> (Pat, Pat) {
323+
(Pat::OwnedStr(ident.name.as_str().to_owned()), Pat::Str(""))
324+
}
325+
326+
pub trait WithSearchPat<'cx> {
323327
type Context: LintContext;
324328
fn search_pat(&self, cx: &Self::Context) -> (Pat, Pat);
325329
fn span(&self) -> Span;
326330
}
327331
macro_rules! impl_with_search_pat {
328332
($cx:ident: $ty:ident with $fn:ident $(($tcx:ident))?) => {
329-
impl<'cx> WithSearchPat for $ty<'cx> {
333+
impl<'cx> WithSearchPat<'cx> for $ty<'cx> {
330334
type Context = $cx<'cx>;
331335
#[allow(unused_variables)]
332336
fn search_pat(&self, cx: &Self::Context) -> (Pat, Pat) {
@@ -346,7 +350,7 @@ impl_with_search_pat!(LateContext: ImplItem with impl_item_search_pat);
346350
impl_with_search_pat!(LateContext: FieldDef with field_def_search_pat);
347351
impl_with_search_pat!(LateContext: Variant with variant_search_pat);
348352

349-
impl<'cx> WithSearchPat for (&FnKind<'cx>, &Body<'cx>, HirId, Span) {
353+
impl<'cx> WithSearchPat<'cx> for (&FnKind<'cx>, &Body<'cx>, HirId, Span) {
350354
type Context = LateContext<'cx>;
351355

352356
fn search_pat(&self, cx: &Self::Context) -> (Pat, Pat) {
@@ -359,7 +363,7 @@ impl<'cx> WithSearchPat for (&FnKind<'cx>, &Body<'cx>, HirId, Span) {
359363
}
360364

361365
// `Attribute` does not have the `hir` associated lifetime, so we cannot use the macro
362-
impl<'cx> WithSearchPat for &'cx Attribute {
366+
impl<'cx> WithSearchPat<'cx> for &'cx Attribute {
363367
type Context = LateContext<'cx>;
364368

365369
fn search_pat(&self, _cx: &Self::Context) -> (Pat, Pat) {
@@ -371,11 +375,24 @@ impl<'cx> WithSearchPat for &'cx Attribute {
371375
}
372376
}
373377

378+
// `Ident` does not have the `hir` associated lifetime, so we cannot use the macro
379+
impl<'cx> WithSearchPat<'cx> for Ident {
380+
type Context = LateContext<'cx>;
381+
382+
fn search_pat(&self, _cx: &Self::Context) -> (Pat, Pat) {
383+
ident_search_pat(*self)
384+
}
385+
386+
fn span(&self) -> Span {
387+
self.span
388+
}
389+
}
390+
374391
/// Checks if the item likely came from a proc-macro.
375392
///
376393
/// This should be called after `in_external_macro` and the initial pattern matching of the ast as
377394
/// it is significantly slower than both of those.
378-
pub fn is_from_proc_macro<T: WithSearchPat>(cx: &T::Context, item: &T) -> bool {
395+
pub fn is_from_proc_macro<'cx, T: WithSearchPat<'cx>>(cx: &T::Context, item: &T) -> bool {
379396
let (start_pat, end_pat) = item.search_pat(cx);
380397
!span_matches_pat(cx.sess(), item.span(), start_pat, end_pat)
381398
}

tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ error: error reading Clippy's configuration file: unknown field `foobar`, expect
44
allow-mixed-uninlined-format-args
55
allow-print-in-tests
66
allow-unwrap-in-tests
7+
allowed-idents
78
allowed-scripts
89
arithmetic-side-effects-allowed
910
arithmetic-side-effects-allowed-binary
@@ -65,6 +66,7 @@ error: error reading Clippy's configuration file: unknown field `barfoo`, expect
6566
allow-mixed-uninlined-format-args
6667
allow-print-in-tests
6768
allow-unwrap-in-tests
69+
allowed-idents
6870
allowed-scripts
6971
arithmetic-side-effects-allowed
7072
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)