]>
Commit | Line | Data |
---|---|---|
fe692bf9 FG |
1 | use clippy_utils::{diagnostics::span_lint, is_from_proc_macro}; |
2 | use rustc_data_structures::fx::FxHashSet; | |
3 | use rustc_hir::{ | |
4 | def::{DefKind, Res}, | |
5 | intravisit::{walk_item, Visitor}, | |
6 | GenericParamKind, HirId, Item, ItemKind, ItemLocalId, Node, Pat, PatKind, | |
7 | }; | |
8 | use rustc_lint::{LateContext, LateLintPass, LintContext}; | |
9 | use rustc_middle::lint::in_external_macro; | |
10 | use rustc_session::{declare_tool_lint, impl_lint_pass}; | |
11 | use rustc_span::Span; | |
12 | use std::borrow::Cow; | |
13 | ||
14 | declare_clippy_lint! { | |
15 | /// ### What it does | |
16 | /// Checks for idents which comprise of a single letter. | |
17 | /// | |
18 | /// Note: This lint can be very noisy when enabled; it may be desirable to only enable it | |
19 | /// temporarily. | |
20 | /// | |
21 | /// ### Why is this bad? | |
22 | /// In many cases it's not, but at times it can severely hinder readability. Some codebases may | |
23 | /// wish to disallow this to improve readability. | |
24 | /// | |
25 | /// ### Example | |
26 | /// ```rust,ignore | |
27 | /// for m in movies { | |
28 | /// let title = m.t; | |
29 | /// } | |
30 | /// ``` | |
31 | /// Use instead: | |
32 | /// ```rust,ignore | |
33 | /// for movie in movies { | |
34 | /// let title = movie.title; | |
35 | /// } | |
36 | /// ``` | |
37 | #[clippy::version = "1.72.0"] | |
38 | pub MIN_IDENT_CHARS, | |
39 | restriction, | |
40 | "disallows idents that are too short" | |
41 | } | |
42 | impl_lint_pass!(MinIdentChars => [MIN_IDENT_CHARS]); | |
43 | ||
44 | #[derive(Clone)] | |
45 | pub struct MinIdentChars { | |
46 | pub allowed_idents_below_min_chars: FxHashSet<String>, | |
47 | pub min_ident_chars_threshold: u64, | |
48 | } | |
49 | ||
50 | impl MinIdentChars { | |
51 | #[expect(clippy::cast_possible_truncation)] | |
52 | fn is_ident_too_short(&self, cx: &LateContext<'_>, str: &str, span: Span) -> bool { | |
53 | !in_external_macro(cx.sess(), span) | |
54 | && str.len() <= self.min_ident_chars_threshold as usize | |
55 | && !str.starts_with('_') | |
56 | && !str.is_empty() | |
57 | && self.allowed_idents_below_min_chars.get(&str.to_owned()).is_none() | |
58 | } | |
59 | } | |
60 | ||
61 | impl LateLintPass<'_> for MinIdentChars { | |
62 | fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) { | |
63 | if self.min_ident_chars_threshold == 0 { | |
64 | return; | |
65 | } | |
66 | ||
67 | walk_item(&mut IdentVisitor { conf: self, cx }, item); | |
68 | } | |
69 | ||
70 | // This is necessary as `Node::Pat`s are not visited in `visit_id`. :/ | |
71 | fn check_pat(&mut self, cx: &LateContext<'_>, pat: &Pat<'_>) { | |
72 | if let PatKind::Binding(_, _, ident, ..) = pat.kind | |
73 | && let str = ident.as_str() | |
74 | && self.is_ident_too_short(cx, str, ident.span) | |
75 | { | |
76 | emit_min_ident_chars(self, cx, str, ident.span); | |
77 | } | |
78 | } | |
79 | } | |
80 | ||
81 | struct IdentVisitor<'cx, 'tcx> { | |
82 | conf: &'cx MinIdentChars, | |
83 | cx: &'cx LateContext<'tcx>, | |
84 | } | |
85 | ||
86 | impl Visitor<'_> for IdentVisitor<'_, '_> { | |
87 | fn visit_id(&mut self, hir_id: HirId) { | |
88 | let Self { conf, cx } = *self; | |
89 | // FIXME(#112534) Reimplementation of `find`, as it uses indexing, which can (and will in | |
90 | // async functions, or really anything async) panic. This should probably be fixed on the | |
91 | // rustc side, this is just a temporary workaround. | |
92 | let node = if hir_id.local_id == ItemLocalId::from_u32(0) { | |
93 | // In this case, we can just use `find`, `Owner`'s `node` field is private anyway so we can't | |
94 | // reimplement it even if we wanted to | |
95 | cx.tcx.hir().find(hir_id) | |
96 | } else { | |
97 | let Some(owner) = cx.tcx.hir_owner_nodes(hir_id.owner).as_owner() else { | |
98 | return; | |
99 | }; | |
100 | owner.nodes.get(hir_id.local_id).copied().flatten().map(|p| p.node) | |
101 | }; | |
102 | let Some(node) = node else { | |
103 | return; | |
104 | }; | |
105 | let Some(ident) = node.ident() else { | |
106 | return; | |
107 | }; | |
108 | ||
109 | let str = ident.as_str(); | |
110 | if conf.is_ident_too_short(cx, str, ident.span) { | |
111 | if let Node::Item(item) = node && let ItemKind::Use(..) = item.kind { | |
112 | return; | |
113 | } | |
114 | // `struct Awa<T>(T)` | |
115 | // ^ | |
116 | if let Node::PathSegment(path) = node { | |
117 | if let Res::Def(def_kind, ..) = path.res && let DefKind::TyParam = def_kind { | |
118 | return; | |
119 | } | |
120 | if matches!(path.res, Res::PrimTy(..)) || path.res.opt_def_id().is_some_and(|def_id| !def_id.is_local()) | |
121 | { | |
122 | return; | |
123 | } | |
124 | } | |
125 | // `struct Awa<T>(T)` | |
126 | // ^ | |
127 | if let Node::GenericParam(generic_param) = node | |
128 | && let GenericParamKind::Type { .. } = generic_param.kind | |
129 | { | |
130 | return; | |
131 | } | |
132 | ||
133 | if is_from_proc_macro(cx, &ident) { | |
134 | return; | |
135 | } | |
136 | ||
137 | emit_min_ident_chars(conf, cx, str, ident.span); | |
138 | } | |
139 | } | |
140 | } | |
141 | ||
142 | fn emit_min_ident_chars(conf: &MinIdentChars, cx: &impl LintContext, ident: &str, span: Span) { | |
143 | let help = if conf.min_ident_chars_threshold == 1 { | |
144 | Cow::Borrowed("this ident consists of a single char") | |
145 | } else { | |
146 | Cow::Owned(format!( | |
147 | "this ident is too short ({} <= {})", | |
148 | ident.len(), | |
149 | conf.min_ident_chars_threshold, | |
150 | )) | |
151 | }; | |
152 | span_lint(cx, MIN_IDENT_CHARS, span, &help); | |
153 | } |