Retain end-of-line comment position when adding dependency (#12360)

<!--
Thank you for contributing to uv! To help us out with reviewing, please
consider the following:

- Does this pull request include a summary of the change? (See below.)
- Does this pull request include a descriptive title?
- Does this pull request include references to any relevant issues?
-->

## Summary

<!-- What's the purpose of the change? What does it do, and why? -->

This fixes a case described in #12333, where trailing comments in
dependencies can be unexpectedly shifted when a new dependency is added.

Fixes #12333.

## Test Plan

<!-- How was it tested? -->

`cargo test` (Added a snapshot test)
This commit is contained in:
Christopher Tee
2025-03-21 22:16:15 +08:00
committed by GitHub
parent 4eb7ab27ab
commit bdef77c3fe
2 changed files with 106 additions and 21 deletions
+50 -21
View File
@@ -1004,27 +1004,56 @@ pub fn add_dependency(
let decor = value.decor_mut();
// If we're adding to the end of the list, treat trailing comments as leading comments
// on the added dependency.
//
// For example, given:
// ```toml
// dependencies = [
// "anyio", # trailing comment
// ]
// ```
//
// If we add `flask` to the end, we want to retain the comment on `anyio`:
// ```toml
// dependencies = [
// "anyio", # trailing comment
// "flask",
// ]
// ```
if index == deps.len() {
decor.set_prefix(deps.trailing().clone());
deps.set_trailing("");
}
// Ensure comments remain on the correct line, post-insertion
match index {
val if val == deps.len() => {
// If we're adding to the end of the list, treat trailing comments as leading comments
// on the added dependency.
//
// For example, given:
// ```toml
// dependencies = [
// "anyio", # trailing comment
// ]
// ```
//
// If we add `flask` to the end, we want to retain the comment on `anyio`:
// ```toml
// dependencies = [
// "anyio", # trailing comment
// "flask",
// ]
// ```
decor.set_prefix(deps.trailing().clone());
deps.set_trailing("");
}
0 => {
// If the dependency is prepended to a non-empty list, do nothing
}
val => {
// Retain position of end-of-line comments when a dependency is inserted right below it.
//
// For example, given:
// ```toml
// dependencies = [
// "anyio", # end-of-line comment
// "flask",
// ]
// ```
//
// If we add `pydantic` (between `anyio` and `flask`), we want to retain the comment on `anyio`:
// ```toml
// dependencies = [
// "anyio", # end-of-line comment
// "pydantic",
// "flask",
// ]
// ```
let targeted_decor = deps.get_mut(val).unwrap().decor_mut();
decor.set_prefix(targeted_decor.prefix().unwrap().clone());
targeted_decor.set_prefix(""); // Re-formatted later by `reformat_array_multiline`
}
};
deps.insert_formatted(index, value);
+56
View File
@@ -8829,6 +8829,62 @@ fn add_preserves_end_of_line_comments() -> Result<()> {
Ok(())
}
#[test]
fn add_preserves_end_of_line_comment_on_non_last_deps() -> Result<()> {
let context = TestContext::new("3.12");
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 = [ # comment
"anyio==3.7.0", # comment 1
"sniffio==1.3.1",
]
"#})?;
uv_snapshot!(context.filters(), context.add().arg("requests==2.31.0"), @r"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 8 packages in [TIME]
Prepared 7 packages in [TIME]
Installed 7 packages in [TIME]
+ anyio==3.7.0
+ certifi==2024.2.2
+ charset-normalizer==3.3.2
+ idna==3.6
+ requests==2.31.0
+ sniffio==1.3.1
+ urllib3==2.2.1
");
let pyproject_toml = context.read("pyproject.toml");
insta::with_settings!({
filters => context.filters(),
}, {
assert_snapshot!(
pyproject_toml, @r###"
[project]
name = "project"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = [ # comment
"anyio==3.7.0", # comment 1
"requests==2.31.0",
"sniffio==1.3.1",
]
"###
);
});
Ok(())
}
#[test]
fn add_direct_url_subdirectory() -> Result<()> {
let context = TestContext::new("3.12");