Skip to content

Commit e961916

Browse files
authored
fix(42368): omit converting jsx spread attributes to Object.assign for ES2018 and up (microsoft#42554)
1 parent 0723904 commit e961916

11 files changed

+747
-18
lines changed

src/compiler/transformers/jsx.ts

+34-18
Original file line numberDiff line numberDiff line change
@@ -292,26 +292,38 @@ namespace ts {
292292
// When there are no attributes, React wants "null"
293293
}
294294
else {
295-
// Map spans of JsxAttribute nodes into object literals and spans
296-
// of JsxSpreadAttribute nodes into expressions.
297-
const segments = flatten<Expression | ObjectLiteralExpression>(
298-
spanMap(attrs, isJsxSpreadAttribute, (attrs, isSpread) => isSpread
299-
? map(attrs, transformJsxSpreadAttributeToExpression)
300-
: factory.createObjectLiteralExpression(map(attrs, transformJsxAttributeToObjectLiteralElement))
301-
)
302-
);
303-
304-
if (isJsxSpreadAttribute(attrs[0])) {
305-
// We must always emit at least one object literal before a spread
306-
// argument.factory.createObjectLiteral
307-
segments.unshift(factory.createObjectLiteralExpression());
295+
const target = compilerOptions.target;
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+
);
308304
}
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+
}
309320

310-
// Either emit one big object literal (no spread attribs), or
311-
// a call to the __assign helper.
312-
objectProperties = singleOrUndefined(segments);
313-
if (!objectProperties) {
314-
objectProperties = emitHelpers().createAssignHelper(segments);
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+
}
315327
}
316328
}
317329

@@ -376,6 +388,10 @@ namespace ts {
376388
return element;
377389
}
378390

