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
@@ -100,38 +101,50 @@ impl From<io::Error> for TryConf {
100101#[ derive( Debug ) ]
101102pub struct ConfError {
102103 pub message : String ,
104+ pub file : Option < PathBuf > ,
103105 pub span : Option < Span > ,
104106}
105107
106108impl ConfError {
107109 fn from_toml ( file : & SourceFile , error : & toml:: de:: Error ) -> Self {
108110 if let Some ( span) = error. span ( ) {
109- Self :: spanned ( file, error. message ( ) , span)
110- } else {
111- Self {
112- message : error. message ( ) . to_string ( ) ,
113- span : None ,
114- }
111+ return Self :: spanned ( file, error. message ( ) , span) ;
112+ } else if let FileName :: Real ( filename) = & file. name
113+ && let Some ( filename) = filename. local_path ( )
114+ {
115+ return Self {
116+ message : error. message ( ) . to_string ( ) ,
117+ file : Some ( filename. to_owned ( ) ) ,
118+ span : None ,
119+ } ;
115120 }
121+
122+ unreachable ! ( ) ;
116123 }
117124
118125 fn spanned ( file : & SourceFile , message : impl Into < String > , span : Range < usize > ) -> Self {
119- Self {
120- message : message. into ( ) ,
121- span : Some ( Span :: new (
122- file. start_pos + BytePos :: from_usize ( span. start ) ,
123- file. start_pos + BytePos :: from_usize ( span. end ) ,
124- SyntaxContext :: root ( ) ,
125- None ,
126- ) ) ,
126+ if let FileName :: Real ( filename) = & file. name && let Some ( filename) = filename. local_path ( ) {
127+ return Self {
128+ message : message. into ( ) ,
129+ file : Some ( filename. to_owned ( ) ) ,
130+ span : Some ( Span :: new (
131+ file. start_pos + BytePos :: from_usize ( span. start ) ,
132+ file. start_pos + BytePos :: from_usize ( span. end ) ,
133+ SyntaxContext :: root ( ) ,
134+ None ,
135+ ) ) ,
136+ } ;
127137 }
138+
139+ unreachable ! ( ) ;
128140 }
129141}
130142
131143impl From < io:: Error > for ConfError {
132144 fn from ( value : io:: Error ) -> Self {
133145 Self {
134146 message : value. to_string ( ) ,
147+ file : None ,
135148 span : None ,
136149 }
137150 }
@@ -144,6 +157,7 @@ macro_rules! define_Conf {
144157 ( $name: ident: $ty: ty = $default: expr) ,
145158 ) * ) => {
146159 /// Clippy lint configuration
160+ #[ derive( Deserialize ) ]
147161 pub struct Conf {
148162 $( $( #[ doc = $doc] ) + pub $name: $ty, ) *
149163 }
@@ -158,15 +172,15 @@ macro_rules! define_Conf {
158172 }
159173 }
160174
175+ #[ allow( non_camel_case_types) ]
161176 #[ derive( Deserialize ) ]
162177 #[ serde( field_identifier, rename_all = "kebab-case" ) ]
163- #[ allow( non_camel_case_types) ]
164178 enum Field { $( $name, ) * third_party, }
165179
166- struct ConfVisitor <' a>( & ' a SourceFile ) ;
180+ struct ConfVisitor <' a>( & ' a SourceFile , & ' a mut TryConf ) ;
167181
168182 impl <' de> Visitor <' de> for ConfVisitor <' _> {
169- type Value = TryConf ;
183+ type Value = ( ) ;
170184
171185 fn expecting( & self , formatter: & mut fmt:: Formatter <' _>) -> fmt:: Result {
172186 formatter. write_str( "Conf" )
@@ -210,8 +224,14 @@ macro_rules! define_Conf {
210224 Ok ( Field :: third_party) => drop( map. next_value:: <IgnoredAny >( ) )
211225 }
212226 }
213- let conf = Conf { $( $name: $name. unwrap_or_else( defaults:: $name) , ) * } ;
214- Ok ( TryConf { conf, errors, warnings } )
227+ $(
228+ if let Some ( $name) = $name {
229+ self . 1 . conf. $name = $name;
230+ }
231+ ) *
232+ self . 1 . errors. extend( errors) ;
233+ self . 1 . warnings. extend( warnings) ;
234+ Ok ( ( ) )
215235 }
216236 }
217237
@@ -536,12 +556,17 @@ define_Conf! {
536556 ( min_ident_chars_threshold: u64 = 1 ) ,
537557}
538558
539- /// Search for the configuration file.
559+ /// Search for any configuration files. The index corresponds to the priority; the higher the index,
560+ /// the lower the priority.
561+ ///
562+ /// Note: It's up to the caller to reverse the priority of configuration files, otherwise the last
563+ /// configuration file will have the highest priority.
540564///
541565/// # Errors
542566///
543- /// Returns any unexpected filesystem error encountered when searching for the config file
544- pub fn lookup_conf_file ( ) -> io:: Result < ( Option < PathBuf > , Vec < String > ) > {
567+ /// Returns any unexpected filesystem error encountered when searching for the config file or when
568+ /// running `cargo metadata`.
569+ pub fn lookup_conf_files ( ) -> Result < ( Vec < PathBuf > , Vec < String > ) , Box < dyn Error + Send + Sync > > {
545570 /// Possible filename to search for.
546571 const CONFIG_FILE_NAMES : [ & str ; 2 ] = [ ".clippy.toml" , "clippy.toml" ] ;
547572
@@ -552,66 +577,74 @@ pub fn lookup_conf_file() -> io::Result<(Option<PathBuf>, Vec<String>)> {
552577 . map_or_else ( || PathBuf :: from ( "." ) , PathBuf :: from)
553578 . canonicalize ( ) ?;
554579
555- let mut found_config : Option < PathBuf > = None ;
580+ let mut found_configs : Vec < PathBuf > = vec ! [ ] ;
556581 let mut warnings = vec ! [ ] ;
557582
583+ // TODO: This will continue searching even outside of the workspace, and even add an erroneous
584+ // configuration file to the list! Is it worth fixing this? `workspace_root` on `cargo metadata`
585+ // doesn't work for clippy_lints' clippy.toml in cwd. We likely can't just use cwd as what if
586+ // it's called in src?
558587 loop {
559588 for config_file_name in & CONFIG_FILE_NAMES {
560589 if let Ok ( config_file) = current. join ( config_file_name) . canonicalize ( ) {
561590 match fs:: metadata ( & config_file) {
562591 Err ( e) if e. kind ( ) == io:: ErrorKind :: NotFound => { } ,
563- Err ( e) => return Err ( e) ,
592+ Err ( e) => return Err ( e. into ( ) ) ,
564593 Ok ( md) if md. is_dir ( ) => { } ,
565594 Ok ( _) => {
566- // warn if we happen to find two config files #8323
567- if let Some ( ref found_config) = found_config {
595+ // Warn if we happen to find two config files #8323
596+ if let [ .., last_config] = & * found_configs
597+ && let Some ( last_config_dir) = last_config. parent ( )
598+ && let Some ( config_file_dir) = config_file. parent ( )
599+ && last_config_dir == config_file_dir
600+ {
568601 warnings. push ( format ! (
569602 "using config file `{}`, `{}` will be ignored" ,
570- found_config . display( ) ,
603+ last_config . display( ) ,
571604 config_file. display( )
572605 ) ) ;
573606 } else {
574- found_config = Some ( config_file) ;
607+ found_configs . push ( config_file) ;
575608 }
576609 } ,
577610 }
578611 }
579612 }
580613
581- if found_config. is_some ( ) {
582- return Ok ( ( found_config, warnings) ) ;
583- }
584-
585- // If the current directory has no parent, we're done searching.
586614 if !current. pop ( ) {
587- return Ok ( ( None , warnings ) ) ;
615+ break ;
588616 }
589617 }
618+
619+ Ok ( ( found_configs, warnings) )
590620}
591621
592622/// Read the `toml` configuration file.
593623///
594624/// In case of error, the function tries to continue as much as possible.
595- pub fn read ( sess : & Session , path : & Path ) -> TryConf {
596- let file = match sess. source_map ( ) . load_file ( path) {
597- Err ( e) => return e. into ( ) ,
598- Ok ( file) => file,
599- } ;
600- match toml:: de:: Deserializer :: new ( file. src . as_ref ( ) . unwrap ( ) ) . deserialize_map ( ConfVisitor ( & file) ) {
601- Ok ( mut conf) => {
602- extend_vec_if_indicator_present ( & mut conf. conf . doc_valid_idents , DEFAULT_DOC_VALID_IDENTS ) ;
603- extend_vec_if_indicator_present ( & mut conf. conf . disallowed_names , DEFAULT_DISALLOWED_NAMES ) ;
604- // TODO: THIS SHOULD BE TESTED, this comment will be gone soon
605- if conf. conf . allowed_idents_below_min_chars . contains ( & ".." . to_owned ( ) ) {
606- conf. conf
607- . allowed_idents_below_min_chars
608- . extend ( DEFAULT_ALLOWED_IDENTS_BELOW_MIN_CHARS . iter ( ) . map ( ToString :: to_string) ) ;
609- }
610-
611- conf
612- } ,
613- Err ( e) => TryConf :: from_toml_error ( & file, & e) ,
625+ pub fn read ( sess : & Session , paths : & [ PathBuf ] ) -> TryConf {
626+ let mut conf = TryConf :: default ( ) ;
627+ for file in paths. iter ( ) . rev ( ) {
628+ let file = match sess. source_map ( ) . load_file ( file) {
629+ Err ( e) => return e. into ( ) ,
630+ Ok ( file) => file,
631+ } ;
632+ match toml:: de:: Deserializer :: new ( file. src . as_ref ( ) . unwrap ( ) ) . deserialize_map ( ConfVisitor ( & file, & mut conf) ) {
633+ Ok ( _) => {
634+ extend_vec_if_indicator_present ( & mut conf. conf . doc_valid_idents , DEFAULT_DOC_VALID_IDENTS ) ;
635+ extend_vec_if_indicator_present ( & mut conf. conf . disallowed_names , DEFAULT_DISALLOWED_NAMES ) ;
636+ // TODO: THIS SHOULD BE TESTED, this comment will be gone soon
637+ if conf. conf . allowed_idents_below_min_chars . contains ( & ".." . to_owned ( ) ) {
638+ conf. conf
639+ . allowed_idents_below_min_chars
640+ . extend ( DEFAULT_ALLOWED_IDENTS_BELOW_MIN_CHARS . iter ( ) . map ( ToString :: to_string) ) ;
641+ }
642+ } ,
643+ Err ( e) => return TryConf :: from_toml_error ( & file, & e) ,
644+ }
614645 }
646+
647+ conf
615648}
616649
617650fn extend_vec_if_indicator_present ( vec : & mut Vec < String > , default : & [ & str ] ) {
0 commit comments