Skip to content

Add configuration options to --explain #10751

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

Merged
merged 2 commits into from
May 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 21 additions & 3 deletions clippy_lints/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -331,8 +331,11 @@ mod zero_div_zero;
mod zero_sized_map_values;
// end lints modules, do not remove this comment, it’s used in `update_lints`

use crate::utils::conf::{format_error, TryConf};
pub use crate::utils::conf::{lookup_conf_file, Conf};
use crate::utils::{
conf::{format_error, metadata::get_configuration_metadata, TryConf},
FindAll,
};

/// Register all pre expansion lints
///
Expand Down Expand Up @@ -388,7 +391,7 @@ pub fn read_conf(sess: &Session, path: &io::Result<(Option<PathBuf>, Vec<String>
conf
}

#[derive(Default)]
#[derive(Default)] //~ ERROR no such field
struct RegistrationGroups {
all: Vec<LintId>,
cargo: Vec<LintId>,
Expand Down Expand Up @@ -471,7 +474,22 @@ pub(crate) struct LintInfo {
pub fn explain(name: &str) {
let target = format!("clippy::{}", name.to_ascii_uppercase());
match declared_lints::LINTS.iter().find(|info| info.lint.name == target) {
Some(info) => print!("{}", info.explanation),
Some(info) => {
println!("{}", info.explanation);
// Check if the lint has configuration
let mdconf = get_configuration_metadata();
if let Some(config_vec_positions) = mdconf
.iter()
.find_all(|cconf| cconf.lints.contains(&info.lint.name_lower()[8..].to_owned()))
{
// If it has, print it
println!("### Configuration for {}:\n", info.lint.name_lower());
for position in config_vec_positions {
let conf = &mdconf[position];
println!(" - {}: {} (default: {})", conf.name, conf.doc, conf.default);
}
}
},
None => println!("unknown lint: {name}"),
}
}
Expand Down
5 changes: 2 additions & 3 deletions clippy_lints/src/utils/conf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -174,16 +174,15 @@ macro_rules! define_Conf {
}
}

#[cfg(feature = "internal")]
pub mod metadata {
use crate::utils::internal_lints::metadata_collector::ClippyConfiguration;
use crate::utils::ClippyConfiguration;

macro_rules! wrap_option {
() => (None);
($x:literal) => (Some($x));
}

pub(crate) fn get_configuration_metadata() -> Vec<ClippyConfiguration> {
pub fn get_configuration_metadata() -> Vec<ClippyConfiguration> {
vec![
$(
{
Expand Down
111 changes: 5 additions & 106 deletions clippy_lints/src/utils/internal_lints/metadata_collector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@
//! a simple mistake)

use crate::renamed_lints::RENAMED_LINTS;
use crate::utils::internal_lints::lint_without_lint_pass::{extract_clippy_version_value, is_lint_ref_type};
use crate::utils::{
collect_configs,
internal_lints::lint_without_lint_pass::{extract_clippy_version_value, is_lint_ref_type},
ClippyConfiguration,
};

use clippy_utils::diagnostics::span_lint;
use clippy_utils::ty::{match_type, walk_ptrs_ty_depth};
Expand Down Expand Up @@ -520,111 +524,6 @@ impl Serialize for ApplicabilityInfo {
}
}

// ==================================================================
// Configuration
// ==================================================================
#[derive(Debug, Clone, Default)]
pub struct ClippyConfiguration {
name: String,
config_type: &'static str,
default: String,
lints: Vec<String>,
doc: String,
#[allow(dead_code)]
deprecation_reason: Option<&'static str>,
}

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()));

Self {
name: to_kebab(name),
lints,
doc,
config_type,
default,
deprecation_reason,
}
}

fn to_markdown_paragraph(&self) -> String {
format!(
"### {}\n{}\n\n**Default Value:** `{}` (`{}`)\n\n{}\n\n",
self.name,
self.doc
.lines()
.map(|line| line.strip_prefix(" ").unwrap_or(line))
.join("\n"),
self.default,
self.config_type,
self.lints
.iter()
.map(|name| name.to_string().split_whitespace().next().unwrap().to_string())
.map(|name| format!("* [{name}](https://rust-lang.github.io/rust-clippy/master/index.html#{name})"))
.join("\n"),
)
}

fn to_markdown_table_entry(&self) -> String {
format!("| [{}](#{}) | `{}` |", self.name, self.name, self.default)
}
}

fn collect_configs() -> Vec<ClippyConfiguration> {
crate::utils::conf::metadata::get_configuration_metadata()
}

/// 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 mut documentation = doc_comment.split_off(split_pos);

// Extract lints
doc_comment.make_ascii_lowercase();
let lints: Vec<String> = doc_comment
.split_off(DOC_START.len())
.split(", ")
.map(str::to_string)
.collect();

// Format documentation correctly
// split off leading `.` from lint name list and indent for correct formatting
documentation = documentation.trim_start_matches('.').trim().replace("\n ", "\n ");

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('_', "-")
}

impl fmt::Display for ClippyConfiguration {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> fmt::Result {
writeln!(
Expand Down
140 changes: 140 additions & 0 deletions clippy_lints/src/utils/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,143 @@ pub mod dump_hir;
pub mod format_args_collector;
#[cfg(feature = "internal")]
pub mod internal_lints;
#[cfg(feature = "internal")]
use itertools::Itertools;

/// Transforms a given `snake_case_string` to a tasty `kebab-case-string`
fn to_kebab(config_name: &str) -> String {
config_name.replace('_', "-")
}

// ==================================================================
// Configuration
// ==================================================================
#[derive(Debug, Clone, Default)] //~ ERROR no such field
pub struct ClippyConfiguration {
pub name: String,
#[allow(dead_code)]
config_type: &'static str,
pub default: String,
pub lints: Vec<String>,
pub doc: String,
#[allow(dead_code)]
deprecation_reason: Option<&'static str>,
}

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()));

Self {
name: to_kebab(name),
lints,
doc,
config_type,
default,
deprecation_reason,
}
}

#[cfg(feature = "internal")]
fn to_markdown_paragraph(&self) -> String {
format!(
"### {}\n{}\n\n**Default Value:** `{}` (`{}`)\n\n{}\n\n",
self.name,
self.doc
.lines()
.map(|line| line.strip_prefix(" ").unwrap_or(line))
.join("\n"),
self.default,
self.config_type,
self.lints
.iter()
.map(|name| name.to_string().split_whitespace().next().unwrap().to_string())
.map(|name| format!("* [{name}](https://rust-lang.github.io/rust-clippy/master/index.html#{name})"))
.join("\n"),
)
}

#[cfg(feature = "internal")]
fn to_markdown_table_entry(&self) -> String {
format!("| [{}](#{}) | `{}` |", self.name, self.name, self.default)
}
}

#[cfg(feature = "internal")]
fn collect_configs() -> Vec<ClippyConfiguration> {
crate::utils::conf::metadata::get_configuration_metadata()
}

/// 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")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm happy that you kept the doc comments ❤️

/// ```
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 mut documentation = doc_comment.split_off(split_pos);

// Extract lints
doc_comment.make_ascii_lowercase();
let lints: Vec<String> = doc_comment
.split_off(DOC_START.len())
.split(", ")
.map(str::to_string)
.collect();

// Format documentation correctly
// split off leading `.` from lint name list and indent for correct formatting
documentation = documentation.trim_start_matches('.').trim().replace("\n ", "\n ");

Some((lints, documentation))
} else {
None
}
}
}

// Shamelessly stolen from find_all (https://github.com/nectariner/find_all)
pub trait FindAll: Iterator + Sized {
fn find_all<P>(&mut self, predicate: P) -> Option<Vec<usize>>
where
P: FnMut(&Self::Item) -> bool;
}

impl<I> FindAll for I
where
I: Iterator,
{
fn find_all<P>(&mut self, mut predicate: P) -> Option<Vec<usize>>
where
P: FnMut(&Self::Item) -> bool,
{
let mut occurences = Vec::<usize>::default();
for (index, element) in self.enumerate() {
if predicate(&element) {
occurences.push(index);
}
}

match occurences.len() {
0 => None,
_ => Some(occurences),
}
}
}