Skip to content

Commit a656f70

Browse files
Preserve inline comments when updating dependencies (#21008)
## Summary We now preserve inline comments attached to the final dependency in a TOML array when `uv add` updates an existing requirement and the entry does not end in a comma. Without a trailing comma, `toml_edit` stores the inline comment in the final item's suffix. Our array formatter previously treated that suffix as a prefix, moving the comment onto the preceding dependency. Move the suffix into the array's trailing decoration before adding the comma so the comment remains attached to the updated dependency. For example, given: ```toml typing = [ "pandas-stubs>=2.0.2", "narwhals>=1.42.0" # narwhals are toothed whales ] ``` Updating `narwhals` now produces: ```toml typing = [ "pandas-stubs>=2.0.2", "narwhals>=1.42", # narwhals are toothed whales ] ``` The regression is covered by an optional-dependency integration test; all 11 related integration tests and 18 `pyproject_mut` tests pass, along with formatting and Clippy. Closes #21006.
1 parent bdef5f8 commit a656f70

2 files changed

Lines changed: 61 additions & 0 deletions

File tree

crates/uv-workspace/src/pyproject_mut.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1894,6 +1894,19 @@ fn reformat_array_multiline(deps: &mut Array) {
18941894
Box::new(iter)
18951895
}
18961896

1897+
// Without a trailing comma, `toml_edit` stores comments after the final item in its
1898+
// suffix. Once we add a trailing comma, those comments must follow the comma instead.
1899+
if !deps.trailing_comma()
1900+
&& let Some(last) = deps.iter_mut().last()
1901+
&& let Some(suffix) = last.decor().suffix().and_then(RawString::as_str)
1902+
&& suffix.contains('#')
1903+
{
1904+
let suffix = suffix.to_string();
1905+
last.decor_mut().set_suffix("");
1906+
let trailing = deps.trailing().as_str().unwrap_or_default();
1907+
deps.set_trailing(format!("{suffix}{trailing}"));
1908+
}
1909+
18971910
let mut indentation_prefix = None;
18981911

18991912
// Calculate the indentation prefix based on the indentation of the first dependency entry.

crates/uv/tests/project/edit.rs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11451,6 +11451,54 @@ fn add_preserves_end_of_line_comment_on_non_last_deps() -> Result<()> {
1145111451
Ok(())
1145211452
}
1145311453

11454+
#[test]
11455+
fn add_preserves_end_of_line_comment_on_updated_optional_dependency() -> Result<()> {
11456+
let context = uv_test::test_context!("3.12");
11457+
11458+
let pyproject_toml = context.temp_dir.child("pyproject.toml");
11459+
pyproject_toml.write_str(indoc! {r#"
11460+
[project]
11461+
name = "project"
11462+
version = "0.1.0"
11463+
requires-python = ">=3.12"
11464+
dependencies = []
11465+
11466+
[project.optional-dependencies]
11467+
typing = [
11468+
"pandas-stubs>=2.0.2",
11469+
"narwhals>=1.42.0" # narwhals are toothed whales native to the Arctic
11470+
]
11471+
"#})?;
11472+
11473+
uv_snapshot!(context.filters(), context.add().arg("narwhals>=1.42").arg("--optional=typing").arg("--frozen"), @"
11474+
exit_code: 0 (success)
11475+
");
11476+
11477+
let pyproject_toml = context.read("pyproject.toml");
11478+
11479+
insta::with_settings!({
11480+
filters => context.filters(),
11481+
}, {
11482+
assert_snapshot!(
11483+
pyproject_toml, @r#"
11484+
[project]
11485+
name = "project"
11486+
version = "0.1.0"
11487+
requires-python = ">=3.12"
11488+
dependencies = []
11489+
11490+
[project.optional-dependencies]
11491+
typing = [
11492+
"pandas-stubs>=2.0.2",
11493+
"narwhals>=1.42", # narwhals are toothed whales native to the Arctic
11494+
]
11495+
"#
11496+
);
11497+
});
11498+
11499+
Ok(())
11500+
}
11501+
1145411502
#[test]
1145511503
fn add_direct_url_subdirectory() -> Result<()> {
1145611504
let context = uv_test::test_context!("3.12");

0 commit comments

Comments
 (0)