Skip to content

Commit

Permalink
fix(7410): allow using JSXElement as JSXAttributeValue (#47994)
Browse files Browse the repository at this point in the history
  • Loading branch information
a-tarasyuk committed May 11, 2022
1 parent 5c2febf commit c300fea
Show file tree
Hide file tree
Showing 24 changed files with 266 additions and 76 deletions.
4 changes: 4 additions & 0 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -435,6 +435,10 @@
"category": "Error",
"code": 1144
},
"'{' or JSX element expected.": {
"category": "Error",
"code": 1145
},
"Declaration expected.": {
"category": "Error",
"code": 1146
Expand Down
4 changes: 2 additions & 2 deletions src/compiler/factory/nodeFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4964,7 +4964,7 @@ namespace ts {
}

// @api
function createJsxAttribute(name: Identifier, initializer: StringLiteral | JsxExpression | undefined) {
function createJsxAttribute(name: Identifier, initializer: JsxAttributeValue | undefined) {
const node = createBaseNode<JsxAttribute>(SyntaxKind.JsxAttribute);
node.name = name;
node.initializer = initializer;
Expand All @@ -4976,7 +4976,7 @@ namespace ts {
}

// @api
function updateJsxAttribute(node: JsxAttribute, name: Identifier, initializer: StringLiteral | JsxExpression | undefined) {
function updateJsxAttribute(node: JsxAttribute, name: Identifier, initializer: JsxAttributeValue | undefined) {
return node.name !== name
|| node.initializer !== initializer
? update(createJsxAttribute(name, initializer), node)
Expand Down
26 changes: 17 additions & 9 deletions src/compiler/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5454,15 +5454,23 @@ namespace ts {

scanJsxIdentifier();
const pos = getNodePos();
return finishNode(
factory.createJsxAttribute(
parseIdentifierName(),
token() !== SyntaxKind.EqualsToken ? undefined :
scanJsxAttributeValue() === SyntaxKind.StringLiteral ? parseLiteralNode() as StringLiteral :
parseJsxExpression(/*inExpressionContext*/ true)
),
pos
);
return finishNode(factory.createJsxAttribute(parseIdentifierName(), parseJsxAttributeValue()), pos);
}

function parseJsxAttributeValue(): JsxAttributeValue | undefined {
if (token() === SyntaxKind.EqualsToken) {
if (scanJsxAttributeValue() === SyntaxKind.StringLiteral) {
return parseLiteralNode() as StringLiteral;
}
if (token() === SyntaxKind.OpenBraceToken) {
return parseJsxExpression(/*inExpressionContext*/ true);
}
if (token() === SyntaxKind.LessThanToken) {
return parseJsxElementOrSelfClosingElementOrFragment(/*inExpressionContext*/ true);
}
parseErrorAtCurrentToken(Diagnostics.or_JSX_element_expected);
}
return undefined;
}

function parseJsxSpreadAttribute(): JsxSpreadAttribute {
Expand Down
17 changes: 12 additions & 5 deletions src/compiler/transformers/jsx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -400,26 +400,33 @@ namespace ts {
return factory.createPropertyAssignment(name, expression);
}

function transformJsxAttributeInitializer(node: StringLiteral | JsxExpression | undefined): Expression {
function transformJsxAttributeInitializer(node: JsxAttributeValue | undefined): Expression {
if (node === undefined) {
return factory.createTrue();
}
else if (node.kind === SyntaxKind.StringLiteral) {
if (node.kind === SyntaxKind.StringLiteral) {
// Always recreate the literal to escape any escape sequences or newlines which may be in the original jsx string and which
// Need to be escaped to be handled correctly in a normal string
const singleQuote = node.singleQuote !== undefined ? node.singleQuote : !isStringDoubleQuoted(node, currentSourceFile);
const literal = factory.createStringLiteral(tryDecodeEntities(node.text) || node.text, singleQuote);
return setTextRange(literal, node);
}
else if (node.kind === SyntaxKind.JsxExpression) {
if (node.kind === SyntaxKind.JsxExpression) {
if (node.expression === undefined) {
return factory.createTrue();
}
return visitNode(node.expression, visitor, isExpression);
}
else {
return Debug.failBadSyntaxKind(node);
if (isJsxElement(node)) {
return visitJsxElement(node, /*isChild*/ false);
}
if (isJsxSelfClosingElement(node)) {
return visitJsxSelfClosingElement(node, /*isChild*/ false);
}
if (isJsxFragment(node)) {
return visitJsxFragment(node, /*isChild*/ false);
}
return Debug.failBadSyntaxKind(node);
}

function visitJsxText(node: JsxText): StringLiteral | undefined {
Expand Down
13 changes: 10 additions & 3 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2612,9 +2612,16 @@ namespace ts {
readonly parent: JsxAttributes;
readonly name: Identifier;
/// JSX attribute initializers are optional; <X y /> is sugar for <X y={true} />
readonly initializer?: StringLiteral | JsxExpression;
readonly initializer?: JsxAttributeValue;
}

export type JsxAttributeValue =
| StringLiteral
| JsxExpression
| JsxElement
| JsxSelfClosingElement
| JsxFragment;

export interface JsxSpreadAttribute extends ObjectLiteralElement {
readonly kind: SyntaxKind.JsxSpreadAttribute;
readonly parent: JsxAttributes;
Expand Down Expand Up @@ -7680,8 +7687,8 @@ namespace ts {
createJsxOpeningFragment(): JsxOpeningFragment;
createJsxJsxClosingFragment(): JsxClosingFragment;
updateJsxFragment(node: JsxFragment, openingFragment: JsxOpeningFragment, children: readonly JsxChild[], closingFragment: JsxClosingFragment): JsxFragment;
createJsxAttribute(name: Identifier, initializer: StringLiteral | JsxExpression | undefined): JsxAttribute;
updateJsxAttribute(node: JsxAttribute, name: Identifier, initializer: StringLiteral | JsxExpression | undefined): JsxAttribute;
createJsxAttribute(name: Identifier, initializer: JsxAttributeValue | undefined): JsxAttribute;
updateJsxAttribute(node: JsxAttribute, name: Identifier, initializer: JsxAttributeValue | undefined): JsxAttribute;
createJsxAttributes(properties: readonly JsxAttributeLike[]): JsxAttributes;
updateJsxAttributes(node: JsxAttributes, properties: readonly JsxAttributeLike[]): JsxAttributes;
createJsxSpreadAttribute(expression: Expression): JsxSpreadAttribute;
Expand Down
11 changes: 6 additions & 5 deletions tests/baselines/reference/api/tsserverlibrary.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1388,8 +1388,9 @@ declare namespace ts {
readonly kind: SyntaxKind.JsxAttribute;
readonly parent: JsxAttributes;
readonly name: Identifier;
readonly initializer?: StringLiteral | JsxExpression;
readonly initializer?: JsxAttributeValue;
}
export type JsxAttributeValue = StringLiteral | JsxExpression | JsxElement | JsxSelfClosingElement | JsxFragment;
export interface JsxSpreadAttribute extends ObjectLiteralElement {
readonly kind: SyntaxKind.JsxSpreadAttribute;
readonly parent: JsxAttributes;
Expand Down Expand Up @@ -3746,8 +3747,8 @@ declare namespace ts {
createJsxOpeningFragment(): JsxOpeningFragment;
createJsxJsxClosingFragment(): JsxClosingFragment;
updateJsxFragment(node: JsxFragment, openingFragment: JsxOpeningFragment, children: readonly JsxChild[], closingFragment: JsxClosingFragment): JsxFragment;
createJsxAttribute(name: Identifier, initializer: StringLiteral | JsxExpression | undefined): JsxAttribute;
updateJsxAttribute(node: JsxAttribute, name: Identifier, initializer: StringLiteral | JsxExpression | undefined): JsxAttribute;
createJsxAttribute(name: Identifier, initializer: JsxAttributeValue | undefined): JsxAttribute;
updateJsxAttribute(node: JsxAttribute, name: Identifier, initializer: JsxAttributeValue | undefined): JsxAttribute;
createJsxAttributes(properties: readonly JsxAttributeLike[]): JsxAttributes;
updateJsxAttributes(node: JsxAttributes, properties: readonly JsxAttributeLike[]): JsxAttributes;
createJsxSpreadAttribute(expression: Expression): JsxSpreadAttribute;
Expand Down Expand Up @@ -11339,9 +11340,9 @@ declare namespace ts {
/** @deprecated Use `factory.updateJsxFragment` or the factory supplied by your transformation context instead. */
const updateJsxFragment: (node: JsxFragment, openingFragment: JsxOpeningFragment, children: readonly JsxChild[], closingFragment: JsxClosingFragment) => JsxFragment;
/** @deprecated Use `factory.createJsxAttribute` or the factory supplied by your transformation context instead. */
const createJsxAttribute: (name: Identifier, initializer: StringLiteral | JsxExpression | undefined) => JsxAttribute;
const createJsxAttribute: (name: Identifier, initializer: JsxAttributeValue | undefined) => JsxAttribute;
/** @deprecated Use `factory.updateJsxAttribute` or the factory supplied by your transformation context instead. */
const updateJsxAttribute: (node: JsxAttribute, name: Identifier, initializer: StringLiteral | JsxExpression | undefined) => JsxAttribute;
const updateJsxAttribute: (node: JsxAttribute, name: Identifier, initializer: JsxAttributeValue | undefined) => JsxAttribute;
/** @deprecated Use `factory.createJsxAttributes` or the factory supplied by your transformation context instead. */
const createJsxAttributes: (properties: readonly JsxAttributeLike[]) => JsxAttributes;
/** @deprecated Use `factory.updateJsxAttributes` or the factory supplied by your transformation context instead. */
Expand Down
11 changes: 6 additions & 5 deletions tests/baselines/reference/api/typescript.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1388,8 +1388,9 @@ declare namespace ts {
readonly kind: SyntaxKind.JsxAttribute;
readonly parent: JsxAttributes;
readonly name: Identifier;
readonly initializer?: StringLiteral | JsxExpression;
readonly initializer?: JsxAttributeValue;
}
export type JsxAttributeValue = StringLiteral | JsxExpression | JsxElement | JsxSelfClosingElement | JsxFragment;
export interface JsxSpreadAttribute extends ObjectLiteralElement {
readonly kind: SyntaxKind.JsxSpreadAttribute;
readonly parent: JsxAttributes;
Expand Down Expand Up @@ -3746,8 +3747,8 @@ declare namespace ts {
createJsxOpeningFragment(): JsxOpeningFragment;
createJsxJsxClosingFragment(): JsxClosingFragment;
updateJsxFragment(node: JsxFragment, openingFragment: JsxOpeningFragment, children: readonly JsxChild[], closingFragment: JsxClosingFragment): JsxFragment;
createJsxAttribute(name: Identifier, initializer: StringLiteral | JsxExpression | undefined): JsxAttribute;
updateJsxAttribute(node: JsxAttribute, name: Identifier, initializer: StringLiteral | JsxExpression | undefined): JsxAttribute;
createJsxAttribute(name: Identifier, initializer: JsxAttributeValue | undefined): JsxAttribute;
updateJsxAttribute(node: JsxAttribute, name: Identifier, initializer: JsxAttributeValue | undefined): JsxAttribute;
createJsxAttributes(properties: readonly JsxAttributeLike[]): JsxAttributes;
updateJsxAttributes(node: JsxAttributes, properties: readonly JsxAttributeLike[]): JsxAttributes;
createJsxSpreadAttribute(expression: Expression): JsxSpreadAttribute;
Expand Down Expand Up @@ -7481,9 +7482,9 @@ declare namespace ts {
/** @deprecated Use `factory.updateJsxFragment` or the factory supplied by your transformation context instead. */
const updateJsxFragment: (node: JsxFragment, openingFragment: JsxOpeningFragment, children: readonly JsxChild[], closingFragment: JsxClosingFragment) => JsxFragment;
/** @deprecated Use `factory.createJsxAttribute` or the factory supplied by your transformation context instead. */
const createJsxAttribute: (name: Identifier, initializer: StringLiteral | JsxExpression | undefined) => JsxAttribute;
const createJsxAttribute: (name: Identifier, initializer: JsxAttributeValue | undefined) => JsxAttribute;
/** @deprecated Use `factory.updateJsxAttribute` or the factory supplied by your transformation context instead. */
const updateJsxAttribute: (node: JsxAttribute, name: Identifier, initializer: StringLiteral | JsxExpression | undefined) => JsxAttribute;
const updateJsxAttribute: (node: JsxAttribute, name: Identifier, initializer: JsxAttributeValue | undefined) => JsxAttribute;
/** @deprecated Use `factory.createJsxAttributes` or the factory supplied by your transformation context instead. */
const createJsxAttributes: (properties: readonly JsxAttributeLike[]) => JsxAttributes;
/** @deprecated Use `factory.updateJsxAttributes` or the factory supplied by your transformation context instead. */
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
tests/cases/conformance/jsx/a.tsx(7,16): error TS1145: '{' or JSX element expected.


==== tests/cases/conformance/jsx/a.tsx (1 errors) ====
declare var React: any;

<div>
<div attr=<div /> />
<div attr=<div>foo</div> />
<div attr=<><div>foo</div></> />
<div attr= />
~
!!! error TS1145: '{' or JSX element expected.
</div>

18 changes: 18 additions & 0 deletions tests/baselines/reference/jsxAttributeInitializer(jsx=preserve).js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
//// [a.tsx]
declare var React: any;

<div>
<div attr=<div /> />
<div attr=<div>foo</div> />
<div attr=<><div>foo</div></> />
<div attr= />
</div>


//// [a.jsx]
<div>
<div attr=<div />/>
<div attr=<div>foo</div>/>
<div attr=<><div>foo</div></>/>
<div attr/>
</div>;
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
=== tests/cases/conformance/jsx/a.tsx ===
declare var React: any;
>React : Symbol(React, Decl(a.tsx, 0, 11))

<div>
<div attr=<div /> />
>attr : Symbol(attr, Decl(a.tsx, 3, 8))

<div attr=<div>foo</div> />
>attr : Symbol(attr, Decl(a.tsx, 4, 8))

<div attr=<><div>foo</div></> />
>attr : Symbol(attr, Decl(a.tsx, 5, 8))

<div attr= />
>attr : Symbol(attr, Decl(a.tsx, 6, 8))

</div>

Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
=== tests/cases/conformance/jsx/a.tsx ===
declare var React: any;
>React : any

<div>
><div> <div attr=<div /> /> <div attr=<div>foo</div> /> <div attr=<><div>foo</div></> /> <div attr= /></div> : any
>div : any

<div attr=<div /> />
><div attr=<div /> /> : any
>div : any
>attr : any
><div /> : any
>div : any

<div attr=<div>foo</div> />
><div attr=<div>foo</div> /> : any
>div : any
>attr : any
><div>foo</div> : any
>div : any
>div : any

<div attr=<><div>foo</div></> />
><div attr=<><div>foo</div></> /> : any
>div : any
>attr : any
><><div>foo</div></> : any
><div>foo</div> : any
>div : any
>div : any

<div attr= />
><div attr= /> : any
>div : any
>attr : true

</div>
>div : any

Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
tests/cases/conformance/jsx/a.tsx(7,16): error TS1145: '{' or JSX element expected.


==== tests/cases/conformance/jsx/a.tsx (1 errors) ====
declare var React: any;

<div>
<div attr=<div /> />
<div attr=<div>foo</div> />
<div attr=<><div>foo</div></> />
<div attr= />
~
!!! error TS1145: '{' or JSX element expected.
</div>

18 changes: 18 additions & 0 deletions tests/baselines/reference/jsxAttributeInitializer(jsx=react).js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
//// [a.tsx]
declare var React: any;

<div>
<div attr=<div /> />
<div attr=<div>foo</div> />
<div attr=<><div>foo</div></> />
<div attr= />
</div>


//// [a.js]
React.createElement("div", null,
React.createElement("div", { attr: React.createElement("div", null) }),
React.createElement("div", { attr: React.createElement("div", null, "foo") }),
React.createElement("div", { attr: React.createElement(React.Fragment, null,
React.createElement("div", null, "foo")) }),
React.createElement("div", { attr: true }));
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
=== tests/cases/conformance/jsx/a.tsx ===
declare var React: any;
>React : Symbol(React, Decl(a.tsx, 0, 11))

<div>
<div attr=<div /> />
>attr : Symbol(attr, Decl(a.tsx, 3, 8))

<div attr=<div>foo</div> />
>attr : Symbol(attr, Decl(a.tsx, 4, 8))

<div attr=<><div>foo</div></> />
>attr : Symbol(attr, Decl(a.tsx, 5, 8))

<div attr= />
>attr : Symbol(attr, Decl(a.tsx, 6, 8))

</div>

40 changes: 40 additions & 0 deletions tests/baselines/reference/jsxAttributeInitializer(jsx=react).types
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
=== tests/cases/conformance/jsx/a.tsx ===
declare var React: any;
>React : any

<div>
><div> <div attr=<div /> /> <div attr=<div>foo</div> /> <div attr=<><div>foo</div></> /> <div attr= /></div> : any
>div : any

<div attr=<div /> />
><div attr=<div /> /> : any
>div : any
>attr : any
><div /> : any
>div : any

<div attr=<div>foo</div> />
><div attr=<div>foo</div> /> : any
>div : any
>attr : any
><div>foo</div> : any
>div : any
>div : any

<div attr=<><div>foo</div></> />
><div attr=<><div>foo</div></> /> : any
>div : any
>attr : any
><><div>foo</div></> : any
><div>foo</div> : any
>div : any
>div : any

<div attr= />
><div attr= /> : any
>div : any
>attr : true

</div>
>div : any

Loading

0 comments on commit c300fea

Please sign in to comment.