Skip to content

Commit ca06aa2

Browse files
committed
clippy_dev: check that all lints are registered with a lint pass
1 parent ab1f092 commit ca06aa2

File tree

8 files changed

+54
-226
lines changed

8 files changed

+54
-226
lines changed

clippy_dev/src/update_lints.rs

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use crate::utils::{FileUpdater, UpdateMode, UpdateStatus, update_text_region_fn}
33
use itertools::Itertools;
44
use std::fmt::Write;
55
use std::path::Path;
6+
use std::process;
67

78
const GENERATED_FILE_COMMENT: &str = "// This file was generated by `cargo dev update_lints`.\n\
89
// Use that command to update this file and do not edit by hand.\n\
@@ -20,7 +21,24 @@ const DOCS_LINK: &str = "https://rust-lang.github.io/rust-clippy/master/index.ht
2021
///
2122
/// Panics if a file path could not read from or then written to
2223
pub fn update(update_mode: UpdateMode) {
23-
generate_lint_files(update_mode, &ParsedData::collect());
24+
let mut data = ParsedData::collect();
25+
let mut is_missing = false;
26+
data.lint_registrations.sort_by(|x, y| x.name.cmp(&y.name));
27+
for (name, lint) in &data.lints {
28+
if matches!(lint.kind, LintKind::Active(_))
29+
&& data.lint_registrations.binary_search_by(|x| x.name.cmp(name)).is_err()
30+
{
31+
is_missing = true;
32+
eprint!(
33+
"error: lint `{name}` is not registered in a lint pass\n declared here: {}\n",
34+
lint.name_span.display(&data.source_map)
35+
);
36+
}
37+
}
38+
if is_missing {
39+
process::exit(1);
40+
}
41+
generate_lint_files(update_mode, &data);
2442
}
2543

2644
#[expect(clippy::too_many_lines)]

clippy_lints/src/doc/mod.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
#![allow(clippy::lint_without_lint_pass)]
2-
31
use clippy_config::Conf;
42
use clippy_utils::attrs::is_doc_hidden;
53
use clippy_utils::diagnostics::{span_lint, span_lint_and_help, span_lint_and_then};

