Skip to content

Commit 00e6d15

Browse files
committed
fix(7410): allow using JSXElement as JSXAttributeValue
1 parent 2d85e1e commit 00e6d15

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
@@ -4856,7 +4856,7 @@ namespace ts {
48564856
}
48574857

48584858
// @api
4859-
function createJsxAttribute(name: Identifier, initializer: StringLiteral | JsxExpression | undefined) {
4859+
function createJsxAttribute(name: Identifier, initializer: JSXAttributeValue | undefined) {
48604860
const node = createBaseNode<JsxAttribute>(SyntaxKind.JsxAttribute);
48614861
node.name = name;
48624862
node.initializer = initializer;
@@ -4868,7 +4868,7 @@ namespace ts {
48684868
}
48694869

48704870
// @api
4871-
function updateJsxAttribute(node: JsxAttribute, name: Identifier, initializer: StringLiteral | JsxExpression | undefined) {
4871+
function updateJsxAttribute(node: JsxAttribute, name: Identifier, initializer: JSXAttributeValue | undefined) {
48724872
return node.name !== name
48734873
|| node.initializer !== initializer
48744874
? update(createJsxAttribute(name, initializer), node)

src/compiler/parser.ts

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

52855285
scanJsxIdentifier();
52865286
const pos = getNodePos();
5287-
return finishNode(
5288-
factory.createJsxAttribute(
5289-
parseIdentifierName(),
5290-
token() !== SyntaxKind.EqualsToken ? undefined :
5291-
scanJsxAttributeValue() === SyntaxKind.StringLiteral ? parseLiteralNode() as StringLiteral :
5292-
parseJsxExpression(/*inExpressionContext*/ true)
5293-
),
5294-
pos
5295-
);
5287+
return finishNode(factory.createJsxAttribute(parseIdentifierName(), parseJsxAttributeValue()), pos);
5288+
}
5289+
5290+
function parseJsxAttributeValue(): JSXAttributeValue | undefined {
5291+
if (token() === SyntaxKind.EqualsToken) {
5292+
if (scanJsxAttributeValue() === SyntaxKind.StringLiteral) {
5293+
return parseLiteralNode() as StringLiteral;
5294+
}
5295+
if (token() === SyntaxKind.OpenBraceToken) {
5296+
return parseJsxExpression(/*inExpressionContext*/ true);
5297+
}
5298+
if (token() === SyntaxKind.LessThanToken) {
5299+
return parseJsxElementOrSelfClosingElementOrFragment(/*inExpressionContext*/ true);
5300+
}
5301+
parseErrorAtCurrentToken(Diagnostics.or_JSX_element_expected);
5302+
}
5303+
return undefined;
52965304
}
52975305

52985306
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
@@ -2590,9 +2590,16 @@ namespace ts {
25902590
readonly parent: JsxAttributes;
25912591
readonly name: Identifier;
25922592
/// JSX attribute initializers are optional; <X y /> is sugar for <X y={true} />
2593-
readonly initializer?: StringLiteral | JsxExpression;
2593+
readonly initializer?: JSXAttributeValue;
25942594
}
25952595

2596+
export type JSXAttributeValue =
2597+
| StringLiteral
2598+
| JsxExpression
2599+
| JsxElement
2600+
| JsxSelfClosingElement
2601+
| JsxFragment;
2602+
25962603
export interface JsxSpreadAttribute extends ObjectLiteralElement {
25972604
readonly kind: SyntaxKind.JsxSpreadAttribute;
25982605
readonly parent: JsxAttributes;
@@ -7583,8 +7590,8 @@ namespace ts {
75837590
createJsxOpeningFragment(): JsxOpeningFragment;
75847591
createJsxJsxClosingFragment(): JsxClosingFragment;
75857592
updateJsxFragment(node: JsxFragment, openingFragment: JsxOpeningFragment, children: readonly JsxChild[], closingFragment: JsxClosingFragment): JsxFragment;
7586-
createJsxAttribute(name: Identifier, initializer: StringLiteral | JsxExpression | undefined): JsxAttribute;
7587-
updateJsxAttribute(node: JsxAttribute, name: Identifier, initializer: StringLiteral | JsxExpression | undefined): JsxAttribute;
7593+
createJsxAttribute(name: Identifier, initializer: JSXAttributeValue | undefined): JsxAttribute;
7594+
updateJsxAttribute(node: JsxAttribute, name: Identifier, initializer: JSXAttributeValue | undefined): JsxAttribute;
75887595
createJsxAttributes(properties: readonly JsxAttributeLike[]): JsxAttributes;
75897596
updateJsxAttributes(node: JsxAttributes, properties: readonly JsxAttributeLike[]): JsxAttributes;
75907597
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
@@ -1372,8 +1372,9 @@ declare namespace ts {
13721372
readonly kind: SyntaxKind.JsxAttribute;
13731373
readonly parent: JsxAttributes;
13741374
readonly name: Identifier;
1375-
readonly initializer?: StringLiteral | JsxExpression;
1375+
readonly initializer?: JSXAttributeValue;
13761376
}
1377+
export type JSXAttributeValue = StringLiteral | JsxExpression | JsxElement | JsxSelfClosingElement | JsxFragment;
13771378
export interface JsxSpreadAttribute extends ObjectLiteralElement {
13781379
readonly kind: SyntaxKind.JsxSpreadAttribute;
13791380
readonly parent: JsxAttributes;
@@ -3691,8 +3692,8 @@ declare namespace ts {
36913692
createJsxOpeningFragment(): JsxOpeningFragment;
36923693
createJsxJsxClosingFragment(): JsxClosingFragment;
36933694
updateJsxFragment(node: JsxFragment, openingFragment: JsxOpeningFragment, children: readonly JsxChild[], closingFragment: JsxClosingFragment): JsxFragment;
3694-
createJsxAttribute(name: Identifier, initializer: StringLiteral | JsxExpression | undefined): JsxAttribute;
3695-
updateJsxAttribute(node: JsxAttribute, name: Identifier, initializer: StringLiteral | JsxExpression | undefined): JsxAttribute;
3695+
createJsxAttribute(name: Identifier, initializer: JSXAttributeValue | undefined): JsxAttribute;
3696+
updateJsxAttribute(node: JsxAttribute, name: Identifier, initializer: JSXAttributeValue | undefined): JsxAttribute;
36963697
createJsxAttributes(properties: readonly JsxAttributeLike[]): JsxAttributes;
36973698
updateJsxAttributes(node: JsxAttributes, properties: readonly JsxAttributeLike[]): JsxAttributes;
36983699
createJsxSpreadAttribute(expression: Expression): JsxSpreadAttribute;
@@ -11189,9 +11190,9 @@ declare namespace ts {
1118911190
/** @deprecated Use `factory.updateJsxFragment` or the factory supplied by your transformation context instead. */
1119011191
const updateJsxFragment: (node: JsxFragment, openingFragment: JsxOpeningFragment, children: readonly JsxChild[], closingFragment: JsxClosingFragment) => JsxFragment;
1119111192
/** @deprecated Use `factory.createJsxAttribute` or the factory supplied by your transformation context instead. */
11192-
const createJsxAttribute: (name: Identifier, initializer: StringLiteral | JsxExpression | undefined) => JsxAttribute;
11193+
const createJsxAttribute: (name: Identifier, initializer: JSXAttributeValue | undefined) => JsxAttribute;
1119311194
/** @deprecated Use `factory.updateJsxAttribute` or the factory supplied by your transformation context instead. */
11194-
const updateJsxAttribute: (node: JsxAttribute, name: Identifier, initializer: StringLiteral | JsxExpression | undefined) => JsxAttribute;
11195+
const updateJsxAttribute: (node: JsxAttribute, name: Identifier, initializer: JSXAttributeValue | undefined) => JsxAttribute;
1119511196
/** @deprecated Use `factory.createJsxAttributes` or the factory supplied by your transformation context instead. */
1119611197
const createJsxAttributes: (properties: readonly JsxAttributeLike[]) => JsxAttributes;
1119711198
/** @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
@@ -1372,8 +1372,9 @@ declare namespace ts {
13721372
readonly kind: SyntaxKind.JsxAttribute;
13731373
readonly parent: JsxAttributes;
13741374
readonly name: Identifier;
1375-
readonly initializer?: StringLiteral | JsxExpression;
1375+
readonly initializer?: JSXAttributeValue;
13761376
}
1377+
export type JSXAttributeValue = StringLiteral | JsxExpression | JsxElement | JsxSelfClosingElement | JsxFragment;
13771378
export interface JsxSpreadAttribute extends ObjectLiteralElement {
13781379
readonly kind: SyntaxKind.JsxSpreadAttribute;
13791380
readonly parent: JsxAttributes;
@@ -3691,8 +3692,8 @@ declare namespace ts {
36913692
createJsxOpeningFragment(): JsxOpeningFragment;
36923693
createJsxJsxClosingFragment(): JsxClosingFragment;
36933694
updateJsxFragment(node: JsxFragment, openingFragment: JsxOpeningFragment, children: readonly JsxChild[], closingFragment: JsxClosingFragment): JsxFragment;
3694-
createJsxAttribute(name: Identifier, initializer: StringLiteral | JsxExpression | undefined): JsxAttribute;
3695-
updateJsxAttribute(node: JsxAttribute, name: Identifier, initializer: StringLiteral | JsxExpression | undefined): JsxAttribute;
3695+
createJsxAttribute(name: Identifier, initializer: JSXAttributeValue | undefined): JsxAttribute;
3696+
updateJsxAttribute(node: JsxAttribute, name: Identifier, initializer: JSXAttributeValue | undefined): JsxAttribute;
36963697
createJsxAttributes(properties: readonly JsxAttributeLike[]): JsxAttributes;
36973698
updateJsxAttributes(node: JsxAttributes, properties: readonly JsxAttributeLike[]): JsxAttributes;
36983699
createJsxSpreadAttribute(expression: Expression): JsxSpreadAttribute;
@@ -7371,9 +7372,9 @@ declare namespace ts {
73717372
/** @deprecated Use `factory.updateJsxFragment` or the factory supplied by your transformation context instead. */
73727373
const updateJsxFragment: (node: JsxFragment, openingFragment: JsxOpeningFragment, children: readonly JsxChild[], closingFragment: JsxClosingFragment) => JsxFragment;
73737374
/** @deprecated Use `factory.createJsxAttribute` or the factory supplied by your transformation context instead. */
7374-
const createJsxAttribute: (name: Identifier, initializer: StringLiteral | JsxExpression | undefined) => JsxAttribute;
7375+
const createJsxAttribute: (name: Identifier, initializer: JSXAttributeValue | undefined) => JsxAttribute;
73757376
/** @deprecated Use `factory.updateJsxAttribute` or the factory supplied by your transformation context instead. */
7376-
const updateJsxAttribute: (node: JsxAttribute, name: Identifier, initializer: StringLiteral | JsxExpression | undefined) => JsxAttribute;
7377+
const updateJsxAttribute: (node: JsxAttribute, name: Identifier, initializer: JSXAttributeValue | undefined) => JsxAttribute;
73777378
/** @deprecated Use `factory.createJsxAttributes` or the factory supplied by your transformation context instead. */
73787379
const createJsxAttributes: (properties: readonly JsxAttributeLike[]) => JsxAttributes;
73797380
/** @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)