diff --git a/.changeset/fix-switch-case-comment-idempotence.md b/.changeset/fix-switch-case-comment-idempotence.md new file mode 100644 index 000000000000..af6a8aea333b --- /dev/null +++ b/.changeset/fix-switch-case-comment-idempotence.md @@ -0,0 +1,5 @@ +--- +"@biomejs/biome": patch +--- + +Fixed [#2786](https://github.com/biomejs/biome/issues/2786): The formatter no longer produces different output on subsequent runs when a `case` clause has a trailing line comment followed by a single block statement. diff --git a/crates/biome_js_formatter/src/js/auxiliary/case_clause.rs b/crates/biome_js_formatter/src/js/auxiliary/case_clause.rs index b5d169f388fd..ed929c351bd6 100644 --- a/crates/biome_js_formatter/src/js/auxiliary/case_clause.rs +++ b/crates/biome_js_formatter/src/js/auxiliary/case_clause.rs @@ -70,12 +70,22 @@ impl FormatNodeRule for FormatJsCaseClause { // default: // break; // } + // If the case clause has a trailing line comment after the colon + // (e.g. `case 1337: // ELITE`), we must not hug the block statement on + // the same line. Doing so produces `case 1337: { // ELITE`, and on the + // next format pass the comment is reparsed as a leading comment inside + // the block, causing it to move to its own line — an idempotence bug. + // The comment is stored as a trailing comment of the `test` node. + let has_trailing_comment = test + .as_ref() + .is_ok_and(|test| f.comments().has_trailing_comments(test.syntax())); + if consequent.is_empty() { // Print nothing to ensure that trailing comments on the same line // are printed on the same line. The parent list formatter takes // care of inserting a hard line break between cases. Ok(()) - } else if is_single_block_statement { + } else if is_single_block_statement && !has_trailing_comment { write![f, [space(), consequent.format()]] } else { // no line break needed after because it is added by the indent in the switch statement diff --git a/crates/biome_js_formatter/tests/specs/js/module/statement/switch_comment.js b/crates/biome_js_formatter/tests/specs/js/module/statement/switch_comment.js index 2c580066140f..8a3eae592673 100644 --- a/crates/biome_js_formatter/tests/specs/js/module/statement/switch_comment.js +++ b/crates/biome_js_formatter/tests/specs/js/module/statement/switch_comment.js @@ -16,3 +16,21 @@ switch(x) { a(); // ab break; } + +// Trailing comment on a case clause followed by a single block statement. +// The comment must stay on the `case` line and not migrate into the block +// on subsequent format passes (idempotence, issue #2786). +function cool(x) { + switch (x) { + case 4: // guaranteed to be random + case 42: // classic + case 1337: // ELITE + { + console.log("x is cool"); + break; + } + default: { + console.error("x is not cool"); + } + } +} diff --git a/crates/biome_js_formatter/tests/specs/js/module/statement/switch_comment.js.snap b/crates/biome_js_formatter/tests/specs/js/module/statement/switch_comment.js.snap index 1cd2885978a6..ed1369305d3f 100644 --- a/crates/biome_js_formatter/tests/specs/js/module/statement/switch_comment.js.snap +++ b/crates/biome_js_formatter/tests/specs/js/module/statement/switch_comment.js.snap @@ -25,6 +25,24 @@ switch(x) { break; } +// Trailing comment on a case clause followed by a single block statement. +// The comment must stay on the `case` line and not migrate into the block +// on subsequent format passes (idempotence, issue #2786). +function cool(x) { + switch (x) { + case 4: // guaranteed to be random + case 42: // classic + case 1337: // ELITE + { + console.log("x is cool"); + break; + } + default: { + console.error("x is not cool"); + } + } +} + ``` @@ -75,4 +93,22 @@ switch (x) { break; } +// Trailing comment on a case clause followed by a single block statement. +// The comment must stay on the `case` line and not migrate into the block +// on subsequent format passes (idempotence, issue #2786). +function cool(x) { + switch (x) { + case 4: // guaranteed to be random + case 42: // classic + case 1337: // ELITE + { + console.log("x is cool"); + break; + } + default: { + console.error("x is not cool"); + } + } +} + ```