Skip to content

Commit 48dad26

Browse files
committed
Auto merge of #7214 - xFrednet:7197-collecting-configuration, r=flip1995,camsteffen
Metadata collection monster searching for Clippy's configuration options This PR teaches our lovely metadata collection monster which configurations are available inside Clippy. It then adds a new *Configuration* section to the lint documentation. --- The implementation uses the `define_Conf!` macro to create a vector of metadata during compilation. This enables easy collection and parsing without the need of searching for the struct during a lint-pass (and it's quite elegant IMO). The information is then parsed into an intermediate struct called `ClippyConfiguration` which will be saved inside the `MetadataCollector` struct itself. It is currently only used to generate the *Configuration* section in the lint documentation, but I'm thinking about adding an overview of available configurations to the website. Saving them in this intermediate state without formatting them right away enables this in the future. The new parsing will also allow us to have a documentation that spans over multiple lines in the future. For example, this will be valid when the old script has been removed: ```rust /// Lint: BLACKLISTED_NAME. /// The list of blacklisted names to lint about. NB: `bar` is not here since it has legitimate uses (blacklisted_names: Vec<String> = ["foo", "baz", "quux"].iter().map(ToString::to_string).collect()) ``` The deprecation reason is also currently being collected but not used any further and that's basically it. --- See: #7172 for the full metadata collection to-do list or to suggest a new feature in connection to it 🙃 --- changelog: none r? `@flip1995` cc `@camsteffen` It would be great if you could also review this PR as you have recently worked on Clippy's `define_Conf!` macro.
2 parents 6bbee5c + f810c11 commit 48dad26

File tree

3 files changed

+171
-5
lines changed

3 files changed

+171
-5
lines changed

clippy_lints/src/lib.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1009,7 +1009,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
10091009
#[cfg(feature = "metadata-collector-lint")]
10101010
{
10111011
if std::env::var("ENABLE_METADATA_COLLECTION").eq(&Ok("1".to_string())) {
1012-
store.register_late_pass(|| box utils::internal_lints::metadata_collector::MetadataCollector::default());
1012+
store.register_late_pass(|| box utils::internal_lints::metadata_collector::MetadataCollector::new());
10131013
}
10141014
}
10151015

clippy_lints/src/utils/conf.rs

