diff --git a/src/compiler/factory.ts b/src/compiler/factory.ts index 65c1c92f366..a566a432791 100644 --- a/src/compiler/factory.ts +++ b/src/compiler/factory.ts @@ -1982,7 +1982,7 @@ namespace ts { node.decorators = asNodeArray(decorators); node.modifiers = asNodeArray(modifiers); node.isExportEquals = isExportEquals; - node.expression = expression; + node.expression = isExportEquals ? parenthesizeBinaryOperand(SyntaxKind.EqualsToken, expression, /*isLeftSideOfBinary*/ false, /*leftOperand*/ undefined) : parenthesizeDefaultExpression(expression); return node; } @@ -3885,6 +3885,27 @@ namespace ts { : e; } + /** + * [Per the spec](https://tc39.github.io/ecma262/#prod-ExportDeclaration), `export default` accepts _AssigmentExpression_ but + * has a lookahead restriction for `function`, `async function`, and `class`. + * + * Basically, that means we need to parenthesize in the following cases: + * + * - BinaryExpression of CommaToken + * - CommaList (synthetic list of multiple comma expressions) + * - FunctionExpression + * - ClassExpression + */ + export function parenthesizeDefaultExpression(e: Expression) { + const check = skipPartiallyEmittedExpressions(e); + return (check.kind === SyntaxKind.ClassExpression || + check.kind === SyntaxKind.FunctionExpression || + check.kind === SyntaxKind.CommaListExpression || + isBinaryExpression(check) && check.operatorToken.kind === SyntaxKind.CommaToken) + ? createParen(e) + : e; + } + /** * Wraps an expression in parentheses if it is needed in order to use the expression * as the expression of a NewExpression node. diff --git a/tests/baselines/reference/exportDefaultParenthesize.js b/tests/baselines/reference/exportDefaultParenthesize.js new file mode 100644 index 00000000000..31fe9b6da3e --- /dev/null +++ b/tests/baselines/reference/exportDefaultParenthesize.js @@ -0,0 +1,72 @@ +//// [tests/cases/compiler/exportDefaultParenthesize.ts] //// + +//// [commalist.ts] +export default { + ['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, +}; + +//// [comma.ts] +export default { + ['foo']: 42 +}; + +//// [functionexpression.ts] +export default () => 42; + + +//// [commalist.js] +export default (_a = {}, + _a['foo' + ''] = 42, + _a['foo' + ''] = 42, + _a['foo' + ''] = 42, + _a['foo' + ''] = 42, + _a['foo' + ''] = 42, + _a['foo' + ''] = 42, + _a['foo' + ''] = 42, + _a['foo' + ''] = 42, + _a['foo' + ''] = 42, + _a['foo' + ''] = 42, + _a['foo' + ''] = 42, + _a['foo' + ''] = 42, + _a['foo' + ''] = 42, + _a['foo' + ''] = 42, + _a['foo' + ''] = 42, + _a['foo' + ''] = 42, + _a['foo' + ''] = 42, + _a['foo' + ''] = 42, + _a['foo' + ''] = 42, + _a['foo' + ''] = 42, + _a['foo' + ''] = 42, + _a['foo' + ''] = 42, + _a['foo' + ''] = 42, + _a); +var _a; +//// [comma.js] +export default (_a = {}, + _a['foo'] = 42, + _a); +var _a; +//// [functionexpression.js] +export default (function () { return 42; }); diff --git a/tests/baselines/reference/exportDefaultParenthesize.symbols b/tests/baselines/reference/exportDefaultParenthesize.symbols new file mode 100644 index 00000000000..684138ebb3b --- /dev/null +++ b/tests/baselines/reference/exportDefaultParenthesize.symbols @@ -0,0 +1,38 @@ +=== tests/cases/compiler/commalist.ts === +export default { +No type information for this code. ['foo'+'']: 42, +No type information for this code. ['foo'+'']: 42, +No type information for this code. ['foo'+'']: 42, +No type information for this code. ['foo'+'']: 42, +No type information for this code. ['foo'+'']: 42, +No type information for this code. ['foo'+'']: 42, +No type information for this code. ['foo'+'']: 42, +No type information for this code. ['foo'+'']: 42, +No type information for this code. ['foo'+'']: 42, +No type information for this code. ['foo'+'']: 42, +No type information for this code. ['foo'+'']: 42, +No type information for this code. ['foo'+'']: 42, +No type information for this code. ['foo'+'']: 42, +No type information for this code. ['foo'+'']: 42, +No type information for this code. ['foo'+'']: 42, +No type information for this code. ['foo'+'']: 42, +No type information for this code. ['foo'+'']: 42, +No type information for this code. ['foo'+'']: 42, +No type information for this code. ['foo'+'']: 42, +No type information for this code. ['foo'+'']: 42, +No type information for this code. ['foo'+'']: 42, +No type information for this code. ['foo'+'']: 42, +No type information for this code. ['foo'+'']: 42, +No type information for this code.}; +No type information for this code. +No type information for this code.=== tests/cases/compiler/comma.ts === +export default { + ['foo']: 42 +>'foo' : Symbol(['foo'], Decl(comma.ts, 0, 16)) + +}; + +=== tests/cases/compiler/functionexpression.ts === +export default () => 42; +No type information for this code. +No type information for this code. \ No newline at end of file diff --git a/tests/baselines/reference/exportDefaultParenthesize.types b/tests/baselines/reference/exportDefaultParenthesize.types new file mode 100644 index 00000000000..43e016a9ea0 --- /dev/null +++ b/tests/baselines/reference/exportDefaultParenthesize.types @@ -0,0 +1,159 @@ +=== tests/cases/compiler/commalist.ts === +export default { +>{ ['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; } + + ['foo'+'']: 42, +>'foo'+'' : string +>'foo' : "foo" +>'' : "" +>42 : 42 + + ['foo'+'']: 42, +>'foo'+'' : string +>'foo' : "foo" +>'' : "" +>42 : 42 + + ['foo'+'']: 42, +>'foo'+'' : string +>'foo' : "foo" +>'' : "" +>42 : 42 + + ['foo'+'']: 42, +>'foo'+'' : string +>'foo' : "foo" +>'' : "" +>42 : 42 + + ['foo'+'']: 42, +>'foo'+'' : string +>'foo' : "foo" +>'' : "" +>42 : 42 + + ['foo'+'']: 42, +>'foo'+'' : string +>'foo' : "foo" +>'' : "" +>42 : 42 + + ['foo'+'']: 42, +>'foo'+'' : string +>'foo' : "foo" +>'' : "" +>42 : 42 + + ['foo'+'']: 42, +>'foo'+'' : string +>'foo' : "foo" +>'' : "" +>42 : 42 + + ['foo'+'']: 42, +>'foo'+'' : string +>'foo' : "foo" +>'' : "" +>42 : 42 + + ['foo'+'']: 42, +>'foo'+'' : string +>'foo' : "foo" +>'' : "" +>42 : 42 + + ['foo'+'']: 42, +>'foo'+'' : string +>'foo' : "foo" +>'' : "" +>42 : 42 + + ['foo'+'']: 42, +>'foo'+'' : string +>'foo' : "foo" +>'' : "" +>42 : 42 + + ['foo'+'']: 42, +>'foo'+'' : string +>'foo' : "foo" +>'' : "" +>42 : 42 + + ['foo'+'']: 42, +>'foo'+'' : string +>'foo' : "foo" +>'' : "" +>42 : 42 + + ['foo'+'']: 42, +>'foo'+'' : string +>'foo' : "foo" +>'' : "" +>42 : 42 + + ['foo'+'']: 42, +>'foo'+'' : string +>'foo' : "foo" +>'' : "" +>42 : 42 + + ['foo'+'']: 42, +>'foo'+'' : string +>'foo' : "foo" +>'' : "" +>42 : 42 + + ['foo'+'']: 42, +>'foo'+'' : string +>'foo' : "foo" +>'' : "" +>42 : 42 + + ['foo'+'']: 42, +>'foo'+'' : string +>'foo' : "foo" +>'' : "" +>42 : 42 + + ['foo'+'']: 42, +>'foo'+'' : string +>'foo' : "foo" +>'' : "" +>42 : 42 + + ['foo'+'']: 42, +>'foo'+'' : string +>'foo' : "foo" +>'' : "" +>42 : 42 + + ['foo'+'']: 42, +>'foo'+'' : string +>'foo' : "foo" +>'' : "" +>42 : 42 + + ['foo'+'']: 42, +>'foo'+'' : string +>'foo' : "foo" +>'' : "" +>42 : 42 + +}; + +=== tests/cases/compiler/comma.ts === +export default { +>{ ['foo']: 42} : { ['foo']: number; } + + ['foo']: 42 +>'foo' : "foo" +>42 : 42 + +}; + +=== tests/cases/compiler/functionexpression.ts === +export default () => 42; +>() => 42 : () => number +>42 : 42 + diff --git a/tests/baselines/reference/exportDefaultParenthesizeES6.js b/tests/baselines/reference/exportDefaultParenthesizeES6.js new file mode 100644 index 00000000000..7d7d686e251 --- /dev/null +++ b/tests/baselines/reference/exportDefaultParenthesizeES6.js @@ -0,0 +1,6 @@ +//// [classexpr.ts] +export default (class Foo {} as any); + +//// [classexpr.js] +export default (class Foo { +}); diff --git a/tests/baselines/reference/exportDefaultParenthesizeES6.symbols b/tests/baselines/reference/exportDefaultParenthesizeES6.symbols new file mode 100644 index 00000000000..bddaccade51 --- /dev/null +++ b/tests/baselines/reference/exportDefaultParenthesizeES6.symbols @@ -0,0 +1,4 @@ +=== tests/cases/compiler/classexpr.ts === +export default (class Foo {} as any); +>Foo : Symbol(Foo, Decl(classexpr.ts, 0, 16)) + diff --git a/tests/baselines/reference/exportDefaultParenthesizeES6.types b/tests/baselines/reference/exportDefaultParenthesizeES6.types new file mode 100644 index 00000000000..a2f76670b23 --- /dev/null +++ b/tests/baselines/reference/exportDefaultParenthesizeES6.types @@ -0,0 +1,7 @@ +=== tests/cases/compiler/classexpr.ts === +export default (class Foo {} as any); +>(class Foo {} as any) : any +>class Foo {} as any : any +>class Foo {} : typeof Foo +>Foo : typeof Foo + diff --git a/tests/baselines/reference/exportEqualsAmd.js b/tests/baselines/reference/exportEqualsAmd.js index 8b66b8896e3..e76b9dbacc8 100644 --- a/tests/baselines/reference/exportEqualsAmd.js +++ b/tests/baselines/reference/exportEqualsAmd.js @@ -5,5 +5,5 @@ export = { ["hi"]: "there" }; define(["require", "exports"], function (require, exports) { "use strict"; var _a; - return _a = {}, _a["hi"] = "there", _a; + return (_a = {}, _a["hi"] = "there", _a); }); diff --git a/tests/baselines/reference/exportEqualsUmd.js b/tests/baselines/reference/exportEqualsUmd.js index 2201f2b8407..1d8a99717ed 100644 --- a/tests/baselines/reference/exportEqualsUmd.js +++ b/tests/baselines/reference/exportEqualsUmd.js @@ -13,5 +13,5 @@ export = { ["hi"]: "there" }; })(function (require, exports) { "use strict"; var _a; - return _a = {}, _a["hi"] = "there", _a; + return (_a = {}, _a["hi"] = "there", _a); }); diff --git a/tests/cases/compiler/exportDefaultParenthesize.ts b/tests/cases/compiler/exportDefaultParenthesize.ts new file mode 100644 index 00000000000..8303eea0234 --- /dev/null +++ b/tests/cases/compiler/exportDefaultParenthesize.ts @@ -0,0 +1,36 @@ +// @target: es5 +// @module: esnext +// @filename: commalist.ts +export default { + ['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, +}; + +// @filename: comma.ts +export default { + ['foo']: 42 +}; + +// @filename: functionexpression.ts +export default () => 42; diff --git a/tests/cases/compiler/exportDefaultParenthesizeES6.ts b/tests/cases/compiler/exportDefaultParenthesizeES6.ts new file mode 100644 index 00000000000..20d1a10cc4c --- /dev/null +++ b/tests/cases/compiler/exportDefaultParenthesizeES6.ts @@ -0,0 +1,4 @@ +// @target: es6 +// @module: esnext +// @filename: classexpr.ts +export default (class Foo {} as any); \ No newline at end of file