From aa0786d0620b977082e518064dd9354abd20cc7d Mon Sep 17 00:00:00 2001 From: Todor Andonov Date: Thu, 28 May 2026 14:11:48 +0300 Subject: [PATCH 1/2] fix: add trailing comma after wrapped arrow expression body in call (#696) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `gen_parameters_or_arguments` has a special branch for single-argument calls whose argument is an arrow function with an expression body. When the body wraps the call across lines, the branch emitted a plain `Signal::NewLine` before the closing paren, never a comma — so: ["a", "b", "c"].map(value => value == "a long paragraph" ) …stayed comma-less even with `trailingCommas: always`. Emit `,` in that conditional branch when `trailingCommas` resolves to `always`. `onlyMultiLine` keeps its previous behavior to avoid churn for the default config; switching it would update many existing specs and could be revisited separately. --- src/generation/generate.rs | 14 +++- .../Arguments_TrailingCommas_Always.txt | 73 +++++++++++++++++++ tests/specs/issues/issue0696.txt | 34 +++++++++ 3 files changed, 120 insertions(+), 1 deletion(-) create mode 100644 tests/specs/general/Arguments_TrailingCommas_Always.txt create mode 100644 tests/specs/issues/issue0696.txt diff --git a/src/generation/generate.rs b/src/generation/generate.rs index b12063546..8b6f27578 100644 --- a/src/generation/generate.rs +++ b/src/generation/generate.rs @@ -7602,6 +7602,18 @@ where items.push_signal(Signal::SpaceIfNotTrailing); } items.push_condition(conditions::indent_if_start_of_line(generated_node)); + // When the arrow's expression body forced the call onto multiple lines and + // the trailing-comma configuration is `always`, emit the trailing comma + // before the closing paren (issue #696). `onlyMultiLine` historically did + // not emit a comma here, so preserve that behavior to avoid churn. + let trailing_break_items = { + let mut break_items = PrintItems::new(); + if matches!(trailing_commas, TrailingCommas::Always) { + break_items.push_sc(sc!(",")); + } + break_items.push_signal(Signal::NewLine); + break_items + }; items.push_condition(if_true_or( "isDifferentLineAndStartLineIndentation", Rc::new(move |context| { @@ -7611,7 +7623,7 @@ where let is_different_start_line_indentation = start_lsil != context.writer_info.line_start_indent_level; Some(is_different_line && is_different_start_line_indentation) }), - Signal::NewLine.into(), + trailing_break_items, if space_around { Signal::SpaceIfNotTrailing.into() } else { PrintItems::new() }, )); } else { diff --git a/tests/specs/general/Arguments_TrailingCommas_Always.txt b/tests/specs/general/Arguments_TrailingCommas_Always.txt new file mode 100644 index 000000000..01269745b --- /dev/null +++ b/tests/specs/general/Arguments_TrailingCommas_Always.txt @@ -0,0 +1,73 @@ +~~ lineWidth: 60, trailingCommas: always ~~ +== should add trailing comma to single-arg call == +foo(x); + +[expect] +foo(x,); + +== should add trailing comma to multi-arg call == +foo(a, b, c); + +[expect] +foo(a, b, c,); + +== should add trailing comma in multi-line call == +foo( + aLongerName, + anotherLongerName +); + +[expect] +foo( + aLongerName, + anotherLongerName, +); + +== should add trailing comma after arrow expression body when call wraps (issue #696) == +[1, 2, 3].map(value => + value == "a quite long paragraph with many words") + +[expect] +[1, 2, 3,].map(value => + value == "a quite long paragraph with many words", +); + +== should add trailing comma after block-bodied arrow == +foo( + (v) => { + return v; + } +) + +[expect] +foo( + (v,) => { + return v; + }, +); + +== should not add trailing comma after rest argument in call when never-trailing applies == +foo(...args); + +[expect] +foo(...args,); + +== should not add trailing comma in dynamic import == +import( + "myreallylongdynamicallyloadedmodulename" +); + +[expect] +import( + "myreallylongdynamicallyloadedmodulename" +); + +== should add trailing comma when last arg is arrow whose body wraps == +a.b().c({ d: 1 }, () => + doSomethingThatDoesNotFitOnTheSameLine()) + +[expect] +a.b().c( + { d: 1, }, + () => doSomethingThatDoesNotFitOnTheSameLine(), +); diff --git a/tests/specs/issues/issue0696.txt b/tests/specs/issues/issue0696.txt new file mode 100644 index 000000000..b26b43db9 --- /dev/null +++ b/tests/specs/issues/issue0696.txt @@ -0,0 +1,34 @@ +~~ lineWidth: 60, trailingCommas: always ~~ +== should add trailing comma after arrow expression body when call breaks across lines (issue #696) == +const test = ["a","b","c"].map(value => + value == "a quite long paragraph with many words") + +[expect] +const test = ["a", "b", "c",].map(value => + value == "a quite long paragraph with many words", +); + +== should add trailing comma after nested expression body that wraps == +longCallExpression(value => + someVeryLongCondition && anotherCondition) + +[expect] +longCallExpression(value => + someVeryLongCondition && anotherCondition, +); + +== should not add trailing comma when arrow body stays on the same line == +foo(v => v); + +[expect] +foo(v => v); + +== should keep dynamic import without trailing comma == +import( + "myreallylongdynamicallyloadedmodulename" +); + +[expect] +import( + "myreallylongdynamicallyloadedmodulename" +); From f836bbe3387e17670b812cd7af499affbcfcf144 Mon Sep 17 00:00:00 2001 From: Todor Andonov Date: Thu, 28 May 2026 14:16:00 +0300 Subject: [PATCH 2/2] remove explanatory comment in trailing-comma fix --- src/generation/generate.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/generation/generate.rs b/src/generation/generate.rs index 8b6f27578..b3c2adc7d 100644 --- a/src/generation/generate.rs +++ b/src/generation/generate.rs @@ -7602,10 +7602,6 @@ where items.push_signal(Signal::SpaceIfNotTrailing); } items.push_condition(conditions::indent_if_start_of_line(generated_node)); - // When the arrow's expression body forced the call onto multiple lines and - // the trailing-comma configuration is `always`, emit the trailing comma - // before the closing paren (issue #696). `onlyMultiLine` historically did - // not emit a comma here, so preserve that behavior to avoid churn. let trailing_break_items = { let mut break_items = PrintItems::new(); if matches!(trailing_commas, TrailingCommas::Always) {