Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(7410): Support JSX Element as JSX Attribute Value #47994

Merged
merged 1 commit into from
May 11, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -4856,7 +4856,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 @@ -4868,7 +4868,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 @@ -5284,15 +5284,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 @@ -2590,9 +2590,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 @@ -7583,8 +7590,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 @@ -1372,8 +1372,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 @@ -3691,8 +3692,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 @@ -11189,9 +11190,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 @@ -1372,8 +1372,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 @@ -3691,8 +3692,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 @@ -7371,9 +7372,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