@@ -170,6 +170,43 @@ impl PartialConfig {
170
170
171
171
:: toml:: to_string ( & cloned) . map_err ( ToTomlError )
172
172
}
173
+
174
+ pub fn from_toml_path ( file_path : & Path ) -> Result < PartialConfig , Error > {
175
+ let mut file = File :: open ( & file_path) ?;
176
+ let mut toml = String :: new ( ) ;
177
+ file. read_to_string ( & mut toml) ?;
178
+ PartialConfig :: from_toml ( & toml) . map_err ( |err| Error :: new ( ErrorKind :: InvalidData , err) )
179
+ }
180
+
181
+ fn from_toml ( toml : & str ) -> Result < PartialConfig , String > {
182
+ let parsed: :: toml:: Value = toml
183
+ . parse ( )
184
+ . map_err ( |e| format ! ( "Could not parse TOML: {}" , e) ) ?;
185
+ let mut err = String :: new ( ) ;
186
+ let table = parsed
187
+ . as_table ( )
188
+ . ok_or_else ( || String :: from ( "Parsed config was not table" ) ) ?;
189
+ for key in table. keys ( ) {
190
+ if !Config :: is_valid_name ( key) {
191
+ let msg = & format ! ( "Warning: Unknown configuration option `{}`\n " , key) ;
192
+ err. push_str ( msg)
193
+ }
194
+ }
195
+ match parsed. try_into ( ) {
196
+ Ok ( parsed_config) => {
197
+ if !err. is_empty ( ) {
198
+ eprint ! ( "{}" , err) ;
199
+ }
200
+ Ok ( parsed_config)
201
+ }
202
+ Err ( e) => {
203
+ err. push_str ( "Error: Decoding config file failed:\n " ) ;
204
+ err. push_str ( format ! ( "{}\n " , e) . as_str ( ) ) ;
205
+ err. push_str ( "Please check your config file." ) ;
206
+ Err ( err)
207
+ }
208
+ }
209
+ }
173
210
}
174
211
175
212
impl Config {
@@ -197,11 +234,8 @@ impl Config {
197
234
/// Returns a `Config` if the config could be read and parsed from
198
235
/// the file, otherwise errors.
199
236
pub fn from_toml_path ( file_path : & Path ) -> Result < Config , Error > {
200
- let mut file = File :: open ( & file_path) ?;
201
- let mut toml = String :: new ( ) ;
202
- file. read_to_string ( & mut toml) ?;
203
- Config :: from_toml ( & toml, file_path. parent ( ) . unwrap ( ) )
204
- . map_err ( |err| Error :: new ( ErrorKind :: InvalidData , err) )
237
+ let partial_config = PartialConfig :: from_toml_path ( file_path) ?;
238
+ Ok ( Config :: default ( ) . fill_from_parsed_config ( partial_config, file_path. parent ( ) . unwrap ( ) ) )
205
239
}
206
240
207
241
/// Resolves the config for input in `dir`.
@@ -213,85 +247,77 @@ impl Config {
213
247
///
214
248
/// Returns the `Config` to use, and the path of the project file if there was
215
249
/// one.
216
- pub fn from_resolved_toml_path ( dir : & Path ) -> Result < ( Config , Option < PathBuf > ) , Error > {
250
+ pub fn from_resolved_toml_path ( dir : & Path ) -> Result < ( Config , Option < Vec < PathBuf > > ) , Error > {
217
251
/// Try to find a project file in the given directory and its parents.
218
252
/// Returns the path of a the nearest project file if one exists,
219
253
/// or `None` if no project file was found.
220
- fn resolve_project_file ( dir : & Path ) -> Result < Option < PathBuf > , Error > {
254
+ fn resolve_project_files ( dir : & Path ) -> Result < Option < Vec < PathBuf > > , Error > {
221
255
let mut current = if dir. is_relative ( ) {
222
256
env:: current_dir ( ) ?. join ( dir)
223
257
} else {
224
258
dir. to_path_buf ( )
225
259
} ;
226
260
227
261
current = dunce:: canonicalize ( current) ?;
262
+ let mut paths = Vec :: new ( ) ;
228
263
229
264
loop {
230
- match get_toml_path ( & current) {
231
- Ok ( Some ( path) ) => return Ok ( Some ( path) ) ,
232
- Err ( e) => return Err ( e) ,
233
- _ => ( ) ,
234
- }
265
+ let current_toml_path = get_toml_path ( & current) ?;
266
+ paths. push ( current_toml_path) ;
235
267
236
268
// If the current directory has no parent, we're done searching.
237
269
if !current. pop ( ) {
238
270
break ;
239
271
}
240
272
}
241
273
274
+ // List of closest -> most distant rustfmt config from the current directory.
275
+ let config_paths: Option < Vec < _ > > = paths. into_iter ( ) . filter ( |p| p. is_some ( ) ) . collect ( ) ;
276
+ let has_paths = config_paths
277
+ . as_ref ( )
278
+ . map_or ( false , |paths| !paths. is_empty ( ) ) ;
279
+ if has_paths {
280
+ return Ok ( config_paths) ;
281
+ }
282
+
242
283
// If nothing was found, check in the home directory.
243
284
if let Some ( home_dir) = dirs:: home_dir ( ) {
244
285
if let Some ( path) = get_toml_path ( & home_dir) ? {
245
- return Ok ( Some ( path) ) ;
286
+ return Ok ( Some ( vec ! [ path] ) ) ;
246
287
}
247
288
}
248
289
249
290
// If none was found ther either, check in the user's configuration directory.
250
291
if let Some ( mut config_dir) = dirs:: config_dir ( ) {
251
292
config_dir. push ( "rustfmt" ) ;
252
293
if let Some ( path) = get_toml_path ( & config_dir) ? {
253
- return Ok ( Some ( path) ) ;
294
+ return Ok ( Some ( vec ! [ path] ) ) ;
254
295
}
255
296
}
256
297
257
298
Ok ( None )
258
299
}
259
300
260
- match resolve_project_file ( dir) ? {
301
+ match resolve_project_files ( dir) ? {
261
302
None => Ok ( ( Config :: default ( ) , None ) ) ,
262
- Some ( path) => Config :: from_toml_path ( & path) . map ( |config| ( config, Some ( path) ) ) ,
303
+ Some ( paths) => {
304
+ let mut config = Config :: default ( ) ;
305
+ let mut used_paths = Vec :: with_capacity ( paths. len ( ) ) ;
306
+ for path in paths. into_iter ( ) . rev ( ) {
307
+ let partial_config = PartialConfig :: from_toml_path ( & path) ?;
308
+ config = config. fill_from_parsed_config ( partial_config, & path) ;
309
+ used_paths. push ( path) ;
310
+ }
311
+
312
+ Ok ( ( config, Some ( used_paths) ) )
313
+ }
263
314
}
264
315
}
265
316
266
317
pub fn from_toml ( toml : & str , dir : & Path ) -> Result < Config , String > {
267
- let parsed: :: toml:: Value = toml
268
- . parse ( )
269
- . map_err ( |e| format ! ( "Could not parse TOML: {}" , e) ) ?;
270
- let mut err = String :: new ( ) ;
271
- let table = parsed
272
- . as_table ( )
273
- . ok_or_else ( || String :: from ( "Parsed config was not table" ) ) ?;
274
- for key in table. keys ( ) {
275
- if !Config :: is_valid_name ( key) {
276
- let msg = & format ! ( "Warning: Unknown configuration option `{}`\n " , key) ;
277
- err. push_str ( msg)
278
- }
279
- }
280
- match parsed. try_into ( ) {
281
- Ok ( parsed_config) => {
282
- if !err. is_empty ( ) {
283
- eprint ! ( "{}" , err) ;
284
- }
285
- let config = Config :: default ( ) . fill_from_parsed_config ( parsed_config, dir) ;
286
- Ok ( config)
287
- }
288
- Err ( e) => {
289
- err. push_str ( "Error: Decoding config file failed:\n " ) ;
290
- err. push_str ( format ! ( "{}\n " , e) . as_str ( ) ) ;
291
- err. push_str ( "Please check your config file." ) ;
292
- Err ( err)
293
- }
294
- }
318
+ let partial_config = PartialConfig :: from_toml ( toml) ?;
319
+ let config = Config :: default ( ) . fill_from_parsed_config ( partial_config, dir) ;
320
+ Ok ( config)
295
321
}
296
322
}
297
323
@@ -300,14 +326,14 @@ impl Config {
300
326
pub fn load_config < O : CliOptions > (
301
327
file_path : Option < & Path > ,
302
328
options : Option < & O > ,
303
- ) -> Result < ( Config , Option < PathBuf > ) , Error > {
329
+ ) -> Result < ( Config , Option < Vec < PathBuf > > ) , Error > {
304
330
let over_ride = match options {
305
331
Some ( opts) => config_path ( opts) ?,
306
332
None => None ,
307
333
} ;
308
334
309
335
let result = if let Some ( over_ride) = over_ride {
310
- Config :: from_toml_path ( over_ride. as_ref ( ) ) . map ( |p| ( p, Some ( over_ride. to_owned ( ) ) ) )
336
+ Config :: from_toml_path ( over_ride. as_ref ( ) ) . map ( |p| ( p, Some ( vec ! [ over_ride. to_owned( ) ] ) ) )
311
337
} else if let Some ( file_path) = file_path {
312
338
Config :: from_resolved_toml_path ( file_path)
313
339
} else {
@@ -417,6 +443,42 @@ mod test {
417
443
}
418
444
}
419
445
446
+ struct TempFile {
447
+ path : PathBuf ,
448
+ }
449
+
450
+ fn make_temp_file ( file_name : & ' static str , content : & ' static str ) -> TempFile {
451
+ use std:: env:: var;
452
+
453
+ // Used in the Rust build system.
454
+ let target_dir = var ( "RUSTFMT_TEST_DIR" ) . map_or_else ( |_| env:: temp_dir ( ) , PathBuf :: from) ;
455
+ let path = target_dir. join ( file_name) ;
456
+
457
+ fs:: create_dir_all ( path. parent ( ) . unwrap ( ) ) . expect ( "couldn't create temp file" ) ;
458
+ let mut file = File :: create ( & path) . expect ( "couldn't create temp file" ) ;
459
+ file. write_all ( content. as_bytes ( ) )
460
+ . expect ( "couldn't write temp file" ) ;
461
+ TempFile { path }
462
+ }
463
+
464
+ impl Drop for TempFile {
465
+ fn drop ( & mut self ) {
466
+ use std:: fs:: remove_file;
467
+ remove_file ( & self . path ) . expect ( "couldn't delete temp file" ) ;
468
+ }
469
+ }
470
+
471
+ struct NullOptions ;
472
+
473
+ impl CliOptions for NullOptions {
474
+ fn apply_to ( & self , _: & mut Config ) {
475
+ unreachable ! ( ) ;
476
+ }
477
+ fn config_path ( & self ) -> Option < & Path > {
478
+ unreachable ! ( ) ;
479
+ }
480
+ }
481
+
420
482
#[ test]
421
483
fn test_config_set ( ) {
422
484
let mut config = Config :: default ( ) ;
@@ -568,6 +630,44 @@ ignore = []
568
630
assert_eq ! ( & toml, & default_config) ;
569
631
}
570
632
633
+ #[ test]
634
+ fn test_merged_config ( ) {
635
+ match option_env ! ( "CFG_RELEASE_CHANNEL" ) {
636
+ // this test requires nightly
637
+ None | Some ( "nightly" ) => {
638
+ let _outer_config = make_temp_file (
639
+ "a/rustfmt.toml" ,
640
+ r#"
641
+ tab_spaces = 2
642
+ fn_call_width = 50
643
+ ignore = ["b/main.rs", "util.rs"]
644
+ "# ,
645
+ ) ;
646
+
647
+ let inner_config = make_temp_file (
648
+ "a/b/rustfmt.toml" ,
649
+ r#"
650
+ version = "two"
651
+ tab_spaces = 3
652
+ ignore = []
653
+ "# ,
654
+ ) ;
655
+
656
+ let inner_dir = inner_config. path . parent ( ) . unwrap ( ) ;
657
+ let ( config, paths) = load_config :: < NullOptions > ( Some ( inner_dir) , None ) . unwrap ( ) ;
658
+
659
+ assert_eq ! ( config. tab_spaces( ) , 3 ) ;
660
+ assert_eq ! ( config. fn_call_width( ) , 50 ) ;
661
+ assert_eq ! ( config. ignore( ) . to_string( ) , r#"["main.rs"]"# ) ;
662
+
663
+ let paths = paths. unwrap ( ) ;
664
+ assert ! ( paths[ 0 ] . ends_with( "a/rustfmt.toml" ) ) ;
665
+ assert ! ( paths[ 1 ] . ends_with( "a/b/rustfmt.toml" ) ) ;
666
+ }
667
+ _ => ( ) ,
668
+ } ;
669
+ }
670
+
571
671
mod unstable_features {
572
672
use super :: super :: * ;
573
673
0 commit comments