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 if_chain::if_chain;
7 use rustc_ast::ast::LitKind;
8 use rustc_data_structures::fx::{FxHashMap, FxHashSet};
10 use rustc_hir::def::{DefKind, Res};
11 use rustc_hir::hir_id::CRATE_HIR_ID;
12 use rustc_hir::intravisit::Visitor;
13 use rustc_hir::{ExprKind, HirId, Item, MutTy, Mutability, Path, TyKind};
14 use rustc_lint::{LateContext, LateLintPass};
15 use rustc_middle::hir::nested_filter;
16 use rustc_semver::RustcVersion;
17 use rustc_session::{declare_tool_lint, impl_lint_pass};
18 use rustc_span::source_map::Spanned;
19 use rustc_span::symbol::Symbol;
20 use rustc_span::{sym, Span};
22 declare_clippy_lint! {
24 /// Ensures every lint is associated to a `LintPass`.
26 /// ### Why is this bad?
27 /// The compiler only knows lints via a `LintPass`. Without
28 /// putting a lint to a `LintPass::get_lints()`'s return, the compiler will not
29 /// know the name of the lint.
31 /// ### Known problems
32 /// Only checks for lints associated using the
33 /// `declare_lint_pass!`, `impl_lint_pass!`, and `lint_array!` macros.
37 /// declare_lint! { pub LINT_1, ... }
38 /// declare_lint! { pub LINT_2, ... }
39 /// declare_lint! { pub FORGOTTEN_LINT, ... }
41 /// declare_lint_pass!(Pass => [LINT_1, LINT_2]);
42 /// // missing FORGOTTEN_LINT
44 pub LINT_WITHOUT_LINT_PASS,
46 "declaring a lint without associating it in a LintPass"
49 declare_clippy_lint! {
51 /// Checks for cases of an auto-generated lint without an updated description,
52 /// i.e. `default lint description`.
54 /// ### Why is this bad?
55 /// Indicates that the lint is not finished.
59 /// declare_lint! { pub COOL_LINT, nursery, "default lint description" }
64 /// declare_lint! { pub COOL_LINT, nursery, "a great new lint" }
68 "found 'default lint description' in a lint declaration"
71 declare_clippy_lint! {
73 /// Checks for invalid `clippy::version` attributes.
77 /// * any valid semantic version
78 pub INVALID_CLIPPY_VERSION_ATTRIBUTE,
80 "found an invalid `clippy::version` attribute"
83 declare_clippy_lint! {
85 /// Checks for declared clippy lints without the `clippy::version` attribute.
87 pub MISSING_CLIPPY_VERSION_ATTRIBUTE,
89 "found clippy lint without `clippy::version` attribute"
92 declare_clippy_lint! {
94 /// Checks for cases of an auto-generated deprecated lint without an updated reason,
95 /// i.e. `"default deprecation note"`.
97 /// ### Why is this bad?
98 /// Indicates that the documentation is incomplete.
102 /// declare_deprecated_lint! {
103 /// /// ### What it does
104 /// /// Nothing. This lint has been deprecated.
106 /// /// ### Deprecation reason
108 /// #[clippy::version = "1.63.0"]
110 /// "default deprecation note"
116 /// declare_deprecated_lint! {
117 /// /// ### What it does
118 /// /// Nothing. This lint has been deprecated.
120 /// /// ### Deprecation reason
121 /// /// This lint has been replaced by `cooler_lint`
122 /// #[clippy::version = "1.63.0"]
124 /// "this lint has been replaced by `cooler_lint`"
127 pub DEFAULT_DEPRECATION_REASON,
129 "found 'default deprecation note' in a deprecated lint declaration"
132 #[derive(Clone, Debug, Default)]
133 pub struct LintWithoutLintPass {
134 declared_lints: FxHashMap<Symbol, Span>,
135 registered_lints: FxHashSet<Symbol>,
138 impl_lint_pass!(LintWithoutLintPass => [DEFAULT_LINT, LINT_WITHOUT_LINT_PASS, INVALID_CLIPPY_VERSION_ATTRIBUTE, MISSING_CLIPPY_VERSION_ATTRIBUTE, DEFAULT_DEPRECATION_REASON]);
140 impl<'tcx> LateLintPass<'tcx> for LintWithoutLintPass {
141 fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
142 if is_lint_allowed(cx, DEFAULT_LINT, item.hir_id())
143 || is_lint_allowed(cx, DEFAULT_DEPRECATION_REASON, item.hir_id())
148 if let hir::ItemKind::Static(ty, Mutability::Not, body_id) = item.kind {
149 let is_lint_ref_ty = is_lint_ref_type(cx, ty);
150 if is_deprecated_lint(cx, ty) || is_lint_ref_ty {
151 check_invalid_clippy_version_attribute(cx, item);
153 let expr = &cx.tcx.hir().body(body_id).value;
156 if let ExprKind::AddrOf(_, _, inner_exp) = expr.kind
157 && let ExprKind::Struct(_, struct_fields, _) = inner_exp.kind {
158 fields = struct_fields;
162 } else if let ExprKind::Struct(_, struct_fields, _) = expr.kind {
163 fields = struct_fields;
170 .find(|f| f.ident.as_str() == "desc")
171 .expect("lints must have a description field");
173 if let ExprKind::Lit(Spanned {
174 node: LitKind::Str(ref sym, _),
178 let sym_str = sym.as_str();
180 if sym_str == "default lint description" {
185 &format!("the lint `{}` has the default lint description", item.ident.name),
189 self.declared_lints.insert(item.ident.name, item.span);
190 } else if sym_str == "default deprecation note" {
193 DEFAULT_DEPRECATION_REASON,
195 &format!("the lint `{}` has the default deprecation reason", item.ident.name),
200 } else if let Some(macro_call) = root_macro_call_first_node(cx, item) {
202 cx.tcx.item_name(macro_call.def_id).as_str(),
203 "impl_lint_pass" | "declare_lint_pass"
207 if let hir::ItemKind::Impl(hir::Impl {
209 items: impl_item_refs,
213 let mut collector = LintCollector {
214 output: &mut self.registered_lints,
217 let body_id = cx.tcx.hir().body_owned_by(
218 cx.tcx.hir().local_def_id(
221 .find(|iiref| iiref.ident.as_str() == "get_lints")
222 .expect("LintPass needs to implement get_lints")
227 collector.visit_expr(cx.tcx.hir().body(body_id).value);
232 fn check_crate_post(&mut self, cx: &LateContext<'tcx>) {
233 if is_lint_allowed(cx, LINT_WITHOUT_LINT_PASS, CRATE_HIR_ID) {
237 for (lint_name, &lint_span) in &self.declared_lints {
238 // When using the `declare_tool_lint!` macro, the original `lint_span`'s
239 // file points to "<rustc macros>".
240 // `compiletest-rs` thinks that's an error in a different file and
241 // just ignores it. This causes the test in compile-fail/lint_pass
242 // not able to capture the error.
243 // Therefore, we need to climb the macro expansion tree and find the
244 // actual span that invoked `declare_tool_lint!`:
245 let lint_span = lint_span.ctxt().outer_expn_data().call_site;
247 if !self.registered_lints.contains(lint_name) {
250 LINT_WITHOUT_LINT_PASS,
252 &format!("the lint `{lint_name}` is not added to any `LintPass`"),
259 pub(super) fn is_lint_ref_type(cx: &LateContext<'_>, ty: &hir::Ty<'_>) -> bool {
264 mutbl: Mutability::Not,
268 if let TyKind::Path(ref path) = inner.kind {
269 if let Res::Def(DefKind::Struct, def_id) = cx.qpath_res(path, inner.hir_id) {
270 return match_def_path(cx, def_id, &paths::LINT);
278 fn check_invalid_clippy_version_attribute(cx: &LateContext<'_>, item: &'_ Item<'_>) {
279 if let Some(value) = extract_clippy_version_value(cx, item) {
280 // The `sym!` macro doesn't work as it only expects a single token.
281 // It's better to keep it this way and have a direct `Symbol::intern` call here.
282 if value == Symbol::intern("pre 1.29.0") {
286 if RustcVersion::parse(value.as_str()).is_err() {
289 INVALID_CLIPPY_VERSION_ATTRIBUTE,
291 "this item has an invalid `clippy::version` attribute",
293 "please use a valid semantic version, see `doc/adding_lints.md`",
299 MISSING_CLIPPY_VERSION_ATTRIBUTE,
301 "this lint is missing the `clippy::version` attribute or version value",
303 "please use a `clippy::version` attribute, see `doc/adding_lints.md`",
308 /// This function extracts the version value of a `clippy::version` attribute if the given value has
310 pub(super) fn extract_clippy_version_value(cx: &LateContext<'_>, item: &'_ Item<'_>) -> Option<Symbol> {
311 let attrs = cx.tcx.hir().attrs(item.hir_id());
312 attrs.iter().find_map(|attr| {
314 // Identify attribute
315 if let ast::AttrKind::Normal(ref attr_kind) = &attr.kind;
316 if let [tool_name, attr_name] = &attr_kind.item.path.segments[..];
317 if tool_name.ident.name == sym::clippy;
318 if attr_name.ident.name == sym::version;
319 if let Some(version) = attr.value_str();
320 then { Some(version) } else { None }
325 struct LintCollector<'a, 'tcx> {
326 output: &'a mut FxHashSet<Symbol>,
327 cx: &'a LateContext<'tcx>,
330 impl<'a, 'tcx> Visitor<'tcx> for LintCollector<'a, 'tcx> {
331 type NestedFilter = nested_filter::All;
333 fn visit_path(&mut self, path: &Path<'_>, _: HirId) {
334 if path.segments.len() == 1 {
335 self.output.insert(path.segments[0].ident.name);
339 fn nested_visit_map(&mut self) -> Self::Map {