Skip to content

Commit c2aa13d

Browse files
authored
Parenthesize export assignments if needed (microsoft#19590)
* parenthesize export assignments if needed * Add default-specific parenthesization to handle lookahead * New parenthesization logic for export default * Handle commalist and comma cases
1 parent a89c055 commit c2aa13d

11 files changed

+350
-3
lines changed

src/compiler/factory.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1982,7 +1982,7 @@ namespace ts {
19821982
node.decorators = asNodeArray(decorators);
19831983
node.modifiers = asNodeArray(modifiers);
19841984
node.isExportEquals = isExportEquals;
1985-
node.expression = expression;
1985+
node.expression = isExportEquals ? parenthesizeBinaryOperand(SyntaxKind.EqualsToken, expression, /*isLeftSideOfBinary*/ false, /*leftOperand*/ undefined) : parenthesizeDefaultExpression(expression);
19861986
return node;
19871987
}
19881988

@@ -3885,6 +3885,27 @@ namespace ts {
38853885
: e;
38863886
}
38873887

3888+
/**
3889+
* [Per the spec](https://tc39.github.io/ecma262/#prod-ExportDeclaration), `export default` accepts _AssigmentExpression_ but
3890+
* has a lookahead restriction for `function`, `async function`, and `class`.
3891+
*
3892+
* Basically, that means we need to parenthesize in the following cases:
3893+
*
3894+
* - BinaryExpression of CommaToken
3895+
* - CommaList (synthetic list of multiple comma expressions)
3896+
* - FunctionExpression
3897+
* - ClassExpression
3898+
*/
3899+
export function parenthesizeDefaultExpression(e: Expression) {
3900+
const check = skipPartiallyEmittedExpressions(e);
3901+
return (check.kind === SyntaxKind.ClassExpression ||
3902+
check.kind === SyntaxKind.FunctionExpression ||
3903+
check.kind === SyntaxKind.CommaListExpression ||
3904+
isBinaryExpression(check) && check.operatorToken.kind === SyntaxKind.CommaToken)
3905+
? createParen(e)
3906+
: e;
3907+
}
3908+
38883909
/**
38893910
* Wraps an expression in parentheses if it is needed in order to use the expression
38903911
* as the expression of a NewExpression node.
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
//// [tests/cases/compiler/exportDefaultParenthesize.ts] ////
2+
3+
//// [commalist.ts]
4+
export default {
5+
['foo'+'']: 42,
6+
['foo'+'']: 42,
7+
['foo'+'']: 42,
8+
['foo'+'']: 42,
9+
['foo'+'']: 42,
10+
['foo'+'']: 42,
11+
['foo'+'']: 42,
12+
['foo'+'']: 42,
13+
['foo'+'']: 42,
14+
['foo'+'']: 42,
15+
['foo'+'']: 42,
16+
['foo'+'']: 42,
17+
['foo'+'']: 42,
18+
['foo'+'']: 42,
19+
['foo'+'']: 42,
20+
['foo'+'']: 42,
21+
['foo'+'']: 42,
22+
['foo'+'']: 42,
23+
['foo'+'']: 42,
24+
['foo'+'']: 42,
25+
['foo'+'']: 42,
26+
['foo'+'']: 42,
27+
['foo'+'']: 42,
28+
};
29+
30+
//// [comma.ts]
31+
export default {
32+
['foo']: 42
33+
};
34+
35+
//// [functionexpression.ts]
36+
export default () => 42;
37+
38+
39+
//// [commalist.js]
40+
export default (_a = {},
41+
_a['foo' + ''] = 42,
42+
_a['foo' + ''] = 42,
43+
_a['foo' + ''] = 42,
44+
_a['foo' + ''] = 42,
45+
_a['foo' + ''] = 42,
46+
_a['foo' + ''] = 42,
47+
_a['foo' + ''] = 42,
48+
_a['foo' + ''] = 42,
49+
_a['foo' + ''] = 42,
50+
_a['foo' + ''] = 42,
51+
_a['foo' + ''] = 42,
52+
_a['foo' + ''] = 42,
53+
_a['foo' + ''] = 42,
54+
_a['foo' + ''] = 42,
55+
_a['foo' + ''] = 42,
56+
_a['foo' + ''] = 42,
57+
_a['foo' + ''] = 42,
58+
_a['foo' + ''] = 42,
59+
_a['foo' + ''] = 42,
60+
_a['foo' + ''] = 42,
61+
_a['foo' + ''] = 42,
62+
_a['foo' + ''] = 42,
63+
_a['foo' + ''] = 42,
64+
_a);
65+
var _a;
66+
//// [comma.js]
67+
export default (_a = {},
68+
_a['foo'] = 42,
69+
_a);
70+
var _a;
71+
//// [functionexpression.js]
72+
export default (function () { return 42; });
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
=== tests/cases/compiler/commalist.ts ===
2+
export default {
3+
No type information for this code. ['foo'+'']: 42,
4+
No type information for this code. ['foo'+'']: 42,
5+
No type information for this code. ['foo'+'']: 42,
6+
No type information for this code. ['foo'+'']: 42,
7+
No type information for this code. ['foo'+'']: 42,
8+
No type information for this code. ['foo'+'']: 42,
9+
No type information for this code. ['foo'+'']: 42,
10+
No type information for this code. ['foo'+'']: 42,
11+
No type information for this code. ['foo'+'']: 42,
12+
No type information for this code. ['foo'+'']: 42,
13+
No type information for this code. ['foo'+'']: 42,
14+
No type information for this code. ['foo'+'']: 42,
15+
No type information for this code. ['foo'+'']: 42,
16+
No type information for this code. ['foo'+'']: 42,
17+
No type information for this code. ['foo'+'']: 42,
18+
No type information for this code. ['foo'+'']: 42,
19+
No type information for this code. ['foo'+'']: 42,
20+
No type information for this code. ['foo'+'']: 42,
21+
No type information for this code. ['foo'+'']: 42,
22+
No type information for this code. ['foo'+'']: 42,
23+
No type information for this code. ['foo'+'']: 42,
24+
No type information for this code. ['foo'+'']: 42,
25+
No type information for this code. ['foo'+'']: 42,
26+
No type information for this code.};
27+
No type information for this code.
28+
No type information for this code.=== tests/cases/compiler/comma.ts ===
29+
export default {
30+
['foo']: 42
31+
>'foo' : Symbol(['foo'], Decl(comma.ts, 0, 16))
32+
33+
};
34+
35+
=== tests/cases/compiler/functionexpression.ts ===
36+
export default () => 42;
37+
No type information for this code.
38+
No type information for this code.
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
=== tests/cases/compiler/commalist.ts ===
2+
export default {
3+
>{ ['foo'+'']: 42, ['foo'+'']: 42, ['foo'+'']: 42, ['foo'+'']: 42, ['foo'+'']: 42, ['foo'+'']: 42, ['foo'+'']: 42, ['foo'+'']: 42, ['foo'+'']: 42, ['foo'+'']: 42, ['foo'+'']: 42, ['foo'+'']: 42, ['foo'+'']: 42, ['foo'+'']: 42, ['foo'+'']: 42, ['foo'+'']: 42, ['foo'+'']: 42, ['foo'+'']: 42, ['foo'+'']: 42, ['foo'+'']: 42, ['foo'+'']: 42, ['foo'+'']: 42, ['foo'+'']: 42,} : { [x: string]: number; }
4+
5+
['foo'+'']: 42,
6+
>'foo'+'' : string
7+
>'foo' : "foo"
8+
>'' : ""
9+
>42 : 42
10+
11+
['foo'+'']: 42,
12+
>'foo'+'' : string
13+
>'foo' : "foo"
14+
>'' : ""
15+
>42 : 42
16+
17+
['foo'+'']: 42,
18+
>'foo'+'' : string
19+
>'foo' : "foo"
20+
>'' : ""
21+
>42 : 42
22+
23+
['foo'+'']: 42,
24+
>'foo'+'' : string
25+
>'foo' : "foo"
26+
>'' : ""
27+
>42 : 42
28+
29+
['foo'+'']: 42,
30+
>'foo'+'' : string
31+
>'foo' : "foo"
32+
>'' : ""
33+
>42 : 42
34+
35+
['foo'+'']: 42,
36+
>'foo'+'' : string
37+
>'foo' : "foo"
38+
>'' : ""
39+
>42 : 42
40+
41+
['foo'+'']: 42,
42+
>'foo'+'' : string
43+
>'foo' : "foo"
44+
>'' : ""
45+
>42 : 42
46+
47+
['foo'+'']: 42,
48+
>'foo'+'' : string
49+
>'foo' : "foo"
50+
>'' : ""
51+
>42 : 42
52+
53+
['foo'+'']: 42,
54+
>'foo'+'' : string
55+
>'foo' : "foo"
56+
>'' : ""
57+
>42 : 42
58+
59+
['foo'+'']: 42,
60+
>'foo'+'' : string
61+
>'foo' : "foo"
62+
>'' : ""
63+
>42 : 42
64+
65+
['foo'+'']: 42,
66+
>'foo'+'' : string
67+
>'foo' : "foo"
68+
>'' : ""
69+
>42 : 42
70+
71+
['foo'+'']: 42,
72+
>'foo'+'' : string
73+
>'foo' : "foo"
74+
>'' : ""
75+
>42 : 42
76+
77+
['foo'+'']: 42,
78+
>'foo'+'' : string
79+
>'foo' : "foo"
80+
>'' : ""
81+
>42 : 42
82+
83+
['foo'+'']: 42,
84+
>'foo'+'' : string
85+
>'foo' : "foo"
86+
>'' : ""
87+
>42 : 42
88+
89+
['foo'+'']: 42,
90+
>'foo'+'' : string
91+
>'foo' : "foo"
92+
>'' : ""
93+
>42 : 42
94+
95+
['foo'+'']: 42,
96+
>'foo'+'' : string
97+
>'foo' : "foo"
98+
>'' : ""
99+
>42 : 42
100+
101+
['foo'+'']: 42,
102+
>'foo'+'' : string
103+
>'foo' : "foo"
104+
>'' : ""
105+
>42 : 42
106+
107+
['foo'+'']: 42,
108+
>'foo'+'' : string
109+
>'foo' : "foo"
110+
>'' : ""
111+
>42 : 42
112+
113+
['foo'+'']: 42,
114+
>'foo'+'' : string
115+
>'foo' : "foo"
116+
>'' : ""
117+
>42 : 42
118+
119+
['foo'+'']: 42,
120+
>'foo'+'' : string
121+
>'foo' : "foo"
122+
>'' : ""
123+
>42 : 42
124+
125+
['foo'+'']: 42,
126+
>'foo'+'' : string
127+
>'foo' : "foo"
128+
>'' : ""
129+
>42 : 42
130+
131+
['foo'+'']: 42,
132+
>'foo'+'' : string
133+
>'foo' : "foo"
134+
>'' : ""
135+
>42 : 42
136+
137+
['foo'+'']: 42,
138+
>'foo'+'' : string
139+
>'foo' : "foo"
140+
>'' : ""
141+
>42 : 42
142+
143+
};
144+
145+
=== tests/cases/compiler/comma.ts ===
146+
export default {
147+
>{ ['foo']: 42} : { ['foo']: number; }
148+
149+
['foo']: 42
150+
>'foo' : "foo"
151+
>42 : 42
152+
153+
};
154+
155+
=== tests/cases/compiler/functionexpression.ts ===
156+
export default () => 42;
157+
>() => 42 : () => number
158+
>42 : 42
159+
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
//// [classexpr.ts]
2+
export default (class Foo {} as any);
3+
4+
//// [classexpr.js]
5+
export default (class Foo {
6+
});
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
=== tests/cases/compiler/classexpr.ts ===
2+
export default (class Foo {} as any);
3+
>Foo : Symbol(Foo, Decl(classexpr.ts, 0, 16))
4+
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
=== tests/cases/compiler/classexpr.ts ===
2+
export default (class Foo {} as any);
3+
>(class Foo {} as any) : any
4+
>class Foo {} as any : any
5+
>class Foo {} : typeof Foo
6+
>Foo : typeof Foo
7+

tests/baselines/reference/exportEqualsAmd.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,5 @@ export = { ["hi"]: "there" };
55
define(["require", "exports"], function (require, exports) {
66
"use strict";
77
var _a;
8-
return _a = {}, _a["hi"] = "there", _a;
8+
return (_a = {}, _a["hi"] = "there", _a);
99
});

tests/baselines/reference/exportEqualsUmd.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,5 @@ export = { ["hi"]: "there" };
1313
})(function (require, exports) {
1414
"use strict";
1515
var _a;
16-
return _a = {}, _a["hi"] = "there", _a;
16+
return (_a = {}, _a["hi"] = "there", _a);
1717
});
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// @target: es5
2+
// @module: esnext
3+
// @filename: commalist.ts
4+
export default {
5+
['foo'+'']: 42,
6+
['foo'+'']: 42,
7+
['foo'+'']: 42,
8+
['foo'+'']: 42,
9+
['foo'+'']: 42,
10+
['foo'+'']: 42,
11+
['foo'+'']: 42,
12+
['foo'+'']: 42,
13+
['foo'+'']: 42,
14+
['foo'+'']: 42,
15+
['foo'+'']: 42,
16+
['foo'+'']: 42,
17+
['foo'+'']: 42,
18+
['foo'+'']: 42,
19+
['foo'+'']: 42,
20+
['foo'+'']: 42,
21+
['foo'+'']: 42,
22+
['foo'+'']: 42,
23+
['foo'+'']: 42,
24+
['foo'+'']: 42,
25+
['foo'+'']: 42,
26+
['foo'+'']: 42,
27+
['foo'+'']: 42,
28+
};
29+
30+
// @filename: comma.ts
31+
export default {
32+
['foo']: 42
33+
};
34+
35+
// @filename: functionexpression.ts
36+
export default () => 42;
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
// @target: es6
2+
// @module: esnext
3+
// @filename: classexpr.ts
4+
export default (class Foo {} as any);

0 commit comments

Comments
 (0)