Skip to content

Commit 80e1a29

Browse files
authored
fix(46305): omit converting jsx (react-jsx) spread attributes to Object.assign for ES2018 and up (microsoft#46317)
1 parent 8718df3 commit 80e1a29

11 files changed

+898
-82
lines changed

src/compiler/transformers/jsx.ts

+50-82
Original file line numberDiff line numberDiff line change
@@ -200,59 +200,27 @@ namespace ts {
200200
}
201201

202202
function convertJsxChildrenToChildrenPropObject(children: readonly JsxChild[]) {
203+
const prop = convertJsxChildrenToChildrenPropAssignment(children);
204+
return prop && factory.createObjectLiteralExpression([prop]);
205+
}
206+
207+
function convertJsxChildrenToChildrenPropAssignment(children: readonly JsxChild[]) {
203208
const nonWhitespaceChildren = getSemanticJsxChildren(children);
204209
if (length(nonWhitespaceChildren) === 1) {
205210
const result = transformJsxChildToExpression(nonWhitespaceChildren[0]);
206-
return result && factory.createObjectLiteralExpression([
207-
factory.createPropertyAssignment("children", result)
208-
]);
211+
return result && factory.createPropertyAssignment("children", result);
209212
}
210213
const result = mapDefined(children, transformJsxChildToExpression);
211-
return !result.length ? undefined : factory.createObjectLiteralExpression([
212-
factory.createPropertyAssignment("children", factory.createArrayLiteralExpression(result))
213-
]);
214+
return length(result) ? factory.createPropertyAssignment("children", factory.createArrayLiteralExpression(result)) : undefined;
214215
}
215216

216217
function visitJsxOpeningLikeElementJSX(node: JsxOpeningLikeElement, children: readonly JsxChild[] | undefined, isChild: boolean, location: TextRange) {
217218
const tagName = getTagName(node);
218-
let objectProperties: Expression;
219+
const childrenProp = children && children.length ? convertJsxChildrenToChildrenPropAssignment(children) : undefined;
219220
const keyAttr = find(node.attributes.properties, p => !!p.name && isIdentifier(p.name) && p.name.escapedText === "key") as JsxAttribute | undefined;
220221
const attrs = keyAttr ? filter(node.attributes.properties, p => p !== keyAttr) : node.attributes.properties;
221-
222-
let segments: Expression[] = [];
223-
if (attrs.length) {
224-
// Map spans of JsxAttribute nodes into object literals and spans
225-
// of JsxSpreadAttribute nodes into expressions.
226-
segments = flatten(
227-
spanMap(attrs, isJsxSpreadAttribute, (attrs, isSpread) => isSpread
228-
? map(attrs, transformJsxSpreadAttributeToExpression)
229-
: factory.createObjectLiteralExpression(map(attrs, transformJsxAttributeToObjectLiteralElement))
230-
)
231-
);
232-
233-
if (isJsxSpreadAttribute(attrs[0])) {
234-
// We must always emit at least one object literal before a spread
235-
// argument.factory.createObjectLiteral
236-
segments.unshift(factory.createObjectLiteralExpression());
237-
}
238-
}
239-
if (children && children.length) {
240-
const result = convertJsxChildrenToChildrenPropObject(children);
241-
if (result) {
242-
segments.push(result);
243-
}
244-
}
245-
246-
if (segments.length === 0) {
247-
objectProperties = factory.createObjectLiteralExpression([]);
248-
// When there are no attributes, React wants {}
249-
}
250-
else {
251-
// Either emit one big object literal (no spread attribs), or
252-
// a call to the __assign helper.
253-
objectProperties = singleOrUndefined(segments) || emitHelpers().createAssignHelper(segments);
254-
}
255-
222+
const objectProperties = length(attrs) ? transformJsxAttributesToObjectProps(attrs, childrenProp) :
223+
factory.createObjectLiteralExpression(childrenProp ? [childrenProp] : emptyArray); // When there are no attributes, React wants {}
256224
return visitJsxOpeningLikeElementOrFragmentJSX(tagName, objectProperties, keyAttr, length(getSemanticJsxChildren(children || emptyArray)), isChild, location);
257225
}
258226

@@ -285,47 +253,9 @@ namespace ts {
285253

286254
function visitJsxOpeningLikeElementCreateElement(node: JsxOpeningLikeElement, children: readonly JsxChild[] | undefined, isChild: boolean, location: TextRange) {
287255
const tagName = getTagName(node);
288-
let objectProperties: Expression | undefined;
289256
const attrs = node.attributes.properties;
290-
if (attrs.length === 0) {
291-
objectProperties = factory.createNull();
292-
// When there are no attributes, React wants "null"
293-
}
294-
else {
295-
const target = getEmitScriptTarget(compilerOptions);
296-
if (target && target >= ScriptTarget.ES2018) {
297-
objectProperties = factory.createObjectLiteralExpression(
298-
flatten<SpreadAssignment | PropertyAssignment>(
299-
spanMap(attrs, isJsxSpreadAttribute, (attrs, isSpread) =>
300-
isSpread ? map(attrs, transformJsxSpreadAttributeToSpreadAssignment) : map(attrs, transformJsxAttributeToObjectLiteralElement)
301-
)
302-
)
303-
);
304-
}
305-
else {
306-
// Map spans of JsxAttribute nodes into object literals and spans
307-
// of JsxSpreadAttribute nodes into expressions.
308-
const segments = flatten<Expression | ObjectLiteralExpression>(
309-
spanMap(attrs, isJsxSpreadAttribute, (attrs, isSpread) => isSpread
310-
? map(attrs, transformJsxSpreadAttributeToExpression)
311-
: factory.createObjectLiteralExpression(map(attrs, transformJsxAttributeToObjectLiteralElement))
312-
)
313-
);
314-
315-
if (isJsxSpreadAttribute(attrs[0])) {
316-
// We must always emit at least one object literal before a spread
317-
// argument.factory.createObjectLiteral
318-
segments.unshift(factory.createObjectLiteralExpression());
319-
}
320-
321-
// Either emit one big object literal (no spread attribs), or
322-
// a call to the __assign helper.
323-
objectProperties = singleOrUndefined(segments);
324-
if (!objectProperties) {
325-
objectProperties = emitHelpers().createAssignHelper(segments);
326-
}
327-
}
328-
}
257+
const objectProperties = length(attrs) ? transformJsxAttributesToObjectProps(attrs) :
258+
factory.createNull(); // When there are no attributes, React wants "null"
329259

330260
const callee = currentFileState.importSpecifier === undefined
331261
? createJsxFactoryExpression(
@@ -392,6 +322,44 @@ namespace ts {
392322
return factory.createSpreadAssignment(visitNode(node.expression, visitor, isExpression));
393323
}
394324

325+
function transformJsxAttributesToObjectProps(attrs: readonly(JsxSpreadAttribute | JsxAttribute)[], children?: PropertyAssignment) {
326+
const target = getEmitScriptTarget(compilerOptions);
327+
return target && target >= ScriptTarget.ES2018 ? factory.createObjectLiteralExpression(transformJsxAttributesToProps(attrs, children)) :
328+
transformJsxAttributesToExpression(attrs, children);
329+
}
330+
331+
function transformJsxAttributesToProps(attrs: readonly(JsxSpreadAttribute | JsxAttribute)[], children?: PropertyAssignment) {
332+
const props = flatten<SpreadAssignment | PropertyAssignment>(spanMap(attrs, isJsxSpreadAttribute, (attrs, isSpread) =>
333+
map(attrs, attr => isSpread ? transformJsxSpreadAttributeToSpreadAssignment(attr as JsxSpreadAttribute) : transformJsxAttributeToObjectLiteralElement(attr as JsxAttribute))));
334+
if (children) {
335+
props.push(children);
336+
}
337+
return props;
338+
}
339+
340+
function transformJsxAttributesToExpression(attrs: readonly(JsxSpreadAttribute | JsxAttribute)[], children?: PropertyAssignment) {
341+
// Map spans of JsxAttribute nodes into object literals and spans
342+
// of JsxSpreadAttribute nodes into expressions.
343+
const expressions = flatten(
344+
spanMap(attrs, isJsxSpreadAttribute, (attrs, isSpread) => isSpread
345+
? map(attrs, transformJsxSpreadAttributeToExpression)
346+
: factory.createObjectLiteralExpression(map(attrs, transformJsxAttributeToObjectLiteralElement))
347+
)
348+
);
349+
350+
if (isJsxSpreadAttribute(attrs[0])) {
351+
// We must always emit at least one object literal before a spread
352+
// argument.factory.createObjectLiteral
353+
expressions.unshift(factory.createObjectLiteralExpression());
354+
}
355+
356+
if (children) {
357+
expressions.push(factory.createObjectLiteralExpression([children]));
358+
}
359+
360+
return singleOrUndefined(expressions) || emitHelpers().createAssignHelper(expressions);
361+
}
362+
395363
function transformJsxSpreadAttributeToExpression(node: JsxSpreadAttribute) {
396364
return visitNode(node.expression, visitor, isExpression);
397365
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
//// [test.tsx]
2+
/// <reference path="/.lib/react16.d.ts" />
3+
4+
export function T1(a: any) {
5+
return <div className={"T1"} { ...a }>T1</div>;
6+
}
7+
8+
export function T2(a: any, b: any) {
9+
return <div className={"T2"} { ...a } { ...b }>T2</div>;
10+
}
11+
12+
export function T3(a: any, b: any) {
13+
return <div { ...a } className={"T3"} { ...b }>T3</div>;
14+
}
15+
16+
export function T4(a: any, b: any) {
17+
return <div className={"T4"} { ...{ ...a, ...b } }>T4</div>;
18+
}
19+
20+
export function T5(a: any, b: any, c: any, d: any) {
21+
return <div className={"T5"} { ...{ ...a, ...b, ...{ c, d } } }>T5</div>;
22+
}
23+
24+
export function T6(a: any, b: any, c: any, d: any) {
25+
return <div className={"T6"} { ...{ ...a, ...b, ...{ ...c, ...d } } }>T6</div>;
26+
}
27+
28+
export function T7(a: any, b: any, c: any, d: any) {
29+
return <div>T7</div>;
30+
}
31+
32+
33+
//// [test.js]
34+
import { jsx as _jsx } from "react/jsx-runtime";
35+
/// <reference path="react16.d.ts" />
36+
export function T1(a) {
37+
return _jsx("div", Object.assign({ className: "T1" }, a, { children: "T1" }), void 0);
38+
}
39+
export function T2(a, b) {
40+
return _jsx("div", Object.assign({ className: "T2" }, a, b, { children: "T2" }), void 0);
41+
}
42+
export function T3(a, b) {
43+
return _jsx("div", Object.assign({}, a, { className: "T3" }, b, { children: "T3" }), void 0);
44+
}
45+
export function T4(a, b) {
46+
return _jsx("div", Object.assign({ className: "T4" }, Object.assign(Object.assign({}, a), b), { children: "T4" }), void 0);
47+
}
48+
export function T5(a, b, c, d) {
49+
return _jsx("div", Object.assign({ className: "T5" }, Object.assign(Object.assign(Object.assign({}, a), b), { c, d }), { children: "T5" }), void 0);
50+
}
51+
export function T6(a, b, c, d) {
52+
return _jsx("div", Object.assign({ className: "T6" }, Object.assign(Object.assign(Object.assign({}, a), b), Object.assign(Object.assign({}, c), d)), { children: "T6" }), void 0);
53+
}
54+
export function T7(a, b, c, d) {
55+
return _jsx("div", { children: "T7" }, void 0);
56+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
=== tests/cases/conformance/jsx/test.tsx ===
2+
/// <reference path="react16.d.ts" />
3+
4+
export function T1(a: any) {
5+
>T1 : Symbol(T1, Decl(test.tsx, 0, 0))
6+
>a : Symbol(a, Decl(test.tsx, 2, 19))
7+
8+
return <div className={"T1"} { ...a }>T1</div>;
9+
>div : Symbol(JSX.IntrinsicElements.div, Decl(react16.d.ts, 2546, 114))
10+
>className : Symbol(className, Decl(test.tsx, 3, 15))
11+
>a : Symbol(a, Decl(test.tsx, 2, 19))
12+
>div : Symbol(JSX.IntrinsicElements.div, Decl(react16.d.ts, 2546, 114))
13+
}
14+
15+
export function T2(a: any, b: any) {
16+
>T2 : Symbol(T2, Decl(test.tsx, 4, 1))
17+
>a : Symbol(a, Decl(test.tsx, 6, 19))
18+
>b : Symbol(b, Decl(test.tsx, 6, 26))
19+
20+
return <div className={"T2"} { ...a } { ...b }>T2</div>;
21+
>div : Symbol(JSX.IntrinsicElements.div, Decl(react16.d.ts, 2546, 114))
22+
>className : Symbol(className, Decl(test.tsx, 7, 15))
23+
>a : Symbol(a, Decl(test.tsx, 6, 19))
24+
>b : Symbol(b, Decl(test.tsx, 6, 26))
25+
>div : Symbol(JSX.IntrinsicElements.div, Decl(react16.d.ts, 2546, 114))
26+
}
27+
28+
export function T3(a: any, b: any) {
29+
>T3 : Symbol(T3, Decl(test.tsx, 8, 1))
30+
>a : Symbol(a, Decl(test.tsx, 10, 19))
31+
>b : Symbol(b, Decl(test.tsx, 10, 26))
32+
33+
return <div { ...a } className={"T3"} { ...b }>T3</div>;
34+
>div : Symbol(JSX.IntrinsicElements.div, Decl(react16.d.ts, 2546, 114))
35+
>a : Symbol(a, Decl(test.tsx, 10, 19))
36+
>className : Symbol(className, Decl(test.tsx, 11, 24))
37+
>b : Symbol(b, Decl(test.tsx, 10, 26))
38+
>div : Symbol(JSX.IntrinsicElements.div, Decl(react16.d.ts, 2546, 114))
39+
}
40+
41+
export function T4(a: any, b: any) {
42+
>T4 : Symbol(T4, Decl(test.tsx, 12, 1))
43+
>a : Symbol(a, Decl(test.tsx, 14, 19))
44+
>b : Symbol(b, Decl(test.tsx, 14, 26))
45+
46+
return <div className={"T4"} { ...{ ...a, ...b } }>T4</div>;
47+
>div : Symbol(JSX.IntrinsicElements.div, Decl(react16.d.ts, 2546, 114))
48+
>className : Symbol(className, Decl(test.tsx, 15, 15))
49+
>a : Symbol(a, Decl(test.tsx, 14, 19))
50+
>b : Symbol(b, Decl(test.tsx, 14, 26))
51+
>div : Symbol(JSX.IntrinsicElements.div, Decl(react16.d.ts, 2546, 114))
52+
}
53+
54+
export function T5(a: any, b: any, c: any, d: any) {
55+
>T5 : Symbol(T5, Decl(test.tsx, 16, 1))
56+
>a : Symbol(a, Decl(test.tsx, 18, 19))
57+
>b : Symbol(b, Decl(test.tsx, 18, 26))
58+
>c : Symbol(c, Decl(test.tsx, 18, 34))
59+
>d : Symbol(d, Decl(test.tsx, 18, 42))
60+
61+
return <div className={"T5"} { ...{ ...a, ...b, ...{ c, d } } }>T5</div>;
62+
>div : Symbol(JSX.IntrinsicElements.div, Decl(react16.d.ts, 2546, 114))
63+
>className : Symbol(className, Decl(test.tsx, 19, 15))
64+
>a : Symbol(a, Decl(test.tsx, 18, 19))
65+
>b : Symbol(b, Decl(test.tsx, 18, 26))
66+
>c : Symbol(c, Decl(test.tsx, 19, 56))
67+
>d : Symbol(d, Decl(test.tsx, 19, 59))
68+
>div : Symbol(JSX.IntrinsicElements.div, Decl(react16.d.ts, 2546, 114))
69+
}
70+
71+
export function T6(a: any, b: any, c: any, d: any) {
72+
>T6 : Symbol(T6, Decl(test.tsx, 20, 1))
73+
>a : Symbol(a, Decl(test.tsx, 22, 19))
74+
>b : Symbol(b, Decl(test.tsx, 22, 26))
75+
>c : Symbol(c, Decl(test.tsx, 22, 34))
76+
>d : Symbol(d, Decl(test.tsx, 22, 42))
77+
78+
return <div className={"T6"} { ...{ ...a, ...b, ...{ ...c, ...d } } }>T6</div>;
79+
>div : Symbol(JSX.IntrinsicElements.div, Decl(react16.d.ts, 2546, 114))
80+
>className : Symbol(className, Decl(test.tsx, 23, 15))
81+
>a : Symbol(a, Decl(test.tsx, 22, 19))
82+
>b : Symbol(b, Decl(test.tsx, 22, 26))
83+
>c : Symbol(c, Decl(test.tsx, 22, 34))
84+
>d : Symbol(d, Decl(test.tsx, 22, 42))
85+
>div : Symbol(JSX.IntrinsicElements.div, Decl(react16.d.ts, 2546, 114))
86+
}
87+
88+
export function T7(a: any, b: any, c: any, d: any) {
89+
>T7 : Symbol(T7, Decl(test.tsx, 24, 1))
90+
>a : Symbol(a, Decl(test.tsx, 26, 19))
91+
>b : Symbol(b, Decl(test.tsx, 26, 26))
92+
>c : Symbol(c, Decl(test.tsx, 26, 34))
93+
>d : Symbol(d, Decl(test.tsx, 26, 42))
94+
95+
return <div>T7</div>;
96+
>div : Symbol(JSX.IntrinsicElements.div, Decl(react16.d.ts, 2546, 114))
97+
>div : Symbol(JSX.IntrinsicElements.div, Decl(react16.d.ts, 2546, 114))
98+
}
99+

0 commit comments

Comments
 (0)