22
33#![ allow( clippy:: module_name_repetitions) ]
44
5+ use rustc_session:: Session ;
6+ use rustc_span:: { BytePos , Pos , SourceFile , Span , SyntaxContext } ;
57use serde:: de:: { Deserializer , IgnoredAny , IntoDeserializer , MapAccess , Visitor } ;
68use serde:: Deserialize ;
7- use std:: error:: Error ;
9+ use std:: fmt:: { Debug , Display , Formatter } ;
10+ use std:: ops:: Range ;
811use std:: path:: { Path , PathBuf } ;
912use std:: str:: FromStr ;
10- use std:: { cmp, env, fmt, fs, io, iter } ;
13+ use std:: { cmp, env, fmt, fs, io} ;
1114
1215#[ rustfmt:: skip]
1316const DEFAULT_DOC_VALID_IDENTS : & [ & str ] = & [
@@ -67,33 +70,61 @@ impl DisallowedPath {
6770#[ derive( Default ) ]
6871pub struct TryConf {
6972 pub conf : Conf ,
70- pub errors : Vec < Box < dyn Error > > ,
71- pub warnings : Vec < Box < dyn Error > > ,
73+ pub errors : Vec < ConfError > ,
74+ pub warnings : Vec < ConfError > ,
7275}
7376
7477impl TryConf {
75- fn from_error ( error : impl Error + ' static ) -> Self {
78+ fn from_toml_error ( file : & SourceFile , error : & toml:: de:: Error ) -> Self {
79+ ConfError :: from_toml ( file, error) . into ( )
80+ }
81+ }
82+
83+ impl From < ConfError > for TryConf {
84+ fn from ( value : ConfError ) -> Self {
7685 Self {
7786 conf : Conf :: default ( ) ,
78- errors : vec ! [ Box :: new ( error ) ] ,
87+ errors : vec ! [ value ] ,
7988 warnings : vec ! [ ] ,
8089 }
8190 }
8291}
8392
93+ impl From < io:: Error > for TryConf {
94+ fn from ( value : io:: Error ) -> Self {
95+ ConfError :: from ( value) . into ( )
96+ }
97+ }
98+
8499#[ derive( Debug ) ]
85- struct ConfError ( String ) ;
100+ pub struct ConfError ( pub String , pub Option < Span > ) ;
86101
87- impl fmt:: Display for ConfError {
88- fn fmt ( & self , f : & mut fmt:: Formatter < ' _ > ) -> fmt:: Result {
89- <String as fmt:: Display >:: fmt ( & self . 0 , f)
102+ impl ConfError {
103+ fn from_toml ( file : & SourceFile , error : & toml:: de:: Error ) -> Self {
104+ if let Some ( span) = error. span ( ) {
105+ Self :: spanned ( file, error. message ( ) , span)
106+ } else {
107+ Self ( error. message ( ) . to_string ( ) , None )
108+ }
90109 }
91- }
92110
93- impl Error for ConfError { }
111+ fn spanned ( file : & SourceFile , message : impl Into < String > , span : Range < usize > ) -> Self {
112+ Self (
113+ message. into ( ) ,
114+ Some ( Span :: new (
115+ file. start_pos + BytePos :: from_usize ( span. start ) ,
116+ file. start_pos + BytePos :: from_usize ( span. end ) ,
117+ SyntaxContext :: root ( ) ,
118+ None ,
119+ ) ) ,
120+ )
121+ }
122+ }
94123
95- fn conf_error ( s : impl Into < String > ) -> Box < dyn Error > {
96- Box :: new ( ConfError ( s. into ( ) ) )
124+ impl From < io:: Error > for ConfError {
125+ fn from ( value : io:: Error ) -> Self {
126+ Self ( value. to_string ( ) , None )
127+ }
97128}
98129
99130macro_rules! define_Conf {
@@ -117,20 +148,14 @@ macro_rules! define_Conf {
117148 }
118149 }
119150
120- impl <' de> Deserialize <' de> for TryConf {
121- fn deserialize<D >( deserializer: D ) -> Result <Self , D :: Error > where D : Deserializer <' de> {
122- deserializer. deserialize_map( ConfVisitor )
123- }
124- }
125-
126151 #[ derive( Deserialize ) ]
127152 #[ serde( field_identifier, rename_all = "kebab-case" ) ]
128153 #[ allow( non_camel_case_types) ]
129154 enum Field { $( $name, ) * third_party, }
130155
131- struct ConfVisitor ;
156+ struct ConfVisitor < ' a> ( & ' a SourceFile ) ;
132157
133- impl <' de> Visitor <' de> for ConfVisitor {
158+ impl <' de> Visitor <' de> for ConfVisitor < ' _> {
134159 type Value = TryConf ;
135160
136161 fn expecting( & self , formatter: & mut fmt:: Formatter <' _>) -> fmt:: Result {
@@ -141,32 +166,38 @@ macro_rules! define_Conf {
141166 let mut errors = Vec :: new( ) ;
142167 let mut warnings = Vec :: new( ) ;
143168 $( let mut $name = None ; ) *
144- // could get `Field` here directly, but get `str` first for diagnostics
145- while let Some ( name) = map. next_key:: <& str >( ) ? {
146- match Field :: deserialize( name. into_deserializer( ) ) ? {
147- $( Field :: $name => {
148- $( warnings. push( conf_error( format!( "deprecated field `{}`. {}" , name, $dep) ) ) ; ) ?
149- match map. next_value( ) {
150- Err ( e) => errors. push( conf_error( e. to_string( ) ) ) ,
169+ // could get `Field` here directly, but get `String` first for diagnostics
170+ while let Some ( name) = map. next_key:: <toml:: Spanned <String >>( ) ? {
171+ match Field :: deserialize( name. get_ref( ) . as_str( ) . into_deserializer( ) ) {
172+ Err ( e) => {
173+ let e: FieldError = e;
174+ errors. push( ConfError :: spanned( self . 0 , e. 0 , name. span( ) ) ) ;
175+ }
176+ $( Ok ( Field :: $name) => {
177+ $( warnings. push( ConfError :: spanned( self . 0 , format!( "deprecated field `{}`. {}" , name. get_ref( ) , $dep) , name. span( ) ) ) ; ) ?
178+ let raw_value = map. next_value:: <toml:: Spanned <toml:: Value >>( ) ?;
179+ let value_span = raw_value. span( ) ;
180+ match <$ty>:: deserialize( raw_value. into_inner( ) ) {
181+ Err ( e) => errors. push( ConfError :: spanned( self . 0 , e. to_string( ) . replace( '\n' , " " ) . trim( ) , value_span) ) ,
151182 Ok ( value) => match $name {
152- Some ( _) => errors. push( conf_error ( format!( "duplicate field `{}`" , name) ) ) ,
183+ Some ( _) => errors. push( ConfError :: spanned ( self . 0 , format!( "duplicate field `{}`" , name. get_ref ( ) ) , name . span ( ) ) ) ,
153184 None => {
154185 $name = Some ( value) ;
155186 // $new_conf is the same as one of the defined `$name`s, so
156187 // this variable is defined in line 2 of this function.
157188 $( match $new_conf {
158- Some ( _) => errors. push( conf_error ( concat!(
189+ Some ( _) => errors. push( ConfError :: spanned ( self . 0 , concat!(
159190 "duplicate field `" , stringify!( $new_conf) ,
160191 "` (provided as `" , stringify!( $name) , "`)"
161- ) ) ) ,
192+ ) , name . span ( ) ) ) ,
162193 None => $new_conf = $name. clone( ) ,
163194 } ) ?
164195 } ,
165196 }
166197 }
167198 } ) *
168- // white-listed; ignore
169- Field :: third_party => drop( map. next_value:: <IgnoredAny >( ) )
199+ // ignore contents of the third_party key
200+ Ok ( Field :: third_party) => drop( map. next_value:: <IgnoredAny >( ) )
170201 }
171202 }
172203 let conf = Conf { $( $name: $name. unwrap_or_else( defaults:: $name) , ) * } ;
@@ -521,19 +552,19 @@ pub fn lookup_conf_file() -> io::Result<(Option<PathBuf>, Vec<String>)> {
521552/// Read the `toml` configuration file.
522553///
523554/// In case of error, the function tries to continue as much as possible.
524- pub fn read ( path : & Path ) -> TryConf {
525- let content = match fs :: read_to_string ( path) {
526- Err ( e) => return TryConf :: from_error ( e ) ,
527- Ok ( content ) => content ,
555+ pub fn read ( sess : & Session , path : & Path ) -> TryConf {
556+ let file = match sess . source_map ( ) . load_file ( path) {
557+ Err ( e) => return e . into ( ) ,
558+ Ok ( file ) => file ,
528559 } ;
529- match toml:: from_str :: < TryConf > ( & content ) {
560+ match toml:: de :: Deserializer :: new ( file . src . as_ref ( ) . unwrap ( ) ) . deserialize_map ( ConfVisitor ( & file ) ) {
530561 Ok ( mut conf) => {
531562 extend_vec_if_indicator_present ( & mut conf. conf . doc_valid_idents , DEFAULT_DOC_VALID_IDENTS ) ;
532563 extend_vec_if_indicator_present ( & mut conf. conf . disallowed_names , DEFAULT_DISALLOWED_NAMES ) ;
533564
534565 conf
535566 } ,
536- Err ( e) => TryConf :: from_error ( e) ,
567+ Err ( e) => TryConf :: from_toml_error ( & file , & e) ,
537568 }
538569}
539570
@@ -545,65 +576,42 @@ fn extend_vec_if_indicator_present(vec: &mut Vec<String>, default: &[&str]) {
545576
546577const SEPARATOR_WIDTH : usize = 4 ;
547578
548- // Check whether the error is "unknown field" and, if so, list the available fields sorted and at
549- // least one per line, more if `CLIPPY_TERMINAL_WIDTH` is set and allows it.
550- pub fn format_error ( error : Box < dyn Error > ) -> String {
551- let s = error. to_string ( ) ;
552-
553- if_chain ! {
554- if error. downcast:: <toml:: de:: Error >( ) . is_ok( ) ;
555- if let Some ( ( prefix, mut fields, suffix) ) = parse_unknown_field_message( & s) ;
556- then {
557- use fmt:: Write ;
558-
559- fields. sort_unstable( ) ;
560-
561- let ( rows, column_widths) = calculate_dimensions( & fields) ;
562-
563- let mut msg = String :: from( prefix) ;
564- for row in 0 ..rows {
565- writeln!( msg) . unwrap( ) ;
566- for ( column, column_width) in column_widths. iter( ) . copied( ) . enumerate( ) {
567- let index = column * rows + row;
568- let field = fields. get( index) . copied( ) . unwrap_or_default( ) ;
569- write!(
570- msg,
571- "{:SEPARATOR_WIDTH$}{field:column_width$}" ,
572- " "
573- )
574- . unwrap( ) ;
575- }
576- }
577- write!( msg, "\n {suffix}" ) . unwrap( ) ;
578- msg
579- } else {
580- s
581- }
579+ #[ derive( Debug ) ]
580+ struct FieldError ( String ) ;
581+
582+ impl std:: error:: Error for FieldError { }
583+
584+ impl Display for FieldError {
585+ fn fmt ( & self , f : & mut Formatter < ' _ > ) -> fmt:: Result {
586+ f. pad ( & self . 0 )
582587 }
583588}
584589
585- // `parse_unknown_field_message` will become unnecessary if
586- // https://github.com/alexcrichton/toml-rs/pull/364 is merged.
587- fn parse_unknown_field_message ( s : & str ) -> Option < ( & str , Vec < & str > , & str ) > {
588- // An "unknown field" message has the following form:
589- // unknown field `UNKNOWN`, expected one of `FIELD0`, `FIELD1`, ..., `FIELDN` at line X column Y
590- // ^^ ^^^^ ^^
591- if_chain ! {
592- if s. starts_with( "unknown field" ) ;
593- let slices = s. split( "`, `" ) . collect:: <Vec <_>>( ) ;
594- let n = slices. len( ) ;
595- if n >= 2 ;
596- if let Some ( ( prefix, first_field) ) = slices[ 0 ] . rsplit_once( " `" ) ;
597- if let Some ( ( last_field, suffix) ) = slices[ n - 1 ] . split_once( "` " ) ;
598- then {
599- let fields = iter:: once( first_field)
600- . chain( slices[ 1 ..n - 1 ] . iter( ) . copied( ) )
601- . chain( iter:: once( last_field) )
602- . collect:: <Vec <_>>( ) ;
603- Some ( ( prefix, fields, suffix) )
604- } else {
605- None
590+ impl serde:: de:: Error for FieldError {
591+ fn custom < T : Display > ( msg : T ) -> Self {
592+ Self ( msg. to_string ( ) )
593+ }
594+
595+ fn unknown_field ( field : & str , expected : & ' static [ & ' static str ] ) -> Self {
596+ // List the available fields sorted and at least one per line, more if `CLIPPY_TERMINAL_WIDTH` is
597+ // set and allows it.
598+ use fmt:: Write ;
599+
600+ let mut expected = expected. to_vec ( ) ;
601+ expected. sort_unstable ( ) ;
602+
603+ let ( rows, column_widths) = calculate_dimensions ( & expected) ;
604+
605+ let mut msg = format ! ( "unknown field `{field}`, expected one of" ) ;
606+ for row in 0 ..rows {
607+ writeln ! ( msg) . unwrap ( ) ;
608+ for ( column, column_width) in column_widths. iter ( ) . copied ( ) . enumerate ( ) {
609+ let index = column * rows + row;
610+ let field = expected. get ( index) . copied ( ) . unwrap_or_default ( ) ;
611+ write ! ( msg, "{:SEPARATOR_WIDTH$}{field:column_width$}" , " " ) . unwrap ( ) ;
612+ }
606613 }
614+ Self ( msg)
607615 }
608616}
609617
0 commit comments