Skip to content

feat(js-formatter): implement delimiterSpacing for JSX#9720

Merged
ematipico merged 7 commits into
biomejs:nextfrom
luisherranz:feat/delimiter-spacing-jsx
Apr 23, 2026
Merged

feat(js-formatter): implement delimiterSpacing for JSX#9720
ematipico merged 7 commits into
biomejs:nextfrom
luisherranz:feat/delimiter-spacing-jsx

Conversation

@luisherranz

Copy link
Copy Markdown
Contributor

Summary

Implements delimiterSpacing for JSX, building on #9719 (JS).

For JSX, affects:

  • Expression attribute values (e.g., attr={ value })
  • Spread attributes (e.g., { ...props })
  • Expression children (e.g., { children })
  • Spread children (e.g., { ...items })
- <Foo bar={value} />
+ <Foo bar={ value } />
- <Foo>{children}</Foo>
+ <Foo>{ children }</Foo>

Depends on: #9719 (JS), which in turn depends on #9718 (core). Since this PR is from a fork, the base is set to next. The diff will include core and JS commits until those PRs are merged.

AI usage

AI assisted in implementing the JSX formatter changes and writing tests.

Test Plan

  • Added snapshot tests for JSX expression attributes and expression children

Add the `delimiterSpacing` formatter option. This option inserts spaces
inside delimiters when content fits on a single line. Empty delimiters
are not affected, and no space is added before the opening delimiter.
The specific delimiters affected depend on the language.

Includes the shared DelimiterSpacing type, global configuration,
overrides support, Prettier migration, CLI flag, generated
schema/bindings, and changeset.
@changeset-bot

changeset-bot Bot commented Mar 30, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 3fb8118

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 14 packages
Name Type
@biomejs/biome Minor
@biomejs/cli-win32-x64 Minor
@biomejs/cli-win32-arm64 Minor
@biomejs/cli-darwin-x64 Minor
@biomejs/cli-darwin-arm64 Minor
@biomejs/cli-linux-x64 Minor
@biomejs/cli-linux-arm64 Minor
@biomejs/cli-linux-x64-musl Minor
@biomejs/cli-linux-arm64-musl Minor
@biomejs/wasm-web Minor
@biomejs/wasm-bundler Minor
@biomejs/wasm-nodejs Minor
@biomejs/backend-jsonrpc Patch
@biomejs/js-api Major

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-actions github-actions Bot added A-CLI Area: CLI A-Project Area: project A-Formatter Area: formatter L-JavaScript Language: JavaScript and super languages labels Mar 30, 2026
Implement delimiterSpacing for JSX nodes: expression attribute values,
spread attributes, expression children, and spread children.
@luisherranz luisherranz force-pushed the feat/delimiter-spacing-jsx branch from 32676cb to 855f84c Compare March 31, 2026 12:07
@luisherranz luisherranz marked this pull request as ready for review March 31, 2026 12:40
@coderabbitai

coderabbitai Bot commented Mar 31, 2026

Copy link
Copy Markdown
Contributor

Walkthrough

This pull request introduces a new delimiterSpacing formatter option to Biome that inserts spaces inside delimiters (parentheses, brackets, template literals, JSX braces) when formatted content fits on a single line. The option is configurable globally via formatter.delimiterSpacing or per-language (JavaScript, JSON, CSS). Configuration infrastructure was added to FormatterConfiguration, JsFormatterConfiguration, and OverrideFormatterConfiguration, alongside a new DelimiterSpacing type in the formatter library. The JavaScript and JSX formatter implementations were updated across numerous files to conditionally apply spacing based on the option value. Service layer integration was added to resolve and propagate the setting through the formatting pipeline. Comprehensive test fixtures validate delimiter spacing behaviour across arrays, arrow functions, call arguments, computed members, control flow, loops, parameters, setters, template literals, unary expressions, and JSX elements.

Suggested reviewers

  • dyc3
🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat(js-formatter): implement delimiterSpacing for JSX' directly describes the main change—adding delimiterSpacing support for JSX constructs.
Description check ✅ Passed The description clearly explains the feature, which constructs are affected, provides before/after examples, and references related PRs and test coverage.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
crates/biome_js_formatter/src/jsx/auxiliary/expression_child.rs (1)

73-91: ⚠️ Potential issue | 🟡 Minor

Don’t pad empty JSX expression children.

When expression is None and there are no dangling comments, the delimiter_spacing branch still emits { }. That breaks the new “empty delimiters stay untouched” contract and makes recovered JSX a bit noisier than necessary. Please only add the extra spaces when there is actual comment content to wrap.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/biome_js_formatter/src/jsx/auxiliary/expression_child.rs` around lines
73 - 91, The current branch pads empty JSX expression children when
delimiter_spacing is true even if there are no dangling comments; change the
logic so you only add surrounding spaces when there are actual dangling comments
to render. Compute a boolean (e.g., has_dangling_comment) using
comments.leading_dangling_trailing_comments(node.syntax()).any(...) to detect
any dangling comments, then replace the else if condition to require both
delimiter_spacing and has_dangling_comment before writing [space(),
format_dangling_comments(node.syntax()), space()]; keep the existing
has_line_comment branch unchanged.
🧹 Nitpick comments (2)
crates/biome_js_formatter/src/js/objects/computed_member_name.rs (1)

18-40: Consider using soft_block_indent_with_maybe_space for consistency.

This implementation uses explicit space() tokens rather than soft_block_indent_with_maybe_space, which other bracket-delimited constructs (arrays, array patterns) use. The current approach means:

  • obj[ veryLongExpressionHere ] won't get soft line breaks when content overflows
  • Behaviour diverges from similar constructs

If computed member names are intentionally kept short/inline, this is fine. Otherwise, consider aligning with the pattern used elsewhere:

♻️ Optional: align with other bracket patterns
-        let should_insert_space = f.options().delimiter_spacing().value();
-
-        if should_insert_space {
-            write![
-                f,
-                [
-                    l_brack_token.format(),
-                    space(),
-                    expression.format(),
-                    space(),
-                    r_brack_token.format(),
-                ]
-            ]
-        } else {
-            write![
-                f,
-                [
-                    l_brack_token.format(),
-                    expression.format(),
-                    r_brack_token.format(),
-                ]
-            ]
-        }
+        let should_insert_space = f.options().delimiter_spacing().value();
+
+        write![
+            f,
+            [
+                l_brack_token.format(),
+                group(&soft_block_indent_with_maybe_space(
+                    &expression.format(),
+                    should_insert_space
+                )),
+                r_brack_token.format(),
+            ]
+        ]
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/biome_js_formatter/src/js/objects/computed_member_name.rs` around
lines 18 - 40, The current computed member formatting uses explicit space()
tokens based on should_insert_space, which prevents soft line breaks; replace
the explicit spacing around expression.format() with the same pattern used for
other bracket-delimited constructs by using soft_block_indent_with_maybe_space
(or the equivalent helper) together with l_brack_token.format(),
r_brack_token.format(), and expression.format() so the formatter will insert a
soft indent/space and allow line breaks when the expression is long; update the
branch that checks should_insert_space (and the else branch if needed) to use
soft_block_indent_with_maybe_space instead of direct space() calls while still
honoring f.options().delimiter_spacing().value() and the existing tokens
l_brack_token and r_brack_token.
crates/biome_js_formatter/tests/specs/js/module/delimiter-spacing/computed_members.js (1)

53-64: Minor duplication of foo[a] test case.

