Auto merge of #10751 - blyxyas:explain_with_config, r=xFrednet

Add configuration options to `--explain`

This PR rearranges some modules, taking `metadata_collector` out of `internal_lints` and making public just the necessary functions for `explain()` to use.

The output looks something like this:
```sh
$ cargo run --bin cargo-clippy --manifest-path ../rust-clippy/Cargo.toml -- --explain cognitive_complexity
### What it does
Checks for methods with high cognitive complexity.

### Why is this bad?
Methods of high cognitive complexity tend to be hard to
both read and maintain. Also LLVM will tend to optimize small methods better.

### Known problems
Sometimes it's hard to find a way to reduce the
complexity.

### Example
You'll see it when you get the warning.

========================================
Configuration for clippy::cognitive_complexity:
- cognitive-complexity-threshold: The maximum cognitive complexity a function can have (default: 25)
```

Fixes #9990
r? `@xFrednet`

---

changelog: Docs: `cargo clippy --explain LINT` now shows possible configuration options for the explained lint
[#10751](https://github.com/rust-lang/rust-clippy/pull/10751)
<!-- changelog_checked -->
This commit is contained in:
bors 2023-05-09 18:41:30 +00:00
commit 1407c7627e
4 changed files with 168 additions and 112 deletions

View File

@ -332,8 +332,11 @@
mod zero_sized_map_values; mod zero_sized_map_values;
// end lints modules, do not remove this comment, its used in `update_lints` // end lints modules, do not remove this comment, its used in `update_lints`
use crate::utils::conf::{format_error, TryConf};
pub use crate::utils::conf::{lookup_conf_file, Conf}; 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 /// Register all pre expansion lints
/// ///
@ -389,7 +392,7 @@ pub fn read_conf(sess: &Session, path: &io::Result<(Option<PathBuf>, Vec<String>
conf conf
} }
#[derive(Default)] #[derive(Default)] //~ ERROR no such field
struct RegistrationGroups { struct RegistrationGroups {
all: Vec<LintId>, all: Vec<LintId>,
cargo: Vec<LintId>, cargo: Vec<LintId>,
@ -472,7 +475,22 @@ pub(crate) struct LintInfo {
pub fn explain(name: &str) { pub fn explain(name: &str) {
let target = format!("clippy::{}", name.to_ascii_uppercase()); let target = format!("clippy::{}", name.to_ascii_uppercase());
match declared_lints::LINTS.iter().find(|info| info.lint.name == target) { 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}"), None => println!("unknown lint: {name}"),
} }
} }

View File

@ -174,16 +174,15 @@ fn visit_map<V>(self, mut map: V) -> Result<Self::Value, V::Error> where V: MapA
} }
} }
#[cfg(feature = "internal")]
pub mod metadata { pub mod metadata {
use crate::utils::internal_lints::metadata_collector::ClippyConfiguration; use crate::utils::ClippyConfiguration;
macro_rules! wrap_option { macro_rules! wrap_option {
() => (None); () => (None);
($x:literal) => (Some($x)); ($x:literal) => (Some($x));
} }
pub(crate) fn get_configuration_metadata() -> Vec<ClippyConfiguration> { pub fn get_configuration_metadata() -> Vec<ClippyConfiguration> {
vec![ vec![
$( $(
{ {

View File

@ -8,7 +8,11 @@
//! a simple mistake) //! a simple mistake)
use crate::renamed_lints::RENAMED_LINTS; 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::diagnostics::span_lint;
use clippy_utils::ty::{match_type, walk_ptrs_ty_depth}; use clippy_utils::ty::{match_type, walk_ptrs_ty_depth};
@ -520,111 +524,6 @@ fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
} }
} }
// ==================================================================
// 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 { impl fmt::Display for ClippyConfiguration {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> fmt::Result {
writeln!( writeln!(

View File

@ -4,3 +4,143 @@
pub mod format_args_collector; pub mod format_args_collector;
#[cfg(feature = "internal")] #[cfg(feature = "internal")]
pub mod internal_lints; 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")
/// ```
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),
}
}
}