Fix JSX parsing failures in ternary expressions with nested JSX attributes#2937
Fix JSX parsing failures in ternary expressions with nested JSX attributes#2937
Conversation
…s and object literals
Add investigation notes and test case for a bug where JSX parsing produces
spurious syntax errors when:
1. A ternary expression has a parenthesized identifier in the truthy branch
2. The falsy branch contains JSX with multi-line attributes
3. JSX attributes contain nested JSX with function calls having multi-property object literals
Root cause: The speculative arrow function parser incorrectly succeeds when parsing
the ternary truthy branch '(x)' as an arrow function parameter list, consuming the
ternary ':' as a return type annotation colon, and then the type parsing error
recovery through JSX-like content coincidentally leaves the token at '{' (from
{a:1,b:2}), which passes the OpenBraceToken check for arrow function body.
The speculative parse commits to an incorrect interpretation and doesn't rewind,
causing cascading syntax errors for all subsequent JSX content.
…ulti-property object literals Port the missing `isMissingList(parameters)` check from TypeScript's `typeHasArrowFunctionBlockingParseError` to the Go parser. When the parser speculatively tries to parse `(identifier) :` as an arrow function with a return type annotation, the return type parsing can interpret `(<JSXElement ...)` as a parenthesized function type (since `<` starts type parameters). If the function type's parameter list is "missing" (no `(` found), TypeScript correctly detects this as a blocking parse error and aborts the speculative parse. The Go port was missing this check, causing the speculative parse to incorrectly succeed and consume tokens from the JSX content. Changes: - Add `isMissing` field to `NodeList` to track missing lists - Mark parameter lists as missing in `parseParameters` when `(` not found - Split FunctionType/ConstructorType from ParenthesizedType in `typeHasArrowFunctionBlockingParseError` to add the missing list check Co-authored-by: jakebailey <5341706+jakebailey@users.noreply.github.com>
…t, use at all corresponding callsites, remove investigation file Co-authored-by: jakebailey <5341706+jakebailey@users.noreply.github.com>
… package Instead of adding an isMissing bool to every NodeList in the ast package, use a package-level sentinel slice (len=0 cap=1) in the parser package. Missing lists are identified by comparing their backing array address against the sentinel. This restricts the change to just the parser package. Co-authored-by: jakebailey <5341706+jakebailey@users.noreply.github.com>
jakebailey
left a comment
There was a problem hiding this comment.
Not sure we can do anything better. We really do rely on this logic.
There was a problem hiding this comment.
Pull request overview
This PR fixes a JSX parsing bug where valid TSX code containing a ternary expression with a parenthesized identifier in the true branch and nested JSX with multi-property object literals in the false branch produces spurious syntax errors.
Root cause: During speculative arrow function parsing, (DEFAULT_NULL_VALUE) : was being tried as an arrow function signature with return type annotation. The return type parser encountered <div ... and interpreted < as type parameters starting a FunctionType node. TypeScript's typeHasArrowFunctionBlockingParseError catches this via isMissingList(parameters) and aborts the speculative parse. The Go port was missing this check.
Changes:
- Adds a
missingListNodespackage-level sentinel andisMissingNodeList()helper inparser.go, porting TypeScript'sMissingList/isMissingListconcept without modifying theastpackage - Changes
parseBracketedList,parseParameters,parseFunctionBlock,parseVariableDeclarationList,parseClassDeclaration,parseEnumDeclaration,parseModuleBlock,parseObjectTypeMembers, andparseParenthesizedArrowFunctionExpressionto usecreateMissingList()instead ofparseEmptyNodeList()when the opening token is absent - Adds the
isMissingNodeList(Parameters)check intypeHasArrowFunctionBlockingParseErrorforKindFunctionType/KindConstructorType - Adds a compiler test with baselines
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
internal/parser/parser.go |
Core fix: adds sentinel-based missing list infrastructure and corrects typeHasArrowFunctionBlockingParseError |
testdata/tests/cases/compiler/jsxTernaryWithObjectInAttribute.tsx |
New compiler test reproducing the reported bug scenario |
testdata/baselines/reference/compiler/jsxTernaryWithObjectInAttribute.errors.txt |
Baseline showing only expected semantic errors (no spurious parse errors) |
testdata/baselines/reference/compiler/jsxTernaryWithObjectInAttribute.symbols |
Symbol resolution baseline showing correct parsing |
testdata/baselines/reference/compiler/jsxTernaryWithObjectInAttribute.types |
Type checking baseline showing correct parsing |
Valid TSX with ternary expressions containing parenthesized identifiers in the true branch and nested JSX with object literals in the false branch produces 30 spurious syntax errors. Standard
tscparses this correctly.Root cause
During speculative arrow function parsing,
(DEFAULT_NULL_VALUE) :is tried as an arrow signature with return type annotation. The return type parser encounters(<div ...and interprets<as the start of type parameters for a function type. This produces aFunctionTypenode with a missing parameter list (no(found).TypeScript's
typeHasArrowFunctionBlockingParseErrorcatches this viaisMissingList(parameters)and aborts the speculative parse. The Go port was missing this check, so the speculative parse incorrectly committed, consuming JSX tokens.Changes
parser.go— sentinel slice for missing lists: Add a package-levelmissingListNodessentinel (make([]*ast.Node, 0, 1)) andisMissingNodeList()helper that compares backing array addresses, porting TS'sMissingList/isMissingListconcept without modifying theastpackageparser.go—createMissingList: Add helper method matching TS'screateMissingList(), used at all corresponding callsites:parseBracketedList,parseParameters,parseFunctionBlock,parseVariableDeclarationList,parseClassDeclaration,parseEnumDeclaration,parseModuleBlock,parseObjectTypeMembers, andparseParenthesizedArrowFunctionExpressionparser.go—typeHasArrowFunctionBlockingParseError: SplitFunctionType/ConstructorTypefromParenthesizedTypecase and add the missing parameter list check, matching the TS reference:Original prompt
✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.