From e6def431107dabf0a2339be5687d318631f9f503 Mon Sep 17 00:00:00 2001 From: Jorge Hermo Date: Fri, 22 Aug 2025 22:09:42 +0200 Subject: [PATCH] Run the formatter in the project root instead of the working directory (#15440) Closes https://github.com/astral-sh/uv/issues/15430 This PR is a branch from https://github.com/astral-sh/uv/pull/15438, opening it as a draft until that is merged to main and I'll merge main with this branch to have a cleaner diff --------- Co-authored-by: Zanie Blue --- crates/uv/src/commands/project/format.rs | 10 ++++- crates/uv/tests/it/format.rs | 54 ++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 1 deletion(-) diff --git a/crates/uv/src/commands/project/format.rs b/crates/uv/src/commands/project/format.rs index bf8f852c9..ceea36135 100644 --- a/crates/uv/src/commands/project/format.rs +++ b/crates/uv/src/commands/project/format.rs @@ -10,6 +10,7 @@ use uv_client::BaseClientBuilder; use uv_configuration::{Preview, PreviewFeatures}; use uv_pep440::Version; use uv_warnings::warn_user; +use uv_workspace::{DiscoveryOptions, VirtualProject, WorkspaceCache}; use crate::child::run_to_completion; use crate::commands::ExitStatus; @@ -36,6 +37,12 @@ pub(crate) async fn format( PreviewFeatures::FORMAT ); } + + let workspace_cache = WorkspaceCache::default(); + let project = + VirtualProject::discover(project_dir, &DiscoveryOptions::default(), &workspace_cache) + .await?; + // Parse version if provided let version = version.as_deref().map(Version::from_str).transpose()?; @@ -55,7 +62,8 @@ pub(crate) async fn format( .context("Failed to install ruff {version}")?; let mut command = Command::new(&ruff_path); - command.current_dir(project_dir); + // Run ruff in the project root + command.current_dir(project.root()); command.arg("format"); if check { diff --git a/crates/uv/tests/it/format.rs b/crates/uv/tests/it/format.rs index 5a4e830e8..90e343bd7 100644 --- a/crates/uv/tests/it/format.rs +++ b/crates/uv/tests/it/format.rs @@ -55,6 +55,60 @@ fn format_project() -> Result<()> { Ok(()) } +#[test] +fn format_from_project_root() -> Result<()> { + let context = TestContext::new_with_versions(&[]); + + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str(indoc! {r#" + [project] + name = "project" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = [] + "#})?; + + // Create an unformatted Python file + let main_py = context.temp_dir.child("main.py"); + main_py.write_str(indoc! {r#" + import sys + def hello(): + print( "Hello, World!" ) + if __name__=="__main__": + hello( ) + "#})?; + + let subdir = context.temp_dir.child("subdir"); + fs_err::create_dir_all(&subdir)?; + + // Using format from a subdirectory should still run in the project root + uv_snapshot!(context.filters(), context.format().current_dir(&subdir), @r" + success: true + exit_code: 0 + ----- stdout ----- + 1 file reformatted + + ----- stderr ----- + warning: `uv format` is experimental and may change without warning. Pass `--preview-features format` to disable this warning. + "); + + // Check that the file was formatted + let formatted_content = fs_err::read_to_string(&main_py)?; + assert_snapshot!(formatted_content, @r#" + import sys + + + def hello(): + print("Hello, World!") + + + if __name__ == "__main__": + hello() + "#); + + Ok(()) +} + #[test] fn format_relative_project() -> Result<()> { let context = TestContext::new_with_versions(&[]);