Skip to content

Commit 36e51a5

Browse files
committed
fix: break container instead of expanding one of many object siblings (#641)
Previously `const a = [{...}, {...}, {...}, {...}];` could format as const a = [{...}, {...}, {...}, { longest, }]; — a single sibling was promoted to inline multi-line while the others stayed inline. dprint's resolver does this whenever every value passes `allows_inline_multi_line` (issue #641). Adjust `gen_separated_values_with_result`: precompute the inline-multi-line flags, then count siblings that would actually compete for the slot — ignoring arrow/function-expression candidates, which are the canonical prettier-style hugging shape (`Array.from(x, () => { … })`, `foo(arg, () => {})`). If two or more non-arrow siblings remain, none get inline-multi-line, so the resolver breaks the container. This aligns the array-of-objects layout with prettier: const a = [ { name: "FPN" }, { name: "FPN Class" }, … ]; The change also applies to function-call arguments and tuple types, keeping behavior consistent across all `gen_separated_values` consumers. Spec updates (existing snaps that pinned the old hugging shape): - specs/expressions/ArrayExpression/ArrayExpression_PreferSingleLine_True.txt - specs/issues/issue0520.txt (large nested-JSON snapshot) - specs/issues/old_repo/issue067.txt - specs/types/TupleType/TupleType_PreferSingleLine_True.txt Last-arg arrow hugging (issue0312) is preserved by the new `is_function_hugging_candidate` helper that drops arrow/fn siblings from the count. New spec: specs/issues/issue0641.txt covering the regression, the preserved arrow-hugging behavior, the single-object-hugging behavior, and tuple-of-types / array-of-arrays variants. Patterns cross-referenced against prettier's tests/format/js/arrays fixtures.
1 parent f7dd1f3 commit 36e51a5

6 files changed

Lines changed: 506 additions & 337 deletions

File tree

src/generation/generate.rs

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7912,6 +7912,26 @@ fn gen_separated_values<'a>(opts: GenSeparatedValuesParams<'a>, context: &mut Co
79127912
gen_separated_values_with_result(opts, context).items
79137913
}
79147914

7915+
/// True when the value is an arrow / function expression (possibly wrapped in
7916+
/// ExprOrSpread / ParenExpr). These are the prettier-style "last arg hugging"
7917+
/// candidates that should keep inline-multi-line privileges even when there are
7918+
/// other siblings that could also expand inline.
7919+
fn is_function_hugging_candidate(value: &NodeOrSeparator) -> bool {
7920+
fn is_arrow_or_fn(expr: Expr) -> bool {
7921+
match expr {
7922+
Expr::Arrow(_) | Expr::Fn(_) => true,
7923+
Expr::Paren(paren) => is_arrow_or_fn(paren.expr),
7924+
_ => false,
7925+
}
7926+
}
7927+
match value {
7928+
NodeOrSeparator::Node(Node::ArrowExpr(_)) | NodeOrSeparator::Node(Node::FnExpr(_)) => true,
7929+
NodeOrSeparator::Node(Node::ExprOrSpread(e)) => is_arrow_or_fn(e.expr),
7930+
NodeOrSeparator::Node(Node::ParenExpr(p)) => is_arrow_or_fn(p.expr),
7931+
_ => false,
7932+
}
7933+
}
7934+
79157935
fn gen_separated_values_with_result<'a>(opts: GenSeparatedValuesParams<'a>, context: &mut Context<'a>) -> GenSeparatedValuesResult {
79167936
let nodes = opts.nodes;
79177937
let separator = opts.separator;
@@ -7931,15 +7951,34 @@ fn gen_separated_values_with_result<'a>(opts: GenSeparatedValuesParams<'a>, cont
79317951
let is_multi_line_or_hanging = is_multi_line_or_hanging_ref.create_resolver();
79327952
let mut generated_nodes = Vec::new();
79337953
let nodes_count = nodes.len();
7954+
let inline_multi_line_flags: Vec<bool> = nodes
7955+
.iter()
7956+
.map(|value| match value {
7957+
NodeOrSeparator::Node(v) => allows_inline_multi_line(*v, context, nodes_count > 1),
7958+
_ => false,
7959+
})
7960+
.collect();
7961+
// Count siblings that would compete for an inline-multi-line slot, but
7962+
// ignore arrow/function-expression last-arg "hugging" candidates — those
7963+
// are the canonical `Array.from(x, () => { ... })` / `foo(arg, () => {})`
7964+
// shape that prettier keeps single-line at the call level. Without this
7965+
// exclusion the array-of-objects fix from issue #641 would regress the
7966+
// last-arrow-arg case.
7967+
let inline_multi_line_count = nodes
7968+
.iter()
7969+
.zip(inline_multi_line_flags.iter())
7970+
.filter(|(value, flag)| **flag && !is_function_hugging_candidate(value))
7971+
.count();
7972+
let allow_any_inline_multi_line = inline_multi_line_count <= 1;
79347973

79357974
for (i, value) in nodes.into_iter().enumerate() {
79367975
let node_index = match &sorted_indexes {
79377976
Some(old_to_new_index) => *old_to_new_index.get(i).unwrap(),
79387977
None => i,
79397978
};
7940-
let (allow_inline_multi_line, allow_inline_single_line) = if let NodeOrSeparator::Node(value) = value {
7941-
let is_last_value = node_index + 1 == nodes_count; // allow the last node to be single line
7942-
(allows_inline_multi_line(value, context, nodes_count > 1), is_last_value)
7979+
let (allow_inline_multi_line, allow_inline_single_line) = if let NodeOrSeparator::Node(_) = value {
7980+
let is_last_value = node_index + 1 == nodes_count;
7981+
(allow_any_inline_multi_line && inline_multi_line_flags[i], is_last_value)
79437982
} else {
79447983
(false, false)
79457984
};

tests/specs/expressions/ArrayExpression/ArrayExpression_PreferSingleLine_True.txt

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,19 +33,22 @@ const a = [
3333
["123456", "123456"], //
3434
];
3535

36-
== should not be multi line if the objects are allowed to be inline multi-line ==
36+
== should break the container when multiple object siblings could each go inline multi-line (issue #641) ==
3737
const t = [{
3838
prop: 5
3939
}, {
4040
other: "string"
4141
}];
4242

4343
[expect]
44-
const t = [{
45-
prop: 5,
46-
}, {
47-
other: "string",
48-
}];
44+
const t = [
45+
{
46+
prop: 5,
47+
},
48+
{
49+
other: "string",
50+
},
51+
];
4952

5053
== should maintain blank lines when exceeding the line width ==
5154
const t = [

0 commit comments

Comments
 (0)