diff options
| author | bors <bors@rust-lang.org> | 2024-01-24 23:13:11 +0000 |
|---|---|---|
| committer | bors <bors@rust-lang.org> | 2024-01-24 23:13:11 +0000 |
| commit | 900a5aa036c6070fdec8acd6d871a4bfc89b2fb4 (patch) | |
| tree | 8cf88dbf80bb13a6ceb0f73633d4be03dd99bfa3 | |
| parent | 76a75bf145bbdfb688354d6f9bdebf3fc56af936 (diff) | |
| parent | 4780637cbc4b914218753a23ee54f5785826e403 (diff) | |
| download | rust-900a5aa036c6070fdec8acd6d871a4bfc89b2fb4.tar.gz rust-900a5aa036c6070fdec8acd6d871a4bfc89b2fb4.zip | |
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
| -rw-r--r-- | clippy_config/src/conf.rs | 66 | ||||
| -rw-r--r-- | clippy_config/src/lib.rs | 1 | ||||
| -rw-r--r-- | tests/ui-toml/toml_unknown_key/clippy.toml | 3 | ||||
| -rw-r--r-- | tests/ui-toml/toml_unknown_key/conf_unknown_key.rs | 1 | ||||
| -rw-r--r-- | tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr | 79 |
5 files changed, 137 insertions, 13 deletions
diff --git a/clippy_config/src/conf.rs b/clippy_config/src/conf.rs index 626b23f7309..4e9ddbf8259 100644 --- a/clippy_config/src/conf.rs +++ b/clippy_config/src/conf.rs @@ -2,7 +2,9 @@ use crate::msrvs::Msrv; 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 @@ impl TryConf { #[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 @@ macro_rules! define_Conf { 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 @@ macro_rules! define_Conf { 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 @@ impl Conf { // 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 @@ impl Conf { 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 @@ impl serde::de::Error for FieldError { 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 } } } diff --git a/clippy_config/src/lib.rs b/clippy_config/src/lib.rs index f5dcb16d670..533e375a310 100644 --- a/clippy_config/src/lib.rs +++ b/clippy_config/src/lib.rs @@ -11,6 +11,7 @@ extern crate rustc_ast; 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; diff --git a/tests/ui-toml/toml_unknown_key/clippy.toml b/tests/ui-toml/toml_unknown_key/clippy.toml index b77b4580051..2b63f6e5c26 100644 --- a/tests/ui-toml/toml_unknown_key/clippy.toml +++ b/tests/ui-toml/toml_unknown_key/clippy.toml @@ -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" diff --git a/tests/ui-toml/toml_unknown_key/conf_unknown_key.rs b/tests/ui-toml/toml_unknown_key/conf_unknown_key.rs index 38009627757..49139b60a9f 100644 --- a/tests/ui-toml/toml_unknown_key/conf_unknown_key.rs +++ b/tests/ui-toml/toml_unknown_key/conf_unknown_key.rs @@ -1,3 +1,4 @@ +//@no-rustfix //@error-in-other-file: unknown field `foobar`, expected one of fn main() {} diff --git a/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr b/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr index 09fc9dfa463..fc683e514ba 100644 --- a/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr +++ b/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr @@ -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 |
