Skip to content
This repository was archived by the owner on May 28, 2025. It is now read-only.

Commit 060e0e9

Browse files
committed
Metadata collection lint: Basic applicability collection
1 parent 637751f commit 060e0e9

File tree

2 files changed

+147
-33
lines changed

2 files changed

+147
-33
lines changed

clippy_lints/src/utils/internal_lints/metadata_collector.rs

Lines changed: 145 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,14 @@
22
//! file and then used to generate the [clippy lint list](https://rust-lang.github.io/rust-clippy/master/index.html)
33
//!
44
//! This module and therefor the entire lint is guarded by a feature flag called
5-
//! `internal_metadata_lint`
5+
//! `metadata-collector-lint`
6+
//!
7+
//! The module transforms all lint names to ascii lowercase to ensure that we don't have mismatches
8+
//! during any comparison or mapping. (Please take care of this, it's not fun to spend time on such
9+
//! a simple mistake)
610
//!
711
//! The metadata currently contains:
8-
//! - [ ] TODO The lint declaration line for [#1303](https://github.com/rust-lang/rust-clippy/issues/1303)
12+
//! - [x] TODO The lint declaration line for [#1303](https://github.com/rust-lang/rust-clippy/issues/1303)
913
//! and [#6492](https://github.com/rust-lang/rust-clippy/issues/6492)
1014
//! - [ ] TODO The Applicability for each lint for [#4310](https://github.com/rust-lang/rust-clippy/issues/4310)
1115
@@ -17,20 +21,34 @@
1721
// - TODO xFrednet 2021-02-13: Collect depreciations and maybe renames
1822

1923
use if_chain::if_chain;
20-
use rustc_hir::{ExprKind, Item, ItemKind, Mutability};
24+
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
25+
use rustc_hir::{self as hir, ExprKind, Item, ItemKind, Mutability};
2126
use rustc_lint::{CheckLintNameResult, LateContext, LateLintPass, LintContext, LintId};
2227
use rustc_session::{declare_tool_lint, impl_lint_pass};
23-
use rustc_span::{sym, Loc, Span};
28+
use rustc_span::{sym, Loc, Span, Symbol};
2429
use serde::Serialize;
2530
use std::fs::OpenOptions;
2631
use std::io::prelude::*;
2732

2833
use crate::utils::internal_lints::is_lint_ref_type;
29-
use crate::utils::span_lint;
34+
use crate::utils::{last_path_segment, match_function_call, match_type, paths, span_lint, walk_ptrs_ty_depth};
3035

36+
/// This is the output file of the lint collector.
3137
const OUTPUT_FILE: &str = "metadata_collection.json";
38+
/// These lints are excluded from the export.
3239
const BLACK_LISTED_LINTS: [&str; 2] = ["lint_author", "deep_code_inspection"];
3340

