From 3e3dc66a559ef980b66eedbf96bae4f2f7f459e9 Mon Sep 17 00:00:00 2001 From: rami3l Date: Fri, 11 Sep 2026 17:31:14 +0200 Subject: [PATCH 1/4] test(config): demonstrate `--config-path` searching behavior --- tests/config/issue_4660/Cargo.lock | 7 +++ tests/config/issue_4660/Cargo.toml | 2 + .../inner_bin/.rustfmt.unstable.toml | 1 + tests/config/issue_4660/inner_bin/Cargo.toml | 6 +++ tests/config/issue_4660/inner_bin/src/main.rs | 3 ++ tests/config/issue_4660/inner_lib/Cargo.toml | 6 +++ .../config/issue_4660/inner_lib/rustfmt.toml | 1 + tests/config/issue_4660/inner_lib/src/lib.rs | 1 + tests/rustfmt/main.rs | 53 +++++++++++++++++++ 9 files changed, 80 insertions(+) create mode 100644 tests/config/issue_4660/Cargo.lock create mode 100644 tests/config/issue_4660/Cargo.toml create mode 100644 tests/config/issue_4660/inner_bin/.rustfmt.unstable.toml create mode 100644 tests/config/issue_4660/inner_bin/Cargo.toml create mode 100644 tests/config/issue_4660/inner_bin/src/main.rs create mode 100644 tests/config/issue_4660/inner_lib/Cargo.toml create mode 100644 tests/config/issue_4660/inner_lib/rustfmt.toml create mode 100644 tests/config/issue_4660/inner_lib/src/lib.rs diff --git a/tests/config/issue_4660/Cargo.lock b/tests/config/issue_4660/Cargo.lock new file mode 100644 index 00000000000..e2e277aef87 --- /dev/null +++ b/tests/config/issue_4660/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "inner_lib" +version = "0.1.0" diff --git a/tests/config/issue_4660/Cargo.toml b/tests/config/issue_4660/Cargo.toml new file mode 100644 index 00000000000..c716e9d0575 --- /dev/null +++ b/tests/config/issue_4660/Cargo.toml @@ -0,0 +1,2 @@ +[workspace] +members = ["inner_lib"] diff --git a/tests/config/issue_4660/inner_bin/.rustfmt.unstable.toml b/tests/config/issue_4660/inner_bin/.rustfmt.unstable.toml new file mode 100644 index 00000000000..c7ad93bafe3 --- /dev/null +++ b/tests/config/issue_4660/inner_bin/.rustfmt.unstable.toml @@ -0,0 +1 @@ +disable_all_formatting = true diff --git a/tests/config/issue_4660/inner_bin/Cargo.toml b/tests/config/issue_4660/inner_bin/Cargo.toml new file mode 100644 index 00000000000..bb6a80cd0ce --- /dev/null +++ b/tests/config/issue_4660/inner_bin/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "inner_bin" +version = "0.1.0" +edition = "2021" + +[dependencies] diff --git a/tests/config/issue_4660/inner_bin/src/main.rs b/tests/config/issue_4660/inner_bin/src/main.rs new file mode 100644 index 00000000000..e7a11a969c0 --- /dev/null +++ b/tests/config/issue_4660/inner_bin/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Hello, world!"); +} diff --git a/tests/config/issue_4660/inner_lib/Cargo.toml b/tests/config/issue_4660/inner_lib/Cargo.toml new file mode 100644 index 00000000000..c9ddf1793b8 --- /dev/null +++ b/tests/config/issue_4660/inner_lib/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "inner_lib" +version = "0.1.0" +edition = "2021" + +[dependencies] diff --git a/tests/config/issue_4660/inner_lib/rustfmt.toml b/tests/config/issue_4660/inner_lib/rustfmt.toml new file mode 100644 index 00000000000..c7ad93bafe3 --- /dev/null +++ b/tests/config/issue_4660/inner_lib/rustfmt.toml @@ -0,0 +1 @@ +disable_all_formatting = true diff --git a/tests/config/issue_4660/inner_lib/src/lib.rs b/tests/config/issue_4660/inner_lib/src/lib.rs new file mode 100644 index 00000000000..42375c99d2c --- /dev/null +++ b/tests/config/issue_4660/inner_lib/src/lib.rs @@ -0,0 +1 @@ +pub fn add(left:u64,right:u64)->u64{left+right} diff --git a/tests/rustfmt/main.rs b/tests/rustfmt/main.rs index ee972d8a6af..8de9cfded88 100644 --- a/tests/rustfmt/main.rs +++ b/tests/rustfmt/main.rs @@ -345,6 +345,59 @@ fn rustfmt_error_improvement_regarding_invalid_toml() { assert!(stderr.contains(&expected_error_message)); } +#[test] +fn config_path_walks_parent_directories_with_dir_name() { + let src_dir = "tests/config/issue_4660/inner_lib/src"; + let src_file = src_dir.to_owned() + "/lib.rs"; + let args = ["--config-path", src_dir, &src_file]; + let (stdout, stderr) = rustfmt(&args); + + assert_eq!( + stderr, + format!( + "Error: unable to find a config file for the given path: `{}`\n", + Path::new(src_dir).display(), + ) + ); + assert_eq!(stdout, ""); +} + +#[test] +fn config_path_walks_parent_directories_with_toml_name() { + let toml_name = ".rustfmt.unstable.toml"; + let src_name = "main.rs"; + let src_dir = "tests/config/issue_4660/inner_bin/src"; + + let toml_file = [src_dir, toml_name].join("/"); + let args = [ + "--config-path", + &[src_dir, toml_name].join("/"), + &[src_dir, src_name].join("/"), + ]; + let (stdout, stderr) = rustfmt(&args); + + assert_eq!( + stderr, + format!( + "Error: unable to find a config file for the given path: `{}`\n", + Path::new(&toml_file).display(), + ) + ); + assert_eq!(stdout, ""); + + let args = ["--config-path", toml_name, src_name]; + let (stdout, stderr) = rustfmt_with_extra(&args, Some(src_dir), &[]); + + assert_eq!( + stderr, + format!( + "Error: unable to find a config file for the given path: `{}`\n", + Path::new(toml_name).display(), + ) + ); + assert_eq!(stdout, ""); +} + #[test] fn rustfmt_allow_not_a_dir_errors() { // See also https://github.com/rust-lang/rustfmt/pull/6624 From dfcc9f8a9969a54707b0e52d683d7dc87d269512 Mon Sep 17 00:00:00 2001 From: rami3l Date: Fri, 11 Sep 2026 21:10:16 +0200 Subject: [PATCH 2/4] refactor(config): generalize `get_toml_path()` --- src/config/mod.rs | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/config/mod.rs b/src/config/mod.rs index a3f9842cd4f..96360a6d44a 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -1,4 +1,5 @@ use std::cell::Cell; +use std::ffi::OsStr; use std::fs::File; use std::io::{Error, ErrorKind, Read}; use std::path::{Path, PathBuf}; @@ -367,7 +368,7 @@ impl Config { current = fs::canonicalize(current)?; loop { - match get_toml_path(¤t) { + match get_toml_path(¤t, &CONFIG_FILE_NAMES) { Ok(Some(path)) => return Ok(Some(path)), Err(e) => return Err(e), _ => (), @@ -381,7 +382,7 @@ impl Config { // If nothing was found, check in the home directory. if let Some(home_dir) = dirs::home_dir() { - if let Some(path) = get_toml_path(&home_dir)? { + if let Some(path) = get_toml_path(&home_dir, &CONFIG_FILE_NAMES)? { return Ok(Some(path)); } } @@ -389,7 +390,7 @@ impl Config { // If none was found there either, check in the user's configuration directory. if let Some(mut config_dir) = dirs::config_dir() { config_dir.push("rustfmt"); - if let Some(path) = get_toml_path(&config_dir)? { + if let Some(path) = get_toml_path(&config_dir, &CONFIG_FILE_NAMES)? { return Ok(Some(path)); } } @@ -494,10 +495,9 @@ pub fn load_config( // Check for the presence of known config file names (`rustfmt.toml`, `.rustfmt.toml`) in `dir` // // Return the path if a config file exists, empty if no file exists, and Error for IO errors -fn get_toml_path(dir: &Path) -> Result, Error> { - const CONFIG_FILE_NAMES: [&str; 2] = [".rustfmt.toml", "rustfmt.toml"]; - for config_file_name in &CONFIG_FILE_NAMES { - let config_file = dir.join(config_file_name); +fn get_toml_path(dir: &Path, file_names: &[impl AsRef]) -> Result, Error> { + for config_file_name in file_names { + let config_file = dir.join(config_file_name.as_ref()); match fs::metadata(&config_file) { // Only return if it's a file to handle the unlikely situation of a directory named // `rustfmt.toml`. @@ -506,12 +506,10 @@ fn get_toml_path(dir: &Path) -> Result, Error> { // `NotFound` => file not found // `NotADirectory` => rare case where expected directory is a file // Otherwise, return the error - Err(e) => { - if !matches!(e.kind(), ErrorKind::NotFound | ErrorKind::NotADirectory) { - let ctx = format!("Failed to get metadata for config file {:?}", &config_file); - let err = anyhow::Error::new(e).context(ctx); - return Err(Error::new(ErrorKind::Other, err)); - } + Err(e) if !matches!(e.kind(), ErrorKind::NotFound | ErrorKind::NotADirectory) => { + let ctx = format!("Failed to get metadata for config file {:?}", config_file); + let err = anyhow::Error::new(e).context(ctx); + return Err(Error::new(ErrorKind::Other, err)); } _ => {} } @@ -535,7 +533,7 @@ fn config_path(options: &dyn CliOptions) -> Result, Error> { match options.config_path() { Some(path) if !path.exists() => config_path_not_found(path.to_str().unwrap()), Some(path) if path.is_dir() => { - let config_file_path = get_toml_path(path)?; + let config_file_path = get_toml_path(path, &CONFIG_FILE_NAMES)?; if config_file_path.is_some() { Ok(config_file_path) } else { @@ -550,6 +548,8 @@ fn config_path(options: &dyn CliOptions) -> Result, Error> { } } +const CONFIG_FILE_NAMES: [&str; 2] = [".rustfmt.toml", "rustfmt.toml"]; + #[cfg(test)] mod test { use super::*; From f675d14fa6a7942e6f3f5a69e9eed2d01ebe3f4c Mon Sep 17 00:00:00 2001 From: rami3l Date: Fri, 11 Sep 2026 21:18:55 +0200 Subject: [PATCH 3/4] refactor(config): generalize `resolve_project_file()` --- src/config/mod.rs | 96 +++++++++++++++++++++++++---------------------- 1 file changed, 52 insertions(+), 44 deletions(-) diff --git a/src/config/mod.rs b/src/config/mod.rs index 96360a6d44a..38f1c48a60a 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -355,50 +355,7 @@ impl Config { style_edition: Option, version: Option, ) -> Result<(Config, Option), Error> { - /// Try to find a project file in the given directory and its parents. - /// Returns the path of the nearest project file if one exists, - /// or `None` if no project file was found. - fn resolve_project_file(dir: &Path) -> Result, Error> { - let mut current = if dir.is_relative() { - env::current_dir()?.join(dir) - } else { - dir.to_path_buf() - }; - - current = fs::canonicalize(current)?; - - loop { - match get_toml_path(¤t, &CONFIG_FILE_NAMES) { - Ok(Some(path)) => return Ok(Some(path)), - Err(e) => return Err(e), - _ => (), - } - - // If the current directory has no parent, we're done searching. - if !current.pop() { - break; - } - } - - // If nothing was found, check in the home directory. - if let Some(home_dir) = dirs::home_dir() { - if let Some(path) = get_toml_path(&home_dir, &CONFIG_FILE_NAMES)? { - return Ok(Some(path)); - } - } - - // If none was found there either, check in the user's configuration directory. - if let Some(mut config_dir) = dirs::config_dir() { - config_dir.push("rustfmt"); - if let Some(path) = get_toml_path(&config_dir, &CONFIG_FILE_NAMES)? { - return Ok(Some(path)); - } - } - - Ok(None) - } - - match resolve_project_file(dir)? { + match resolve_project_file(dir, &CONFIG_FILE_NAMES, true)? { None => Ok(( Config::default_for_possible_style_edition(style_edition, edition, version), None, @@ -456,6 +413,57 @@ impl Config { } } +/// Try to find a project file in the given directory and its parents. +/// Returns the path of the nearest project file if one exists, +/// or `None` if no project file was found. +fn resolve_project_file( + dir: &Path, + file_names: &[impl AsRef], + user_dirs: bool, +) -> Result, Error> { + let mut current = if dir.is_relative() { + env::current_dir()?.join(dir) + } else { + dir.to_path_buf() + }; + + current = fs::canonicalize(current)?; + + loop { + match get_toml_path(¤t, file_names) { + Ok(Some(path)) => return Ok(Some(path)), + Err(e) => return Err(e), + _ => (), + } + + // If the current directory has no parent, we're done searching. + if !current.pop() { + break; + } + } + + if !user_dirs { + return Ok(None); + } + + // If nothing was found, check in the home directory. + if let Some(home_dir) = dirs::home_dir() { + if let Some(path) = get_toml_path(&home_dir, file_names)? { + return Ok(Some(path)); + } + } + + // If none was found there either, check in the user's configuration directory. + if let Some(mut config_dir) = dirs::config_dir() { + config_dir.push("rustfmt"); + if let Some(path) = get_toml_path(&config_dir, file_names)? { + return Ok(Some(path)); + } + } + + Ok(None) +} + /// Loads a config by checking the client-supplied options and if appropriate, the /// file system (including searching the file system for overrides). pub fn load_config( From 3f3d54b5f88c97f465e8645a410d60f96ad11467 Mon Sep 17 00:00:00 2001 From: rami3l Date: Fri, 11 Sep 2026 21:40:19 +0200 Subject: [PATCH 4/4] fix(config): search for config file recursively on `--config-path` --- src/config/mod.rs | 18 +++++++++++------- tests/rustfmt/main.rs | 31 +++++++++---------------------- 2 files changed, 20 insertions(+), 29 deletions(-) diff --git a/src/config/mod.rs b/src/config/mod.rs index 38f1c48a60a..6526a31b92c 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -539,19 +539,23 @@ fn config_path(options: &dyn CliOptions) -> Result, Error> { // Read the config_path and convert to parent dir if a file is provided. // If a config file cannot be found from the given path, return error. match options.config_path() { - Some(path) if !path.exists() => config_path_not_found(path.to_str().unwrap()), - Some(path) if path.is_dir() => { - let config_file_path = get_toml_path(path, &CONFIG_FILE_NAMES)?; + Some(path) => { + let config_file_path = + if path.as_os_str().as_encoded_bytes().last() == Some(&b'/') || path.is_dir() { + // If the path is a known directory, we interpret itself as the base directory. + resolve_project_file(path, &CONFIG_FILE_NAMES, false) + } else if let Some((dir, file)) = path.parent().zip(path.file_name()) { + // Otherwise, we search for the file's base name in its parent directory. + resolve_project_file(dir, &[file], false) + } else { + Ok(None) + }?; if config_file_path.is_some() { Ok(config_file_path) } else { config_path_not_found(path.to_str().unwrap()) } } - Some(path) => Ok(Some( - // Canonicalize only after checking above that the `path.exists()`. - path.canonicalize()?, - )), None => Ok(None), } } diff --git a/tests/rustfmt/main.rs b/tests/rustfmt/main.rs index 8de9cfded88..6b7d7a68d5a 100644 --- a/tests/rustfmt/main.rs +++ b/tests/rustfmt/main.rs @@ -352,13 +352,9 @@ fn config_path_walks_parent_directories_with_dir_name() { let args = ["--config-path", src_dir, &src_file]; let (stdout, stderr) = rustfmt(&args); - assert_eq!( - stderr, - format!( - "Error: unable to find a config file for the given path: `{}`\n", - Path::new(src_dir).display(), - ) - ); + assert_eq!(stderr, ""); + // Due to `disable_all_formatting = true` in `tests/config/issue_4660/inner_lib/rustfmt.toml`, + // the source file should not be modified. assert_eq!(stdout, ""); } @@ -368,7 +364,6 @@ fn config_path_walks_parent_directories_with_toml_name() { let src_name = "main.rs"; let src_dir = "tests/config/issue_4660/inner_bin/src"; - let toml_file = [src_dir, toml_name].join("/"); let args = [ "--config-path", &[src_dir, toml_name].join("/"), @@ -376,25 +371,17 @@ fn config_path_walks_parent_directories_with_toml_name() { ]; let (stdout, stderr) = rustfmt(&args); - assert_eq!( - stderr, - format!( - "Error: unable to find a config file for the given path: `{}`\n", - Path::new(&toml_file).display(), - ) - ); + assert_eq!(stderr, ""); + // Due to `disable_all_formatting = true` in `tests/config/issue_4660/inner_bin/.rustfmt.unstable.toml`, + // the source file should not be modified. assert_eq!(stdout, ""); let args = ["--config-path", toml_name, src_name]; let (stdout, stderr) = rustfmt_with_extra(&args, Some(src_dir), &[]); - assert_eq!( - stderr, - format!( - "Error: unable to find a config file for the given path: `{}`\n", - Path::new(toml_name).display(), - ) - ); + assert_eq!(stderr, ""); + // Due to `disable_all_formatting = true` in `tests/config/issue_4660/inner_bin/.rustfmt.unstable.toml`, + // the source file should not be modified. assert_eq!(stdout, ""); }