Skip to content

fix(parser): allow nested destructured arrow functions inside ternary-consequent bodies#10152

Open
Zelys-DFKH wants to merge 2 commits into
biomejs:mainfrom
Zelys-DFKH:fix/parser-nested-destructured-arrow-ternary
Open

fix(parser): allow nested destructured arrow functions inside ternary-consequent bodies#10152
Zelys-DFKH wants to merge 2 commits into
biomejs:mainfrom
Zelys-DFKH:fix/parser-nested-destructured-arrow-ternary

Conversation

@Zelys-DFKH
Copy link
Copy Markdown

@Zelys-DFKH Zelys-DFKH commented Apr 29, 2026

Fixes #10131.

The parser was rejecting valid code like this:

const handleClick = onClick
  ? (offset: number) =>
      ({ photo, index, event }: ClickHandlerProps<TPhoto>) => {
        onClick({ photos: photosArray, index: offset + index, photo, event });
      }
  : undefined;

parse_arrow_body uses parse_assignment_expression_or_higher_no_arrow when in_conditional_consequent is set and the body starts with ({ or ([. The guard is there for a real reason: in TypeScript mode, the ternary : can be misread as a return-type annotation, and the alternate's => gets consumed as the arrow, making a parenthesized object literal look like a valid arrow function.

The guard was too broad. It was blocking legitimate nested arrows along with the false positives it was meant to prevent.

Before falling back to _no_arrow, a new helper scans forward token by token (tracking paren depth) to find the closing ), then checks whether => follows immediately. If it does, the expression must be a nested arrow, so normal parsing is allowed. => only appears in arrow function syntax, which makes it a cleaner discriminator than checking : (TypeScript return-type annotations also produce :).

fn is_paren_group_followed_by_fat_arrow(p: &mut JsParser) -> bool {
    // tracks ( / ) depth so parens inside type annotations don't mislead
    ...
    return p.nth_at(offset + 1, T![=>]);
}

One pre-existing edge case stays unchanged from main: a nested arrow with both destructuring params and a return-type annotation, like ({x}: T): R => body, inside another arrow body in a ternary consequent. That's a separate problem and I kept scope narrow here.

Test plan

Added crates/biome_js_parser/tests/js_test_suite/ok/conditional_nested_arrow_in_consequent.ts and .js, covering:

  • Object destructuring: (offset: number) => ({ photo, index, event }: ClickHandlerProps<TPhoto>) => { ... }
  • Array destructuring: (n: number) => ([a, b]: [string, string]) => { ... }
  • Existing behavior preserved: (i: number) => ({ [CONTENT_SLOT]: i }) still parses the body as a parenthesized object expression

All 688 parser spec tests pass.

Docs

No documentation changes needed.

…-consequent bodies

Fixes biomejs#10131. When `parse_arrow_body` enters the expression-body path with
`in_conditional_consequent` set and the body starts with `({` or `([`, it was
falling back to `parse_assignment_expression_or_higher_no_arrow` unconditionally.
That guard prevents a TypeScript speculative-parse false positive (the ternary `:`
being consumed as a return-type annotation), but it was too broad and also blocked
legitimate nested arrow functions.

The fix adds `is_paren_group_followed_by_fat_arrow`, a token-level lookahead that
scans forward (tracking paren depth) and checks whether `=>` immediately follows
the closing `)`. `=>` is syntactically unambiguous and a better discriminator than
`:`. The `_no_arrow` guard is kept only when `=>` does not follow.

Tests added for both TypeScript and JavaScript.
@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Apr 29, 2026

🦋 Changeset detected

Latest commit: 1db27bc

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

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

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-Parser Area: parser L-JavaScript Language: JavaScript and super languages labels Apr 29, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 29, 2026

Walkthrough

This PR fixes a parser regression where curried arrow functions with destructured parameters inside a ternary consequent were mis-parsed. It adds a lookahead that matches a parenthesised parameter group to check for a following =>, forces the ternary-consequent flag off when parsing arrow bodies, and suppresses speculative “no-arrow” parsing for ({ or ([ starts unless the group is actually followed by =>. A changeset and JS/TS regression tests were added.

Suggested reviewers

  • dyc3
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed Title clearly and concisely describes the main fix: allowing nested destructured arrow functions inside ternary-consequent bodies, matching the core change.
Linked Issues check ✅ Passed The PR fully addresses #10131 by implementing parser fixes [parser function.rs, changeset] to restore correct parsing of nested destructured arrow functions in ternary consequents and adding regression tests [.ts and .js fixtures].
Out of Scope Changes check ✅ Passed All changes are tightly scoped to fixing the nested destructured arrow parsing issue; no unrelated modifications present.
Description check ✅ Passed The PR description clearly explains the parsing bug, the root cause of the overly broad guard, and the solution using a token-level lookahead helper.

✏️ 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.

Copy link
Copy Markdown
Contributor

@dyc3 dyc3 left a comment

Choose a reason for hiding this comment

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

nitpick: i think all the comments added in this pr are a bit hard to understand. they already include some abbreviated code samples, but maybe some more complete ones would help? idk, maybe i need sleep.

@@ -0,0 +1,22 @@
// Regression test for https://github.com/biomejs/biome/issues/10131 (JavaScript variant)
// Curried arrow in a ternary consequent where the inner arrow's parameters
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.

what does "curried" mean in this context?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Sorry for the unclear comments — I've been writing a novel and apparently it's bleeding into my code reviews. Rewrote the function.rs comment blocks with concrete before/after code snippets instead of prose. Left the test fixture comments as-is to avoid touching the snapshots — happy to update those separately if you'd prefer a different description there. Ping me if anything's still off.

@codspeed-hq
Copy link
Copy Markdown

codspeed-hq Bot commented Apr 29, 2026

Merging this PR will not alter performance

✅ 58 untouched benchmarks
⏩ 196 skipped benchmarks1


Comparing Zelys-DFKH:fix/parser-nested-destructured-arrow-ternary (1db27bc) with main (e94acb2)

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.

// paren is the one checked for `=>`. Without this, `(a: () => T) =>` would stop
// at the inner `)` and return false.
fn is_paren_group_followed_by_fat_arrow(p: &mut JsParser) -> bool {
debug_assert!(p.at(T!['(']));
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.

We usually want to add a small message with debug assertions. The message should be actionable, or try to be. The message should explain the error and hint at a possible solution (hence, actionable)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-Parser Area: parser L-JavaScript Language: JavaScript and super languages

Projects

None yet

Development

Successfully merging this pull request may close these issues.

🐛 Biome breaks this code when formatting since v2.4.9

3 participants