From 28f9bb86f836753a4d9d490a2ae7ab8512106d67 Mon Sep 17 00:00:00 2001 From: Dextheking1 Date: Thu, 24 Sep 2026 02:00:14 +0200 Subject: [PATCH 1/2] fix: indent match arm body when conditions break onto multiple lines When a match arm's conditions don't fit on one line, the `=>` moves to its own indented line, but the body kept the arm's base indent for its own line breaks. This made nested structures (e.g. arrays, calls) indent relative to the wrong level. Wrap `=> ` and the body in `indentIfBreak` tied to the conditions group's break status, so the body gets the extra indent level only when the conditions actually broke. Fixes #2435 --- src/printer.mjs | 18 ++++++--- tests/match/__snapshots__/jsfmt.spec.mjs.snap | 40 ++++++++++++++++++- tests/match/match.php | 7 ++++ 3 files changed, 59 insertions(+), 6 deletions(-) diff --git a/src/printer.mjs b/src/printer.mjs index f789a340b..9a9286684 100644 --- a/src/printer.mjs +++ b/src/printer.mjs @@ -49,6 +49,7 @@ const { indent, dedent, ifBreak, + indentIfBreak, hardline, hardlineWithoutBreakParent, softline, @@ -2885,17 +2886,24 @@ function printNode(path, options, print) { ? hardline : ""; + const condsGroupId = Symbol("match-arm-conds"); + return [ "", hardline, maybeEmptyLineBetweenArms, ...maybeLeadingComment, group([ - group([conds, indent(line)]), - "=> ", - body, - maybeTrailingComma, - ...maybeTrailingComment, + group([conds, indent(line)], { id: condsGroupId }), + // When the conditions break onto multiple lines, the `=>` moves to + // its own indented line - the body then needs the same extra + // indent level for its own line breaks (see #2435). + indentIfBreak( + ["=> ", body, maybeTrailingComma, ...maybeTrailingComment], + { + groupId: condsGroupId, + } + ), ]), ].slice(!path.isFirst ? 0 : 1); }, "arms"); diff --git a/tests/match/__snapshots__/jsfmt.spec.mjs.snap b/tests/match/__snapshots__/jsfmt.spec.mjs.snap index 2279ebfaf..3852dd2a0 100644 --- a/tests/match/__snapshots__/jsfmt.spec.mjs.snap +++ b/tests/match/__snapshots__/jsfmt.spec.mjs.snap @@ -1,4 +1,4 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing exports[`match.php 1`] = ` ====================================options===================================== @@ -70,6 +70,13 @@ match($really_really_really_really_really_really_really_really_long_variable_nam default => null }; +// https://github.com/prettier/plugin-php/issues/2435 +// When the conditions of a match arm break onto multiple lines, the body +// on the indented \`=>\` line must keep the extra indent for its own breaks. +echo match ($operation) { + "short", "really very long option 2" => SomeClass::fairlyLongMethodNameThatRunsOn($variable, ["short", "short"]), +}; + =====================================output===================================== null }; +// https://github.com/prettier/plugin-php/issues/2435 +// When the conditions of a match arm break onto multiple lines, the body +// on the indented \`=>\` line must keep the extra indent for its own breaks. +echo match ($operation) { + "short", + "really very long option 2" + => SomeClass::fairlyLongMethodNameThatRunsOn($variable, [ + "short", + "short" + ]) +}; + ================================================================================ `; @@ -231,6 +250,13 @@ match($really_really_really_really_really_really_really_really_long_variable_nam default => null }; +// https://github.com/prettier/plugin-php/issues/2435 +// When the conditions of a match arm break onto multiple lines, the body +// on the indented \`=>\` line must keep the extra indent for its own breaks. +echo match ($operation) { + "short", "really very long option 2" => SomeClass::fairlyLongMethodNameThatRunsOn($variable, ["short", "short"]), +}; + =====================================output===================================== null, }; +// https://github.com/prettier/plugin-php/issues/2435 +// When the conditions of a match arm break onto multiple lines, the body +// on the indented \`=>\` line must keep the extra indent for its own breaks. +echo match ($operation) { + "short", + "really very long option 2" + => SomeClass::fairlyLongMethodNameThatRunsOn($variable, [ + "short", + "short", + ]), +}; + ================================================================================ `; diff --git a/tests/match/match.php b/tests/match/match.php index 907a13086..fc668ecb3 100644 --- a/tests/match/match.php +++ b/tests/match/match.php @@ -59,3 +59,10 @@ match($really_really_really_really_really_really_really_really_long_variable_name) { default => null }; + +// https://github.com/prettier/plugin-php/issues/2435 +// When the conditions of a match arm break onto multiple lines, the body +// on the indented `=>` line must keep the extra indent for its own breaks. +echo match ($operation) { + "short", "really very long option 2" => SomeClass::fairlyLongMethodNameThatRunsOn($variable, ["short", "short"]), +}; From 9f4bac789f046f740656dcaccea0339d3ca47ca1 Mon Sep 17 00:00:00 2001 From: Dextheking1 Date: Fri, 23 Oct 2026 11:19:31 +0200 Subject: [PATCH 2/2] test: cover match arms with chain and array bodies when conditions break --- tests/match/__snapshots__/jsfmt.spec.mjs.snap | 138 ++++++++++++++++++ tests/match/match.php | 36 +++++ 2 files changed, 174 insertions(+) diff --git a/tests/match/__snapshots__/jsfmt.spec.mjs.snap b/tests/match/__snapshots__/jsfmt.spec.mjs.snap index 3852dd2a0..567672ea3 100644 --- a/tests/match/__snapshots__/jsfmt.spec.mjs.snap +++ b/tests/match/__snapshots__/jsfmt.spec.mjs.snap @@ -77,6 +77,42 @@ echo match ($operation) { "short", "really very long option 2" => SomeClass::fairlyLongMethodNameThatRunsOn($variable, ["short", "short"]), }; +// Same, with a method chain as the body and a trailing comment on the arm. +echo match ($operation) { + "a very long first option text", + "a very long second option text", + "a third option" + => $service + ->firstStep($input) + ->secondStep(outerFunction(innerFunction(deepestFunction($value)))) + ->thirdStep(fn($item) => $item + 1), // trailing comment + default => null, +}; + +// Same, with a method chain whose last step takes a nested chain argument. +echo match ($operation) { + "a very long first option text", + "a very long second option text", + "a third option" + => $service + ->firstStep($input) + ->secondStep($second) + ->thirdStep(makeThing($inner->levelFour($deep))->levelTwo($second)), + default => null, +}; + +// Same, with an array as the body. +echo match ($operation) { + "a very long first option text", + "a very long second option text", + "a third option" + => [ + "first entry of the array body", + "second entry of the array body", + ], + default => null, +}; + =====================================output===================================== $service + ->firstStep($input) + ->secondStep(outerFunction(innerFunction(deepestFunction($value)))) + ->thirdStep(fn($item) => $item + 1), // trailing comment + default => null +}; + +// Same, with a method chain whose last step takes a nested chain argument. +echo match ($operation) { + "a very long first option text", + "a very long second option text", + "a third option" + => $service + ->firstStep($input) + ->secondStep($second) + ->thirdStep(makeThing($inner->levelFour($deep))->levelTwo($second)), + default => null +}; + +// Same, with an array as the body. +echo match ($operation) { + "a very long first option text", + "a very long second option text", + "a third option" + => ["first entry of the array body", "second entry of the array body"], + default => null +}; + ================================================================================ `; @@ -257,6 +326,42 @@ echo match ($operation) { "short", "really very long option 2" => SomeClass::fairlyLongMethodNameThatRunsOn($variable, ["short", "short"]), }; +// Same, with a method chain as the body and a trailing comment on the arm. +echo match ($operation) { + "a very long first option text", + "a very long second option text", + "a third option" + => $service + ->firstStep($input) + ->secondStep(outerFunction(innerFunction(deepestFunction($value)))) + ->thirdStep(fn($item) => $item + 1), // trailing comment + default => null, +}; + +// Same, with a method chain whose last step takes a nested chain argument. +echo match ($operation) { + "a very long first option text", + "a very long second option text", + "a third option" + => $service + ->firstStep($input) + ->secondStep($second) + ->thirdStep(makeThing($inner->levelFour($deep))->levelTwo($second)), + default => null, +}; + +// Same, with an array as the body. +echo match ($operation) { + "a very long first option text", + "a very long second option text", + "a third option" + => [ + "first entry of the array body", + "second entry of the array body", + ], + default => null, +}; + =====================================output===================================== $service + ->firstStep($input) + ->secondStep(outerFunction(innerFunction(deepestFunction($value)))) + ->thirdStep(fn($item) => $item + 1), // trailing comment + default => null, +}; + +// Same, with a method chain whose last step takes a nested chain argument. +echo match ($operation) { + "a very long first option text", + "a very long second option text", + "a third option" + => $service + ->firstStep($input) + ->secondStep($second) + ->thirdStep(makeThing($inner->levelFour($deep))->levelTwo($second)), + default => null, +}; + +// Same, with an array as the body. +echo match ($operation) { + "a very long first option text", + "a very long second option text", + "a third option" + => ["first entry of the array body", "second entry of the array body"], + default => null, +}; + ================================================================================ `; diff --git a/tests/match/match.php b/tests/match/match.php index fc668ecb3..7c1420632 100644 --- a/tests/match/match.php +++ b/tests/match/match.php @@ -66,3 +66,39 @@ echo match ($operation) { "short", "really very long option 2" => SomeClass::fairlyLongMethodNameThatRunsOn($variable, ["short", "short"]), }; + +// Same, with a method chain as the body and a trailing comment on the arm. +echo match ($operation) { + "a very long first option text", + "a very long second option text", + "a third option" + => $service + ->firstStep($input) + ->secondStep(outerFunction(innerFunction(deepestFunction($value)))) + ->thirdStep(fn($item) => $item + 1), // trailing comment + default => null, +}; + +// Same, with a method chain whose last step takes a nested chain argument. +echo match ($operation) { + "a very long first option text", + "a very long second option text", + "a third option" + => $service + ->firstStep($input) + ->secondStep($second) + ->thirdStep(makeThing($inner->levelFour($deep))->levelTwo($second)), + default => null, +}; + +// Same, with an array as the body. +echo match ($operation) { + "a very long first option text", + "a very long second option text", + "a third option" + => [ + "first entry of the array body", + "second entry of the array body", + ], + default => null, +};