clippy_lints_internal/src/lib.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,6 @@ static LINTS: &[&Lint] = &[
5050
derive_deserialize_allowing_unknown::DERIVE_DESERIALIZE_ALLOWING_UNKNOWN,
5151
lint_without_lint_pass::DEFAULT_LINT,
5252
lint_without_lint_pass::INVALID_CLIPPY_VERSION_ATTRIBUTE,
53-
lint_without_lint_pass::LINT_WITHOUT_LINT_PASS,
5453
lint_without_lint_pass::MISSING_CLIPPY_VERSION_ATTRIBUTE,
5554
msrv_attr_impl::MISSING_MSRV_ATTR_IMPL,
5655
outer_expn_data_pass::OUTER_EXPN_EXPN_DATA,
@@ -69,7 +68,7 @@ pub fn register_lints(store: &mut LintStore) {
6968
store.register_late_pass(|_| Box::new(collapsible_calls::CollapsibleCalls));
7069
store.register_late_pass(|_| Box::new(derive_deserialize_allowing_unknown::DeriveDeserializeAllowingUnknown));
7170
store.register_late_pass(|_| Box::<symbols::Symbols>::default());
72-
store.register_late_pass(|_| Box::<lint_without_lint_pass::LintWithoutLintPass>::default());
71+
store.register_late_pass(|_| Box::new(lint_without_lint_pass::LintWithoutLintPass));
7372
store.register_late_pass(|_| Box::new(unnecessary_def_path::UnnecessaryDefPath));
7473
store.register_late_pass(|_| Box::new(outer_expn_data_pass::OuterExpnDataPass));
7574
store.register_late_pass(|_| Box::new(msrv_attr_impl::MsrvAttrImpl));
Lines changed: 32 additions & 148 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,14 @@
11
use crate::internal_paths;
22
use clippy_utils::diagnostics::{span_lint, span_lint_and_help};
3-
use clippy_utils::is_lint_allowed;
4-
use clippy_utils::macros::root_macro_call_first_node;
53
use rustc_ast::ast::LitKind;
6-
use rustc_data_structures::fx::{FxIndexMap, FxIndexSet};
74
use rustc_hir as hir;
85
use rustc_hir::def::{DefKind, Res};
9-
use rustc_hir::hir_id::CRATE_HIR_ID;
10-
use rustc_hir::intravisit::Visitor;
11-
use rustc_hir::{ExprKind, HirId, Item, MutTy, Mutability, Path, TyKind};
6+
use rustc_hir::{ExprKind, Item, MutTy, Mutability, TyKind};
127
use rustc_lint::{LateContext, LateLintPass};
13-
use rustc_middle::hir::nested_filter;
14-
use rustc_session::{declare_tool_lint, impl_lint_pass};
8+
use rustc_session::{declare_lint_pass, declare_tool_lint};
159
use rustc_span::source_map::Spanned;
10+
use rustc_span::sym;
1611
use rustc_span::symbol::Symbol;
17-
use rustc_span::{Span, sym};
18-
19-
declare_tool_lint! {
20-
/// ### What it does
21-
/// Ensures every lint is associated to a `LintPass`.
22-
///
23-
/// ### Why is this bad?
24-
/// The compiler only knows lints via a `LintPass`. Without
25-
/// putting a lint to a `LintPass::lint_vec()`'s return, the compiler will not
26-
/// know the name of the lint.
27-
///
28-
/// ### Known problems
29-
/// Only checks for lints associated using the `declare_lint_pass!` and
30-
/// `impl_lint_pass!` macros.
31-
///
32-
/// ### Example
33-
/// ```rust,ignore
34-
/// declare_lint! { pub LINT_1, ... }
35-
/// declare_lint! { pub LINT_2, ... }
36-
/// declare_lint! { pub FORGOTTEN_LINT, ... }
37-
/// // ...
38-
/// declare_lint_pass!(Pass => [LINT_1, LINT_2]);
39-
/// // missing FORGOTTEN_LINT
40-
/// ```
41-
pub clippy::LINT_WITHOUT_LINT_PASS,
42-
Warn,
43-
"declaring a lint without associating it in a LintPass",
44-
report_in_external_macro: true
45-
46-
}
4712

4813
declare_tool_lint! {
4914
/// ### What it does
@@ -90,109 +55,47 @@ declare_tool_lint! {
9055
report_in_external_macro: true
9156
}
9257

93-
#[derive(Clone, Debug, Default)]
94-
pub struct LintWithoutLintPass {
95-
declared_lints: FxIndexMap<Symbol, Span>,
96-
registered_lints: FxIndexSet<Symbol>,
97-
}
98-
99-
impl_lint_pass!(LintWithoutLintPass => [
58+
declare_lint_pass!(LintWithoutLintPass => [
10059
DEFAULT_LINT,
101-
LINT_WITHOUT_LINT_PASS,
10260
INVALID_CLIPPY_VERSION_ATTRIBUTE,
10361
MISSING_CLIPPY_VERSION_ATTRIBUTE,
10462
]);
10563

10664
impl<'tcx> LateLintPass<'tcx> for LintWithoutLintPass {
10765
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
108-
if let hir::ItemKind::Static(Mutability::Not, ident, ty, body_id) = item.kind {
109-
if is_lint_ref_type(cx, ty) {
110-
check_invalid_clippy_version_attribute(cx, item);
66+
if let hir::ItemKind::Static(Mutability::Not, ident, ty, body_id) = item.kind
67+
&& is_lint_ref_type(cx, ty)
68+
{
69+
check_invalid_clippy_version_attribute(cx, item);
11170

112-
let expr = &cx.tcx.hir_body(body_id).value;
113-
let fields = if let ExprKind::AddrOf(_, _, inner_exp) = expr.kind
114-
&& let ExprKind::Struct(_, struct_fields, _) = inner_exp.kind
115-
{
116-
struct_fields
117-
} else {
118-
return;
119-
};
71+
let expr = &cx.tcx.hir_body(body_id).value;
72+
let fields = if let ExprKind::AddrOf(_, _, inner_exp) = expr.kind
73+
&& let ExprKind::Struct(_, struct_fields, _) = inner_exp.kind
74+
{
75+
struct_fields
76+
} else {
77+
return;
78+
};
12079

121-
let field = fields
122-
.iter()
123-
.find(|f| f.ident.as_str() == "desc")
124-
.expect("lints must have a description field");
80+
let field = fields
81+
.iter()
82+
.find(|f| f.ident.as_str() == "desc")
83+
.expect("lints must have a description field");
12584

126-
if let ExprKind::Lit(Spanned {
127-
node: LitKind::Str(sym, _),
128-
..
129-
}) = field.expr.kind
130-
{
131-
let sym_str = sym.as_str();
132-
if sym_str == "default lint description" {
133-
span_lint(
134-
cx,
135-
DEFAULT_LINT,
136-
item.span,
137-
format!("the lint `{}` has the default lint description", ident.name),
138-
);
139-
}
140-
self.declared_lints.insert(ident.name, item.span);
141-
}
142-
}
143-
} else if let Some(macro_call) = root_macro_call_first_node(cx, item) {
144-
if !matches!(
145-
cx.tcx.item_name(macro_call.def_id).as_str(),
146-
"impl_lint_pass" | "declare_lint_pass"
147-
) {
148-
return;
149-
}
150-
if let hir::ItemKind::Impl(hir::Impl {
151-
of_trait: None,
152-
items: impl_item_refs,
85+
if let ExprKind::Lit(Spanned {
86+
node: LitKind::Str(sym, _),
15387
..
154-
}) = item.kind
88+
}) = field.expr.kind
15589
{
156-
let mut collector = LintCollector {
157-
output: &mut self.registered_lints,
158-
cx,
159-
};
160-
let body = cx.tcx.hir_body_owned_by(
161-
impl_item_refs
162-
.iter()
163-
.find(|iiref| iiref.ident.as_str() == "lint_vec")
164-
.expect("LintPass needs to implement lint_vec")
165-
.id
166-
.owner_id
167-
.def_id,
168-
);
169-
collector.visit_expr(body.value);
170-
}
171-
}
172-
}
173-
174-
fn check_crate_post(&mut self, cx: &LateContext<'tcx>) {
175-
if is_lint_allowed(cx, LINT_WITHOUT_LINT_PASS, CRATE_HIR_ID) {
176-
return;
177-
}
178-
179-
for (lint_name, &lint_span) in &self.declared_lints {
180-
// When using the `declare_tool_lint!` macro, the original `lint_span`'s
181-
// file points to "<rustc macros>".
182-
// `compiletest-rs` thinks that's an error in a different file and
183-
// just ignores it. This causes the test in compile-fail/lint_pass
184-
// not able to capture the error.
185-
// Therefore, we need to climb the macro expansion tree and find the
186-
// actual span that invoked `declare_tool_lint!`:
187-
let lint_span = lint_span.ctxt().outer_expn_data().call_site;
188-
189-
if !self.registered_lints.contains(lint_name) {
190-
span_lint(
191-
cx,
192-
LINT_WITHOUT_LINT_PASS,
193-
lint_span,
194-
format!("the lint `{lint_name}` is not added to any `LintPass`"),
195-
);
90+
let sym_str = sym.as_str();
91+
if sym_str == "default lint description" {
92+
span_lint(
93+
cx,
94+
DEFAULT_LINT,
95+
item.span,
96+
format!("the lint `{}` has the default lint description", ident.name),
97+
);
98+
}
19699
}
197100
}
198101
}
@@ -261,22 +164,3 @@ pub(super) fn extract_clippy_version_value(cx: &LateContext<'_>, item: &'_ Item<
261164
}
262165
})
263166
}
264-
265-
struct LintCollector<'a, 'tcx> {
266-
output: &'a mut FxIndexSet<Symbol>,
267-
cx: &'a LateContext<'tcx>,
268-
}
269-
270-
impl<'tcx> Visitor<'tcx> for LintCollector<'_, 'tcx> {
271-
type NestedFilter = nested_filter::All;
272-
273-
fn visit_path(&mut self, path: &Path<'_>, _: HirId) {
274-
if path.segments.len() == 1 {
275-
self.output.insert(path.segments[0].ident.name);
276-
}
277-
}
278-
279-
fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt {
280-
self.cx.tcx
281-
}
282-
}

tests/ui-internal/check_formulation.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
#![deny(clippy::almost_standard_lint_formulation)]
2-
#![allow(clippy::lint_without_lint_pass)]
32
#![feature(rustc_private)]
43

54
#[macro_use]

tests/ui-internal/check_formulation.stderr

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
error: non-standard lint formulation
2-
--> tests/ui-internal/check_formulation.rs:24:5
2+
--> tests/ui-internal/check_formulation.rs:23:5
33
|
44
LL | /// Check for lint formulations that are correct
55
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -12,7 +12,7 @@ LL | #![deny(clippy::almost_standard_lint_formulation)]
1212
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
1313

1414
error: non-standard lint formulation
15-
--> tests/ui-internal/check_formulation.rs:35:5
15+
--> tests/ui-internal/check_formulation.rs:34:5
1616
|
1717
LL | /// Detects uses of incorrect formulations
1818
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/ui-internal/lint_without_lint_pass.rs

Lines changed: 0 additions & 49 deletions
This file was deleted.

tests/ui-internal/lint_without_lint_pass.stderr

Lines changed: 0 additions & 21 deletions
This file was deleted.

0 commit comments

Comments
 (0)