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;
|
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() {
|
for item in deps.iter_mut() {
|
||||||
let decor = item.decor_mut();
|
let decor = item.decor_mut();
|
||||||
let mut prefix = String::new();
|
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())) {
|
for comment in find_comments(decor.prefix()).chain(find_comments(decor.suffix())) {
|
||||||
match comment.comment_type {
|
match comment.comment_type {
|
||||||
CommentType::OwnLine => {
|
CommentType::OwnLine => {
|
||||||
|
|
|
||||||
|
|
@ -13595,3 +13595,58 @@ fn add_path_outside_workspace_no_default() -> Result<()> {
|
||||||
|
|
||||||
Ok(())
|
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