@@ -170,6 +170,43 @@ impl PartialConfig {
170170
171171 :: toml:: to_string ( & cloned) . map_err ( ToTomlError )
172172 }
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+ }
173210}
174211
175212impl Config {
@@ -197,11 +234,8 @@ impl Config {
197234 /// Returns a `Config` if the config could be read and parsed from
198235 /// the file, otherwise errors.
199236 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 ( ) ) )
205239 }
206240
207241 /// Resolves the config for input in `dir`.
@@ -213,85 +247,77 @@ impl Config {
213247 ///
214248 /// Returns the `Config` to use, and the path of the project file if there was
215249 /// 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 > {
217251 /// Try to find a project file in the given directory and its parents.
218252 /// Returns the path of a the nearest project file if one exists,
219253 /// 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 > {
221255 let mut current = if dir. is_relative ( ) {
222256 env:: current_dir ( ) ?. join ( dir)
223257 } else {
224258 dir. to_path_buf ( )
225259 } ;
226260
227261 current = dunce:: canonicalize ( current) ?;
262+ let mut paths = Vec :: new ( ) ;
228263
229264 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) ;
235267
236268 // If the current directory has no parent, we're done searching.
237269 if !current. pop ( ) {
238270 break ;
239271 }
240272 }
241273
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+
242283 // If nothing was found, check in the home directory.
243284 if let Some ( home_dir) = dirs:: home_dir ( ) {
244285 if let Some ( path) = get_toml_path ( & home_dir) ? {
245- return Ok ( Some ( path) ) ;
286+ return Ok ( Some ( vec ! [ path] ) ) ;
246287 }
247288 }
248289
249290 // If none was found ther either, check in the user's configuration directory.
250291 if let Some ( mut config_dir) = dirs:: config_dir ( ) {
251292 config_dir. push ( "rustfmt" ) ;
252293 if let Some ( path) = get_toml_path ( & config_dir) ? {
253- return Ok ( Some ( path) ) ;
294+ return Ok ( Some ( vec ! [ path] ) ) ;
254295 }
255296 }
256297
257298 Ok ( None )
258299 }
259300
260- match resolve_project_file ( dir) ? {
301+ match resolve_project_files ( dir) ? {
261302 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+ }
263314 }
264315 }
265316
266317 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)
295321 }
296322}
297323
@@ -300,14 +326,14 @@ impl Config {
300326pub fn load_config < O : CliOptions > (
301327 file_path : Option < & Path > ,
302328 options : Option < & O > ,
303- ) -> Result < ( Config , Option < PathBuf > ) , Error > {
329+ ) -> Result < ( Config , Option < Vec < PathBuf > > ) , Error > {
304330 let over_ride = match options {
305331 Some ( opts) => config_path ( opts) ?,
306332 None => None ,
307333 } ;
308334
309335 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( ) ] ) ) )
311337 } else if let Some ( file_path) = file_path {
312338 Config :: from_resolved_toml_path ( file_path)
313339 } else {
@@ -417,6 +443,42 @@ mod test {
417443 }
418444 }
419445
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+
420482 #[ test]
421483 fn test_config_set ( ) {
422484 let mut config = Config :: default ( ) ;
@@ -568,6 +630,44 @@ ignore = []
568630 assert_eq ! ( & toml, & default_config) ;
569631 }
570632
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+
571671 mod unstable_features {
572672 use super :: super :: * ;
573673
0 commit comments