41+
// TODO xFrednet 2021-02-15: `span_lint_and_then` & `span_lint_hir_and_then` requires special
42+
// handling
43+
#[rustfmt::skip]
44+
const LINT_EMISSION_FUNCTIONS: [&[&str]; 5] = [
45+
&["clippy_lints", "utils", "diagnostics", "span_lint"],
46+
&["clippy_lints", "utils", "diagnostics", "span_lint_and_help"],
47+
&["clippy_lints", "utils", "diagnostics", "span_lint_and_note"],
48+
&["clippy_lints", "utils", "diagnostics", "span_lint_hir"],
49+
&["clippy_lints", "utils", "diagnostics", "span_lint_and_sugg"],
50+
];
51+
3452
declare_clippy_lint! {
3553
/// **What it does:** Collects metadata about clippy lints for the website.
3654
///
@@ -66,12 +84,21 @@ impl_lint_pass!(MetadataCollector => [INTERNAL_METADATA_COLLECTOR]);
6684
#[derive(Debug, Clone, Default)]
6785
pub struct MetadataCollector {
6886
lints: Vec<LintMetadata>,
87+
applicability_into: FxHashMap<String, ApplicabilityInfo>,
6988
}
7089

7190
impl Drop for MetadataCollector {
91+
/// You might ask: How hacky is this?
92+
/// My answer: YES
7293
fn drop(&mut self) {
73-
// You might ask: How hacky is this?
74-
// My answer: YES
94+
let mut applicability_info = std::mem::take(&mut self.applicability_into);
95+
96+
// Mapping the final data
97+
self.lints
98+
.iter_mut()
99+
.for_each(|x| x.applicability = applicability_info.remove(&x.id));
100+
101+
// Outputting
75102
let mut file = OpenOptions::new().write(true).create(true).open(OUTPUT_FILE).unwrap();
76103
writeln!(file, "{}", serde_json::to_string_pretty(&self.lints).unwrap()).unwrap();
77104
}
@@ -83,6 +110,21 @@ struct LintMetadata {
83110
id_span: SerializableSpan,
84111
group: String,
85112
docs: String,
113+
/// This field is only used in the output and will only be
114+
/// mapped shortly before the actual output.
115+
applicability: Option<ApplicabilityInfo>,
116+
}
117+
118+
impl LintMetadata {
119+
fn new(id: String, id_span: SerializableSpan, group: String, docs: String) -> Self {
120+
Self {
121+
id,
122+
id_span,
123+
group,
124+
docs,
125+
applicability: None,
126+
}
127+
}
86128
}
87129

88130
#[derive(Debug, Clone, Serialize)]
@@ -101,12 +143,31 @@ impl SerializableSpan {
101143

102144
Self {
103145
path: format!("{}", loc.file.name),
104-
line: 1,
146+
line: loc.line,
105147
}
106148
}
107149
}
108150

151+
#[derive(Debug, Clone, Default, Serialize)]
152+
struct ApplicabilityInfo {
153+
/// Indicates if any of the lint emissions uses multiple spans. This is related to
154+
/// [rustfix#141](https://github.com/rust-lang/rustfix/issues/141) as such suggestions can
155+
/// currently not be applied automatically.
156+
has_multi_suggestion: bool,
157+
/// These are all the available applicability values for the lint suggestions
158+
applicabilities: FxHashSet<String>,
159+
}
160+
109161
impl<'tcx> LateLintPass<'tcx> for MetadataCollector {
162+
/// Collecting lint declarations like:
163+
/// ```rust, ignore
164+
/// declare_clippy_lint! {
165+
/// /// **What it does:** Something IDK.
166+
/// pub SOME_LINT,
167+
/// internal,
168+
/// "Who am I?"
169+
/// }
170+
/// ```
110171
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
111172
if_chain! {
112173
if let ItemKind::Static(ref ty, Mutability::Not, body_id) = item.kind;
@@ -115,7 +176,7 @@ impl<'tcx> LateLintPass<'tcx> for MetadataCollector {
115176
if let ExprKind::AddrOf(_, _, ref inner_exp) = expr.kind;
116177
if let ExprKind::Struct(_, _, _) = inner_exp.kind;
117178
then {
118-
let lint_name = item.ident.name.as_str().to_string().to_ascii_lowercase();
179+
let lint_name = sym_to_string(item.ident.name).to_ascii_lowercase();
119180
if BLACK_LISTED_LINTS.contains(&lint_name.as_str()) {
120181
return;
121182
}
@@ -126,33 +187,50 @@ impl<'tcx> LateLintPass<'tcx> for MetadataCollector {
126187
if let Some(group_some) = get_lint_group(cx, lint_lst[0]) {
127188
group = group_some;
128189
} else {
129-
lint_collection_error(cx, item, "Unable to determine lint group");
190+
lint_collection_error_item(cx, item, "Unable to determine lint group");
130191
return;
131192
}
132193
} else {
133-
lint_collection_error(cx, item, "Unable to find lint in lint_store");
194+
lint_collection_error_item(cx, item, "Unable to find lint in lint_store");
134195
return;
135196
}
136197

137198
let docs: String;
138199
if let Some(docs_some) = extract_attr_docs(item) {
139200
docs = docs_some;
140201
} else {
141-
lint_collection_error(cx, item, "could not collect the lint documentation");
202+
lint_collection_error_item(cx, item, "could not collect the lint documentation");
142203
return;
143204
};
144205

145-
self.lints.push(LintMetadata {
146-
id: lint_name,
147-
id_span: SerializableSpan::from_item(cx, item),
206+
self.lints.push(LintMetadata::new(
207+
lint_name,
208+
SerializableSpan::from_item(cx, item),
148209
group,
149210
docs,
211+
));
212+
}
213+
}
214+
}
215+
216+
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
217+
if let Some(args) = match_simple_lint_emission(cx, expr) {
218+
if let Some((lint_name, mut applicability)) = extract_emission_info(cx, args) {
219+
let app_info = self.applicability_into.entry(lint_name).or_default();
220+
applicability.drain(..).for_each(|x| {
221+
app_info.applicabilities.insert(x);
150222
});
223+
} else {
224+
lint_collection_error_span(cx, expr.span, "I found this but I can't get the lint or applicability");
151225
}
152226
}
153227
}
154228
}
155229

230+
fn sym_to_string(sym: Symbol) -> String {
231+
sym.as_str().to_string()
232+
}
233+
156234
/// This function collects all documentation that has been added to an item using
157235
/// `#[doc = r""]` attributes. Several attributes are aggravated using line breaks
158236
///
@@ -166,23 +244,11 @@ impl<'tcx> LateLintPass<'tcx> for MetadataCollector {
166244
fn extract_attr_docs(item: &Item<'_>) -> Option<String> {
167245
item.attrs
168246
.iter()
169-
.filter_map(|ref x| x.doc_str())
170-
.fold(None, |acc, sym| {
171-
let mut doc_str = sym.as_str().to_string();
172-
doc_str.push('\n');
173-
174-
#[allow(clippy::option_if_let_else)] // See clippy#6737
175-
if let Some(mut x) = acc {
176-
x.push_str(&doc_str);
177-
Some(x)
178-
} else {
179-
Some(doc_str)
180-
}
181-
182-
// acc.map_or(Some(doc_str), |mut x| {
183-
// x.push_str(&doc_str);
184-
// Some(x)
185-
// })
247+
.filter_map(|ref x| x.doc_str().map(|sym| sym.as_str().to_string()))
248+
.reduce(|mut acc, sym| {
249+
acc.push_str(&sym);
250+
acc.push('\n');
251+
acc
186252
})
187253
}
188254

@@ -196,11 +262,57 @@ fn get_lint_group(cx: &LateContext<'_>, lint_id: LintId) -> Option<String> {
196262
None
197263
}
198264

199-
fn lint_collection_error(cx: &LateContext<'_>, item: &Item<'_>, message: &str) {
265+
fn lint_collection_error_item(cx: &LateContext<'_>, item: &Item<'_>, message: &str) {
200266
span_lint(
201267
cx,
202268
INTERNAL_METADATA_COLLECTOR,
203269
item.ident.span,
204270
&format!("Metadata collection error for `{}`: {}", item.ident.name, message),
205271
);
206272
}
273+
274+
fn lint_collection_error_span(cx: &LateContext<'_>, span: Span, message: &str) {
275+
span_lint(
276+
cx,
277+
INTERNAL_METADATA_COLLECTOR,
278+
span,
279+
&format!("Metadata collection error: {}", message),
280+
);
281+
}
282+
283+
fn match_simple_lint_emission<'tcx>(
284+
cx: &LateContext<'tcx>,
285+
expr: &'tcx hir::Expr<'_>,
286+
) -> Option<&'tcx [hir::Expr<'tcx>]> {
287+
LINT_EMISSION_FUNCTIONS
288+
.iter()
289+
.find_map(|emission_fn| match_function_call(cx, expr, emission_fn).map(|args| args))
290+
}
291+
292+
/// This returns the lint name and the possible applicability of this emission
293+
fn extract_emission_info<'tcx>(cx: &LateContext<'tcx>, args: &[hir::Expr<'_>]) -> Option<(String, Vec<String>)> {
294+
let mut lint_name = None;
295+
let mut applicability = None;
296+
297+
for arg in args {
298+
let (arg_ty, _) = walk_ptrs_ty_depth(cx.typeck_results().expr_ty(&arg));
299+
300+
if match_type(cx, arg_ty, &paths::LINT) {
301+
// If we found the lint arg, extract the lint name
302+
if let ExprKind::Path(ref lint_path) = arg.kind {
303+
lint_name = Some(last_path_segment(lint_path).ident.name)
304+
}
305+
} else if match_type(cx, arg_ty, &paths::APPLICABILITY) {
306+
if let ExprKind::Path(ref path) = arg.kind {
307+
applicability = Some(last_path_segment(path).ident.name)
308+
}
309+
}
310+
}
311+
312+
lint_name.map(|lint_name| {
313+
(
314+
sym_to_string(lint_name).to_ascii_lowercase(),
315+
applicability.map(sym_to_string).map_or_else(Vec::new, |x| vec![x]),
316+
)
317+
})
318+
}

clippy_utils/src/paths.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
//! See <https://github.com/rust-lang/rust-clippy/issues/5393> for more information.
66
77
pub const ANY_TRAIT: [&str; 3] = ["core", "any", "Any"];
8+
#[cfg(feature = "metadata-collector-lint")]
9+
pub const APPLICABILITY: [&str; 2] = ["rustc_lint_defs", "Applicability"];
810
pub const ARC_PTR_EQ: [&str; 4] = ["alloc", "sync", "Arc", "ptr_eq"];
911
pub const ASMUT_TRAIT: [&str; 3] = ["core", "convert", "AsMut"];
1012
pub const ASREF_TRAIT: [&str; 3] = ["core", "convert", "AsRef"];

0 commit comments

Comments
 (0)