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
@@ -100,38 +101,50 @@ impl From<io::Error> for TryConf {
100
101
#[ derive( Debug ) ]
101
102
pub struct ConfError {
102
103
pub message : String ,
104
+ pub file : Option < PathBuf > ,
103
105
pub span : Option < Span > ,
104
106
}
105
107
106
108
impl ConfError {
107
109
fn from_toml ( file : & SourceFile , error : & toml:: de:: Error ) -> Self {
108
110
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
+ } ;
115
120
}
121
+
122
+ unreachable ! ( ) ;
116
123
}
117
124
118
125
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
+ } ;
127
137
}
138
+
139
+ unreachable ! ( ) ;
128
140
}
129
141
}
130
142
131
143
impl From < io:: Error > for ConfError {
132
144
fn from ( value : io:: Error ) -> Self {
133
145
Self {
134
146
message : value. to_string ( ) ,
147
+ file : None ,
135
148
span : None ,
136
149
}
137
150
}
@@ -144,6 +157,7 @@ macro_rules! define_Conf {
144
157
( $name: ident: $ty: ty = $default: expr) ,
145
158
) * ) => {
146
159
/// Clippy lint configuration
160
+ #[ derive( Deserialize ) ]
147
161
pub struct Conf {
148
162
$( $( #[ doc = $doc] ) + pub $name: $ty, ) *
149
163
}
@@ -158,15 +172,15 @@ macro_rules! define_Conf {
158
172
}
159
173
}
160
174
175
+ #[ allow( non_camel_case_types) ]
161
176
#[ derive( Deserialize ) ]
162
177
#[ serde( field_identifier, rename_all = "kebab-case" ) ]
163
- #[ allow( non_camel_case_types) ]
164
178
enum Field { $( $name, ) * third_party, }
165
179
166
- struct ConfVisitor <' a>( & ' a SourceFile ) ;
180
+ struct ConfVisitor <' a>( & ' a SourceFile , & ' a mut TryConf ) ;
167
181
168
182
impl <' de> Visitor <' de> for ConfVisitor <' _> {
169
- type Value = TryConf ;
183
+ type Value = ( ) ;
170
184
171
185
fn expecting( & self , formatter: & mut fmt:: Formatter <' _>) -> fmt:: Result {
172
186
formatter. write_str( "Conf" )
@@ -210,8 +224,14 @@ macro_rules! define_Conf {
210
224
Ok ( Field :: third_party) => drop( map. next_value:: <IgnoredAny >( ) )
211
225
}
212
226
}
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 ( ( ) )
215
235
}
216
236
}
217
237
@@ -536,12 +556,17 @@ define_Conf! {
536
556
( min_ident_chars_threshold: u64 = 1 ) ,
537
557
}
538
558
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.
540
564
///
541
565
/// # Errors
542
566
///
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 > > {
545
570
/// Possible filename to search for.
546
571
const CONFIG_FILE_NAMES : [ & str ; 2 ] = [ ".clippy.toml" , "clippy.toml" ] ;
547
572
@@ -552,66 +577,74 @@ pub fn lookup_conf_file() -> io::Result<(Option<PathBuf>, Vec<String>)> {
552
577
. map_or_else ( || PathBuf :: from ( "." ) , PathBuf :: from)
553
578
. canonicalize ( ) ?;
554
579
555
- let mut found_config : Option < PathBuf > = None ;
580
+ let mut found_configs : Vec < PathBuf > = vec ! [ ] ;
556
581
let mut warnings = vec ! [ ] ;
557
582
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?
558
587
loop {
559
588
for config_file_name in & CONFIG_FILE_NAMES {
560
589
if let Ok ( config_file) = current. join ( config_file_name) . canonicalize ( ) {
561
590
match fs:: metadata ( & config_file) {
562
591
Err ( e) if e. kind ( ) == io:: ErrorKind :: NotFound => { } ,
563
- Err ( e) => return Err ( e) ,
592
+ Err ( e) => return Err ( e. into ( ) ) ,
564
593
Ok ( md) if md. is_dir ( ) => { } ,
565
594
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
+ {
568
601
warnings. push ( format ! (
569
602
"using config file `{}`, `{}` will be ignored" ,
570
- found_config . display( ) ,
603
+ last_config . display( ) ,
571
604
config_file. display( )
572
605
) ) ;
573
606
} else {
574
- found_config = Some ( config_file) ;
607
+ found_configs . push ( config_file) ;
575
608
}
576
609
} ,
577
610
}
578
611
}
579
612
}
580
613
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.
586
614
if !current. pop ( ) {
587
- return Ok ( ( None , warnings ) ) ;
615
+ break ;
588
616
}
589
617
}
618
+
619
+ Ok ( ( found_configs, warnings) )
590
620
}
591
621
592
622
/// Read the `toml` configuration file.
593
623
///
594
624
/// 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
+ }
614
645
}
646
+
647
+ conf
615
648
}
616
649
617
650
fn extend_vec_if_indicator_present ( vec : & mut Vec < String > , default : & [ & str ] ) {
0 commit comments