+30-2
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,13 @@ impl TryConf {
2626

2727
macro_rules! define_Conf {
2828
($(
29-
#[$doc:meta]
29+
#[doc = $doc:literal]
3030
$(#[conf_deprecated($dep:literal)])?
3131
($name:ident: $ty:ty = $default:expr),
3232
)*) => {
3333
/// Clippy lint configuration
3434
pub struct Conf {
35-
$(#[$doc] pub $name: $ty,)*
35+
$(#[doc = $doc] pub $name: $ty,)*
3636
}
3737

3838
mod defaults {
@@ -89,6 +89,34 @@ macro_rules! define_Conf {
8989
Ok(TryConf { conf, errors })
9090
}
9191
}
92+
93+
#[cfg(feature = "metadata-collector-lint")]
94+
pub mod metadata {
95+
use crate::utils::internal_lints::metadata_collector::ClippyConfiguration;
96+
97+
macro_rules! wrap_option {
98+
() => (None);
99+
($x:literal) => (Some($x));
100+
}
101+
102+
pub(crate) fn get_configuration_metadata() -> Vec<ClippyConfiguration> {
103+
vec![
104+
$(
105+
{
106+
let deprecation_reason = wrap_option!($($dep)?);
107+
108+
ClippyConfiguration::new(
109+
stringify!($name),
110+
stringify!($ty),
111+
format!("{:?}", super::defaults::$name()),
112+
$doc,
113+
deprecation_reason,
114+
)
115+
},
116+
)+
117+
]
118+
}
119+
}
92120
};
93121
}
94122

clippy_lints/src/utils/internal_lints/metadata_collector.rs

+140-2
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ use rustc_session::{declare_tool_lint, impl_lint_pass};
1919
use rustc_span::{sym, Loc, Span, Symbol};
2020
use serde::{ser::SerializeStruct, Serialize, Serializer};
2121
use std::collections::BinaryHeap;
22+
use std::fmt;
2223
use std::fs::{self, OpenOptions};
2324
use std::io::prelude::*;
2425
use std::path::Path;
@@ -41,6 +42,30 @@ const EXCLUDED_LINT_GROUPS: [&str; 1] = ["clippy::internal"];
4142
/// Collected deprecated lint will be assigned to this group in the JSON output
4243
const DEPRECATED_LINT_GROUP_STR: &str = "DEPRECATED";
4344

45+
/// This template will be used to format the configuration section in the lint documentation.
46+
/// The `configurations` parameter will be replaced with one or multiple formatted
47+
/// `ClippyConfiguration` instances. See `CONFIGURATION_VALUE_TEMPLATE` for further customizations
48+
macro_rules! CONFIGURATION_SECTION_TEMPLATE {
49+
() => {
50+
r#"
51+
**Configuration**
52+
This lint has the following configuration variables:
53+
54+
{configurations}
55+
"#
56+
};
57+
}
58+
/// This template will be used to format an individual `ClippyConfiguration` instance in the
59+
/// lint documentation.
60+
///
61+
/// The format function will provide strings for the following parameters: `name`, `ty`, `doc` and
62+
/// `default`
63+
macro_rules! CONFIGURATION_VALUE_TEMPLATE {
64+
() => {
65+
"* {name}: {ty}: {doc} (defaults to `{default}`)\n"
66+
};
67+
}
68+
4469
const LINT_EMISSION_FUNCTIONS: [&[&str]; 7] = [
4570
&["clippy_utils", "diagnostics", "span_lint"],
4671
&["clippy_utils", "diagnostics", "span_lint_and_help"],
@@ -102,13 +127,33 @@ declare_clippy_lint! {
102127
impl_lint_pass!(MetadataCollector => [INTERNAL_METADATA_COLLECTOR]);
103128

104129
#[allow(clippy::module_name_repetitions)]
105-
#[derive(Debug, Clone, Default)]
130+
#[derive(Debug, Clone)]
106131
pub struct MetadataCollector {
107132
/// All collected lints
108133
///
109134
/// We use a Heap here to have the lints added in alphabetic order in the export
110135
lints: BinaryHeap<LintMetadata>,
111136
applicability_info: FxHashMap<String, ApplicabilityInfo>,
137+
config: Vec<ClippyConfiguration>,
138+
}
139+
140+
impl MetadataCollector {
141+
pub fn new() -> Self {
142+
Self {
143+
lints: BinaryHeap::<LintMetadata>::default(),
144+
applicability_info: FxHashMap::<String, ApplicabilityInfo>::default(),
145+
config: collect_configs(),
146+
}
147+
}
148+
149+
fn get_lint_configs(&self, lint_name: &str) -> Option<String> {
150+
self.config
151+
.iter()
152+
.filter(|config| config.lints.iter().any(|lint| lint == lint_name))
153+
.map(ToString::to_string)
154+
.reduce(|acc, x| acc + &x)
155+
.map(|configurations| format!(CONFIGURATION_SECTION_TEMPLATE!(), configurations = configurations))
156+
}
112157
}
113158

114159
impl Drop for MetadataCollector {
@@ -214,6 +259,95 @@ impl Serialize for ApplicabilityInfo {
214259
}
215260
}
216261

262+
// ==================================================================
263+
// Configuration
264+
// ==================================================================
265+
#[derive(Debug, Clone, Default)]
266+
pub struct ClippyConfiguration {
267+
name: String,
268+
config_type: &'static str,
269+
default: String,
270+
lints: Vec<String>,
271+
doc: String,
272+
deprecation_reason: Option<&'static str>,
273+
}
274+
275+
impl ClippyConfiguration {
276+
pub fn new(
277+
name: &'static str,
278+
config_type: &'static str,
279+
default: String,
280+
doc_comment: &'static str,
281+
deprecation_reason: Option<&'static str>,
282+
) -> Self {
283+
let (lints, doc) = parse_config_field_doc(doc_comment)
284+
.unwrap_or_else(|| (vec![], "[ERROR] MALFORMED DOC COMMENT".to_string()));
285+
286+
Self {
287+
name: to_kebab(name),
288+
lints,
289+
doc,
290+
config_type,
291+
default,
292+
deprecation_reason,
293+
}
294+
}
295+
}
296+
297+
fn collect_configs() -> Vec<ClippyConfiguration> {
298+
crate::utils::conf::metadata::get_configuration_metadata()
299+
}
300+
301+
/// This parses the field documentation of the config struct.
302+
///
303+
/// ```rust, ignore
304+
/// parse_config_field_doc(cx, "Lint: LINT_NAME_1, LINT_NAME_2. Papa penguin, papa penguin")
305+
/// ```
306+
///
307+
/// Would yield:
308+
/// ```rust, ignore
309+
/// Some(["lint_name_1", "lint_name_2"], "Papa penguin, papa penguin")
310+
/// ```
311+
fn parse_config_field_doc(doc_comment: &str) -> Option<(Vec<String>, String)> {
312+
const DOC_START: &str = " Lint: ";
313+
if_chain! {
314+
if doc_comment.starts_with(DOC_START);
315+
if let Some(split_pos) = doc_comment.find('.');
316+
then {
317+
let mut doc_comment = doc_comment.to_string();
318+
let documentation = doc_comment.split_off(split_pos);
319+
320+
doc_comment.make_ascii_lowercase();
321+
let lints: Vec<String> = doc_comment.split_off(DOC_START.len()).split(", ").map(str::to_string).collect();
322+
323+
Some((lints, documentation))
324+
} else {
325+
None
326+
}
327+
}
328+
}
329+
330+
/// Transforms a given `snake_case_string` to a tasty `kebab-case-string`
331+
fn to_kebab(config_name: &str) -> String {
332+
config_name.replace('_', "-")
333+
}
334+
335+
impl fmt::Display for ClippyConfiguration {
336+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> fmt::Result {
337+
write!(
338+
f,
339+
CONFIGURATION_VALUE_TEMPLATE!(),
340+
name = self.name,
341+
ty = self.config_type,
342+
doc = self.doc,
343+
default = self.default
344+
)
345+
}
346+
}
347+
348+
// ==================================================================
349+
// Lint pass
350+
// ==================================================================
217351
impl<'hir> LateLintPass<'hir> for MetadataCollector {
218352
/// Collecting lint declarations like:
219353
/// ```rust, ignore
@@ -235,8 +369,12 @@ impl<'hir> LateLintPass<'hir> for MetadataCollector {
235369
if !BLACK_LISTED_LINTS.contains(&lint_name.as_str());
236370
// metadata extraction
237371
if let Some(group) = get_lint_group_or_lint(cx, &lint_name, item);
238-
if let Some(docs) = extract_attr_docs_or_lint(cx, item);
372+
if let Some(mut docs) = extract_attr_docs_or_lint(cx, item);
239373
then {
374+
if let Some(configuration_section) = self.get_lint_configs(&lint_name) {
375+
docs.push_str(&configuration_section);
376+
}
377+
240378
self.lints.push(LintMetadata::new(
241379
lint_name,
242380
SerializableSpan::from_item(cx, item),

0 commit comments

Comments
 (0)