3
3
#![ allow( clippy:: module_name_repetitions) ]
4
4
5
5
use rustc_session:: Session ;
6
- use rustc_span:: { BytePos , Pos , SourceFile , Span , SyntaxContext } ;
6
+ use rustc_span:: { BytePos , FileName , Pos , SourceFile , Span , SyntaxContext } ;
7
7
use serde:: de:: { Deserializer , IgnoredAny , IntoDeserializer , MapAccess , Visitor } ;
8
8
use serde:: Deserialize ;
9
+ use std:: error:: Error ;
9
10
use std:: fmt:: { Debug , Display , Formatter } ;
10
11
use std:: ops:: Range ;
11
- use std:: path:: { Path , PathBuf } ;
12
+ use std:: path:: PathBuf ;
12
13
use std:: str:: FromStr ;
13
14
use std:: { cmp, env, fmt, fs, io} ;
14
15
@@ -99,38 +100,50 @@ impl From<io::Error> for TryConf {
99
100
#[ derive( Debug ) ]
100
101
pub struct ConfError {
101
102
pub message : String ,
103
+ pub file : Option < PathBuf > ,
102
104
pub span : Option < Span > ,
103
105
}
104
106
105
107
impl ConfError {
106
108
fn from_toml ( file : & SourceFile , error : & toml:: de:: Error ) -> Self {
107
109
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
+ } ;
114
119
}
120
+
121
+ unreachable ! ( ) ;
115
122
}
116
123
117
124
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
+ } ;
126
136
}
137
+
138
+ unreachable ! ( ) ;
127
139
}
128
140
}
129
141
130
142
impl From < io:: Error > for ConfError {
131
143
fn from ( value : io:: Error ) -> Self {
132
144
Self {
133
145
message : value. to_string ( ) ,
146
+ file : None ,
134
147
span : None ,
135
148
}
136
149
}
@@ -143,6 +156,7 @@ macro_rules! define_Conf {
143
156
( $name: ident: $ty: ty = $default: expr) ,
144
157
) * ) => {
145
158
/// Clippy lint configuration
159
+ #[ derive( Deserialize ) ]
146
160
pub struct Conf {
147
161
$( $( #[ doc = $doc] ) + pub $name: $ty, ) *
148
162
}
@@ -157,15 +171,15 @@ macro_rules! define_Conf {
157
171
}
158
172
}
159
173
174
+ #[ allow( non_camel_case_types) ]
160
175
#[ derive( Deserialize ) ]
161
176
#[ serde( field_identifier, rename_all = "kebab-case" ) ]
162
- #[ allow( non_camel_case_types) ]
163
177
enum Field { $( $name, ) * third_party, }
164
178
165
- struct ConfVisitor <' a>( & ' a SourceFile ) ;
179
+ struct ConfVisitor <' a>( & ' a SourceFile , & ' a mut TryConf ) ;
166
180
167
181
impl <' de> Visitor <' de> for ConfVisitor <' _> {
168
- type Value = TryConf ;
182
+ type Value = ( ) ;
169
183
170
184
fn expecting( & self , formatter: & mut fmt:: Formatter <' _>) -> fmt:: Result {
171
185
formatter. write_str( "Conf" )
@@ -209,8 +223,14 @@ macro_rules! define_Conf {
209
223
Ok ( Field :: third_party) => drop( map. next_value:: <IgnoredAny >( ) )
210
224
}
211
225
}
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 ( ( ) )
214
234
}
215
235
}
216
236
@@ -524,12 +544,17 @@ define_Conf! {
524
544
( allow_private_module_inception: bool = false ) ,
525
545
}
526
546
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.
528
552
///
529
553
/// # Errors
530
554
///
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 > > {
533
558
/// Possible filename to search for.
534
559
const CONFIG_FILE_NAMES : [ & str ; 2 ] = [ ".clippy.toml" , "clippy.toml" ] ;
535
560
@@ -540,60 +565,68 @@ pub fn lookup_conf_file() -> io::Result<(Option<PathBuf>, Vec<String>)> {
540
565
. map_or_else ( || PathBuf :: from ( "." ) , PathBuf :: from)
541
566
. canonicalize ( ) ?;
542
567
543
- let mut found_config : Option < PathBuf > = None ;
568
+ let mut found_configs : Vec < PathBuf > = vec ! [ ] ;
544
569
let mut warnings = vec ! [ ] ;
545
570
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?
546
575
loop {
547
576
for config_file_name in & CONFIG_FILE_NAMES {
548
577
if let Ok ( config_file) = current. join ( config_file_name) . canonicalize ( ) {
549
578
match fs:: metadata ( & config_file) {
550
579
Err ( e) if e. kind ( ) == io:: ErrorKind :: NotFound => { } ,
551
- Err ( e) => return Err ( e) ,
580
+ Err ( e) => return Err ( e. into ( ) ) ,
552
581
Ok ( md) if md. is_dir ( ) => { } ,
553
582
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
+ {
556
589
warnings. push ( format ! (
557
590
"using config file `{}`, `{}` will be ignored" ,
558
- found_config . display( ) ,
591
+ last_config . display( ) ,
559
592
config_file. display( )
560
593
) ) ;
561
594
} else {
562
- found_config = Some ( config_file) ;
595
+ found_configs . push ( config_file) ;
563
596
}
564
597
} ,
565
598
}
566
599
}
567
600
}
568
601
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.
574
602
if !current. pop ( ) {
575
- return Ok ( ( None , warnings ) ) ;
603
+ break ;
576
604
}
577
605
}
606
+
607
+ Ok ( ( found_configs, warnings) )
578
608
}
579
609
580
610
/// Read the `toml` configuration file.
581
611
///
582
612
/// 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
+ }
596
627
}
628
+
629
+ conf
597
630
}
598
631
599
632
fn extend_vec_if_indicator_present ( vec : & mut Vec < String > , default : & [ & str ] ) {
0 commit comments