Skip to content

Commit 900a5aa

Browse files
committed
Auto merge of rust-lang#12180 - y21:conf_lev_distance, r=blyxyas
Suggest existing configuration option if one is found While working on/testing rust-lang#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
2 parents 76a75bf + 4780637 commit 900a5aa

File tree

5 files changed

+137
-13
lines changed

5 files changed

+137
-13
lines changed

clippy_config/src/conf.rs

+54-12
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ use crate::msrvs::Msrv;
22
use crate::types::{DisallowedPath, MacroMatcher, MatchLintBehaviour, PubUnderscoreFieldsBehaviour, Rename};
33
use crate::ClippyConfiguration;
44
use rustc_data_structures::fx::FxHashSet;
5+
use rustc_errors::Applicability;
56
use rustc_session::Session;
7+
use rustc_span::edit_distance::edit_distance;
68
use rustc_span::{BytePos, Pos, SourceFile, Span, SyntaxContext};
79
use serde::de::{IgnoredAny, IntoDeserializer, MapAccess, Visitor};
810
use serde::{Deserialize, Deserializer, Serialize};
@@ -59,18 +61,25 @@ impl TryConf {
5961
#[derive(Debug)]
6062
struct ConfError {
6163
message: String,
64+
suggestion: Option<Suggestion>,
6265
span: Span,
6366
}
6467

6568
impl ConfError {
6669
fn from_toml(file: &SourceFile, error: &toml::de::Error) -> Self {
6770
let span = error.span().unwrap_or(0..file.source_len.0 as usize);
68-
Self::spanned(file, error.message(), span)
71+
Self::spanned(file, error.message(), None, span)
6972
}
7073

71-
fn spanned(file: &SourceFile, message: impl Into<String>, span: Range<usize>) -> Self {
74+
fn spanned(
75+
file: &SourceFile,
76+
message: impl Into<String>,
77+
suggestion: Option<Suggestion>,
78+
span: Range<usize>,
79+
) -> Self {
7280
Self {
7381
message: message.into(),
82+
suggestion,
7483
span: Span::new(
7584
file.start_pos + BytePos::from_usize(span.start),
7685
file.start_pos + BytePos::from_usize(span.end),
@@ -147,16 +156,18 @@ macro_rules! define_Conf {
147156
match Field::deserialize(name.get_ref().as_str().into_deserializer()) {
148157
Err(e) => {
149158
let e: FieldError = e;
150-
errors.push(ConfError::spanned(self.0, e.0, name.span()));
159+
errors.push(ConfError::spanned(self.0, e.error, e.suggestion, name.span()));
151160
}
152161
$(Ok(Field::$name) => {
153-
$(warnings.push(ConfError::spanned(self.0, format!("deprecated field `{}`. {}", name.get_ref(), $dep), name.span()));)?
162+
$(warnings.push(ConfError::spanned(self.0, format!("deprecated field `{}`. {}", name.get_ref(), $dep), None, name.span()));)?
154163
let raw_value = map.next_value::<toml::Spanned<toml::Value>>()?;
155164
let value_span = raw_value.span();
156165
match <$ty>::deserialize(raw_value.into_inner()) {
157-
Err(e) => errors.push(ConfError::spanned(self.0, e.to_string().replace('\n', " ").trim(), value_span)),
166+
Err(e) => errors.push(ConfError::spanned(self.0, e.to_string().replace('\n', " ").trim(), None, value_span)),
158167
Ok(value) => match $name {
159-
Some(_) => errors.push(ConfError::spanned(self.0, format!("duplicate field `{}`", name.get_ref()), name.span())),
168+
Some(_) => {
169+
errors.push(ConfError::spanned(self.0, format!("duplicate field `{}`", name.get_ref()), None, name.span()));
170+
}
160171
None => {
161172
$name = Some(value);
162173
// $new_conf is the same as one of the defined `$name`s, so
@@ -165,7 +176,7 @@ macro_rules! define_Conf {
165176
Some(_) => errors.push(ConfError::spanned(self.0, concat!(
166177
"duplicate field `", stringify!($new_conf),
167178
"` (provided as `", stringify!($name), "`)"
168-
), name.span())),
179+
), None, name.span())),
169180
None => $new_conf = $name.clone(),
170181
})?
171182
},
@@ -673,10 +684,16 @@ impl Conf {
673684

674685
// all conf errors are non-fatal, we just use the default conf in case of error
675686
for error in errors {
676-
sess.dcx().span_err(
687+
let mut diag = sess.dcx().struct_span_err(
677688
error.span,
678689
format!("error reading Clippy's configuration file: {}", error.message),
679690
);
691+
692+
if let Some(sugg) = error.suggestion {
693+
diag.span_suggestion(error.span, sugg.message, sugg.suggestion, Applicability::MaybeIncorrect);
694+
}
695+
696+
diag.emit();
680697
}
681698

682699
for warning in warnings {
@@ -693,19 +710,31 @@ impl Conf {
693710
const SEPARATOR_WIDTH: usize = 4;
694711

695712
#[derive(Debug)]
696-
struct FieldError(String);
713+
struct FieldError {
714+
error: String,
715+
suggestion: Option<Suggestion>,
716+
}
717+
718+
#[derive(Debug)]
719+
struct Suggestion {
720+
message: &'static str,
721+
suggestion: &'static str,
722+
}
697723

698724
impl std::error::Error for FieldError {}
699725

700726
impl Display for FieldError {
701727
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
702-
f.pad(&self.0)
728+
f.pad(&self.error)
703729
}
704730
}
705731

706732
impl serde::de::Error for FieldError {
707733
fn custom<T: Display>(msg: T) -> Self {
708-
Self(msg.to_string())
734+
Self {
735+
error: msg.to_string(),
736+
suggestion: None,
737+
}
709738
}
710739

711740
fn unknown_field(field: &str, expected: &'static [&'static str]) -> Self {
@@ -727,7 +756,20 @@ impl serde::de::Error for FieldError {
727756
write!(msg, "{:SEPARATOR_WIDTH$}{field:column_width$}", " ").unwrap();
728757
}
729758
}
730-
Self(msg)
759+
760+
let suggestion = expected
761+
.iter()
762+
.filter_map(|expected| {
763+
let dist = edit_distance(field, expected, 4)?;
764+
Some((dist, expected))
765+
})
766+
.min_by_key(|&(dist, _)| dist)
767+
.map(|(_, suggestion)| Suggestion {
768+
message: "perhaps you meant",
769+
suggestion,
770+
});
771+
772+
Self { error: msg, suggestion }
731773
}
732774
}
733775

clippy_config/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ extern crate rustc_ast;
1111
extern crate rustc_data_structures;
1212
#[allow(unused_extern_crates)]
1313
extern crate rustc_driver;
14+
extern crate rustc_errors;
1415
extern crate rustc_session;
1516
extern crate rustc_span;
1617

tests/ui-toml/toml_unknown_key/clippy.toml

+3
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ foobar = 42
33
# so is this one
44
barfoo = 53
55

6+
# when using underscores instead of dashes, suggest the correct one
7+
allow_mixed_uninlined_format_args = true
8+
69
# that one is ignored
710
[third-party]
811
clippy-feature = "nightly"
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
//@no-rustfix
12
//@error-in-other-file: unknown field `foobar`, expected one of
23

34
fn main() {}

tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr

+78-1
Original file line numberDiff line numberDiff line change
@@ -152,5 +152,82 @@ error: error reading Clippy's configuration file: unknown field `barfoo`, expect
152152
LL | barfoo = 53
153153
| ^^^^^^
154154

155-
error: aborting due to 2 previous errors
155+
error: error reading Clippy's configuration file: unknown field `allow_mixed_uninlined_format_args`, expected one of
156+
absolute-paths-allowed-crates
157+
absolute-paths-max-segments
158+
accept-comment-above-attributes
159+
accept-comment-above-statement
160+
allow-dbg-in-tests
161+
allow-expect-in-tests
162+
allow-mixed-uninlined-format-args
163+
allow-one-hash-in-raw-strings
164+
allow-print-in-tests
165+
allow-private-module-inception
166+
allow-unwrap-in-tests
167+
allowed-dotfiles
168+
allowed-duplicate-crates
169+
allowed-idents-below-min-chars
170+
allowed-scripts
171+
arithmetic-side-effects-allowed
172+
arithmetic-side-effects-allowed-binary
173+
arithmetic-side-effects-allowed-unary
174+
array-size-threshold
175+
avoid-breaking-exported-api
176+
await-holding-invalid-types
177+
blacklisted-names
178+
cargo-ignore-publish
179+
check-private-items
180+
cognitive-complexity-threshold
181+
cyclomatic-complexity-threshold
182+
disallowed-macros
183+
disallowed-methods
184+
disallowed-names
185+
disallowed-types
186+
doc-valid-idents
187+
enable-raw-pointer-heuristic-for-send
188+
enforce-iter-loop-reborrow
189+
enforced-import-renames
190+
enum-variant-name-threshold
191+
enum-variant-size-threshold
192+
excessive-nesting-threshold
193+
future-size-threshold
194+
ignore-interior-mutability
195+
large-error-threshold
196+
literal-representation-threshold
197+
matches-for-let-else
198+
max-fn-params-bools
199+
max-include-file-size
200+
max-struct-bools
201+
max-suggested-slice-pattern-length
202+
max-trait-bounds
203+
min-ident-chars-threshold
204+
missing-docs-in-crate-items
205+
msrv
206+
pass-by-value-size-limit
207+
pub-underscore-fields-behavior
208+
semicolon-inside-block-ignore-singleline
209+
semicolon-outside-block-ignore-multiline
210+
single-char-binding-names-threshold
211+
stack-size-threshold
212+
standard-macro-braces
213+
struct-field-name-threshold
214+
suppress-restriction-lint-in-const
215+
third-party
216+
too-large-for-stack
217+
too-many-arguments-threshold
218+
too-many-lines-threshold
219+
trivial-copy-size-limit
220+
type-complexity-threshold
221+
unnecessary-box-size
222+
unreadable-literal-lint-fractions
223+
upper-case-acronyms-aggressive
224+
vec-box-size-threshold
225+
verbose-bit-mask-threshold
226+
warn-on-all-wildcard-imports
227+
--> $DIR/$DIR/clippy.toml:7:1
228+
|
229+
LL | allow_mixed_uninlined_format_args = true
230+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: perhaps you meant: `allow-mixed-uninlined-format-args`
231+
232+
error: aborting due to 3 previous errors
156233

0 commit comments

Comments
 (0)