33#![ allow( clippy:: module_name_repetitions) ]
44
55use rustc_session:: Session ;
6- use rustc_span:: { BytePos , Pos , SourceFile , Span , SyntaxContext } ;
6+ use rustc_span:: { BytePos , FileName , Pos , SourceFile , Span , SyntaxContext } ;
77use serde:: de:: { Deserializer , IgnoredAny , IntoDeserializer , MapAccess , Visitor } ;
88use serde:: Deserialize ;
9+ use std:: error:: Error ;
910use std:: fmt:: { Debug , Display , Formatter } ;
1011use std:: ops:: Range ;
11- use std:: path:: { Path , PathBuf } ;
12+ use std:: path:: PathBuf ;
1213use std:: str:: FromStr ;
1314use std:: { cmp, env, fmt, fs, io} ;
1415
@@ -99,38 +100,50 @@ impl From<io::Error> for TryConf {
99100#[ derive( Debug ) ]
100101pub struct ConfError {
101102 pub message : String ,
103+ pub file : Option < PathBuf > ,
102104 pub span : Option < Span > ,
103105}
104106
105107impl ConfError {
106108 fn from_toml ( file : & SourceFile , error : & toml:: de:: Error ) -> Self {
107109 if let Some ( span) = error. span ( ) {
108- Self :: spanned ( file, error. message ( ) , span)
109- } else {
110- Self {
111- message : error. message ( ) . to_string ( ) ,
112- span : None ,
113- }
110+ return Self :: spanned ( file, error. message ( ) , span) ;
111+ } else if let FileName :: Real ( filename) = & file. name
112+ && let Some ( filename) = filename. local_path ( )
113+ {
114+ return Self {
115+ message : error. message ( ) . to_string ( ) ,
116+ file : Some ( filename. to_owned ( ) ) ,
117+ span : None ,
118+ } ;
114119 }
120+
121+ unreachable ! ( ) ;
115122 }
116123
117124 fn spanned ( file : & SourceFile , message : impl Into < String > , span : Range < usize > ) -> Self {
118- Self {
119- message : message. into ( ) ,
120- span : Some ( Span :: new (
121- file. start_pos + BytePos :: from_usize ( span. start ) ,
122- file. start_pos + BytePos :: from_usize ( span. end ) ,
123- SyntaxContext :: root ( ) ,
124- None ,
125- ) ) ,
125+ if let FileName :: Real ( filename) = & file. name && let Some ( filename) = filename. local_path ( ) {
126+ return Self {
127+ message : message. into ( ) ,
128+ file : Some ( filename. to_owned ( ) ) ,
129+ span : Some ( Span :: new (
130+ file. start_pos + BytePos :: from_usize ( span. start ) ,
131+ file. start_pos + BytePos :: from_usize ( span. end ) ,
132+ SyntaxContext :: root ( ) ,
133+ None ,
134+ ) ) ,
135+ } ;
126136 }
137+
138+ unreachable ! ( ) ;
127139 }
128140}
129141
130142impl From < io:: Error > for ConfError {
131143 fn from ( value : io:: Error ) -> Self {
132144 Self {
133145 message : value. to_string ( ) ,
146+ file : None ,
134147 span : None ,
135148 }
136149 }
@@ -143,6 +156,7 @@ macro_rules! define_Conf {
143156 ( $name: ident: $ty: ty = $default: expr) ,
144157 ) * ) => {
145158 /// Clippy lint configuration
159+ #[ derive( Deserialize ) ]
146160 pub struct Conf {
147161 $( $( #[ doc = $doc] ) + pub $name: $ty, ) *
148162 }
@@ -157,15 +171,15 @@ macro_rules! define_Conf {
157171 }
158172 }
159173
174+ #[ allow( non_camel_case_types) ]
160175 #[ derive( Deserialize ) ]
161176 #[ serde( field_identifier, rename_all = "kebab-case" ) ]
162- #[ allow( non_camel_case_types) ]
163177 enum Field { $( $name, ) * third_party, }
164178
165- struct ConfVisitor <' a>( & ' a SourceFile ) ;
179+ struct ConfVisitor <' a>( & ' a SourceFile , & ' a mut TryConf ) ;
166180
167181 impl <' de> Visitor <' de> for ConfVisitor <' _> {
168- type Value = TryConf ;
182+ type Value = ( ) ;
169183
170184 fn expecting( & self , formatter: & mut fmt:: Formatter <' _>) -> fmt:: Result {
171185 formatter. write_str( "Conf" )
@@ -209,8 +223,14 @@ macro_rules! define_Conf {
209223 Ok ( Field :: third_party) => drop( map. next_value:: <IgnoredAny >( ) )
210224 }
211225 }
212- let conf = Conf { $( $name: $name. unwrap_or_else( defaults:: $name) , ) * } ;
213- Ok ( TryConf { conf, errors, warnings } )
226+ $(
227+ if let Some ( $name) = $name {
228+ self . 1 . conf. $name = $name;
229+ }
230+ ) *
231+ self . 1 . errors. extend( errors) ;
232+ self . 1 . warnings. extend( warnings) ;
233+ Ok ( ( ) )
214234 }
215235 }
216236
@@ -524,12 +544,17 @@ define_Conf! {
524544 ( allow_private_module_inception: bool = false ) ,
525545}
526546
527- /// Search for the configuration file.
547+ /// Search for any configuration files. The index corresponds to the priority; the higher the index,
548+ /// the lower the priority.
549+ ///
550+ /// Note: It's up to the caller to reverse the priority of configuration files, otherwise the last
551+ /// configuration file will have the highest priority.
528552///
529553/// # Errors
530554///
531- /// Returns any unexpected filesystem error encountered when searching for the config file
532- pub fn lookup_conf_file ( ) -> io:: Result < ( Option < PathBuf > , Vec < String > ) > {
555+ /// Returns any unexpected filesystem error encountered when searching for the config file or when
556+ /// running `cargo metadata`.
557+ pub fn lookup_conf_files ( ) -> Result < ( Vec < PathBuf > , Vec < String > ) , Box < dyn Error + Send + Sync > > {
533558 /// Possible filename to search for.
534559 const CONFIG_FILE_NAMES : [ & str ; 2 ] = [ ".clippy.toml" , "clippy.toml" ] ;
535560
@@ -540,60 +565,68 @@ pub fn lookup_conf_file() -> io::Result<(Option<PathBuf>, Vec<String>)> {
540565 . map_or_else ( || PathBuf :: from ( "." ) , PathBuf :: from)
541566 . canonicalize ( ) ?;
542567
543- let mut found_config : Option < PathBuf > = None ;
568+ let mut found_configs : Vec < PathBuf > = vec ! [ ] ;
544569 let mut warnings = vec ! [ ] ;
545570
571+ // TODO: This will continue searching even outside of the workspace, and even add an erroneous
572+ // configuration file to the list! Is it worth fixing this? `workspace_root` on `cargo metadata`
573+ // doesn't work for clippy_lints' clippy.toml in cwd. We likely can't just use cwd as what if
574+ // it's called in src?
546575 loop {
547576 for config_file_name in & CONFIG_FILE_NAMES {
548577 if let Ok ( config_file) = current. join ( config_file_name) . canonicalize ( ) {
549578 match fs:: metadata ( & config_file) {
550579 Err ( e) if e. kind ( ) == io:: ErrorKind :: NotFound => { } ,
551- Err ( e) => return Err ( e) ,
580+ Err ( e) => return Err ( e. into ( ) ) ,
552581 Ok ( md) if md. is_dir ( ) => { } ,
553582 Ok ( _) => {
554- // warn if we happen to find two config files #8323
555- if let Some ( ref found_config) = found_config {
583+ // Warn if we happen to find two config files #8323
584+ if let [ .., last_config] = & * found_configs
585+ && let Some ( last_config_dir) = last_config. parent ( )
586+ && let Some ( config_file_dir) = config_file. parent ( )
587+ && last_config_dir == config_file_dir
588+ {
556589 warnings. push ( format ! (
557590 "using config file `{}`, `{}` will be ignored" ,
558- found_config . display( ) ,
591+ last_config . display( ) ,
559592 config_file. display( )
560593 ) ) ;
561594 } else {
562- found_config = Some ( config_file) ;
595+ found_configs . push ( config_file) ;
563596 }
564597 } ,
565598 }
566599 }
567600 }
568601
569- if found_config. is_some ( ) {
570- return Ok ( ( found_config, warnings) ) ;
571- }
572-
573- // If the current directory has no parent, we're done searching.
574602 if !current. pop ( ) {
575- return Ok ( ( None , warnings ) ) ;
603+ break ;
576604 }
577605 }
606+
607+ Ok ( ( found_configs, warnings) )
578608}
579609
580610/// Read the `toml` configuration file.
581611///
582612/// In case of error, the function tries to continue as much as possible.
583- pub fn read ( sess : & Session , path : & Path ) -> TryConf {
584- let file = match sess. source_map ( ) . load_file ( path) {
585- Err ( e) => return e. into ( ) ,
586- Ok ( file) => file,
587- } ;
588- match toml:: de:: Deserializer :: new ( file. src . as_ref ( ) . unwrap ( ) ) . deserialize_map ( ConfVisitor ( & file) ) {
589- Ok ( mut conf) => {
590- extend_vec_if_indicator_present ( & mut conf. conf . doc_valid_idents , DEFAULT_DOC_VALID_IDENTS ) ;
591- extend_vec_if_indicator_present ( & mut conf. conf . disallowed_names , DEFAULT_DISALLOWED_NAMES ) ;
592-
593- conf
594- } ,
595- Err ( e) => TryConf :: from_toml_error ( & file, & e) ,
613+ pub fn read ( sess : & Session , paths : & [ PathBuf ] ) -> TryConf {
614+ let mut conf = TryConf :: default ( ) ;
615+ for file in paths. iter ( ) . rev ( ) {
616+ let file = match sess. source_map ( ) . load_file ( file) {
617+ Err ( e) => return e. into ( ) ,
618+ Ok ( file) => file,
619+ } ;
620+ match toml:: de:: Deserializer :: new ( file. src . as_ref ( ) . unwrap ( ) ) . deserialize_map ( ConfVisitor ( & file, & mut conf) ) {
621+ Ok ( _) => {
622+ extend_vec_if_indicator_present ( & mut conf. conf . doc_valid_idents , DEFAULT_DOC_VALID_IDENTS ) ;
623+ extend_vec_if_indicator_present ( & mut conf. conf . disallowed_names , DEFAULT_DISALLOWED_NAMES ) ;
624+ } ,
625+ Err ( e) => return TryConf :: from_toml_error ( & file, & e) ,
626+ }
596627 }
628+
629+ conf
597630}
598631
599632fn extend_vec_if_indicator_present ( vec : & mut Vec < String > , default : & [ & str ] ) {
0 commit comments