Skip to content

📝 test.each(...) arguments not wrapped, overflowing lineWidth (2.3.6 regression) #10727

Description

@ichizok

Environment information

Details
CLI:
  Version:                      2.5.0
  Color support:                true

Platform:
  CPU Architecture:             x86_64
  OS:                           linux

Environment:
  BIOME_DISTRIBUTION:                npm
  BIOME_LOG_PATH:                    unset
  BIOME_LOG_PREFIX_NAME:             unset
  BIOME_LOG_LEVEL:                   unset
  BIOME_LOG_KIND:                    unset
  BIOME_CONFIG_PATH:                 unset
  BIOME_THREADS:                     unset
  BIOME_WATCHER_KIND:                unset
  BIOME_WATCHER_POLLING_INTERVAL:    unset
  NO_COLOR:                     unset
  TERM:                         xterm-256color
  JS_RUNTIME_VERSION:           v26.3.1
  JS_RUNTIME_NAME:              node
  NODE_PACKAGE_MANAGER:         npm/11.16.0

Biome Configuration:
  Status:                       Loaded successfully.
  Path:                         biome.json
  Formatter enabled:            true
  Linter enabled:               true
  Assist enabled:               true
  VCS enabled:                  false
  HTML full support enabled:    unset

Formatter:
  Format with errors:           unset
  Indent style:                 Space
  Indent width:                 2
  Line ending:                  unset
  Line width:                   unset
  Attribute position:           unset
  Bracket spacing:              unset
  Includes:                     unset

JavaScript Formatter:
  Enabled:                      unset
  JSX quote style:              unset
  Quote properties:             unset
  Trailing commas:              unset
  Semicolons:                   unset
  Arrow parentheses:            unset
  Bracket spacing:              unset
  Bracket same line:            unset
  Quote style:                  unset
  Indent style:                 unset
  Indent width:                 unset
  Line ending:                  unset
  Line width:                   unset
  Attribute position:           unset

JSON Formatter:
  Enabled:                      unset
  Indent style:                 unset
  Indent width:                 unset
  Line ending:                  unset
  Line width:                   unset
  Trailing Commas:              unset
  Expand lists:                 unset

CSS Formatter:
  Enabled:                      unset
  Indent style:                 unset
  Indent width:                 unset
  Line ending:                  unset
  Line width:                   unset
  Quote style:                  unset

GraphQL Formatter:
  Enabled:                      unset
  Indent style:                 unset
  Indent width:                 unset
  Line ending:                  unset
  Line width:                   unset
  Bracket spacing:              unset
  Quote style:                  unset

Workspace:
  Open Documents:               0

Environment

  • Biome: reproduced on 2.3.6 → 2.5.0 (latest). Last good version: 2.3.5.
  • Prettier (for comparison): 3.x

Problem

Since v2.3.6, the formatter collapses the arguments of a table-driven test call test.each([...])("description", (args) => { ... }) onto a single line, the ])("description", (args) => { opening line even when it clearly exceeds the configured lineWidth.

This output no longer matches Prettier. Up to and including v2.3.5, Biome broke the arguments onto separate lines, identical to Prettier's output; since v2.3.6 Biome diverges from Prettier on this pattern.

The change appears to be an unintended side effect of a lint-rule fix (#7287), not a deliberate formatter change (there is no formatter entry in the 2.3.6 changelog).

Reproduction

repro.ts:

test.each([[1, 2]])(
  "a description that is long enough to push the hugged opening line beyond the print width",
  (a, b) => {
    expect(a).toBe(b);
  },
);

Run with default options (lineWidth/printWidth = 80):

biome format repro.ts
prettier repro.ts

Expected (Biome ≤ 2.3.5 and Prettier)

No fixes.

Actual (Biome 2.3.6+)

test.each([
    [1, 2],
])("a description that is long enough to push the hugged opening line beyond the print width", (a, b) => {
    expect(a).toBe(b);
});

The opening line is ~100+ columns wide, well over lineWidth = 80.

Bisect

version result
≤ 2.3.5 expanded (matches Prettier)
2.3.6 … 2.5.0 single-line / overflows lineWidth

Only recognized test names trigger it (test.each / it.each / describe.each → collapsed; a non-test callee such as foo.each(...) is unaffected and still expands). This confirms it is gated on test-call detection, not on width.

Root cause

The formatter's "put all arguments on one line" layout for test calls is here:

|| (is_test_call? && is_first_arg_string_literal_or_template)

|| (is_test_call? && is_first_arg_string_literal_or_template)

is_test_call comes from the shared JsCallExpression::is_test_call_expression() in below, which is used by both the linter and the formatter.

pub fn is_test_call_expression(&self) -> SyntaxResult<bool> {

Commit aa55c8d (#7287) extended that shared method to also recognize the .each / .for double-call pattern:

 (Some(Ok(AnyJsCallArgument::AnyJsExpression(_))), Some(Ok(second)), third)
-    if arguments.args().len() <= 3 && callee.contains_a_test_pattern()? =>
+    if arguments.args().len() <= 3
+        && (callee.contains_a_test_pattern()
+            || matches!(callee, AnyJsExpression::JsCallExpression(call_expression)
+                 if call_expression.callee()?.contains_a_test_each_pattern())) =>

The PR targeted the noDuplicateTestHooks lint rule, but because is_test_call_expression() is shared, test.each([...])("desc", cb) is now classified as a test call and takes the single-line layout in call_arguments.rs so overflowing lineWidth. The formatter code itself was not changed in 2.3.6.

Configuration

{
  "formatter": {
    "enabled": true,
    "indentStyle": "space",
    "indentWidth": 2
  }
}

Playground link

https://biomejs.dev/playground/?indentStyle=space&tab=formatter&pane=Diagnostics&code=dABlAHMAdAAuAGUAYQBjAGgAKABbAFsAMQAsACAAMgBdAF0AKQAoAAoAIAAgACIAYQAgAGQAZQBzAGMAcgBpAHAAdABpAG8AbgAgAHQAaABhAHQAIABpAHMAIABsAG8AbgBnACAAZQBuAG8AdQBnAGgAIAB0AG8AIABwAHUAcwBoACAAdABoAGUAIABoAHUAZwBnAGUAZAAgAG8AcABlAG4AaQBuAGcAIABsAGkAbgBlACAAYgBlAHkAbwBuAGQAIAB0AGgAZQAgAHAAcgBpAG4AdAAgAHcAaQBkAHQAaAAiACwACgAgACAAKABhACwAIABiACkAIAA9AD4AIAB7AAoAIAAgACAAIABlAHgAcABlAGMAdAAoAGEAKQAuAHQAbwBCAGUAKABiACkAOwAKACAAIAB9ACwACgApADsACgA%3D

Code of Conduct

  • I agree to follow Biome's Code of Conduct

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-FormatterArea: formatterL-JavaScriptLanguage: JavaScript and super languagesS-Bug-confirmedStatus: report has been confirmed as a valid bug

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions