-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Metadata collection monster searching for Clippy's configuration options #7214
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 4 commits
210ec72
88ae2d1
b03642e
b740a04
f810c11
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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"], | ||
|
@@ -102,13 +127,32 @@ 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<LintMetadata>, | ||
applicability_info: FxHashMap<String, ApplicabilityInfo>, | ||
config: Vec<ClippyConfiguration>, | ||
} | ||
|
||
impl MetadataCollector { | ||
pub fn new() -> Self { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Doesn't this trigger our There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's the first time that I've heard of that lint... so, I guess not 🤔. Unless it got merged after this branch was crated from master. I would also expect it not to trigger as It didn't trigger during testing. We'll see if bors complains about it 🙃 |
||
Self { | ||
lints: BinaryHeap::<LintMetadata>::default(), | ||
applicability_info: FxHashMap::<String, ApplicabilityInfo>::default(), | ||
config: collect_configs(), | ||
} | ||
} | ||
|
||
fn get_lint_configs(&self, lint_name: &str) -> Option<String> { | ||
self.config | ||
.iter() | ||
.filter_map(|x| x.lints.iter().any(|x| x == lint_name).then(|| format!("{}", x))) | ||
camsteffen marked this conversation as resolved.
Show resolved
Hide resolved
|
||
.reduce(|acc, x| acc + &x) | ||
.map(|configurations| format!(CONFIGURATION_SECTION_TEMPLATE!(), configurations = configurations)) | ||
} | ||
} | ||
|
||
impl Drop for MetadataCollector { | ||
|
@@ -214,6 +258,107 @@ 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 { | ||
name: String, | ||
lints: Vec<String>, | ||
doc: String, | ||
config_type: &'static str, | ||
default: String, | ||
deprecation_reason: Option<&'static str>, | ||
} | ||
camsteffen marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
fn collect_configs() -> Vec<ClippyConfiguration> { | ||
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() | ||
} | ||
|
||
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); | ||
camsteffen marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
} | ||
|
||
default.to_string() | ||
} | ||
|
||
/// 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>, 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<String> = 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` | ||
camsteffen marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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 | ||
// ================================================================== | ||
impl<'hir> LateLintPass<'hir> for MetadataCollector { | ||
/// Collecting lint declarations like: | ||
/// ```rust, ignore | ||
|
@@ -235,8 +380,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), | ||
|
Uh oh!
There was an error while loading. Please reload this page.