Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Avoid duplicating first-entry comments in uv add #9109

Merged
merged 1 commit into from
Nov 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 87 additions & 1 deletion crates/uv-workspace/src/pyproject_mut.rs
Original file line number Diff line number Diff line change
Expand Up @@ -957,6 +957,23 @@ 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("");
Expand All @@ -979,7 +996,76 @@ pub fn add_dependency(
.unwrap()
.clone();

deps.get_mut(index).unwrap().decor_mut().set_prefix(prefix);
// However, if the prefix includes a comment, we don't want to duplicate it.
// Depending on the location of the comment, we either want to leave it as-is, or
// attach it to the entry that's being moved to the next line.
//
// For example, given:
// ```toml
// dependencies = [ # comment
// "flask",
// ]
// ```
//
// If we add `anyio` to the beginning, we want to retain the comment on the open
// bracket:
// ```toml
// dependencies = [ # comment
// "anyio",
// "flask",
// ]
// ```
//
// However, given:
// ```toml
// dependencies = [
// # comment
// "flask",
// ]
// ```
//
// If we add `anyio` to the beginning, we want the comment to move down with the
// existing entry:
// entry:
// ```toml
// dependencies = [
// "anyio",
// # comment
// "flask",
// ]
if let Some(prefix) = prefix.as_str() {
// Treat anything before the first own-line comment as a prefix on the new
// entry; anything after the first own-line comment is a prefix on the existing
// entry.
//
// This is equivalent to using the first and last line content as the prefix for
// the new entry, and the rest as the prefix for the existing entry.
if let Some((first_line, rest)) = prefix.split_once(['\r', '\n']) {
// Determine the appropriate newline character.
let newline = {
let mut chars = prefix[first_line.len()..].chars();
match (chars.next(), chars.next()) {
(Some('\r'), Some('\n')) => "\r\n",
(Some('\r'), _) => "\r",
(Some('\n'), _) => "\n",
_ => "\n",
}
};
let last_line = rest.lines().last().unwrap_or_default();
let prefix = format!("{first_line}{newline}{last_line}");
deps.get_mut(index).unwrap().decor_mut().set_prefix(prefix);

let prefix = format!("{newline}{rest}");
deps.get_mut(index + 1)
.unwrap()
.decor_mut()
.set_prefix(prefix);
} else {
deps.get_mut(index).unwrap().decor_mut().set_prefix(prefix);
}
} else {
deps.get_mut(index).unwrap().decor_mut().set_prefix(prefix);
}
}

reformat_array_multiline(deps);
Expand Down
149 changes: 149 additions & 0 deletions crates/uv/tests/it/edit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7681,3 +7681,152 @@ fn add_preserves_trailing_depth() -> Result<()> {

Ok(())
}

#[test]
fn add_preserves_first_own_line_comment() -> 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
"sniffio==1.3.1",
]
"#})?;

uv_snapshot!(context.filters(), context.add().arg("charset-normalizer"), @r###"
success: true
exit_code: 0
----- stdout -----

----- stderr -----
Resolved 3 packages in [TIME]
Prepared 2 packages in [TIME]
Installed 2 packages in [TIME]
+ charset-normalizer==3.3.2
+ sniffio==1.3.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 = [
"charset-normalizer>=3.3.2",
# comment
"sniffio==1.3.1",
]
"###
);
});
Ok(())
}

#[test]
fn add_preserves_first_line_bracket_comment() -> 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
"sniffio==1.3.1",
]
"#})?;

uv_snapshot!(context.filters(), context.add().arg("charset-normalizer"), @r###"
success: true
exit_code: 0
----- stdout -----

----- stderr -----
Resolved 3 packages in [TIME]
Prepared 2 packages in [TIME]
Installed 2 packages in [TIME]
+ charset-normalizer==3.3.2
+ sniffio==1.3.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
"charset-normalizer>=3.3.2",
"sniffio==1.3.1",
]
"###
);
});
Ok(())
}

#[test]
fn add_no_indent() -> 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 = [
"sniffio==1.3.1"
]
"#})?;

uv_snapshot!(context.filters(), context.add().arg("charset-normalizer"), @r###"
success: true
exit_code: 0
----- stdout -----

----- stderr -----
Resolved 3 packages in [TIME]
Prepared 2 packages in [TIME]
Installed 2 packages in [TIME]
+ charset-normalizer==3.3.2
+ sniffio==1.3.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 = [
"charset-normalizer>=3.3.2",
"sniffio==1.3.1",
]
"###
);
});
Ok(())
}
Loading