Lines 53 and 63 both test foo[a]. Since they're in different sections with different semantic purposes, this is fine, but you could consider removing one if consolidation is desired.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@crates/biome_js_formatter/tests/specs/js/module/delimiter-spacing/computed_members.js`
around lines 53 - 64, The test file contains a duplicated test case for the
computed member expression foo[a] in two different sections; to resolve, remove
one of the duplicate occurrences (either the first foo[a] or the later one under
the "Computed member expressions - dynamic expressions" section) or consolidate
them into a single test entry so the suite only checks foo[a] once while
preserving coverage for other cases like foo["a"] and foo[bar].
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.changeset/delimiter-spacing-js.md:
- Around line 5-15: Update the changeset text in
.changeset/delimiter-spacing-js.md to reflect that delimiterSpacing applies to
both JavaScript and JSX brace spacing: modify the title and body to mention JSX
braces in addition to JS, add a JSX example demonstrating spacing inside JSX
expression braces (e.g., <Component prop={ value }/> style), and ensure wording
clarifies the behavior for template literals, array/object/parenthesis
delimiters, logical NOT chains, and that empty delimiters/opening-space rules
are unchanged so the release note accurately covers both JS and JSX.

In
`@crates/biome_js_formatter/tests/specs/js/module/delimiter-spacing/template_elements.js`:
- Around line 45-47: The test inputs for arr1/arr2/arr3 are inconsistent: arr1
uses an extra space in the template expression (`${ []}`) while arr2 and arr3
use `${[... ]}`; either remove the extra space in the arr1 template expression
to match the others (change the template to use `${[]}`) or, if the spacing is
intentional to test normalization, add a brief comment above the arr1
declaration explaining the purpose; locate the arr1, arr2, arr3 declarations in
the delimiter-spacing template_elements test and apply the chosen change.

In `@crates/biome_js_formatter/tests/specs/jsx/delimiter_spacing/options.json`:
- Around line 1-8: The $schema field value in this JSON spec is using one too
many "../" segments; update the "$schema" property so it uses six parent
directories instead of seven (change
"../../../../../../../packages/@biomejs/biome/configuration_schema.json" to
"../../../../../../packages/@biomejs/biome/configuration_schema.json") to
correct the schema path resolution.

---

Outside diff comments:
In `@crates/biome_js_formatter/src/jsx/auxiliary/expression_child.rs`:
- Around line 73-91: The current branch pads empty JSX expression children when
delimiter_spacing is true even if there are no dangling comments; change the
logic so you only add surrounding spaces when there are actual dangling comments
to render. Compute a boolean (e.g., has_dangling_comment) using
comments.leading_dangling_trailing_comments(node.syntax()).any(...) to detect
any dangling comments, then replace the else if condition to require both
delimiter_spacing and has_dangling_comment before writing [space(),
format_dangling_comments(node.syntax()), space()]; keep the existing
has_line_comment branch unchanged.

---

Nitpick comments:
In `@crates/biome_js_formatter/src/js/objects/computed_member_name.rs`:
- Around line 18-40: The current computed member formatting uses explicit
space() tokens based on should_insert_space, which prevents soft line breaks;
replace the explicit spacing around expression.format() with the same pattern
used for other bracket-delimited constructs by using
soft_block_indent_with_maybe_space (or the equivalent helper) together with
l_brack_token.format(), r_brack_token.format(), and expression.format() so the
formatter will insert a soft indent/space and allow line breaks when the
expression is long; update the branch that checks should_insert_space (and the
else branch if needed) to use soft_block_indent_with_maybe_space instead of
direct space() calls while still honoring
f.options().delimiter_spacing().value() and the existing tokens l_brack_token
and r_brack_token.

In
`@crates/biome_js_formatter/tests/specs/js/module/delimiter-spacing/computed_members.js`:
- Around line 53-64: The test file contains a duplicated test case for the
computed member expression foo[a] in two different sections; to resolve, remove
one of the duplicate occurrences (either the first foo[a] or the later one under
the "Computed member expressions - dynamic expressions" section) or consolidate
them into a single test entry so the suite only checks foo[a] once while
preserving coverage for other cases like foo["a"] and foo[bar].
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: f1423606-ce38-40ad-89f2-07b096b22cb6

📥 Commits

Reviewing files that changed from the base of the PR and between 701767a and 855f84c.