391+
function transformJsxSpreadAttributeToSpreadAssignment(node: JsxSpreadAttribute) {
392+
return factory.createSpreadAssignment(visitNode(node.expression, visitor, isExpression));
393+
}
394+
379395
function transformJsxSpreadAttributeToExpression(node: JsxSpreadAttribute) {
380396
return visitNode(node.expression, visitor, isExpression);
381397
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
//// [test.tsx]
2+
declare const React: any;
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+
29+
//// [test.js]
30+
export function T1(a) {
31+
return React.createElement("div", Object.assign({ className: "T1" }, a), "T1");
32+
}
33+
export function T2(a, b) {
34+
return React.createElement("div", Object.assign({ className: "T2" }, a, b), "T2");
35+
}
36+
export function T3(a, b) {
37+
return React.createElement("div", Object.assign({}, a, { className: "T3" }, b), "T3");
38+
}
39+
export function T4(a, b) {
40+
return React.createElement("div", Object.assign({ className: "T4" }, Object.assign(Object.assign({}, a), b)), "T4");
41+
}
42+
export function T5(a, b, c, d) {
43+
return React.createElement("div", Object.assign({ className: "T5" }, Object.assign(Object.assign(Object.assign({}, a), b), { c, d })), "T5");
44+
}
45+
export function T6(a, b, c, d) {
46+
return React.createElement("div", Object.assign({ className: "T6" }, Object.assign(Object.assign(Object.assign({}, a), b), Object.assign(Object.assign({}, c), d))), "T6");
47+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
=== tests/cases/conformance/jsx/test.tsx ===
2+
declare const React: any;
3+
>React : Symbol(React, Decl(test.tsx, 0, 13))
4+
5+
export function T1(a: any) {
6+
>T1 : Symbol(T1, Decl(test.tsx, 0, 25))
7+
>a : Symbol(a, Decl(test.tsx, 2, 19))
8+
9+
return <div className={"T1"} { ...a }>T1</div>;
10+
>className : Symbol(className, Decl(test.tsx, 3, 15))
11+
>a : Symbol(a, Decl(test.tsx, 2, 19))
12+
}
13+
14+
export function T2(a: any, b: any) {
15+
>T2 : Symbol(T2, Decl(test.tsx, 4, 1))
16+
>a : Symbol(a, Decl(test.tsx, 6, 19))
17+
>b : Symbol(b, Decl(test.tsx, 6, 26))
18+
19+
return <div className={"T2"} { ...a } { ...b }>T2</div>;
20+
>className : Symbol(className, Decl(test.tsx, 7, 15))
21+
>a : Symbol(a, Decl(test.tsx, 6, 19))
22+
>b : Symbol(b, Decl(test.tsx, 6, 26))
23+
}
24+
25+
export function T3(a: any, b: any) {
26+
>T3 : Symbol(T3, Decl(test.tsx, 8, 1))
27+
>a : Symbol(a, Decl(test.tsx, 10, 19))
28+
>b : Symbol(b, Decl(test.tsx, 10, 26))
29+
30+
return <div { ...a } className={"T3"} { ...b }>T3</div>;
31+
>a : Symbol(a, Decl(test.tsx, 10, 19))
32+
>className : Symbol(className, Decl(test.tsx, 11, 24))
33+
>b : Symbol(b, Decl(test.tsx, 10, 26))
34+
}
35+
36+
export function T4(a: any, b: any) {
37+
>T4 : Symbol(T4, Decl(test.tsx, 12, 1))
38+
>a : Symbol(a, Decl(test.tsx, 14, 19))
39+
>b : Symbol(b, Decl(test.tsx, 14, 26))
40+
41+
return <div className={"T4"} { ...{ ...a, ...b } }>T4</div>;
42+
>className : Symbol(className, Decl(test.tsx, 15, 15))
43+
>a : Symbol(a, Decl(test.tsx, 14, 19))
44+
>b : Symbol(b, Decl(test.tsx, 14, 26))
45+
}
46+
47+
export function T5(a: any, b: any, c: any, d: any) {
48+
>T5 : Symbol(T5, Decl(test.tsx, 16, 1))
49+
>a : Symbol(a, Decl(test.tsx, 18, 19))
50+
>b : Symbol(b, Decl(test.tsx, 18, 26))
51+
>c : Symbol(c, Decl(test.tsx, 18, 34))
52+
>d : Symbol(d, Decl(test.tsx, 18, 42))
53+
54+
return <div className={"T5"} { ...{ ...a, ...b, ...{ c, d } } }>T5</div>;
55+
>className : Symbol(className, Decl(test.tsx, 19, 15))
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, 19, 56))
59+
>d : Symbol(d, Decl(test.tsx, 19, 59))
60+
}
61+
62+
export function T6(a: any, b: any, c: any, d: any) {
63+
>T6 : Symbol(T6, Decl(test.tsx, 20, 1))
64+
>a : Symbol(a, Decl(test.tsx, 22, 19))
65+
>b : Symbol(b, Decl(test.tsx, 22, 26))
66+
>c : Symbol(c, Decl(test.tsx, 22, 34))
67+
>d : Symbol(d, Decl(test.tsx, 22, 42))
68+
69+
return <div className={"T6"} { ...{ ...a, ...b, ...{ ...c, ...d } } }>T6</div>;
70+
>className : Symbol(className, Decl(test.tsx, 23, 15))
71+
>a : Symbol(a, Decl(test.tsx, 22, 19))
72+
>b : Symbol(b, Decl(test.tsx, 22, 26))
73+
>c : Symbol(c, Decl(test.tsx, 22, 34))
74+
>d : Symbol(d, Decl(test.tsx, 22, 42))
75+
}
76+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
=== tests/cases/conformance/jsx/test.tsx ===
2+
declare const React: any;
3+
>React : any
4+
5+
export function T1(a: any) {
6+
>T1 : (a: any) => any
7+
>a : any
8+
9+
return <div className={"T1"} { ...a }>T1</div>;
10+
><div className={"T1"} { ...a }>T1</div> : error
11+
>div : any
12+
>className : string
13+
>"T1" : "T1"
14+
>a : any
15+
>div : any
16+
}
17+
18+
export function T2(a: any, b: any) {
19+
>T2 : (a: any, b: any) => any
20+
>a : any
21+
>b : any
22+
23+
return <div className={"T2"} { ...a } { ...b }>T2</div>;
24+
><div className={"T2"} { ...a } { ...b }>T2</div> : error
25+
>div : any
26+
>className : string
27+
>"T2" : "T2"
28+
>a : any
29+
>b : any
30+
>div : any
31+
}
32+
33+
export function T3(a: any, b: any) {
34+
>T3 : (a: any, b: any) => any
35+
>a : any
36+
>b : any
37+
38+
return <div { ...a } className={"T3"} { ...b }>T3</div>;
39+
><div { ...a } className={"T3"} { ...b }>T3</div> : error
40+
>div : any
41+
>a : any
42+
>className : string
43+
>"T3" : "T3"
44+
>b : any
45+
>div : any
46+
}
47+
48+
export function T4(a: any, b: any) {
49+
>T4 : (a: any, b: any) => any
50+
>a : any
51+
>b : any
52+
53+
return <div className={"T4"} { ...{ ...a, ...b } }>T4</div>;
54+
><div className={"T4"} { ...{ ...a, ...b } }>T4</div> : error
55+
>div : any
56+
>className : string
57+
>"T4" : "T4"
58+
>{ ...a, ...b } : any
59+
>a : any
60+
>b : any
61+
>div : any
62+
}
63+
64+
export function T5(a: any, b: any, c: any, d: any) {
65+
>T5 : (a: any, b: any, c: any, d: any) => any
66+
>a : any
67+
>b : any
68+
>c : any
69+
>d : any
70+
71+
return <div className={"T5"} { ...{ ...a, ...b, ...{ c, d } } }>T5</div>;
72+
><div className={"T5"} { ...{ ...a, ...b, ...{ c, d } } }>T5</div> : error
73+
>div : any
74+
>className : string
75+
>"T5" : "T5"
76+
>{ ...a, ...b, ...{ c, d } } : any
77+
>a : any
78+
>b : any
79+
>{ c, d } : { c: any; d: any; }
80+
>c : any
81+
>d : any
82+
>div : any
83+
}
84+
85+
export function T6(a: any, b: any, c: any, d: any) {
86+
>T6 : (a: any, b: any, c: any, d: any) => any
87+
>a : any
88+
>b : any
89+
>c : any
90+
>d : any
91+
92+
return <div className={"T6"} { ...{ ...a, ...b, ...{ ...c, ...d } } }>T6</div>;
93+
><div className={"T6"} { ...{ ...a, ...b, ...{ ...c, ...d } } }>T6</div> : error
94+
>div : any
95+
>className : string
96+
>"T6" : "T6"
97+
>{ ...a, ...b, ...{ ...c, ...d } } : any
98+
>a : any
99+
>b : any
100+
>{ ...c, ...d } : any
101+
>c : any
102+
>d : any
103+
>div : any
104+
}
105+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
//// [test.tsx]
2+
declare const React: any;
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+
29+
//// [test.js]
30+
export function T1(a) {
31+
return React.createElement("div", { className: "T1", ...a }, "T1");
32+
}
33+
export function T2(a, b) {
34+
return React.createElement("div", { className: "T2", ...a, ...b }, "T2");
35+
}
36+
export function T3(a, b) {
37+
return React.createElement("div", { ...a, className: "T3", ...b }, "T3");
38+
}
39+
export function T4(a, b) {
40+
return React.createElement("div", { className: "T4", ...{ ...a, ...b } }, "T4");
41+
}
42+
export function T5(a, b, c, d) {
43+
return React.createElement("div", { className: "T5", ...{ ...a, ...b, ...{ c, d } } }, "T5");
44+
}
45+
export function T6(a, b, c, d) {
46+
return React.createElement("div", { className: "T6", ...{ ...a, ...b, ...{ ...c, ...d } } }, "T6");
47+
}

0 commit comments

Comments
 (0)