From 210ec728e5d7f428b22b78ee721ed6507cc6f925 Mon Sep 17 00:00:00 2001 From: xFrednet Date: Tue, 11 May 2021 20:23:52 +0200 Subject: [PATCH 1/5] Metadata collection monster searching for configurations --- clippy_lints/src/lib.rs | 2 +- clippy_lints/src/utils/conf.rs | 24 ++++- .../internal_lints/metadata_collector.rs | 88 ++++++++++++++++++- 3 files changed, 109 insertions(+), 5 deletions(-) diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 0c86441cf3fe8..f0fae6ee1c76b 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -1009,7 +1009,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: #[cfg(feature = "metadata-collector-lint")] { if std::env::var("ENABLE_METADATA_COLLECTION").eq(&Ok("1".to_string())) { - store.register_late_pass(|| box utils::internal_lints::metadata_collector::MetadataCollector::default()); + store.register_late_pass(|| box utils::internal_lints::metadata_collector::MetadataCollector::new()); } } diff --git a/clippy_lints/src/utils/conf.rs b/clippy_lints/src/utils/conf.rs index 52c1dc3bdd239..98b86f73a1f6b 100644 --- a/clippy_lints/src/utils/conf.rs +++ b/clippy_lints/src/utils/conf.rs @@ -26,13 +26,13 @@ impl TryConf { macro_rules! define_Conf { ($( - #[$doc:meta] + #[doc = $doc:literal] $(#[conf_deprecated($dep:literal)])? ($name:ident: $ty:ty = $default:expr), )*) => { /// Clippy lint configuration pub struct Conf { - $(#[$doc] pub $name: $ty,)* + $(#[doc = $doc] pub $name: $ty,)* } mod defaults { @@ -89,6 +89,24 @@ macro_rules! define_Conf { Ok(TryConf { conf, errors }) } } + + #[cfg(feature = "metadata-collector-lint")] + pub mod metadata { + use crate::utils::internal_lints::metadata_collector::ClippyConfigurationBasicInfo; + + pub(crate) fn get_configuration_metadata() -> Vec { + vec![ + $( + ClippyConfigurationBasicInfo { + name: stringify!($name), + config_type: stringify!($ty), + default: stringify!($default), + doc_comment: $doc, + }, + )+ + ] + } + } }; } @@ -100,7 +118,7 @@ define_Conf! { (blacklisted_names: Vec = ["foo", "baz", "quux"].iter().map(ToString::to_string).collect()), /// Lint: COGNITIVE_COMPLEXITY. The maximum cognitive complexity a function can have (cognitive_complexity_threshold: u64 = 25), - /// DEPRECATED LINT: CYCLOMATIC_COMPLEXITY. Use the Cognitive Complexity lint instead. + /// Lint: CYCLOMATIC_COMPLEXITY. Use the Cognitive Complexity lint instead. #[conf_deprecated("Please use `cognitive-complexity-threshold` instead")] (cyclomatic_complexity_threshold: Option = None), /// Lint: DOC_MARKDOWN. The list of words this lint should not consider as identifiers needing ticks diff --git a/clippy_lints/src/utils/internal_lints/metadata_collector.rs b/clippy_lints/src/utils/internal_lints/metadata_collector.rs index fc4912ba52ff9..308f61beec3a7 100644 --- a/clippy_lints/src/utils/internal_lints/metadata_collector.rs +++ b/clippy_lints/src/utils/internal_lints/metadata_collector.rs @@ -102,13 +102,24 @@ declare_clippy_lint! { impl_lint_pass!(MetadataCollector => [INTERNAL_METADATA_COLLECTOR]); #[allow(clippy::module_name_repetitions)] -#[derive(Debug, Clone, Default)] +#[derive(Debug, Clone)] pub struct MetadataCollector { /// All collected lints /// /// We use a Heap here to have the lints added in alphabetic order in the export lints: BinaryHeap, applicability_info: FxHashMap, + config: Vec, +} + +impl MetadataCollector { + pub fn new() -> Self { + Self { + lints: BinaryHeap::::default(), + applicability_info: FxHashMap::::default(), + config: collect_configs(), + } + } } impl Drop for MetadataCollector { @@ -214,6 +225,81 @@ impl Serialize for ApplicabilityInfo { } } +#[derive(Debug)] +pub(crate) struct ClippyConfigurationBasicInfo { + pub name: &'static str, + pub config_type: &'static str, + pub default: &'static str, + pub doc_comment: &'static str, +} + +#[derive(Debug, Clone, Default)] +struct ClippyConfiguration { + name: String, + lints: Vec, + doc: String, + config_type: &'static str, + default: String, +} + +// ================================================================== +// Configuration +// ================================================================== +fn collect_configs() -> Vec { + let cons = crate::utils::conf::metadata::get_configuration_metadata(); + cons.iter() + .map(move |x| { + let (lints, doc) = parse_config_field_doc(x.doc_comment) + .unwrap_or_else(|| (vec![], "[ERROR] MALFORMED DOC COMMENT".to_string())); + + ClippyConfiguration { + name: to_kebab(x.name), + lints, + doc, + config_type: x.config_type, + default: x.default.to_string(), + } + }) + .collect() +} + +/// This parses the field documentation of the config struct. +/// +/// ```rust, ignore +/// parse_config_field_doc(cx, "Lint: LINT_NAME_1, LINT_NAME_2. Papa penguin, papa penguin") +/// ``` +/// +/// Would yield: +/// ```rust, ignore +/// Some(["lint_name_1", "lint_name_2"], "Papa penguin, papa penguin") +/// ``` +fn parse_config_field_doc(doc_comment: &str) -> Option<(Vec, String)> { + const DOC_START: &str = " Lint: "; + if_chain! { + if doc_comment.starts_with(DOC_START); + if let Some(split_pos) = doc_comment.find('.'); + then { + let mut doc_comment = doc_comment.to_string(); + let documentation = doc_comment.split_off(split_pos); + + doc_comment.make_ascii_lowercase(); + let lints: Vec = doc_comment.split_off(DOC_START.len()).split(", ").map(str::to_string).collect(); + + Some((lints, documentation)) + } else { + None + } + } +} + +/// Transforms a given `snake_case_string` to a tasty `kebab-case-string` +fn to_kebab(config_name: &str) -> String { + config_name.replace('_', "-") +} + +// ================================================================== +// Lint pass +// ================================================================== impl<'hir> LateLintPass<'hir> for MetadataCollector { /// Collecting lint declarations like: /// ```rust, ignore From 88ae2d1155c48d1c2b0dc4d049f0411778a94035 Mon Sep 17 00:00:00 2001 From: xFrednet Date: Tue, 11 May 2021 21:47:10 +0200 Subject: [PATCH 2/5] Metadata formatting the configuration section --- .../internal_lints/metadata_collector.rs | 58 +++++++++++++++++-- 1 file changed, 54 insertions(+), 4 deletions(-) diff --git a/clippy_lints/src/utils/internal_lints/metadata_collector.rs b/clippy_lints/src/utils/internal_lints/metadata_collector.rs index 308f61beec3a7..bb1c8ae95859a 100644 --- a/clippy_lints/src/utils/internal_lints/metadata_collector.rs +++ b/clippy_lints/src/utils/internal_lints/metadata_collector.rs @@ -19,6 +19,7 @@ use rustc_session::{declare_tool_lint, impl_lint_pass}; use rustc_span::{sym, Loc, Span, Symbol}; use serde::{ser::SerializeStruct, Serialize, Serializer}; use std::collections::BinaryHeap; +use std::fmt; use std::fs::{self, OpenOptions}; use std::io::prelude::*; use std::path::Path; @@ -41,6 +42,30 @@ const EXCLUDED_LINT_GROUPS: [&str; 1] = ["clippy::internal"]; /// Collected deprecated lint will be assigned to this group in the JSON output const DEPRECATED_LINT_GROUP_STR: &str = "DEPRECATED"; +/// This template will be used to format the configuration section in the lint documentation. +/// The `configurations` parameter will be replaced with one or multiple formatted +/// `ClippyConfiguration` instances. See `CONFIGURATION_VALUE_TEMPLATE` for further customizations +macro_rules! CONFIGURATION_SECTION_TEMPLATE { + () => { + r#" +**Configuration** +This lint has the following configuration variables: + +{configurations} +"# + }; +} +/// This template will be used to format an individual `ClippyConfiguration` instance in the +/// lint documentation. +/// +/// The format function will provide strings for the following parameters: `name`, `ty`, `doc` and +/// `default` +macro_rules! CONFIGURATION_VALUE_TEMPLATE { + () => { + "* {name}: {ty}: {doc} (defaults to `{default}`)\n" + }; +} + const LINT_EMISSION_FUNCTIONS: [&[&str]; 7] = [ &["clippy_utils", "diagnostics", "span_lint"], &["clippy_utils", "diagnostics", "span_lint_and_help"], @@ -120,6 +145,14 @@ impl MetadataCollector { config: collect_configs(), } } + + fn get_lint_configs(&self, lint_name: &str) -> Option { + self.config + .iter() + .filter_map(|x| x.lints.iter().any(|x| x == lint_name).then(|| format!("{}", x))) + .reduce(|acc, x| acc + &x) + .map(|configurations| format!(CONFIGURATION_SECTION_TEMPLATE!(), configurations = configurations)) + } } impl Drop for MetadataCollector { @@ -225,6 +258,9 @@ impl Serialize for ApplicabilityInfo { } } +// ================================================================== +// Configuration +// ================================================================== #[derive(Debug)] pub(crate) struct ClippyConfigurationBasicInfo { pub name: &'static str, @@ -242,9 +278,6 @@ struct ClippyConfiguration { default: String, } -// ================================================================== -// Configuration -// ================================================================== fn collect_configs() -> Vec { let cons = crate::utils::conf::metadata::get_configuration_metadata(); cons.iter() @@ -297,6 +330,19 @@ fn to_kebab(config_name: &str) -> String { config_name.replace('_', "-") } +impl fmt::Display for ClippyConfiguration { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + CONFIGURATION_VALUE_TEMPLATE!(), + name = self.name, + ty = self.config_type, + doc = self.doc, + default = self.default + ) + } +} + // ================================================================== // Lint pass // ================================================================== @@ -321,8 +367,12 @@ impl<'hir> LateLintPass<'hir> for MetadataCollector { if !BLACK_LISTED_LINTS.contains(&lint_name.as_str()); // metadata extraction if let Some(group) = get_lint_group_or_lint(cx, &lint_name, item); - if let Some(docs) = extract_attr_docs_or_lint(cx, item); + if let Some(mut docs) = extract_attr_docs_or_lint(cx, item); then { + if let Some(configuration_section) = self.get_lint_configs(&lint_name) { + docs.push_str(&configuration_section); + } + self.lints.push(LintMetadata::new( lint_name, SerializableSpan::from_item(cx, item), From b03642e51f4c1cfc51858dd792aa689b0e6597d7 Mon Sep 17 00:00:00 2001 From: xFrednet Date: Wed, 12 May 2021 17:30:04 +0200 Subject: [PATCH 3/5] Metadata collection clarifying default configuration values --- .../src/utils/internal_lints/metadata_collector.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/clippy_lints/src/utils/internal_lints/metadata_collector.rs b/clippy_lints/src/utils/internal_lints/metadata_collector.rs index bb1c8ae95859a..4a8a46be04164 100644 --- a/clippy_lints/src/utils/internal_lints/metadata_collector.rs +++ b/clippy_lints/src/utils/internal_lints/metadata_collector.rs @@ -290,12 +290,22 @@ fn collect_configs() -> Vec { lints, doc, config_type: x.config_type, - default: x.default.to_string(), + default: clarify_default(x.default), } }) .collect() } +fn clarify_default(default: &'static str) -> String { + if let Some((_start, init)) = default.split_once('[') { + if let Some((init, _end)) = init.split_once(']') { + return format!("[{}]", init); + } + } + + default.to_string() +} + /// This parses the field documentation of the config struct. /// /// ```rust, ignore From b740a04dc49b32185e69a2128bcb8425d82b0c66 Mon Sep 17 00:00:00 2001 From: xFrednet Date: Wed, 12 May 2021 18:47:32 +0200 Subject: [PATCH 4/5] Metadata collection collecting configuration deprecation reason --- clippy_lints/src/utils/conf.rs | 21 +++++++++++++------ .../internal_lints/metadata_collector.rs | 7 +++++-- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/clippy_lints/src/utils/conf.rs b/clippy_lints/src/utils/conf.rs index 98b86f73a1f6b..25163bb9f3797 100644 --- a/clippy_lints/src/utils/conf.rs +++ b/clippy_lints/src/utils/conf.rs @@ -97,11 +97,20 @@ macro_rules! define_Conf { pub(crate) fn get_configuration_metadata() -> Vec { vec![ $( - ClippyConfigurationBasicInfo { - name: stringify!($name), - config_type: stringify!($ty), - default: stringify!($default), - doc_comment: $doc, + { + #[allow(unused_mut, unused_assignments)] + let mut deprecation_reason = None; + + // only set if a deprecation reason was set + $(deprecation_reason = Some(stringify!($dep));)? + + ClippyConfigurationBasicInfo { + name: stringify!($name), + config_type: stringify!($ty), + default: stringify!($default), + doc_comment: $doc, + deprecation_reason, + } }, )+ ] @@ -118,7 +127,7 @@ define_Conf! { (blacklisted_names: Vec = ["foo", "baz", "quux"].iter().map(ToString::to_string).collect()), /// Lint: COGNITIVE_COMPLEXITY. The maximum cognitive complexity a function can have (cognitive_complexity_threshold: u64 = 25), - /// Lint: CYCLOMATIC_COMPLEXITY. Use the Cognitive Complexity lint instead. + /// DEPRECATED LINT: CYCLOMATIC_COMPLEXITY. Use the Cognitive Complexity lint instead. #[conf_deprecated("Please use `cognitive-complexity-threshold` instead")] (cyclomatic_complexity_threshold: Option = None), /// Lint: DOC_MARKDOWN. The list of words this lint should not consider as identifiers needing ticks diff --git a/clippy_lints/src/utils/internal_lints/metadata_collector.rs b/clippy_lints/src/utils/internal_lints/metadata_collector.rs index 4a8a46be04164..bd0de8ad0341a 100644 --- a/clippy_lints/src/utils/internal_lints/metadata_collector.rs +++ b/clippy_lints/src/utils/internal_lints/metadata_collector.rs @@ -267,6 +267,7 @@ pub(crate) struct ClippyConfigurationBasicInfo { pub config_type: &'static str, pub default: &'static str, pub doc_comment: &'static str, + pub deprecation_reason: Option<&'static str>, } #[derive(Debug, Clone, Default)] @@ -276,6 +277,7 @@ struct ClippyConfiguration { doc: String, config_type: &'static str, default: String, + deprecation_reason: Option<&'static str>, } fn collect_configs() -> Vec { @@ -291,18 +293,19 @@ fn collect_configs() -> Vec { doc, config_type: x.config_type, default: clarify_default(x.default), + deprecation_reason: x.deprecation_reason, } }) .collect() } fn clarify_default(default: &'static str) -> String { - if let Some((_start, init)) = default.split_once('[') { + if let Some((_start, init)) = default.split_once('[') { if let Some((init, _end)) = init.split_once(']') { return format!("[{}]", init); } } - + default.to_string() } From f810c11d3c44af357ad7af6cf9fc9ab582591c01 Mon Sep 17 00:00:00 2001 From: xFrednet Date: Sat, 15 May 2021 19:00:49 +0200 Subject: [PATCH 5/5] Applying PR suggestions and cleaning up --- clippy_lints/src/utils/conf.rs | 27 ++++---- .../internal_lints/metadata_collector.rs | 61 ++++++++----------- 2 files changed, 39 insertions(+), 49 deletions(-) diff --git a/clippy_lints/src/utils/conf.rs b/clippy_lints/src/utils/conf.rs index 25163bb9f3797..fd2dddb3b96e5 100644 --- a/clippy_lints/src/utils/conf.rs +++ b/clippy_lints/src/utils/conf.rs @@ -92,25 +92,26 @@ macro_rules! define_Conf { #[cfg(feature = "metadata-collector-lint")] pub mod metadata { - use crate::utils::internal_lints::metadata_collector::ClippyConfigurationBasicInfo; + use crate::utils::internal_lints::metadata_collector::ClippyConfiguration; - pub(crate) fn get_configuration_metadata() -> Vec { + macro_rules! wrap_option { + () => (None); + ($x:literal) => (Some($x)); + } + + pub(crate) fn get_configuration_metadata() -> Vec { vec![ $( { - #[allow(unused_mut, unused_assignments)] - let mut deprecation_reason = None; + let deprecation_reason = wrap_option!($($dep)?); - // only set if a deprecation reason was set - $(deprecation_reason = Some(stringify!($dep));)? - - ClippyConfigurationBasicInfo { - name: stringify!($name), - config_type: stringify!($ty), - default: stringify!($default), - doc_comment: $doc, + ClippyConfiguration::new( + stringify!($name), + stringify!($ty), + format!("{:?}", super::defaults::$name()), + $doc, deprecation_reason, - } + ) }, )+ ] diff --git a/clippy_lints/src/utils/internal_lints/metadata_collector.rs b/clippy_lints/src/utils/internal_lints/metadata_collector.rs index bd0de8ad0341a..b7c86ae6e1844 100644 --- a/clippy_lints/src/utils/internal_lints/metadata_collector.rs +++ b/clippy_lints/src/utils/internal_lints/metadata_collector.rs @@ -149,7 +149,8 @@ impl MetadataCollector { fn get_lint_configs(&self, lint_name: &str) -> Option { self.config .iter() - .filter_map(|x| x.lints.iter().any(|x| x == lint_name).then(|| format!("{}", x))) + .filter(|config| config.lints.iter().any(|lint| lint == lint_name)) + .map(ToString::to_string) .reduce(|acc, x| acc + &x) .map(|configurations| format!(CONFIGURATION_SECTION_TEMPLATE!(), configurations = configurations)) } @@ -261,52 +262,40 @@ impl Serialize for ApplicabilityInfo { // ================================================================== // Configuration // ================================================================== -#[derive(Debug)] -pub(crate) struct ClippyConfigurationBasicInfo { - pub name: &'static str, - pub config_type: &'static str, - pub default: &'static str, - pub doc_comment: &'static str, - pub deprecation_reason: Option<&'static str>, -} - #[derive(Debug, Clone, Default)] -struct ClippyConfiguration { +pub struct ClippyConfiguration { name: String, - lints: Vec, - doc: String, config_type: &'static str, default: String, + lints: Vec, + doc: String, deprecation_reason: Option<&'static str>, } -fn collect_configs() -> Vec { - let cons = crate::utils::conf::metadata::get_configuration_metadata(); - cons.iter() - .map(move |x| { - let (lints, doc) = parse_config_field_doc(x.doc_comment) - .unwrap_or_else(|| (vec![], "[ERROR] MALFORMED DOC COMMENT".to_string())); - - ClippyConfiguration { - name: to_kebab(x.name), - lints, - doc, - config_type: x.config_type, - default: clarify_default(x.default), - deprecation_reason: x.deprecation_reason, - } - }) - .collect() -} +impl ClippyConfiguration { + pub fn new( + name: &'static str, + config_type: &'static str, + default: String, + doc_comment: &'static str, + deprecation_reason: Option<&'static str>, + ) -> Self { + let (lints, doc) = parse_config_field_doc(doc_comment) + .unwrap_or_else(|| (vec![], "[ERROR] MALFORMED DOC COMMENT".to_string())); -fn clarify_default(default: &'static str) -> String { - if let Some((_start, init)) = default.split_once('[') { - if let Some((init, _end)) = init.split_once(']') { - return format!("[{}]", init); + Self { + name: to_kebab(name), + lints, + doc, + config_type, + default, + deprecation_reason, } } +} - default.to_string() +fn collect_configs() -> Vec { + crate::utils::conf::metadata::get_configuration_metadata() } /// This parses the field documentation of the config struct.