⛔ Files ignored due to path filters (23)
  • crates/biome_cli/tests/snapshots/main_cases_help/check_help.snap is excluded by !**/*.snap and included by **
  • crates/biome_cli/tests/snapshots/main_cases_help/ci_help.snap is excluded by !**/*.snap and included by **
  • crates/biome_cli/tests/snapshots/main_cases_help/format_help.snap is excluded by !**/*.snap and included by **
  • crates/biome_cli/tests/snapshots/main_commands_format/applies_global_delimiter_spacing.snap is excluded by !**/*.snap and included by **
  • crates/biome_cli/tests/snapshots/main_commands_format/language_overrides_global_delimiter_spacing.snap is excluded by !**/*.snap and included by **
  • crates/biome_configuration/tests/invalid/formatter_extraneous_field.json.snap is excluded by !**/*.snap and included by **
  • crates/biome_configuration/tests/invalid/formatter_quote_style.json.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_formatter/tests/specs/js/module/delimiter-spacing/arrays.js.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_formatter/tests/specs/js/module/delimiter-spacing/arrow_functions.js.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_formatter/tests/specs/js/module/delimiter-spacing/call_arguments.js.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_formatter/tests/specs/js/module/delimiter-spacing/computed_members.js.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_formatter/tests/specs/js/module/delimiter-spacing/conditionals_misc.js.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_formatter/tests/specs/js/module/delimiter-spacing/control_flow.js.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_formatter/tests/specs/js/module/delimiter-spacing/loops.js.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_formatter/tests/specs/js/module/delimiter-spacing/parameters_catch.js.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_formatter/tests/specs/js/module/delimiter-spacing/parenthesized.js.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_formatter/tests/specs/js/module/delimiter-spacing/setters.js.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_formatter/tests/specs/js/module/delimiter-spacing/template_elements.js.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_formatter/tests/specs/js/module/delimiter-spacing/unary.js.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_formatter/tests/specs/jsx/delimiter_spacing/jsx_expression_attrs.jsx.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_formatter/tests/specs/jsx/delimiter_spacing/jsx_expression_children.jsx.snap is excluded by !**/*.snap and included by **
  • packages/@biomejs/backend-jsonrpc/src/workspace.ts is excluded by !**/backend-jsonrpc/src/workspace.ts and included by **
  • packages/@biomejs/biome/configuration_schema.json is excluded by !**/configuration_schema.json and included by **
