Metadata collection lint: Basic applicability collection

This commit is contained in:
xFrednet 2021-02-14 16:17:07 +01:00
parent 637751ff62
commit 060e0e9f93
2 changed files with 148 additions and 34 deletions

View file

@ -2,10 +2,14 @@
//! file and then used to generate the [clippy lint list](https://rust-lang.github.io/rust-clippy/master/index.html)
//!
//! This module and therefor the entire lint is guarded by a feature flag called
//! `internal_metadata_lint`
//! `metadata-collector-lint`
//!
//! The module transforms all lint names to ascii lowercase to ensure that we don't have mismatches
//! during any comparison or mapping. (Please take care of this, it's not fun to spend time on such
//! a simple mistake)
//!
//! The metadata currently contains:
//! - [ ] TODO The lint declaration line for [#1303](https://github.com/rust-lang/rust-clippy/issues/1303)
//! - [x] TODO The lint declaration line for [#1303](https://github.com/rust-lang/rust-clippy/issues/1303)
//! and [#6492](https://github.com/rust-lang/rust-clippy/issues/6492)
//! - [ ] TODO The Applicability for each lint for [#4310](https://github.com/rust-lang/rust-clippy/issues/4310)
@ -17,20 +21,34 @@
// - TODO xFrednet 2021-02-13: Collect depreciations and maybe renames
use if_chain::if_chain;
use rustc_hir::{ExprKind, Item, ItemKind, Mutability};
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
use rustc_hir::{self as hir, ExprKind, Item, ItemKind, Mutability};
use rustc_lint::{CheckLintNameResult, LateContext, LateLintPass, LintContext, LintId};
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::{sym, Loc, Span};
use rustc_span::{sym, Loc, Span, Symbol};
use serde::Serialize;
use std::fs::OpenOptions;
use std::io::prelude::*;
use crate::utils::internal_lints::is_lint_ref_type;
use crate::utils::span_lint;
use crate::utils::{last_path_segment, match_function_call, match_type, paths, span_lint, walk_ptrs_ty_depth};
/// This is the output file of the lint collector.
const OUTPUT_FILE: &str = "metadata_collection.json";
/// These lints are excluded from the export.
const BLACK_LISTED_LINTS: [&str; 2] = ["lint_author", "deep_code_inspection"];
// TODO xFrednet 2021-02-15: `span_lint_and_then` & `span_lint_hir_and_then` requires special
// handling
#[rustfmt::skip]
const LINT_EMISSION_FUNCTIONS: [&[&str]; 5] = [
&["clippy_lints", "utils", "diagnostics", "span_lint"],
&["clippy_lints", "utils", "diagnostics", "span_lint_and_help"],
&["clippy_lints", "utils", "diagnostics", "span_lint_and_note"],
&["clippy_lints", "utils", "diagnostics", "span_lint_hir"],
&["clippy_lints", "utils", "diagnostics", "span_lint_and_sugg"],
];
declare_clippy_lint! {
/// **What it does:** Collects metadata about clippy lints for the website.
///
@ -66,12 +84,21 @@ impl_lint_pass!(MetadataCollector => [INTERNAL_METADATA_COLLECTOR]);
#[derive(Debug, Clone, Default)]
pub struct MetadataCollector {
lints: Vec<LintMetadata>,
applicability_into: FxHashMap<String, ApplicabilityInfo>,
}
impl Drop for MetadataCollector {
/// You might ask: How hacky is this?
/// My answer: YES
fn drop(&mut self) {
// You might ask: How hacky is this?
// My answer: YES
let mut applicability_info = std::mem::take(&mut self.applicability_into);
// Mapping the final data
self.lints
.iter_mut()
.for_each(|x| x.applicability = applicability_info.remove(&x.id));
// Outputting
let mut file = OpenOptions::new().write(true).create(true).open(OUTPUT_FILE).unwrap();
writeln!(file, "{}", serde_json::to_string_pretty(&self.lints).unwrap()).unwrap();
}
@ -83,6 +110,21 @@ struct LintMetadata {
id_span: SerializableSpan,
group: String,
docs: String,
/// This field is only used in the output and will only be
/// mapped shortly before the actual output.
applicability: Option<ApplicabilityInfo>,
}
impl LintMetadata {
fn new(id: String, id_span: SerializableSpan, group: String, docs: String) -> Self {
Self {
id,
id_span,
group,
docs,
applicability: None,
}
}
}
#[derive(Debug, Clone, Serialize)]
@ -101,12 +143,31 @@ impl SerializableSpan {
Self {
path: format!("{}", loc.file.name),
line: 1,
line: loc.line,
}
}
}
#[derive(Debug, Clone, Default, Serialize)]
struct ApplicabilityInfo {
/// Indicates if any of the lint emissions uses multiple spans. This is related to
/// [rustfix#141](https://github.com/rust-lang/rustfix/issues/141) as such suggestions can
/// currently not be applied automatically.
has_multi_suggestion: bool,
/// These are all the available applicability values for the lint suggestions
applicabilities: FxHashSet<String>,
}
impl<'tcx> LateLintPass<'tcx> for MetadataCollector {
/// Collecting lint declarations like:
/// ```rust, ignore
/// declare_clippy_lint! {
/// /// **What it does:** Something IDK.
/// pub SOME_LINT,
/// internal,
/// "Who am I?"
/// }
/// ```
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
if_chain! {
if let ItemKind::Static(ref ty, Mutability::Not, body_id) = item.kind;
@ -115,7 +176,7 @@ impl<'tcx> LateLintPass<'tcx> for MetadataCollector {
if let ExprKind::AddrOf(_, _, ref inner_exp) = expr.kind;
if let ExprKind::Struct(_, _, _) = inner_exp.kind;
then {
let lint_name = item.ident.name.as_str().to_string().to_ascii_lowercase();
let lint_name = sym_to_string(item.ident.name).to_ascii_lowercase();
if BLACK_LISTED_LINTS.contains(&lint_name.as_str()) {
return;
}
@ -126,11 +187,11 @@ impl<'tcx> LateLintPass<'tcx> for MetadataCollector {
if let Some(group_some) = get_lint_group(cx, lint_lst[0]) {
group = group_some;
} else {
lint_collection_error(cx, item, "Unable to determine lint group");
lint_collection_error_item(cx, item, "Unable to determine lint group");
return;
}
} else {
lint_collection_error(cx, item, "Unable to find lint in lint_store");
lint_collection_error_item(cx, item, "Unable to find lint in lint_store");
return;
}
@ -138,19 +199,36 @@ impl<'tcx> LateLintPass<'tcx> for MetadataCollector {
if let Some(docs_some) = extract_attr_docs(item) {
docs = docs_some;
} else {
lint_collection_error(cx, item, "could not collect the lint documentation");
lint_collection_error_item(cx, item, "could not collect the lint documentation");
return;
};
self.lints.push(LintMetadata {
id: lint_name,
id_span: SerializableSpan::from_item(cx, item),
self.lints.push(LintMetadata::new(
lint_name,
SerializableSpan::from_item(cx, item),
group,
docs,
});
));
}
}
}
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
if let Some(args) = match_simple_lint_emission(cx, expr) {
if let Some((lint_name, mut applicability)) = extract_emission_info(cx, args) {
let app_info = self.applicability_into.entry(lint_name).or_default();
applicability.drain(..).for_each(|x| {
app_info.applicabilities.insert(x);
});
} else {
lint_collection_error_span(cx, expr.span, "I found this but I can't get the lint or applicability");
}
}
}
}
fn sym_to_string(sym: Symbol) -> String {
sym.as_str().to_string()
}
/// This function collects all documentation that has been added to an item using
@ -166,23 +244,11 @@ impl<'tcx> LateLintPass<'tcx> for MetadataCollector {
fn extract_attr_docs(item: &Item<'_>) -> Option<String> {
item.attrs
.iter()
.filter_map(|ref x| x.doc_str())
.fold(None, |acc, sym| {
let mut doc_str = sym.as_str().to_string();
doc_str.push('\n');
#[allow(clippy::option_if_let_else)] // See clippy#6737
if let Some(mut x) = acc {
x.push_str(&doc_str);
Some(x)
} else {
Some(doc_str)
}
// acc.map_or(Some(doc_str), |mut x| {
// x.push_str(&doc_str);
// Some(x)
// })
.filter_map(|ref x| x.doc_str().map(|sym| sym.as_str().to_string()))
.reduce(|mut acc, sym| {
acc.push_str(&sym);
acc.push('\n');
acc
})
}
@ -196,7 +262,7 @@ fn get_lint_group(cx: &LateContext<'_>, lint_id: LintId) -> Option<String> {
None
}
fn lint_collection_error(cx: &LateContext<'_>, item: &Item<'_>, message: &str) {
fn lint_collection_error_item(cx: &LateContext<'_>, item: &Item<'_>, message: &str) {
span_lint(
cx,
INTERNAL_METADATA_COLLECTOR,
@ -204,3 +270,49 @@ fn lint_collection_error(cx: &LateContext<'_>, item: &Item<'_>, message: &str) {
&format!("Metadata collection error for `{}`: {}", item.ident.name, message),
);
}
fn lint_collection_error_span(cx: &LateContext<'_>, span: Span, message: &str) {
span_lint(
cx,
INTERNAL_METADATA_COLLECTOR,
span,
&format!("Metadata collection error: {}", message),
);
}
fn match_simple_lint_emission<'tcx>(
cx: &LateContext<'tcx>,
expr: &'tcx hir::Expr<'_>,
) -> Option<&'tcx [hir::Expr<'tcx>]> {
LINT_EMISSION_FUNCTIONS
.iter()
.find_map(|emission_fn| match_function_call(cx, expr, emission_fn).map(|args| args))
}
/// This returns the lint name and the possible applicability of this emission
fn extract_emission_info<'tcx>(cx: &LateContext<'tcx>, args: &[hir::Expr<'_>]) -> Option<(String, Vec<String>)> {
let mut lint_name = None;
let mut applicability = None;
for arg in args {
let (arg_ty, _) = walk_ptrs_ty_depth(cx.typeck_results().expr_ty(&arg));
if match_type(cx, arg_ty, &paths::LINT) {
// If we found the lint arg, extract the lint name
if let ExprKind::Path(ref lint_path) = arg.kind {
lint_name = Some(last_path_segment(lint_path).ident.name)
}
} else if match_type(cx, arg_ty, &paths::APPLICABILITY) {
if let ExprKind::Path(ref path) = arg.kind {
applicability = Some(last_path_segment(path).ident.name)
}
}
}
lint_name.map(|lint_name| {
(
sym_to_string(lint_name).to_ascii_lowercase(),
applicability.map(sym_to_string).map_or_else(Vec::new, |x| vec![x]),
)
})
}

View file

@ -5,6 +5,8 @@
//! See <https://github.com/rust-lang/rust-clippy/issues/5393> for more information.
pub const ANY_TRAIT: [&str; 3] = ["core", "any", "Any"];
#[cfg(feature = "metadata-collector-lint")]
pub const APPLICABILITY: [&str; 2] = ["rustc_lint_defs", "Applicability"];
pub const ARC_PTR_EQ: [&str; 4] = ["alloc", "sync", "Arc", "ptr_eq"];
pub const ASMUT_TRAIT: [&str; 3] = ["core", "convert", "AsMut"];
pub const ASREF_TRAIT: [&str; 3] = ["core", "convert", "AsRef"];