diff --git a/clippy_lints/src/utils/internal_lints/metadata_collector.rs b/clippy_lints/src/utils/internal_lints/metadata_collector.rs index dc4267697cba..677f9fcc83f2 100644 --- a/clippy_lints/src/utils/internal_lints/metadata_collector.rs +++ b/clippy_lints/src/utils/internal_lints/metadata_collector.rs @@ -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, + applicability_into: FxHashMap, } 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, +} + +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, +} + 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 { 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 { 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)> { + 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]), + ) + }) +} diff --git a/clippy_utils/src/paths.rs b/clippy_utils/src/paths.rs index 5e6733a300f2..91e5cd6c0465 100644 --- a/clippy_utils/src/paths.rs +++ b/clippy_utils/src/paths.rs @@ -5,6 +5,8 @@ //! See 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"];