@@ -184,6 +184,43 @@ impl PartialConfig {
184184
185185 :: toml:: to_string ( & cloned) . map_err ( ToTomlError )
186186 }
187+
188+ pub fn from_toml_path ( file_path : & Path ) -> Result < PartialConfig , Error > {
189+ let mut file = File :: open ( & file_path) ?;
190+ let mut toml = String :: new ( ) ;
191+ file. read_to_string ( & mut toml) ?;
192+ PartialConfig :: from_toml ( & toml) . map_err ( |err| Error :: new ( ErrorKind :: InvalidData , err) )
193+ }
194+
195+ fn from_toml ( toml : & str ) -> Result < PartialConfig , String > {
196+ let parsed: :: toml:: Value = toml
197+ . parse ( )
198+ . map_err ( |e| format ! ( "Could not parse TOML: {}" , e) ) ?;
199+ let mut err = String :: new ( ) ;
200+ let table = parsed
201+ . as_table ( )
202+ . ok_or_else ( || String :: from ( "Parsed config was not table" ) ) ?;
203+ for key in table. keys ( ) {
204+ if !Config :: is_valid_name ( key) {
205+ let msg = & format ! ( "Warning: Unknown configuration option `{}`\n " , key) ;
206+ err. push_str ( msg)
207+ }
208+ }
209+ match parsed. try_into ( ) {
210+ Ok ( parsed_config) => {
211+ if !err. is_empty ( ) {
212+ eprint ! ( "{}" , err) ;
213+ }
214+ Ok ( parsed_config)
215+ }
216+ Err ( e) => {
217+ err. push_str ( "Error: Decoding config file failed:\n " ) ;
218+ err. push_str ( format ! ( "{}\n " , e) . as_str ( ) ) ;
219+ err. push_str ( "Please check your config file." ) ;
220+ Err ( err)
221+ }
222+ }
223+ }
187224}
188225
189226impl Config {
@@ -211,11 +248,8 @@ impl Config {
211248 /// Returns a `Config` if the config could be read and parsed from
212249 /// the file, otherwise errors.
213250 pub fn from_toml_path ( file_path : & Path ) -> Result < Config , Error > {
214- let mut file = File :: open ( & file_path) ?;
215- let mut toml = String :: new ( ) ;
216- file. read_to_string ( & mut toml) ?;
217- Config :: from_toml ( & toml, file_path. parent ( ) . unwrap ( ) )
218- . map_err ( |err| Error :: new ( ErrorKind :: InvalidData , err) )
251+ let partial_config = PartialConfig :: from_toml_path ( file_path) ?;
252+ Ok ( Config :: default ( ) . fill_from_parsed_config ( partial_config, file_path. parent ( ) . unwrap ( ) ) )
219253 }
220254
221255 /// Resolves the config for input in `dir`.
@@ -227,85 +261,74 @@ impl Config {
227261 ///
228262 /// Returns the `Config` to use, and the path of the project file if there was
229263 /// one.
230- pub fn from_resolved_toml_path ( dir : & Path ) -> Result < ( Config , Option < PathBuf > ) , Error > {
264+ pub fn from_resolved_toml_path ( dir : & Path ) -> Result < ( Config , Option < Vec < PathBuf > > ) , Error > {
231265 /// Try to find a project file in the given directory and its parents.
232266 /// Returns the path of a the nearest project file if one exists,
233267 /// or `None` if no project file was found.
234- fn resolve_project_file ( dir : & Path ) -> Result < Option < PathBuf > , Error > {
268+ fn resolve_project_files ( dir : & Path ) -> Result < Option < Vec < PathBuf > > , Error > {
235269 let mut current = if dir. is_relative ( ) {
236270 env:: current_dir ( ) ?. join ( dir)
237271 } else {
238272 dir. to_path_buf ( )
239273 } ;
240274
241275 current = dunce:: canonicalize ( current) ?;
276+ let mut paths = Vec :: new ( ) ;
242277
243278 loop {
244- match get_toml_path ( & current) {
245- Ok ( Some ( path) ) => return Ok ( Some ( path) ) ,
246- Err ( e) => return Err ( e) ,
247- _ => ( ) ,
248- }
279+ let current_toml_path = get_toml_path ( & current) ?;
280+ paths. push ( current_toml_path) ;
249281
250282 // If the current directory has no parent, we're done searching.
251283 if !current. pop ( ) {
252284 break ;
253285 }
254286 }
255287
288+ if !paths. is_empty ( ) {
289+ return Ok ( paths. into_iter ( ) . filter ( |p| p. is_some ( ) ) . collect ( ) ) ;
290+ }
291+
256292 // If nothing was found, check in the home directory.
257293 if let Some ( home_dir) = dirs:: home_dir ( ) {
258294 if let Some ( path) = get_toml_path ( & home_dir) ? {
259- return Ok ( Some ( path) ) ;
295+ return Ok ( Some ( vec ! [ path] ) ) ;
260296 }
261297 }
262298
263299 // If none was found ther either, check in the user's configuration directory.
264300 if let Some ( mut config_dir) = dirs:: config_dir ( ) {
265301 config_dir. push ( "rustfmt" ) ;
266302 if let Some ( path) = get_toml_path ( & config_dir) ? {
267- return Ok ( Some ( path) ) ;
303+ return Ok ( Some ( vec ! [ path] ) ) ;
268304 }
269305 }
270306
271307 Ok ( None )
272308 }
273309
274- match resolve_project_file ( dir) ? {
310+ let files = resolve_project_files ( dir) ;
311+
312+ match files? {
275313 None => Ok ( ( Config :: default ( ) , None ) ) ,
276- Some ( path) => Config :: from_toml_path ( & path) . map ( |config| ( config, Some ( path) ) ) ,
314+ Some ( paths) => {
315+ let mut config = Config :: default ( ) ;
316+ let mut used_paths = Vec :: with_capacity ( paths. len ( ) ) ;
317+ for path in paths. into_iter ( ) . rev ( ) {
318+ let partial_config = PartialConfig :: from_toml_path ( & path) ?;
319+ config = config. fill_from_parsed_config ( partial_config, & path) ;
320+ used_paths. push ( path) ;
321+ }
322+
323+ Ok ( ( config, Some ( used_paths) ) )
324+ }
277325 }
278326 }
279327
280328 pub fn from_toml ( toml : & str , dir : & Path ) -> Result < Config , String > {
281- let parsed: :: toml:: Value = toml
282- . parse ( )
283- . map_err ( |e| format ! ( "Could not parse TOML: {}" , e) ) ?;
284- let mut err = String :: new ( ) ;
285- let table = parsed
286- . as_table ( )
287- . ok_or_else ( || String :: from ( "Parsed config was not table" ) ) ?;
288- for key in table. keys ( ) {
289- if !Config :: is_valid_name ( key) {
290- let msg = & format ! ( "Warning: Unknown configuration option `{}`\n " , key) ;
291- err. push_str ( msg)
292- }
293- }
294- match parsed. try_into ( ) {
295- Ok ( parsed_config) => {
296- if !err. is_empty ( ) {
297- eprint ! ( "{}" , err) ;
298- }
299- let config = Config :: default ( ) . fill_from_parsed_config ( parsed_config, dir) ;
300- Ok ( config)
301- }
302- Err ( e) => {
303- err. push_str ( "Error: Decoding config file failed:\n " ) ;
304- err. push_str ( format ! ( "{}\n " , e) . as_str ( ) ) ;
305- err. push_str ( "Please check your config file." ) ;
306- Err ( err)
307- }
308- }
329+ let partial_config = PartialConfig :: from_toml ( toml) ?;
330+ let config = Config :: default ( ) . fill_from_parsed_config ( partial_config, dir) ;
331+ Ok ( config)
309332 }
310333}
311334
@@ -314,14 +337,14 @@ impl Config {
314337pub fn load_config < O : CliOptions > (
315338 file_path : Option < & Path > ,
316339 options : Option < & O > ,
317- ) -> Result < ( Config , Option < PathBuf > ) , Error > {
340+ ) -> Result < ( Config , Option < Vec < PathBuf > > ) , Error > {
318341 let over_ride = match options {
319342 Some ( opts) => config_path ( opts) ?,
320343 None => None ,
321344 } ;
322345
323346 let result = if let Some ( over_ride) = over_ride {
324- Config :: from_toml_path ( over_ride. as_ref ( ) ) . map ( |p| ( p, Some ( over_ride. to_owned ( ) ) ) )
347+ Config :: from_toml_path ( over_ride. as_ref ( ) ) . map ( |p| ( p, Some ( vec ! [ over_ride. to_owned( ) ] ) ) )
325348 } else if let Some ( file_path) = file_path {
326349 Config :: from_resolved_toml_path ( file_path)
327350 } else {
@@ -433,6 +456,42 @@ mod test {
433456 }
434457 }
435458
459+ struct TempFile {
460+ path : PathBuf ,
461+ }
462+
463+ fn make_temp_file ( file_name : & ' static str , content : & ' static str ) -> TempFile {
464+ use std:: env:: var;
465+
466+ // Used in the Rust build system.
467+ let target_dir = var ( "RUSTFMT_TEST_DIR" ) . map_or_else ( |_| env:: temp_dir ( ) , PathBuf :: from) ;
468+ let path = target_dir. join ( file_name) ;
469+
470+ fs:: create_dir_all ( path. parent ( ) . unwrap ( ) ) . expect ( "couldn't create temp file" ) ;
471+ let mut file = File :: create ( & path) . expect ( "couldn't create temp file" ) ;
472+ file. write_all ( content. as_bytes ( ) )
473+ . expect ( "couldn't write temp file" ) ;
474+ TempFile { path }
475+ }
476+
477+ impl Drop for TempFile {
478+ fn drop ( & mut self ) {
479+ use std:: fs:: remove_file;
480+ remove_file ( & self . path ) . expect ( "couldn't delete temp file" ) ;
481+ }
482+ }
483+
484+ struct NullOptions ;
485+
486+ impl CliOptions for NullOptions {
487+ fn apply_to ( & self , _: & mut Config ) {
488+ unreachable ! ( ) ;
489+ }
490+ fn config_path ( & self ) -> Option < & Path > {
491+ unreachable ! ( ) ;
492+ }
493+ }
494+
436495 #[ test]
437496 fn test_config_set ( ) {
438497 let mut config = Config :: default ( ) ;
@@ -585,6 +644,37 @@ ignore = []
585644 assert_eq ! ( & toml, & default_config) ;
586645 }
587646
647+ #[ test]
648+ fn test_merged_config ( ) {
649+ let _outer_config = make_temp_file (
650+ "a/rustfmt.toml" ,
651+ r#"
652+ tab_spaces = 2
653+ fn_call_width = 50
654+ ignore = ["b/main.rs", "util.rs"]
655+ "# ,
656+ ) ;
657+
658+ let inner_config = make_temp_file (
659+ "a/b/rustfmt.toml" ,
660+ r#"
661+ tab_spaces = 3
662+ ignore = []
663+ "# ,
664+ ) ;
665+
666+ let inner_dir = inner_config. path . parent ( ) . unwrap ( ) ;
667+ let ( config, paths) = load_config :: < NullOptions > ( Some ( inner_dir) , None ) . unwrap ( ) ;
668+
669+ assert_eq ! ( config. tab_spaces( ) , 3 ) ;
670+ assert_eq ! ( config. fn_call_width( ) , 50 ) ;
671+ assert_eq ! ( config. ignore( ) . to_string( ) , r#"["main.rs"]"# ) ;
672+
673+ let paths = paths. unwrap ( ) ;
674+ assert ! ( paths[ 0 ] . ends_with( "a/rustfmt.toml" ) ) ;
675+ assert ! ( paths[ 1 ] . ends_with( "a/b/rustfmt.toml" ) ) ;
676+ }
677+
588678 mod unstable_features {
589679 use super :: super :: * ;
590680
0 commit comments