]> git.proxmox.com Git - rustc.git/blob - src/tools/clippy/clippy_lints/src/utils/internal_lints/lint_without_lint_pass.rs
bump version to 1.80.1+dfsg1-1~bpo12+pve1
[rustc.git] / src / tools / clippy / clippy_lints / src / utils / internal_lints / lint_without_lint_pass.rs
1 use crate::utils::internal_lints::metadata_collector::is_deprecated_lint;
2 use clippy_utils::diagnostics::{span_lint, span_lint_and_help};
3 use clippy_utils::macros::root_macro_call_first_node;
4 use clippy_utils::{is_lint_allowed, match_def_path, paths};
5 use rustc_ast::ast::LitKind;
6 use rustc_data_structures::fx::{FxHashMap, FxHashSet};
7 use rustc_hir::def::{DefKind, Res};
8 use rustc_hir::hir_id::CRATE_HIR_ID;
9 use rustc_hir::intravisit::Visitor;
10 use rustc_hir::{ExprKind, HirId, Item, MutTy, Mutability, Path, TyKind};
11 use rustc_lint::{LateContext, LateLintPass};
12 use rustc_middle::hir::nested_filter;
13 use rustc_semver::RustcVersion;
14 use rustc_session::impl_lint_pass;
15 use rustc_span::source_map::Spanned;
16 use rustc_span::symbol::Symbol;
17 use rustc_span::{sym, Span};
18 use {rustc_ast as ast, rustc_hir as hir};
19
20 declare_clippy_lint! {
21 /// ### What it does
22 /// Ensures every lint is associated to a `LintPass`.
23 ///
24 /// ### Why is this bad?
25 /// The compiler only knows lints via a `LintPass`. Without
26 /// putting a lint to a `LintPass::get_lints()`'s return, the compiler will not
27 /// know the name of the lint.
28 ///
29 /// ### Known problems
30 /// Only checks for lints associated using the `declare_lint_pass!` and
31 /// `impl_lint_pass!` macros.
32 ///
33 /// ### Example
34 /// ```rust,ignore
35 /// declare_lint! { pub LINT_1, ... }
36 /// declare_lint! { pub LINT_2, ... }
37 /// declare_lint! { pub FORGOTTEN_LINT, ... }
38 /// // ...
39 /// declare_lint_pass!(Pass => [LINT_1, LINT_2]);
40 /// // missing FORGOTTEN_LINT
41 /// ```
42 pub LINT_WITHOUT_LINT_PASS,
43 internal,
44 "declaring a lint without associating it in a LintPass"
45 }
46
47 declare_clippy_lint! {
48 /// ### What it does
49 /// Checks for cases of an auto-generated lint without an updated description,
50 /// i.e. `default lint description`.
51 ///
52 /// ### Why is this bad?
53 /// Indicates that the lint is not finished.
54 ///
55 /// ### Example
56 /// ```rust,ignore
57 /// declare_lint! { pub COOL_LINT, nursery, "default lint description" }
58 /// ```
59 ///
60 /// Use instead:
61 /// ```rust,ignore
62 /// declare_lint! { pub COOL_LINT, nursery, "a great new lint" }
63 /// ```
64 pub DEFAULT_LINT,
65 internal,
66 "found 'default lint description' in a lint declaration"
67 }
68
69 declare_clippy_lint! {
70 /// ### What it does
71 /// Checks for invalid `clippy::version` attributes.
72 ///
73 /// Valid values are:
74 /// * "pre 1.29.0"
75 /// * any valid semantic version
76 pub INVALID_CLIPPY_VERSION_ATTRIBUTE,
77 internal,
78 "found an invalid `clippy::version` attribute"
79 }
80
81 declare_clippy_lint! {
82 /// ### What it does
83 /// Checks for declared clippy lints without the `clippy::version` attribute.
84 ///
85 pub MISSING_CLIPPY_VERSION_ATTRIBUTE,
86 internal,
87 "found clippy lint without `clippy::version` attribute"
88 }
89
90 declare_clippy_lint! {
91 /// ### What it does
92 /// Checks for cases of an auto-generated deprecated lint without an updated reason,
93 /// i.e. `"default deprecation note"`.
94 ///
95 /// ### Why is this bad?
96 /// Indicates that the documentation is incomplete.
97 ///
98 /// ### Example
99 /// ```rust,ignore
100 /// declare_deprecated_lint! {
101 /// /// ### What it does
102 /// /// Nothing. This lint has been deprecated.
103 /// ///
104 /// /// ### Deprecation reason
105 /// /// TODO
106 /// #[clippy::version = "1.63.0"]
107 /// pub COOL_LINT,
108 /// "default deprecation note"
109 /// }
110 /// ```
111 ///
112 /// Use instead:
113 /// ```rust,ignore
114 /// declare_deprecated_lint! {
115 /// /// ### What it does
116 /// /// Nothing. This lint has been deprecated.
117 /// ///
118 /// /// ### Deprecation reason
119 /// /// This lint has been replaced by `cooler_lint`
120 /// #[clippy::version = "1.63.0"]
121 /// pub COOL_LINT,
122 /// "this lint has been replaced by `cooler_lint`"
123 /// }
124 /// ```
125 pub DEFAULT_DEPRECATION_REASON,
126 internal,
127 "found 'default deprecation note' in a deprecated lint declaration"
128 }
129
130 #[derive(Clone, Debug, Default)]
131 pub struct LintWithoutLintPass {
132 declared_lints: FxHashMap<Symbol, Span>,
133 registered_lints: FxHashSet<Symbol>,
134 }
135
136 impl_lint_pass!(LintWithoutLintPass => [DEFAULT_LINT, LINT_WITHOUT_LINT_PASS, INVALID_CLIPPY_VERSION_ATTRIBUTE, MISSING_CLIPPY_VERSION_ATTRIBUTE, DEFAULT_DEPRECATION_REASON]);
137
138 impl<'tcx> LateLintPass<'tcx> for LintWithoutLintPass {
139 fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
140 if is_lint_allowed(cx, DEFAULT_LINT, item.hir_id())
141 || is_lint_allowed(cx, DEFAULT_DEPRECATION_REASON, item.hir_id())
142 {
143 return;
144 }
145
146 if let hir::ItemKind::Static(ty, Mutability::Not, body_id) = item.kind {
147 let is_lint_ref_ty = is_lint_ref_type(cx, ty);
148 if is_deprecated_lint(cx, ty) || is_lint_ref_ty {
149 check_invalid_clippy_version_attribute(cx, item);
150
151 let expr = &cx.tcx.hir().body(body_id).value;
152 let fields;
153 if is_lint_ref_ty {
154 if let ExprKind::AddrOf(_, _, inner_exp) = expr.kind
155 && let ExprKind::Struct(_, struct_fields, _) = inner_exp.kind
156 {
157 fields = struct_fields;
158 } else {
159 return;
160 }
161 } else if let ExprKind::Struct(_, struct_fields, _) = expr.kind {
162 fields = struct_fields;
163 } else {
164 return;
165 }
166
167 let field = fields
168 .iter()
169 .find(|f| f.ident.as_str() == "desc")
170 .expect("lints must have a description field");
171
172 if let ExprKind::Lit(Spanned {
173 node: LitKind::Str(ref sym, _),
174 ..
175 }) = field.expr.kind
176 {
177 let sym_str = sym.as_str();
178 if is_lint_ref_ty {
179 if sym_str == "default lint description" {
180 span_lint(
181 cx,
182 DEFAULT_LINT,
183 item.span,
184 format!("the lint `{}` has the default lint description", item.ident.name),
185 );
186 }
187
188 self.declared_lints.insert(item.ident.name, item.span);
189 } else if sym_str == "default deprecation note" {
190 span_lint(
191 cx,
192 DEFAULT_DEPRECATION_REASON,
193 item.span,
194 format!("the lint `{}` has the default deprecation reason", item.ident.name),
195 );
196 }
197 }
198 }
199 } else if let Some(macro_call) = root_macro_call_first_node(cx, item) {
200 if !matches!(
201 cx.tcx.item_name(macro_call.def_id).as_str(),
202 "impl_lint_pass" | "declare_lint_pass"
203 ) {
204 return;
205 }
206 if let hir::ItemKind::Impl(hir::Impl {
207 of_trait: None,
208 items: impl_item_refs,
209 ..
210 }) = item.kind
211 {
212 let mut collector = LintCollector {
213 output: &mut self.registered_lints,
214 cx,
215 };
216 let body_id = cx.tcx.hir().body_owned_by(
217 impl_item_refs
218 .iter()
219 .find(|iiref| iiref.ident.as_str() == "get_lints")
220 .expect("LintPass needs to implement get_lints")
221 .id
222 .owner_id
223 .def_id,
224 );
225 collector.visit_expr(cx.tcx.hir().body(body_id).value);
226 }
227 }
228 }
229
230 fn check_crate_post(&mut self, cx: &LateContext<'tcx>) {
231 if is_lint_allowed(cx, LINT_WITHOUT_LINT_PASS, CRATE_HIR_ID) {
232 return;
233 }
234
235 for (lint_name, &lint_span) in &self.declared_lints {
236 // When using the `declare_tool_lint!` macro, the original `lint_span`'s
237 // file points to "<rustc macros>".
238 // `compiletest-rs` thinks that's an error in a different file and
239 // just ignores it. This causes the test in compile-fail/lint_pass
240 // not able to capture the error.
241 // Therefore, we need to climb the macro expansion tree and find the
242 // actual span that invoked `declare_tool_lint!`:
243 let lint_span = lint_span.ctxt().outer_expn_data().call_site;
244
245 if !self.registered_lints.contains(lint_name) {
246 span_lint(
247 cx,
248 LINT_WITHOUT_LINT_PASS,
249 lint_span,
250 format!("the lint `{lint_name}` is not added to any `LintPass`"),
251 );
252 }
253 }
254 }
255 }
256
257 pub(super) fn is_lint_ref_type(cx: &LateContext<'_>, ty: &hir::Ty<'_>) -> bool {
258 if let TyKind::Ref(
259 _,
260 MutTy {
261 ty: inner,
262 mutbl: Mutability::Not,
263 },
264 ) = ty.kind
265 {
266 if let TyKind::Path(ref path) = inner.kind {
267 if let Res::Def(DefKind::Struct, def_id) = cx.qpath_res(path, inner.hir_id) {
268 return match_def_path(cx, def_id, &paths::LINT);
269 }
270 }
271 }
272
273 false
274 }
275
276 fn check_invalid_clippy_version_attribute(cx: &LateContext<'_>, item: &'_ Item<'_>) {
277 if let Some(value) = extract_clippy_version_value(cx, item) {
278 // The `sym!` macro doesn't work as it only expects a single token.
279 // It's better to keep it this way and have a direct `Symbol::intern` call here.
280 if value == Symbol::intern("pre 1.29.0") {
281 return;
282 }
283
284 if RustcVersion::parse(value.as_str()).is_err() {
285 span_lint_and_help(
286 cx,
287 INVALID_CLIPPY_VERSION_ATTRIBUTE,
288 item.span,
289 "this item has an invalid `clippy::version` attribute",
290 None,
291 "please use a valid semantic version, see `doc/adding_lints.md`",
292 );
293 }
294 } else {
295 span_lint_and_help(
296 cx,
297 MISSING_CLIPPY_VERSION_ATTRIBUTE,
298 item.span,
299 "this lint is missing the `clippy::version` attribute or version value",
300 None,
301 "please use a `clippy::version` attribute, see `doc/adding_lints.md`",
302 );
303 }
304 }
305
306 /// This function extracts the version value of a `clippy::version` attribute if the given value has
307 /// one
308 pub(super) fn extract_clippy_version_value(cx: &LateContext<'_>, item: &'_ Item<'_>) -> Option<Symbol> {
309 let attrs = cx.tcx.hir().attrs(item.hir_id());
310 attrs.iter().find_map(|attr| {
311 if let ast::AttrKind::Normal(ref attr_kind) = &attr.kind
312 // Identify attribute
313 && let [tool_name, attr_name] = &attr_kind.item.path.segments[..]
314 && tool_name.ident.name == sym::clippy
315 && attr_name.ident.name == sym::version
316 && let Some(version) = attr.value_str()
317 {
318 Some(version)
319 } else {
320 None
321 }
322 })
323 }
324
325 struct LintCollector<'a, 'tcx> {
326 output: &'a mut FxHashSet<Symbol>,
327 cx: &'a LateContext<'tcx>,
328 }
329
330 impl<'a, 'tcx> Visitor<'tcx> for LintCollector<'a, 'tcx> {
331 type NestedFilter = nested_filter::All;
332
333 fn visit_path(&mut self, path: &Path<'_>, _: HirId) {
334 if path.segments.len() == 1 {
335 self.output.insert(path.segments[0].ident.name);
336 }
337 }
338
339 fn nested_visit_map(&mut self) -> Self::Map {
340 self.cx.tcx.hir()
341 }
342 }