mirror of https://github.com/astral-sh/uv
Ensure consistent indentation when adding dependencies (#14991)
## Summary
The basic problem here is that when we had multiple items in an inline
array, and that array expanded to multiple lines, we accidentally
changed the indentation part-way through due to how prefixes work in the
TOML.
Here's Claude's explanation of the root cause, which I find pretty
decent:
```
Here's what happened step by step:
1. First item ("iniconfig"): Has empty prefix "" → indentation_prefix stays None → uses default 4 spaces
2. Second item ("ruff"): Has empty prefix "" → indentation_prefix stays None → uses default 4 spaces
3. Third item ("typing-extensions"): Has prefix " " (single space from inline format) → indentation_prefix becomes
Some(" ") → uses only 1 space!
This produced:
[dependency-groups]
dev = [
"iniconfig>=2.0.0",
"ruff",
"typing-extensions", # ← Only 1 space instead of 4!
]
Why the Third Item Had a Different Prefix
In inline arrays like ["ruff", "typing-extensions"], the items are separated by commas and spaces. When parsed by
the TOML library:
- "ruff" has no prefix (it comes right after [)
- "typing-extensions" has a single space prefix (the space after the comma)
The Fix
Moving the indentation calculation outside the loop ensures it's calculated only once:
// Calculate indentation ONCE before the loop
if let Some(first_item) = deps.iter().next() {
let decor_prefix = /* get prefix from first item */
indentation_prefix = (!decor_prefix.is_empty()).then_some(decor_prefix.to_string());
}
// Now use the same indentation for ALL items
for item in deps.iter_mut() {
// Apply consistent indentation to every item
}
This ensures all items get the same indentation (4 spaces by default when converting from inline arrays), producing
the correct output:
[dependency-groups]
dev = [
"iniconfig>=2.0.0",
"ruff",
"typing-extensions", # ← Correct 4-space indentation
]
The bug only affected arrays being converted from inline to multiline format, where different items might have
different residual formatting from their inline representation.
```
Closes #14961.
---------
Co-authored-by: Zanie Blue <contact@zanie.dev>
This commit is contained in:
parent
fc0f637406
commit
3564e882d7
|
|
@ -1562,29 +1562,29 @@ fn reformat_array_multiline(deps: &mut Array) {
|
|||
|
||||
let mut indentation_prefix = None;
|
||||
|
||||
// Calculate the indentation prefix based on the indentation of the first dependency entry.
|
||||
if let Some(first_item) = deps.iter().next() {
|
||||
let decor_prefix = first_item
|
||||
.decor()
|
||||
.prefix()
|
||||
.and_then(|s| s.as_str())
|
||||
.and_then(|s| s.lines().last())
|
||||
.unwrap_or_default();
|
||||
|
||||
let decor_prefix = decor_prefix
|
||||
.split_once('#')
|
||||
.map(|(s, _)| s)
|
||||
.unwrap_or(decor_prefix);
|
||||
|
||||
indentation_prefix = (!decor_prefix.is_empty()).then_some(decor_prefix.to_string());
|
||||
}
|
||||
|
||||
let indentation_prefix_str = format!("\n{}", indentation_prefix.as_deref().unwrap_or(" "));
|
||||
|
||||
for item in deps.iter_mut() {
|
||||
let decor = item.decor_mut();
|
||||
let mut prefix = String::new();
|
||||
|
||||
// Calculate the indentation prefix based on the indentation of the first dependency entry.
|
||||
if indentation_prefix.is_none() {
|
||||
let decor_prefix = decor
|
||||
.prefix()
|
||||
.and_then(|s| s.as_str())
|
||||
.and_then(|s| s.lines().last())
|
||||
.unwrap_or_default();
|
||||
|
||||
let decor_prefix = decor_prefix
|
||||
.split_once('#')
|
||||
.map(|(s, _)| s)
|
||||
.unwrap_or(decor_prefix);
|
||||
|
||||
indentation_prefix = (!decor_prefix.is_empty()).then_some(decor_prefix.to_string());
|
||||
}
|
||||
|
||||
let indentation_prefix_str =
|
||||
format!("\n{}", indentation_prefix.as_deref().unwrap_or(" "));
|
||||
|
||||
for comment in find_comments(decor.prefix()).chain(find_comments(decor.suffix())) {
|
||||
match comment.comment_type {
|
||||
CommentType::OwnLine => {
|
||||
|
|
|
|||
|
|
@ -13595,3 +13595,58 @@ fn add_path_outside_workspace_no_default() -> Result<()> {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// See: <https://github.com/astral-sh/uv/issues/14961>
|
||||
#[test]
|
||||
fn add_multiline_indentation() -> 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"
|
||||
|
||||
[dependency-groups]
|
||||
dev = ["ruff", "typing-extensions"]
|
||||
"#})?;
|
||||
|
||||
uv_snapshot!(context.filters(), context.add().arg("iniconfig").arg("--dev"), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Resolved 4 packages in [TIME]
|
||||
Prepared 3 packages in [TIME]
|
||||
Installed 3 packages in [TIME]
|
||||
+ iniconfig==2.0.0
|
||||
+ ruff==0.3.4
|
||||
+ typing-extensions==4.10.0
|
||||
");
|
||||
|
||||
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"
|
||||
|
||||
[dependency-groups]
|
||||
dev = [
|
||||
"iniconfig>=2.0.0",
|
||||
"ruff",
|
||||
"typing-extensions",
|
||||
]
|
||||
"#
|
||||
);
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue