@@ -2,7 +2,9 @@ use crate::msrvs::Msrv;
22use crate :: types:: { DisallowedPath , MacroMatcher , MatchLintBehaviour , PubUnderscoreFieldsBehaviour , Rename } ;
33use crate :: ClippyConfiguration ;
44use rustc_data_structures:: fx:: FxHashSet ;
5+ use rustc_errors:: Applicability ;
56use rustc_session:: Session ;
7+ use rustc_span:: edit_distance:: edit_distance;
68use rustc_span:: { BytePos , Pos , SourceFile , Span , SyntaxContext } ;
79use serde:: de:: { IgnoredAny , IntoDeserializer , MapAccess , Visitor } ;
810use serde:: { Deserialize , Deserializer , Serialize } ;
@@ -26,7 +28,7 @@ const DEFAULT_DOC_VALID_IDENTS: &[&str] = &[
2628 "NaN" , "NaNs" ,
2729 "OAuth" , "GraphQL" ,
2830 "OCaml" ,
29- "OpenGL" , "OpenMP" , "OpenSSH" , "OpenSSL" , "OpenStreetMap" , "OpenDNS " ,
31+ "OpenDNS" , " OpenGL", "OpenMP" , "OpenSSH" , "OpenSSL" , "OpenStreetMap" , "OpenTelemetry " ,
3032 "WebGL" , "WebGL2" , "WebGPU" ,
3133 "TensorFlow" ,
3234 "TrueType" ,
@@ -59,18 +61,25 @@ impl TryConf {
5961#[ derive( Debug ) ]
6062struct ConfError {
6163 message : String ,
64+ suggestion : Option < Suggestion > ,
6265 span : Span ,
6366}
6467
6568impl 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 } ,
@@ -523,7 +534,11 @@ define_Conf! {
523534 ///
524535 /// Additional dotfiles (files or directories starting with a dot) to allow
525536 ( allowed_dotfiles: FxHashSet <String > = FxHashSet :: default ( ) ) ,
526- /// Lint: EXPLICIT_ITER_LOOP
537+ /// Lint: MULTIPLE_CRATE_VERSIONS.
538+ ///
539+ /// A list of crate names to allow duplicates of
540+ ( allowed_duplicate_crates: FxHashSet <String > = FxHashSet :: default ( ) ) ,
541+ /// Lint: EXPLICIT_ITER_LOOP.
527542 ///
528543 /// Whether to recommend using implicit into iter for reborrowed values.
529544 ///
@@ -543,15 +558,15 @@ define_Conf! {
543558 /// for _ in &mut *rmvec {}
544559 /// ```
545560 ( enforce_iter_loop_reborrow: bool = false ) ,
546- /// Lint: MISSING_SAFETY_DOC, UNNECESSARY_SAFETY_DOC, MISSING_PANICS_DOC, MISSING_ERRORS_DOC
561+ /// Lint: MISSING_SAFETY_DOC, UNNECESSARY_SAFETY_DOC, MISSING_PANICS_DOC, MISSING_ERRORS_DOC.
547562 ///
548563 /// Whether to also run the listed lints on private items.
549564 ( check_private_items: bool = false ) ,
550- /// Lint: PUB_UNDERSCORE_FIELDS
565+ /// Lint: PUB_UNDERSCORE_FIELDS.
551566 ///
552567 /// Lint "public" fields in a struct that are prefixed with an underscore based on their
553568 /// exported visibility, or whether they are marked as "pub".
554- ( pub_underscore_fields_behavior: PubUnderscoreFieldsBehaviour = PubUnderscoreFieldsBehaviour :: PublicallyExported ) ,
569+ ( pub_underscore_fields_behavior: PubUnderscoreFieldsBehaviour = PubUnderscoreFieldsBehaviour :: PubliclyExported ) ,
555570}
556571
557572/// Search for the configuration file.
@@ -669,10 +684,16 @@ impl Conf {
669684
670685 // all conf errors are non-fatal, we just use the default conf in case of error
671686 for error in errors {
672- sess. dcx ( ) . span_err (
687+ let mut diag = sess. dcx ( ) . struct_span_err (
673688 error. span ,
674689 format ! ( "error reading Clippy's configuration file: {}" , error. message) ,
675690 ) ;
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 ( ) ;
676697 }
677698
678699 for warning in warnings {
@@ -689,19 +710,31 @@ impl Conf {
689710const SEPARATOR_WIDTH : usize = 4 ;
690711
691712#[ derive( Debug ) ]
692- 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+ }
693723
694724impl std:: error:: Error for FieldError { }
695725
696726impl Display for FieldError {
697727 fn fmt ( & self , f : & mut Formatter < ' _ > ) -> fmt:: Result {
698- f. pad ( & self . 0 )
728+ f. pad ( & self . error )
699729 }
700730}
701731
702732impl serde:: de:: Error for FieldError {
703733 fn custom < T : Display > ( msg : T ) -> Self {
704- Self ( msg. to_string ( ) )
734+ Self {
735+ error : msg. to_string ( ) ,
736+ suggestion : None ,
737+ }
705738 }
706739
707740 fn unknown_field ( field : & str , expected : & ' static [ & ' static str ] ) -> Self {
@@ -723,7 +756,20 @@ impl serde::de::Error for FieldError {
723756 write ! ( msg, "{:SEPARATOR_WIDTH$}{field:column_width$}" , " " ) . unwrap ( ) ;
724757 }
725758 }
726- 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 }
727773 }
728774}
729775
0 commit comments