From 3f888167f1109f80cbddd08029b0d836ac2f3229 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Sat, 26 Sep 2026 18:52:34 +0200 Subject: [PATCH 1/2] Add failing test for JSX attribute initializer with multiple elements (#64458) --- .../jsxAttributeInitializerMultipleElementsNoCrash.tsx | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 tsc/testdata/tests/cases/compiler/jsxAttributeInitializerMultipleElementsNoCrash.tsx diff --git a/tsc/testdata/tests/cases/compiler/jsxAttributeInitializerMultipleElementsNoCrash.tsx b/tsc/testdata/tests/cases/compiler/jsxAttributeInitializerMultipleElementsNoCrash.tsx new file mode 100644 index 0000000000000..f620a2fbb2901 --- /dev/null +++ b/tsc/testdata/tests/cases/compiler/jsxAttributeInitializerMultipleElementsNoCrash.tsx @@ -0,0 +1,7 @@ +// @jsx: react,react-jsx +// @noTypesAndSymbols: true + +declare namespace JSX { interface IntrinsicElements { [x: string]: any } } +declare const React: any; + +const a =
/>; From 66c38a0680bfd9d8e73f9da86d2a9b439b7a2026 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Sat, 26 Sep 2026 18:52:35 +0200 Subject: [PATCH 2/2] Don't wrap JSX attribute values in synthetic sibling-element binary expressions parseJsxAttributeValue called parseJsxElementOrSelfClosingElementOrFragment with mustBeUnary=false, so the "JSX expressions must have one parent element" recovery could return a comma BinaryExpression as a JsxAttribute initializer, violating the JsxAttributeValue contract and crashing the react/react-jsx transforms. Fixes #64458 --- tsc/internal/parser/parser.go | 4 ++- ...tipleElementsNoCrash(jsx=react).errors.txt | 20 ++++++++++++ ...lizerMultipleElementsNoCrash(jsx=react).js | 12 +++++++ ...eElementsNoCrash(jsx=react-jsx).errors.txt | 32 +++++++++++++++++++ ...rMultipleElementsNoCrash(jsx=react-jsx).js | 12 +++++++ ...xAttributeValueBinaryExpression.errors.txt | 17 +++++++--- .../jsxAttributeValueBinaryExpression.js | 3 +- 7 files changed, 94 insertions(+), 6 deletions(-) create mode 100644 tsc/testdata/baselines/reference/compiler/jsxAttributeInitializerMultipleElementsNoCrash(jsx=react).errors.txt create mode 100644 tsc/testdata/baselines/reference/compiler/jsxAttributeInitializerMultipleElementsNoCrash(jsx=react).js create mode 100644 tsc/testdata/baselines/reference/compiler/jsxAttributeInitializerMultipleElementsNoCrash(jsx=react-jsx).errors.txt create mode 100644 tsc/testdata/baselines/reference/compiler/jsxAttributeInitializerMultipleElementsNoCrash(jsx=react-jsx).js diff --git a/tsc/internal/parser/parser.go b/tsc/internal/parser/parser.go index f54b4bcebbdf3..1d149790b7565 100644 --- a/tsc/internal/parser/parser.go +++ b/tsc/internal/parser/parser.go @@ -5093,7 +5093,9 @@ func (p *Parser) parseJsxAttributeValue() *ast.Expression { return p.parseJsxExpression( /*inExpressionContext*/ true) } if p.token == ast.KindLessThanToken { - return p.parseJsxElementOrSelfClosingElementOrFragment(true /*inExpressionContext*/, -1, nil, false) + // An attribute value must be a single JsxAttributeValue, so don't allow the sibling-element + // recovery to wrap it in a synthetic binary expression. + return p.parseJsxElementOrSelfClosingElementOrFragment(true /*inExpressionContext*/, -1 /*topInvalidNodePosition*/, nil /*openingTag*/, true /*mustBeUnary*/) } p.parseErrorAtCurrentToken(diagnostics.X_or_JSX_element_expected) } diff --git a/tsc/testdata/baselines/reference/compiler/jsxAttributeInitializerMultipleElementsNoCrash(jsx=react).errors.txt b/tsc/testdata/baselines/reference/compiler/jsxAttributeInitializerMultipleElementsNoCrash(jsx=react).errors.txt new file mode 100644 index 0000000000000..8d85ee0d7508e --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/jsxAttributeInitializerMultipleElementsNoCrash(jsx=react).errors.txt @@ -0,0 +1,20 @@ +jsxAttributeInitializerMultipleElementsNoCrash.tsx(4,11): error TS2657: JSX expressions must have one parent element. +jsxAttributeInitializerMultipleElementsNoCrash.tsx(4,29): error TS1003: Identifier expected. +jsxAttributeInitializerMultipleElementsNoCrash.tsx(4,39): error TS1109: Expression expected. +jsxAttributeInitializerMultipleElementsNoCrash.tsx(4,40): error TS1109: Expression expected. + + +==== jsxAttributeInitializerMultipleElementsNoCrash.tsx (4 errors) ==== + declare namespace JSX { interface IntrinsicElements { [x: string]: any } } + declare const React: any; + + const a =
/>; + ~~~~~~~~~~~~~~~~~~~~~~~~~~ +!!! error TS2657: JSX expressions must have one parent element. + ~ +!!! error TS1003: Identifier expected. + ~ +!!! error TS1109: Expression expected. + ~ +!!! error TS1109: Expression expected. + \ No newline at end of file diff --git a/tsc/testdata/baselines/reference/compiler/jsxAttributeInitializerMultipleElementsNoCrash(jsx=react).js b/tsc/testdata/baselines/reference/compiler/jsxAttributeInitializerMultipleElementsNoCrash(jsx=react).js new file mode 100644 index 0000000000000..1781372e99826 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/jsxAttributeInitializerMultipleElementsNoCrash(jsx=react).js @@ -0,0 +1,12 @@ +//// [tests/cases/compiler/jsxAttributeInitializerMultipleElementsNoCrash.tsx] //// + +//// [jsxAttributeInitializerMultipleElementsNoCrash.tsx] +declare namespace JSX { interface IntrinsicElements { [x: string]: any } } +declare const React: any; + +const a =
/>; + + +//// [jsxAttributeInitializerMultipleElementsNoCrash.js] +"use strict"; +const a = (React.createElement("div", { attr: React.createElement("span", null) }), React.createElement("span", null)) / > ; diff --git a/tsc/testdata/baselines/reference/compiler/jsxAttributeInitializerMultipleElementsNoCrash(jsx=react-jsx).errors.txt b/tsc/testdata/baselines/reference/compiler/jsxAttributeInitializerMultipleElementsNoCrash(jsx=react-jsx).errors.txt new file mode 100644 index 0000000000000..6a8c9ca6381fe --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/jsxAttributeInitializerMultipleElementsNoCrash(jsx=react-jsx).errors.txt @@ -0,0 +1,32 @@ +jsxAttributeInitializerMultipleElementsNoCrash.tsx(4,11): error TS2875: This JSX tag requires the module path 'react/jsx-runtime' to exist, but none could be found. Make sure you have types for the appropriate package installed. +jsxAttributeInitializerMultipleElementsNoCrash.tsx(4,11): error TS7026: JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists. +jsxAttributeInitializerMultipleElementsNoCrash.tsx(4,11): error TS2657: JSX expressions must have one parent element. +jsxAttributeInitializerMultipleElementsNoCrash.tsx(4,21): error TS7026: JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists. +jsxAttributeInitializerMultipleElementsNoCrash.tsx(4,29): error TS1003: Identifier expected. +jsxAttributeInitializerMultipleElementsNoCrash.tsx(4,29): error TS7026: JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists. +jsxAttributeInitializerMultipleElementsNoCrash.tsx(4,39): error TS1109: Expression expected. +jsxAttributeInitializerMultipleElementsNoCrash.tsx(4,40): error TS1109: Expression expected. + + +==== jsxAttributeInitializerMultipleElementsNoCrash.tsx (8 errors) ==== + declare namespace JSX { interface IntrinsicElements { [x: string]: any } } + declare const React: any; + + const a =
/>; + ~~~~~~~~~~~~~~~~~~ +!!! error TS2875: This JSX tag requires the module path 'react/jsx-runtime' to exist, but none could be found. Make sure you have types for the appropriate package installed. + ~~~~~~~~~~~~~~~~~~ +!!! error TS7026: JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists. + ~~~~~~~~~~~~~~~~~~~~~~~~~~ +!!! error TS2657: JSX expressions must have one parent element. + ~~~~~~~~ +!!! error TS7026: JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists. + ~ +!!! error TS1003: Identifier expected. + ~~~~~~~~ +!!! error TS7026: JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists. + ~ +!!! error TS1109: Expression expected. + ~ +!!! error TS1109: Expression expected. + \ No newline at end of file diff --git a/tsc/testdata/baselines/reference/compiler/jsxAttributeInitializerMultipleElementsNoCrash(jsx=react-jsx).js b/tsc/testdata/baselines/reference/compiler/jsxAttributeInitializerMultipleElementsNoCrash(jsx=react-jsx).js new file mode 100644 index 0000000000000..0db391e644a76 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/jsxAttributeInitializerMultipleElementsNoCrash(jsx=react-jsx).js @@ -0,0 +1,12 @@ +//// [tests/cases/compiler/jsxAttributeInitializerMultipleElementsNoCrash.tsx] //// + +//// [jsxAttributeInitializerMultipleElementsNoCrash.tsx] +declare namespace JSX { interface IntrinsicElements { [x: string]: any } } +declare const React: any; + +const a =
/>; + + +//// [jsxAttributeInitializerMultipleElementsNoCrash.js] +import { jsx as _jsx } from "react/jsx-runtime"; +const a = (_jsx("div", { attr: _jsx("span", {}) }), _jsx("span", {})) / > ; diff --git a/tsc/testdata/baselines/reference/compiler/jsxAttributeValueBinaryExpression.errors.txt b/tsc/testdata/baselines/reference/compiler/jsxAttributeValueBinaryExpression.errors.txt index c6524a1a9811b..2eccf0021f44e 100644 --- a/tsc/testdata/baselines/reference/compiler/jsxAttributeValueBinaryExpression.errors.txt +++ b/tsc/testdata/baselines/reference/compiler/jsxAttributeValueBinaryExpression.errors.txt @@ -1,17 +1,26 @@ +jsxAttributeValueBinaryExpression.tsx(1,1): error TS2657: JSX expressions must have one parent element. jsxAttributeValueBinaryExpression.tsx(1,2): error TS2304: Cannot find name 'X'. jsxAttributeValueBinaryExpression.tsx(1,6): error TS7026: JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists. -jsxAttributeValueBinaryExpression.tsx(1,6): error TS2657: JSX expressions must have one parent element. +jsxAttributeValueBinaryExpression.tsx(1,10): error TS1003: Identifier expected. jsxAttributeValueBinaryExpression.tsx(1,10): error TS7026: JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists. +jsxAttributeValueBinaryExpression.tsx(1,16): error TS1109: Expression expected. +jsxAttributeValueBinaryExpression.tsx(1,17): error TS1109: Expression expected. -==== jsxAttributeValueBinaryExpression.tsx (4 errors) ==== +==== jsxAttributeValueBinaryExpression.tsx (7 errors) ==== /> + ~~~~~~~~~~~~~ +!!! error TS2657: JSX expressions must have one parent element. ~ !!! error TS2304: Cannot find name 'X'. ~~~~ !!! error TS7026: JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists. - ~~~~~~~~ -!!! error TS2657: JSX expressions must have one parent element. + ~ +!!! error TS1003: Identifier expected. ~~~~ !!! error TS7026: JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists. + ~ +!!! error TS1109: Expression expected. + +!!! error TS1109: Expression expected. \ No newline at end of file diff --git a/tsc/testdata/baselines/reference/compiler/jsxAttributeValueBinaryExpression.js b/tsc/testdata/baselines/reference/compiler/jsxAttributeValueBinaryExpression.js index c15b7090c5b79..b693be6fd9e0b 100644 --- a/tsc/testdata/baselines/reference/compiler/jsxAttributeValueBinaryExpression.js +++ b/tsc/testdata/baselines/reference/compiler/jsxAttributeValueBinaryExpression.js @@ -6,4 +6,5 @@ //// [jsxAttributeValueBinaryExpression.jsx] "use strict"; -, />; +(/>, ) / > +;