Auto merge of #12180 - y21:conf_lev_distance, r=blyxyas
Suggest existing configuration option if one is found While working on/testing #12179, I made the mistake of using underscores instead of dashes for the field name in the clippy.toml file and ended up being confused for a few minutes until I found out what's wrong. With this change, clippy will suggest an existing field if there's one that's similar. ``` 1 | allow_mixed_uninlined_format_args = true | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: perhaps you meant: `allow-mixed-uninlined-format-args` ``` (in hindsight, the current behavior of printing all the config options makes it obvious in most cases but I still think a suggestion like this would be nice to have) I had to play around with the value a bit. A max distance of 5 seemed a bit too strong since it'd suggest changing `foobar` to `msrv`, which seemed odd, and 4 seemed just good enough to detect a typo of five underscores. changelog: when an invalid field in clippy.toml is found, suggest the closest existing one if one is found
This commit is contained in:
commit
900a5aa036
@ -2,7 +2,9 @@
|
||||
use crate::types::{DisallowedPath, MacroMatcher, MatchLintBehaviour, PubUnderscoreFieldsBehaviour, Rename};
|
||||
use crate::ClippyConfiguration;
|
||||
use rustc_data_structures::fx::FxHashSet;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_session::Session;
|
||||
use rustc_span::edit_distance::edit_distance;
|
||||
use rustc_span::{BytePos, Pos, SourceFile, Span, SyntaxContext};
|
||||
use serde::de::{IgnoredAny, IntoDeserializer, MapAccess, Visitor};
|
||||
use serde::{Deserialize, Deserializer, Serialize};
|
||||
@ -59,18 +61,25 @@ fn from_toml_error(file: &SourceFile, error: &toml::de::Error) -> Self {
|
||||
#[derive(Debug)]
|
||||
struct ConfError {
|
||||
message: String,
|
||||
suggestion: Option<Suggestion>,
|
||||
span: Span,
|
||||
}
|
||||
|
||||
impl ConfError {
|
||||
fn from_toml(file: &SourceFile, error: &toml::de::Error) -> Self {
|
||||
let span = error.span().unwrap_or(0..file.source_len.0 as usize);
|
||||
Self::spanned(file, error.message(), span)
|
||||
Self::spanned(file, error.message(), None, span)
|
||||
}
|
||||
|
||||
fn spanned(file: &SourceFile, message: impl Into<String>, span: Range<usize>) -> Self {
|
||||
fn spanned(
|
||||
file: &SourceFile,
|
||||
message: impl Into<String>,
|
||||
suggestion: Option<Suggestion>,
|
||||
span: Range<usize>,
|
||||
) -> Self {
|
||||
Self {
|
||||
message: message.into(),
|
||||
suggestion,
|
||||
span: Span::new(
|
||||
file.start_pos + BytePos::from_usize(span.start),
|
||||
file.start_pos + BytePos::from_usize(span.end),
|
||||
@ -147,16 +156,18 @@ fn visit_map<V>(self, mut map: V) -> Result<Self::Value, V::Error> where V: MapA
|
||||
match Field::deserialize(name.get_ref().as_str().into_deserializer()) {
|
||||
Err(e) => {
|
||||
let e: FieldError = e;
|
||||
errors.push(ConfError::spanned(self.0, e.0, name.span()));
|
||||
errors.push(ConfError::spanned(self.0, e.error, e.suggestion, name.span()));
|
||||
}
|
||||
$(Ok(Field::$name) => {
|
||||
$(warnings.push(ConfError::spanned(self.0, format!("deprecated field `{}`. {}", name.get_ref(), $dep), name.span()));)?
|
||||
$(warnings.push(ConfError::spanned(self.0, format!("deprecated field `{}`. {}", name.get_ref(), $dep), None, name.span()));)?
|
||||
let raw_value = map.next_value::<toml::Spanned<toml::Value>>()?;
|
||||
let value_span = raw_value.span();
|
||||
match <$ty>::deserialize(raw_value.into_inner()) {
|
||||
Err(e) => errors.push(ConfError::spanned(self.0, e.to_string().replace('\n', " ").trim(), value_span)),
|
||||
Err(e) => errors.push(ConfError::spanned(self.0, e.to_string().replace('\n', " ").trim(), None, value_span)),
|
||||
Ok(value) => match $name {
|
||||
Some(_) => errors.push(ConfError::spanned(self.0, format!("duplicate field `{}`", name.get_ref()), name.span())),
|
||||
Some(_) => {
|
||||
errors.push(ConfError::spanned(self.0, format!("duplicate field `{}`", name.get_ref()), None, name.span()));
|
||||
}
|
||||
None => {
|
||||
$name = Some(value);
|
||||
// $new_conf is the same as one of the defined `$name`s, so
|
||||
@ -165,7 +176,7 @@ fn visit_map<V>(self, mut map: V) -> Result<Self::Value, V::Error> where V: MapA
|
||||
Some(_) => errors.push(ConfError::spanned(self.0, concat!(
|
||||
"duplicate field `", stringify!($new_conf),
|
||||
"` (provided as `", stringify!($name), "`)"
|
||||
), name.span())),
|
||||
), None, name.span())),
|
||||
None => $new_conf = $name.clone(),
|
||||
})?
|
||||
},
|
||||
@ -673,10 +684,16 @@ fn read_inner(sess: &Session, path: &io::Result<(Option<PathBuf>, Vec<String>)>)
|
||||
|
||||
// all conf errors are non-fatal, we just use the default conf in case of error
|
||||
for error in errors {
|
||||
sess.dcx().span_err(
|
||||
let mut diag = sess.dcx().struct_span_err(
|
||||
error.span,
|
||||
format!("error reading Clippy's configuration file: {}", error.message),
|
||||
);
|
||||
|
||||
if let Some(sugg) = error.suggestion {
|
||||
diag.span_suggestion(error.span, sugg.message, sugg.suggestion, Applicability::MaybeIncorrect);
|
||||
}
|
||||
|
||||
diag.emit();
|
||||
}
|
||||
|
||||
for warning in warnings {
|
||||
@ -693,19 +710,31 @@ fn read_inner(sess: &Session, path: &io::Result<(Option<PathBuf>, Vec<String>)>)
|
||||
const SEPARATOR_WIDTH: usize = 4;
|
||||
|
||||
#[derive(Debug)]
|
||||
struct FieldError(String);
|
||||
struct FieldError {
|
||||
error: String,
|
||||
suggestion: Option<Suggestion>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Suggestion {
|
||||
message: &'static str,
|
||||
suggestion: &'static str,
|
||||
}
|
||||
|
||||
impl std::error::Error for FieldError {}
|
||||
|
||||
impl Display for FieldError {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
f.pad(&self.0)
|
||||
f.pad(&self.error)
|
||||
}
|
||||
}
|
||||
|
||||
impl serde::de::Error for FieldError {
|
||||
fn custom<T: Display>(msg: T) -> Self {
|
||||
Self(msg.to_string())
|
||||
Self {
|
||||
error: msg.to_string(),
|
||||
suggestion: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn unknown_field(field: &str, expected: &'static [&'static str]) -> Self {
|
||||
@ -727,7 +756,20 @@ fn unknown_field(field: &str, expected: &'static [&'static str]) -> Self {
|
||||
write!(msg, "{:SEPARATOR_WIDTH$}{field:column_width$}", " ").unwrap();
|
||||
}
|
||||
}
|
||||
Self(msg)
|
||||
|
||||
let suggestion = expected
|
||||
.iter()
|
||||
.filter_map(|expected| {
|
||||
let dist = edit_distance(field, expected, 4)?;
|
||||
Some((dist, expected))
|
||||
})
|
||||
.min_by_key(|&(dist, _)| dist)
|
||||
.map(|(_, suggestion)| Suggestion {
|
||||
message: "perhaps you meant",
|
||||
suggestion,
|
||||
});
|
||||
|
||||
Self { error: msg, suggestion }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -11,6 +11,7 @@
|
||||
extern crate rustc_data_structures;
|
||||
#[allow(unused_extern_crates)]
|
||||
extern crate rustc_driver;
|
||||
extern crate rustc_errors;
|
||||
extern crate rustc_session;
|
||||
extern crate rustc_span;
|
||||
|
||||
|
@ -3,6 +3,9 @@ foobar = 42
|
||||
# so is this one
|
||||
barfoo = 53
|
||||
|
||||
# when using underscores instead of dashes, suggest the correct one
|
||||
allow_mixed_uninlined_format_args = true
|
||||
|
||||
# that one is ignored
|
||||
[third-party]
|
||||
clippy-feature = "nightly"
|
||||
|
@ -1,3 +1,4 @@
|
||||
//@no-rustfix
|
||||
//@error-in-other-file: unknown field `foobar`, expected one of
|
||||
|
||||
fn main() {}
|
||||
|
@ -152,5 +152,82 @@ error: error reading Clippy's configuration file: unknown field `barfoo`, expect
|
||||
LL | barfoo = 53
|
||||
| ^^^^^^
|
||||
|
||||
error: aborting due to 2 previous errors
|
||||
error: error reading Clippy's configuration file: unknown field `allow_mixed_uninlined_format_args`, expected one of
|
||||
absolute-paths-allowed-crates
|
||||
absolute-paths-max-segments
|
||||
accept-comment-above-attributes
|
||||
accept-comment-above-statement
|
||||
allow-dbg-in-tests
|
||||
allow-expect-in-tests
|
||||
allow-mixed-uninlined-format-args
|
||||
allow-one-hash-in-raw-strings
|
||||
allow-print-in-tests
|
||||
allow-private-module-inception
|
||||
allow-unwrap-in-tests
|
||||
allowed-dotfiles
|
||||
allowed-duplicate-crates
|
||||
allowed-idents-below-min-chars
|
||||
allowed-scripts
|
||||
arithmetic-side-effects-allowed
|
||||
arithmetic-side-effects-allowed-binary
|
||||
arithmetic-side-effects-allowed-unary
|
||||
array-size-threshold
|
||||
avoid-breaking-exported-api
|
||||
await-holding-invalid-types
|
||||
blacklisted-names
|
||||
cargo-ignore-publish
|
||||
check-private-items
|
||||
cognitive-complexity-threshold
|
||||
cyclomatic-complexity-threshold
|
||||
disallowed-macros
|
||||
disallowed-methods
|
||||
disallowed-names
|
||||
disallowed-types
|
||||
doc-valid-idents
|
||||
enable-raw-pointer-heuristic-for-send
|
||||
enforce-iter-loop-reborrow
|
||||
enforced-import-renames
|
||||
enum-variant-name-threshold
|
||||
enum-variant-size-threshold
|
||||
excessive-nesting-threshold
|
||||
future-size-threshold
|
||||
ignore-interior-mutability
|
||||
large-error-threshold
|
||||
literal-representation-threshold
|
||||
matches-for-let-else
|
||||
max-fn-params-bools
|
||||
max-include-file-size
|
||||
max-struct-bools
|
||||
max-suggested-slice-pattern-length
|
||||
max-trait-bounds
|
||||
min-ident-chars-threshold
|
||||
missing-docs-in-crate-items
|
||||
msrv
|
||||
pass-by-value-size-limit
|
||||
pub-underscore-fields-behavior
|
||||
semicolon-inside-block-ignore-singleline
|
||||
semicolon-outside-block-ignore-multiline
|
||||
single-char-binding-names-threshold
|
||||
stack-size-threshold
|
||||
standard-macro-braces
|
||||
struct-field-name-threshold
|
||||
suppress-restriction-lint-in-const
|
||||
third-party
|
||||
too-large-for-stack
|
||||
too-many-arguments-threshold
|
||||
too-many-lines-threshold
|
||||
trivial-copy-size-limit
|
||||
type-complexity-threshold
|
||||
unnecessary-box-size
|
||||
unreadable-literal-lint-fractions
|
||||
upper-case-acronyms-aggressive
|
||||
vec-box-size-threshold
|
||||
verbose-bit-mask-threshold
|
||||
warn-on-all-wildcard-imports
|
||||
--> $DIR/$DIR/clippy.toml:7:1
|
||||
|
|
||||
LL | allow_mixed_uninlined_format_args = true
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: perhaps you meant: `allow-mixed-uninlined-format-args`
|
||||
|
||||
error: aborting due to 3 previous errors
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user