📒 Files selected for processing (55)
  • .changeset/delimiter-spacing-js.md
  • .changeset/delimiter-spacing-jsx.md
  • .changeset/delimiter-spacing-option.md
  • crates/biome_cli/src/execute/migrate/prettier.rs
  • crates/biome_cli/tests/commands/format.rs
  • crates/biome_configuration/src/formatter.rs
  • crates/biome_configuration/src/javascript/formatter.rs
  • crates/biome_configuration/src/overrides.rs
  • crates/biome_formatter/src/lib.rs
  • crates/biome_js_formatter/src/context.rs
  • crates/biome_js_formatter/src/js/assignments/array_assignment_pattern.rs
  • crates/biome_js_formatter/src/js/auxiliary/template_element.rs
  • crates/biome_js_formatter/src/js/bindings/array_binding_pattern.rs
  • crates/biome_js_formatter/src/js/bindings/parameters.rs
  • crates/biome_js_formatter/src/js/classes/setter_class_member.rs
  • crates/biome_js_formatter/src/js/declarations/catch_declaration.rs
  • crates/biome_js_formatter/src/js/expressions/array_expression.rs
  • crates/biome_js_formatter/src/js/expressions/arrow_function_expression.rs
  • crates/biome_js_formatter/src/js/expressions/call_arguments.rs
  • crates/biome_js_formatter/src/js/expressions/computed_member_expression.rs
  • crates/biome_js_formatter/src/js/expressions/parenthesized_expression.rs
  • crates/biome_js_formatter/src/js/expressions/unary_expression.rs
  • crates/biome_js_formatter/src/js/objects/computed_member_name.rs
  • crates/biome_js_formatter/src/js/objects/setter_object_member.rs
  • crates/biome_js_formatter/src/js/statements/do_while_statement.rs
  • crates/biome_js_formatter/src/js/statements/for_in_statement.rs
  • crates/biome_js_formatter/src/js/statements/for_of_statement.rs
  • crates/biome_js_formatter/src/js/statements/for_statement.rs
  • crates/biome_js_formatter/src/js/statements/if_statement.rs
  • crates/biome_js_formatter/src/js/statements/switch_statement.rs
  • crates/biome_js_formatter/src/js/statements/while_statement.rs
  • crates/biome_js_formatter/src/jsx/attribute/expression_attribute_value.rs
  • crates/biome_js_formatter/src/jsx/attribute/spread_attribute.rs
  • crates/biome_js_formatter/src/jsx/auxiliary/expression_child.rs
  • crates/biome_js_formatter/src/jsx/auxiliary/spread_child.rs
  • crates/biome_js_formatter/src/lib.rs
  • crates/biome_js_formatter/src/utils/jsx.rs
  • crates/biome_js_formatter/tests/specs/js/module/delimiter-spacing/arrays.js
  • crates/biome_js_formatter/tests/specs/js/module/delimiter-spacing/arrow_functions.js
  • crates/biome_js_formatter/tests/specs/js/module/delimiter-spacing/call_arguments.js
  • crates/biome_js_formatter/tests/specs/js/module/delimiter-spacing/computed_members.js
  • crates/biome_js_formatter/tests/specs/js/module/delimiter-spacing/conditionals_misc.js
  • crates/biome_js_formatter/tests/specs/js/module/delimiter-spacing/control_flow.js
  • crates/biome_js_formatter/tests/specs/js/module/delimiter-spacing/loops.js
  • crates/biome_js_formatter/tests/specs/js/module/delimiter-spacing/options.json
  • crates/biome_js_formatter/tests/specs/js/module/delimiter-spacing/parameters_catch.js
  • crates/biome_js_formatter/tests/specs/js/module/delimiter-spacing/parenthesized.js
  • crates/biome_js_formatter/tests/specs/js/module/delimiter-spacing/setters.js
  • crates/biome_js_formatter/tests/specs/js/module/delimiter-spacing/template_elements.js
  • crates/biome_js_formatter/tests/specs/js/module/delimiter-spacing/unary.js
  • crates/biome_js_formatter/tests/specs/jsx/delimiter_spacing/jsx_expression_attrs.jsx
  • crates/biome_js_formatter/tests/specs/jsx/delimiter_spacing/jsx_expression_children.jsx
  • crates/biome_js_formatter/tests/specs/jsx/delimiter_spacing/options.json
  • crates/biome_service/src/file_handlers/javascript.rs
  • crates/biome_service/src/settings.rs

Comment thread .changeset/delimiter-spacing-js.md
Comment thread crates/biome_js_formatter/src/js/statements/for_of_statement.rs Outdated
@codspeed-hq

codspeed-hq Bot commented Apr 6, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 58 untouched benchmarks
⏩ 196 skipped benchmarks1


Comparing luisherranz:feat/delimiter-spacing-jsx (3fb8118) with next (f516854)

Open in CodSpeed

Footnotes

  1. 196 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@ematipico ematipico added this to the Biome v2.5 milestone Apr 19, 2026
@github-actions github-actions Bot removed the A-Project Area: project label Apr 23, 2026

@ematipico ematipico left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll take care of the changeset towards the end

@luisherranz

Copy link
Copy Markdown
Contributor Author

Sorry, I haven't had time to review the rest of the PRs to get them up to date, but I think I can find some time to do it this afternoon or tomorrow.

@ematipico ematipico merged commit 19d68b2 into biomejs:next Apr 23, 2026
27 of 28 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-CLI Area: CLI A-Formatter Area: formatter L-JavaScript Language: JavaScript and super languages

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants