Skip to content

Commit c300fea

Browse files
authored
fix(7410): allow using JSXElement as JSXAttributeValue (#47994)
1 parent 5c2febf commit c300fea

24 files changed

+266
-76
lines changed

src/compiler/diagnosticMessages.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -435,6 +435,10 @@
435435
"category": "Error",
436436
"code": 1144
437437
},
438+
"'{' or JSX element expected.": {
439+
"category": "Error",
440+
"code": 1145
441+
},
438442
"Declaration expected.": {
439443
"category": "Error",
440444
"code": 1146

src/compiler/factory/nodeFactory.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4964,7 +4964,7 @@ namespace ts {
49644964
}
49654965

49664966
// @api
4967-
function createJsxAttribute(name: Identifier, initializer: StringLiteral | JsxExpression | undefined) {
4967+
function createJsxAttribute(name: Identifier, initializer: JsxAttributeValue | undefined) {
49684968
const node = createBaseNode<JsxAttribute>(SyntaxKind.JsxAttribute);
49694969
node.name = name;
49704970
node.initializer = initializer;
@@ -4976,7 +4976,7 @@ namespace ts {
49764976
}
49774977

49784978
// @api
4979-
function updateJsxAttribute(node: JsxAttribute, name: Identifier, initializer: StringLiteral | JsxExpression | undefined) {
4979+
function updateJsxAttribute(node: JsxAttribute, name: Identifier, initializer: JsxAttributeValue | undefined) {
49804980
return node.name !== name
49814981
|| node.initializer !== initializer
49824982
? update(createJsxAttribute(name, initializer), node)

src/compiler/parser.ts

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5454,15 +5454,23 @@ namespace ts {
54545454

54555455
scanJsxIdentifier();
54565456
const pos = getNodePos();
5457-
return finishNode(
5458-
factory.createJsxAttribute(
5459-
parseIdentifierName(),
5460-
token() !== SyntaxKind.EqualsToken ? undefined :
5461-
scanJsxAttributeValue() === SyntaxKind.StringLiteral ? parseLiteralNode() as StringLiteral :
5462-
parseJsxExpression(/*inExpressionContext*/ true)
5463-
),
5464-
pos
5465-
);
5457+
return finishNode(factory.createJsxAttribute(parseIdentifierName(), parseJsxAttributeValue()), pos);
5458+
}
5459+
5460+
function parseJsxAttributeValue(): JsxAttributeValue | undefined {
5461+
if (token() === SyntaxKind.EqualsToken) {
5462+
if (scanJsxAttributeValue() === SyntaxKind.StringLiteral) {
5463+
return parseLiteralNode() as StringLiteral;
5464+
}
5465+
if (token() === SyntaxKind.OpenBraceToken) {
5466+
return parseJsxExpression(/*inExpressionContext*/ true);
5467+
}
5468+
if (token() === SyntaxKind.LessThanToken) {
5469+
return parseJsxElementOrSelfClosingElementOrFragment(/*inExpressionContext*/ true);
5470+
}
5471+
parseErrorAtCurrentToken(Diagnostics.or_JSX_element_expected);
5472+
}
5473+
return undefined;
54665474
}
54675475

54685476
function parseJsxSpreadAttribute(): JsxSpreadAttribute {

src/compiler/transformers/jsx.ts

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -400,26 +400,33 @@ namespace ts {
400400
return factory.createPropertyAssignment(name, expression);
401401
}
402402

403-
function transformJsxAttributeInitializer(node: StringLiteral | JsxExpression | undefined): Expression {
403+
function transformJsxAttributeInitializer(node: JsxAttributeValue | undefined): Expression {
404404
if (node === undefined) {
405405
return factory.createTrue();
406406
}
407-
else if (node.kind === SyntaxKind.StringLiteral) {
407+
if (node.kind === SyntaxKind.StringLiteral) {
408408
// Always recreate the literal to escape any escape sequences or newlines which may be in the original jsx string and which
409409
// Need to be escaped to be handled correctly in a normal string
410410
const singleQuote = node.singleQuote !== undefined ? node.singleQuote : !isStringDoubleQuoted(node, currentSourceFile);
411411
const literal = factory.createStringLiteral(tryDecodeEntities(node.text) || node.text, singleQuote);
412412
return setTextRange(literal, node);
413413
}
414-
else if (node.kind === SyntaxKind.JsxExpression) {
414+
if (node.kind === SyntaxKind.JsxExpression) {
415415
if (node.expression === undefined) {
416416
return factory.createTrue();
417417
}
418418
return visitNode(node.expression, visitor, isExpression);
419419
}
420-
else {
421-
return Debug.failBadSyntaxKind(node);
420+
if (isJsxElement(node)) {
421+
return visitJsxElement(node, /*isChild*/ false);
422+
}
423+
if (isJsxSelfClosingElement(node)) {
424+
return visitJsxSelfClosingElement(node, /*isChild*/ false);
425+
}
426+
if (isJsxFragment(node)) {
427+
return visitJsxFragment(node, /*isChild*/ false);
422428
}
429+
return Debug.failBadSyntaxKind(node);
423430
}
424431

425432
function visitJsxText(node: JsxText): StringLiteral | undefined {

src/compiler/types.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2612,9 +2612,16 @@ namespace ts {
26122612
readonly parent: JsxAttributes;
26132613
readonly name: Identifier;
26142614
/// JSX attribute initializers are optional; <X y /> is sugar for <X y={true} />
2615-
readonly initializer?: StringLiteral | JsxExpression;
2615+
readonly initializer?: JsxAttributeValue;
26162616
}
26172617

2618+
export type JsxAttributeValue =
2619+
| StringLiteral
2620+
| JsxExpression
2621+
| JsxElement
2622+
| JsxSelfClosingElement
2623+
| JsxFragment;
2624+
26182625
export interface JsxSpreadAttribute extends ObjectLiteralElement {
26192626
readonly kind: SyntaxKind.JsxSpreadAttribute;
26202627
readonly parent: JsxAttributes;
@@ -7680,8 +7687,8 @@ namespace ts {
76807687
createJsxOpeningFragment(): JsxOpeningFragment;
76817688
createJsxJsxClosingFragment(): JsxClosingFragment;
76827689
updateJsxFragment(node: JsxFragment, openingFragment: JsxOpeningFragment, children: readonly JsxChild[], closingFragment: JsxClosingFragment): JsxFragment;
7683-
createJsxAttribute(name: Identifier, initializer: StringLiteral | JsxExpression | undefined): JsxAttribute;
7684-
updateJsxAttribute(node: JsxAttribute, name: Identifier, initializer: StringLiteral | JsxExpression | undefined): JsxAttribute;
7690+
createJsxAttribute(name: Identifier, initializer: JsxAttributeValue | undefined): JsxAttribute;
7691+
updateJsxAttribute(node: JsxAttribute, name: Identifier, initializer: JsxAttributeValue | undefined): JsxAttribute;
76857692
createJsxAttributes(properties: readonly JsxAttributeLike[]): JsxAttributes;
76867693
updateJsxAttributes(node: JsxAttributes, properties: readonly JsxAttributeLike[]): JsxAttributes;
76877694
createJsxSpreadAttribute(expression: Expression): JsxSpreadAttribute;

tests/baselines/reference/api/tsserverlibrary.d.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1388,8 +1388,9 @@ declare namespace ts {
13881388
readonly kind: SyntaxKind.JsxAttribute;
13891389
readonly parent: JsxAttributes;
13901390
readonly name: Identifier;
1391-
readonly initializer?: StringLiteral | JsxExpression;
1391+
readonly initializer?: JsxAttributeValue;
13921392
}
1393+
export type JsxAttributeValue = StringLiteral | JsxExpression | JsxElement | JsxSelfClosingElement | JsxFragment;
13931394
export interface JsxSpreadAttribute extends ObjectLiteralElement {
13941395
readonly kind: SyntaxKind.JsxSpreadAttribute;
13951396
readonly parent: JsxAttributes;
@@ -3746,8 +3747,8 @@ declare namespace ts {
37463747
createJsxOpeningFragment(): JsxOpeningFragment;
37473748
createJsxJsxClosingFragment(): JsxClosingFragment;
37483749
updateJsxFragment(node: JsxFragment, openingFragment: JsxOpeningFragment, children: readonly JsxChild[], closingFragment: JsxClosingFragment): JsxFragment;
3749-
createJsxAttribute(name: Identifier, initializer: StringLiteral | JsxExpression | undefined): JsxAttribute;
3750-
updateJsxAttribute(node: JsxAttribute, name: Identifier, initializer: StringLiteral | JsxExpression | undefined): JsxAttribute;
3750+
createJsxAttribute(name: Identifier, initializer: JsxAttributeValue | undefined): JsxAttribute;
3751+
updateJsxAttribute(node: JsxAttribute, name: Identifier, initializer: JsxAttributeValue | undefined): JsxAttribute;
37513752
createJsxAttributes(properties: readonly JsxAttributeLike[]): JsxAttributes;
37523753
updateJsxAttributes(node: JsxAttributes, properties: readonly JsxAttributeLike[]): JsxAttributes;
37533754
createJsxSpreadAttribute(expression: Expression): JsxSpreadAttribute;
@@ -11339,9 +11340,9 @@ declare namespace ts {
1133911340
/** @deprecated Use `factory.updateJsxFragment` or the factory supplied by your transformation context instead. */
1134011341
const updateJsxFragment: (node: JsxFragment, openingFragment: JsxOpeningFragment, children: readonly JsxChild[], closingFragment: JsxClosingFragment) => JsxFragment;
1134111342
/** @deprecated Use `factory.createJsxAttribute` or the factory supplied by your transformation context instead. */
11342-
const createJsxAttribute: (name: Identifier, initializer: StringLiteral | JsxExpression | undefined) => JsxAttribute;
11343+
const createJsxAttribute: (name: Identifier, initializer: JsxAttributeValue | undefined) => JsxAttribute;
1134311344
/** @deprecated Use `factory.updateJsxAttribute` or the factory supplied by your transformation context instead. */
11344-
const updateJsxAttribute: (node: JsxAttribute, name: Identifier, initializer: StringLiteral | JsxExpression | undefined) => JsxAttribute;
11345+
const updateJsxAttribute: (node: JsxAttribute, name: Identifier, initializer: JsxAttributeValue | undefined) => JsxAttribute;
1134511346
/** @deprecated Use `factory.createJsxAttributes` or the factory supplied by your transformation context instead. */
1134611347
const createJsxAttributes: (properties: readonly JsxAttributeLike[]) => JsxAttributes;
1134711348
/** @deprecated Use `factory.updateJsxAttributes` or the factory supplied by your transformation context instead. */

tests/baselines/reference/api/typescript.d.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1388,8 +1388,9 @@ declare namespace ts {
13881388
readonly kind: SyntaxKind.JsxAttribute;
13891389
readonly parent: JsxAttributes;
13901390
readonly name: Identifier;
1391-
readonly initializer?: StringLiteral | JsxExpression;
1391+
readonly initializer?: JsxAttributeValue;
13921392
}
1393+
export type JsxAttributeValue = StringLiteral | JsxExpression | JsxElement | JsxSelfClosingElement | JsxFragment;
13931394
export interface JsxSpreadAttribute extends ObjectLiteralElement {
13941395
readonly kind: SyntaxKind.JsxSpreadAttribute;
13951396
readonly parent: JsxAttributes;
@@ -3746,8 +3747,8 @@ declare namespace ts {
37463747
createJsxOpeningFragment(): JsxOpeningFragment;
37473748
createJsxJsxClosingFragment(): JsxClosingFragment;
37483749
updateJsxFragment(node: JsxFragment, openingFragment: JsxOpeningFragment, children: readonly JsxChild[], closingFragment: JsxClosingFragment): JsxFragment;
3749-
createJsxAttribute(name: Identifier, initializer: StringLiteral | JsxExpression | undefined): JsxAttribute;
3750-
updateJsxAttribute(node: JsxAttribute, name: Identifier, initializer: StringLiteral | JsxExpression | undefined): JsxAttribute;
3750+
createJsxAttribute(name: Identifier, initializer: JsxAttributeValue | undefined): JsxAttribute;
3751+
updateJsxAttribute(node: JsxAttribute, name: Identifier, initializer: JsxAttributeValue | undefined): JsxAttribute;
37513752
createJsxAttributes(properties: readonly JsxAttributeLike[]): JsxAttributes;
37523753
updateJsxAttributes(node: JsxAttributes, properties: readonly JsxAttributeLike[]): JsxAttributes;
37533754
createJsxSpreadAttribute(expression: Expression): JsxSpreadAttribute;
@@ -7481,9 +7482,9 @@ declare namespace ts {
74817482
/** @deprecated Use `factory.updateJsxFragment` or the factory supplied by your transformation context instead. */
74827483
const updateJsxFragment: (node: JsxFragment, openingFragment: JsxOpeningFragment, children: readonly JsxChild[], closingFragment: JsxClosingFragment) => JsxFragment;
74837484
/** @deprecated Use `factory.createJsxAttribute` or the factory supplied by your transformation context instead. */
7484-
const createJsxAttribute: (name: Identifier, initializer: StringLiteral | JsxExpression | undefined) => JsxAttribute;
7485+
const createJsxAttribute: (name: Identifier, initializer: JsxAttributeValue | undefined) => JsxAttribute;
74857486
/** @deprecated Use `factory.updateJsxAttribute` or the factory supplied by your transformation context instead. */
7486-
const updateJsxAttribute: (node: JsxAttribute, name: Identifier, initializer: StringLiteral | JsxExpression | undefined) => JsxAttribute;
7487+
const updateJsxAttribute: (node: JsxAttribute, name: Identifier, initializer: JsxAttributeValue | undefined) => JsxAttribute;
74877488
/** @deprecated Use `factory.createJsxAttributes` or the factory supplied by your transformation context instead. */
74887489
const createJsxAttributes: (properties: readonly JsxAttributeLike[]) => JsxAttributes;
74897490
/** @deprecated Use `factory.updateJsxAttributes` or the factory supplied by your transformation context instead. */
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
tests/cases/conformance/jsx/a.tsx(7,16): error TS1145: '{' or JSX element expected.
2+
3+
4+
==== tests/cases/conformance/jsx/a.tsx (1 errors) ====
5+
declare var React: any;
6+
7+
<div>
8+
<div attr=<div /> />
9+
<div attr=<div>foo</div> />
10+
<div attr=<><div>foo</div></> />
11+
<div attr= />
12+
~
13+
!!! error TS1145: '{' or JSX element expected.
14+
</div>
15+
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
//// [a.tsx]
2+
declare var React: any;
3+
4+
<div>
5+
<div attr=<div /> />
6+
<div attr=<div>foo</div> />
7+
<div attr=<><div>foo</div></> />
8+
<div attr= />
9+
</div>
10+
11+
12+
//// [a.jsx]
13+
<div>
14+
<div attr=<div />/>
15+
<div attr=<div>foo</div>/>
16+
<div attr=<><div>foo</div></>/>
17+
<div attr/>
18+
</div>;
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
=== tests/cases/conformance/jsx/a.tsx ===
2+
declare var React: any;
3+
>React : Symbol(React, Decl(a.tsx, 0, 11))
4+
5+
<div>
6+
<div attr=<div /> />
7+
>attr : Symbol(attr, Decl(a.tsx, 3, 8))
8+
9+
<div attr=<div>foo</div> />
10+
>attr : Symbol(attr, Decl(a.tsx, 4, 8))
11+
12+
<div attr=<><div>foo</div></> />
13+
>attr : Symbol(attr, Decl(a.tsx, 5, 8))
14+
15+
<div attr= />
16+
>attr : Symbol(attr, Decl(a.tsx, 6, 8))
17+
18+
</div>
19+
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
=== tests/cases/conformance/jsx/a.tsx ===
2+
declare var React: any;
3+
>React : any
4+
5+
<div>
6+
><div> <div attr=<div /> /> <div attr=<div>foo</div> /> <div attr=<><div>foo</div></> /> <div attr= /></div> : any
7+
>div : any
8+
9+
<div attr=<div /> />
10+
><div attr=<div /> /> : any
11+
>div : any
12+
>attr : any
13+
><div /> : any
14+
>div : any
15+
16+
<div attr=<div>foo</div> />
17+
><div attr=<div>foo</div> /> : any
18+
>div : any
19+
>attr : any
20+
><div>foo</div> : any
21+
>div : any
22+
>div : any
23+
24+
<div attr=<><div>foo</div></> />
25+
><div attr=<><div>foo</div></> /> : any
26+
>div : any
27+
>attr : any
28+
><><div>foo</div></> : any
29+
><div>foo</div> : any
30+
>div : any
31+
>div : any
32+
33+
<div attr= />
34+
><div attr= /> : any
35+
>div : any
36+
>attr : true
37+
38+
</div>
39+
>div : any
40+
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
tests/cases/conformance/jsx/a.tsx(7,16): error TS1145: '{' or JSX element expected.
2+
3+
4+
==== tests/cases/conformance/jsx/a.tsx (1 errors) ====
5+
declare var React: any;
6+
7+
<div>
8+
<div attr=<div /> />
9+
<div attr=<div>foo</div> />
10+
<div attr=<><div>foo</div></> />
11+
<div attr= />
12+
~
13+
!!! error TS1145: '{' or JSX element expected.
14+
</div>
15+
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
//// [a.tsx]
2+
declare var React: any;
3+
4+
<div>
5+
<div attr=<div /> />
6+
<div attr=<div>foo</div> />
7+
<div attr=<><div>foo</div></> />
8+
<div attr= />
9+
</div>
10+
11+
12+
//// [a.js]
13+
React.createElement("div", null,
14+
React.createElement("div", { attr: React.createElement("div", null) }),
15+
React.createElement("div", { attr: React.createElement("div", null, "foo") }),
16+
React.createElement("div", { attr: React.createElement(React.Fragment, null,
17+
React.createElement("div", null, "foo")) }),
18+
React.createElement("div", { attr: true }));
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
=== tests/cases/conformance/jsx/a.tsx ===
2+
declare var React: any;
3+
>React : Symbol(React, Decl(a.tsx, 0, 11))
4+
5+
<div>
6+
<div attr=<div /> />
7+
>attr : Symbol(attr, Decl(a.tsx, 3, 8))
8+
9+
<div attr=<div>foo</div> />
10+
>attr : Symbol(attr, Decl(a.tsx, 4, 8))
11+
12+
<div attr=<><div>foo</div></> />
13+
>attr : Symbol(attr, Decl(a.tsx, 5, 8))
14+
15+
<div attr= />
16+
>attr : Symbol(attr, Decl(a.tsx, 6, 8))
17+
18+
</div>
19+
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
=== tests/cases/conformance/jsx/a.tsx ===
2+
declare var React: any;
3+
>React : any
4+
5+
<div>
6+
><div> <div attr=<div /> /> <div attr=<div>foo</div> /> <div attr=<><div>foo</div></> /> <div attr= /></div> : any
7+
>div : any
8+
9+
<div attr=<div /> />
10+
><div attr=<div /> /> : any
11+
>div : any
12+
>attr : any
13+
><div /> : any
14+
>div : any
15+
16+
<div attr=<div>foo</div> />
17+
><div attr=<div>foo</div> /> : any
18+
>div : any
19+
>attr : any
20+
><div>foo</div> : any
21+
>div : any
22+
>div : any
23+
24+
<div attr=<><div>foo</div></> />
25+
><div attr=<><div>foo</div></> /> : any
26+
>div : any
27+
>attr : any
28+
><><div>foo</div></> : any
29+
><div>foo</div> : any
30+
>div : any
31+
>div : any
32+
33+
<div attr= />
34+
><div attr= /> : any
35+
>div : any
36+
>attr : true
37+
38+
</div>
39+
>div : any
40+

0 commit comments

Comments
 (0)