@@ -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,74 @@ 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+ if !paths. is_empty ( ) {
275+ return Ok ( paths. into_iter ( ) . filter ( |p| p. is_some ( ) ) . collect ( ) ) ;
276+ }
277+
242278 // If nothing was found, check in the home directory.
243279 if let Some ( home_dir) = dirs:: home_dir ( ) {
244280 if let Some ( path) = get_toml_path ( & home_dir) ? {
245- return Ok ( Some ( path) ) ;
281+ return Ok ( Some ( vec ! [ path] ) ) ;
246282 }
247283 }
248284
249285 // If none was found ther either, check in the user's configuration directory.
250286 if let Some ( mut config_dir) = dirs:: config_dir ( ) {
251287 config_dir. push ( "rustfmt" ) ;
252288 if let Some ( path) = get_toml_path ( & config_dir) ? {
253- return Ok ( Some ( path) ) ;
289+ return Ok ( Some ( vec ! [ path] ) ) ;
254290 }
255291 }
256292
257293 Ok ( None )
258294 }
259295
260- match resolve_project_file ( dir) ? {
296+ let files = resolve_project_files ( dir) ;
297+
298+ match files? {
261299 None => Ok ( ( Config :: default ( ) , None ) ) ,
262- Some ( path) => Config :: from_toml_path ( & path) . map ( |config| ( config, Some ( path) ) ) ,
300+ Some ( paths) => {
301+ let mut config = Config :: default ( ) ;
302+ let mut used_paths = Vec :: with_capacity ( paths. len ( ) ) ;
303+ for path in paths. into_iter ( ) . rev ( ) {
304+ let partial_config = PartialConfig :: from_toml_path ( & path) ?;
305+ config = config. fill_from_parsed_config ( partial_config, & path) ;
306+ used_paths. push ( path) ;
307+ }
308+
309+ Ok ( ( config, Some ( used_paths) ) )
310+ }
263311 }
264312 }
265313
266314 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- }
315+ let partial_config = PartialConfig :: from_toml ( toml) ?;
316+ let config = Config :: default ( ) . fill_from_parsed_config ( partial_config, dir) ;
317+ Ok ( config)
295318 }
296319}
297320
@@ -300,14 +323,14 @@ impl Config {
300323pub fn load_config < O : CliOptions > (
301324 file_path : Option < & Path > ,
302325 options : Option < & O > ,
303- ) -> Result < ( Config , Option < PathBuf > ) , Error > {
326+ ) -> Result < ( Config , Option < Vec < PathBuf > > ) , Error > {
304327 let over_ride = match options {
305328 Some ( opts) => config_path ( opts) ?,
306329 None => None ,
307330 } ;
308331
309332 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 ( ) ) ) )
333+ Config :: from_toml_path ( over_ride. as_ref ( ) ) . map ( |p| ( p, Some ( vec ! [ over_ride. to_owned( ) ] ) ) )
311334 } else if let Some ( file_path) = file_path {
312335 Config :: from_resolved_toml_path ( file_path)
313336 } else {
@@ -417,6 +440,42 @@ mod test {
417440 }
418441 }
419442
443+ struct TempFile {
444+ path : PathBuf ,
445+ }
446+
447+ fn make_temp_file ( file_name : & ' static str , content : & ' static str ) -> TempFile {
448+ use std:: env:: var;
449+
450+ // Used in the Rust build system.
451+ let target_dir = var ( "RUSTFMT_TEST_DIR" ) . map_or_else ( |_| env:: temp_dir ( ) , PathBuf :: from) ;
452+ let path = target_dir. join ( file_name) ;
453+
454+ fs:: create_dir_all ( path. parent ( ) . unwrap ( ) ) . expect ( "couldn't create temp file" ) ;
455+ let mut file = File :: create ( & path) . expect ( "couldn't create temp file" ) ;
456+ file. write_all ( content. as_bytes ( ) )
457+ . expect ( "couldn't write temp file" ) ;
458+ TempFile { path }
459+ }
460+
461+ impl Drop for TempFile {
462+ fn drop ( & mut self ) {
463+ use std:: fs:: remove_file;
464+ remove_file ( & self . path ) . expect ( "couldn't delete temp file" ) ;
465+ }
466+ }
467+
468+ struct NullOptions ;
469+
470+ impl CliOptions for NullOptions {
471+ fn apply_to ( & self , _: & mut Config ) {
472+ unreachable ! ( ) ;
473+ }
474+ fn config_path ( & self ) -> Option < & Path > {
475+ unreachable ! ( ) ;
476+ }
477+ }
478+
420479 #[ test]
421480 fn test_config_set ( ) {
422481 let mut config = Config :: default ( ) ;
@@ -568,6 +627,37 @@ ignore = []
568627 assert_eq ! ( & toml, & default_config) ;
569628 }
570629
630+ #[ test]
631+ fn test_merged_config ( ) {
632+ let _outer_config = make_temp_file (
633+ "a/rustfmt.toml" ,
634+ r#"
635+ tab_spaces = 2
636+ fn_call_width = 50
637+ ignore = ["b/main.rs", "util.rs"]
638+ "# ,
639+ ) ;
640+
641+ let inner_config = make_temp_file (
642+ "a/b/rustfmt.toml" ,
643+ r#"
644+ tab_spaces = 3
645+ ignore = []
646+ "# ,
647+ ) ;
648+
649+ let inner_dir = inner_config. path . parent ( ) . unwrap ( ) ;
650+ let ( config, paths) = load_config :: < NullOptions > ( Some ( inner_dir) , None ) . unwrap ( ) ;
651+
652+ assert_eq ! ( config. tab_spaces( ) , 3 ) ;
653+ assert_eq ! ( config. fn_call_width( ) , 50 ) ;
654+ assert_eq ! ( config. ignore( ) . to_string( ) , r#"["main.rs"]"# ) ;
655+
656+ let paths = paths. unwrap ( ) ;
657+ assert ! ( paths[ 0 ] . ends_with( "a/rustfmt.toml" ) ) ;
658+ assert ! ( paths[ 1 ] . ends_with( "a/b/rustfmt.toml" ) ) ;
659+ }
660+
571661 mod unstable_features {
572662 use super :: super :: * ;
573663
0 commit comments