Skip to content

Commit eb9025e

Browse files
committed
Changed parser.ts to support all forms of export default described in #3792 (comment).
Added corresponding tests. NB: export defaults are not yet transformed correctly.
1 parent 5fb3976 commit eb9025e

File tree

140 files changed

+5478
-29
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

140 files changed

+5478
-29
lines changed

src/compiler/binder.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -426,6 +426,15 @@ namespace ts {
426426
symbol = createSymbol(SymbolFlags.None, name);
427427
}
428428
}
429+
else if (isDefaultExport && parent && symbol.declarations && symbol.declarations.length) {
430+
// && parent because we only want to check the export default symbol.
431+
// if there is already an export default declaration, make sure that their local names are the same.
432+
if (symbol.declarations[0].localSymbol !== node.localSymbol) {
433+
const message = Diagnostics.Merged_default_exports_must_have_the_same_name;
434+
file.bindDiagnostics.push(createDiagnosticForNode(getNameOfDeclaration(symbol.declarations[0]) || symbol.declarations[0], message));
435+
file.bindDiagnostics.push(createDiagnosticForNode(getNameOfDeclaration(node) || node, message));
436+
}
437+
}
429438
}
430439

431440
addDeclarationToSymbol(symbol, node, includes);
@@ -472,8 +481,8 @@ namespace ts {
472481
}
473482
const exportKind = symbolFlags & SymbolFlags.Value ? SymbolFlags.ExportValue : 0;
474483
const local = declareSymbol(container.locals!, /*parent*/ undefined, node, exportKind, symbolExcludes);
475-
local.exportSymbol = declareSymbol(container.symbol.exports!, container.symbol, node, symbolFlags, symbolExcludes);
476484
node.localSymbol = local;
485+
local.exportSymbol = declareSymbol(container.symbol.exports!, container.symbol, node, symbolFlags, symbolExcludes);
477486
return local;
478487
}
479488
else {
@@ -1381,7 +1390,7 @@ namespace ts {
13811390
function bindJSDocTypeAlias(node: JSDocTypedefTag | JSDocCallbackTag) {
13821391
if (node.fullName) {
13831392
setParentPointers(node, node.fullName);
1384-
}
1393+
}
13851394
}
13861395

13871396
function bindCallExpressionFlow(node: CallExpression) {

src/compiler/diagnosticMessages.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2490,6 +2490,10 @@
24902490
"code": 2734
24912491
},
24922492

2493+
"Merged default exports must have the same name.": {
2494+
"category": "Error",
2495+
"code": 2734
2496+
},
24932497
"Import declaration '{0}' is using private name '{1}'.": {
24942498
"category": "Error",
24952499
"code": 4000

src/compiler/parser.ts

Lines changed: 46 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1414,11 +1414,11 @@ namespace ts {
14141414
case SyntaxKind.ExportKeyword:
14151415
nextToken();
14161416
if (token() === SyntaxKind.DefaultKeyword) {
1417-
return lookAhead(nextTokenCanFollowDefaultKeyword);
1417+
return lookAhead(nextTokenCanFollowDefaultModifier);
14181418
}
14191419
return token() !== SyntaxKind.AsteriskToken && token() !== SyntaxKind.AsKeyword && token() !== SyntaxKind.OpenBraceToken && canFollowModifier();
14201420
case SyntaxKind.DefaultKeyword:
1421-
return nextTokenCanFollowDefaultKeyword();
1421+
return nextTokenCanFollowDefaultModifier();
14221422
case SyntaxKind.StaticKeyword:
14231423
case SyntaxKind.GetKeyword:
14241424
case SyntaxKind.SetKeyword:
@@ -1441,12 +1441,37 @@ namespace ts {
14411441
|| isLiteralPropertyName();
14421442
}
14431443

1444-
function nextTokenCanFollowDefaultKeyword(): boolean {
1444+
/**
1445+
* For export default assignments like
1446+
* "export default foo"
1447+
* "export default async"
1448+
* no modifiers should be parsed.
1449+
*/
1450+
function nextTokenCanFollowDefaultModifier(): boolean {
14451451
nextToken();
1446-
return token() === SyntaxKind.ClassKeyword || token() === SyntaxKind.FunctionKeyword ||
1447-
token() === SyntaxKind.InterfaceKeyword ||
1448-
(token() === SyntaxKind.AbstractKeyword && lookAhead(nextTokenIsClassKeywordOnSameLine)) ||
1449-
(token() === SyntaxKind.AsyncKeyword && lookAhead(nextTokenIsFunctionKeywordOnSameLine));
1452+
switch (token()) {
1453+
case SyntaxKind.ClassKeyword:
1454+
case SyntaxKind.FunctionKeyword:
1455+
case SyntaxKind.EnumKeyword:
1456+
case SyntaxKind.NamespaceKeyword:
1457+
case SyntaxKind.ModuleKeyword:
1458+
case SyntaxKind.ConstKeyword:
1459+
return true;
1460+
1461+
// The following keywords aren't reserved keywords and could be identifiers.
1462+
// We need to look at the next token to make sure these should be parsed as a modifier keyword,
1463+
// and not as an identifier. If they are followed by an identifier or a keyword on the same line,
1464+
// the current statement is not an export default assignment.
1465+
// See https://github.com/Microsoft/TypeScript/blob/master/doc/spec.md#221-reserved-words
1466+
case SyntaxKind.InterfaceKeyword:
1467+
case SyntaxKind.AbstractKeyword:
1468+
case SyntaxKind.DeclareKeyword:
1469+
case SyntaxKind.AsyncKeyword:
1470+
case SyntaxKind.TypeKeyword:
1471+
return lookAhead(nextTokenIsIdentifierOrKeywordOnSameLine);
1472+
default:
1473+
return false;
1474+
}
14501475
}
14511476

14521477
// True if positioned at the start of a list element
@@ -4081,14 +4106,14 @@ namespace ts {
40814106
let expression: MemberExpression;
40824107
if (token() === SyntaxKind.ImportKeyword) {
40834108
if (lookAhead(nextTokenIsOpenParenOrLessThan)) {
4084-
// We don't want to eagerly consume all import keyword as import call expression so we look ahead to find "("
4085-
// For example:
4086-
// var foo3 = require("subfolder
4087-
// import * as foo1 from "module-from-node
4088-
// We want this import to be a statement rather than import call expression
4089-
sourceFile.flags |= NodeFlags.PossiblyContainsDynamicImport;
4090-
expression = parseTokenNode<PrimaryExpression>();
4091-
}
4109+
// We don't want to eagerly consume all import keyword as import call expression so we look a head to find "("
4110+
// For example:
4111+
// var foo3 = require("subfolder
4112+
// import * as foo1 from "module-from-node
4113+
// We want this import to be a statement rather than import call expression
4114+
sourceFile.flags |= NodeFlags.PossiblyContainsDynamicImport;
4115+
expression = parseTokenNode<PrimaryExpression>();
4116+
}
40924117
else if (lookAhead(nextTokenIsDot)) {
40934118
// This is an 'import.*' metaproperty (i.e. 'import.meta')
40944119
const fullStart = scanner.getStartPos();
@@ -4101,7 +4126,7 @@ namespace ts {
41014126

41024127
sourceFile.flags |= NodeFlags.PossiblyContainsImportMeta;
41034128
}
4104-
else {
4129+
else {
41054130
expression = parseMemberExpressionOrHigher();
41064131
}
41074132
}
@@ -5118,11 +5143,6 @@ namespace ts {
51185143
return tokenIsIdentifierOrKeyword(token()) && !scanner.hasPrecedingLineBreak();
51195144
}
51205145

5121-
function nextTokenIsClassKeywordOnSameLine() {
5122-
nextToken();
5123-
return token() === SyntaxKind.ClassKeyword && !scanner.hasPrecedingLineBreak();
5124-
}
5125-
51265146
function nextTokenIsFunctionKeywordOnSameLine() {
51275147
nextToken();
51285148
return token() === SyntaxKind.FunctionKeyword && !scanner.hasPrecedingLineBreak();
@@ -6233,7 +6253,7 @@ namespace ts {
62336253
sourceFile.externalModuleIndicator =
62346254
forEach(sourceFile.statements, isAnExternalModuleIndicatorNode) ||
62356255
getImportMetaIfNecessary(sourceFile);
6236-
}
6256+
}
62376257

62386258
function isAnExternalModuleIndicatorNode(node: Node) {
62396259
return hasModifier(node, ModifierFlags.Export)
@@ -6243,17 +6263,17 @@ namespace ts {
62436263
|| node.kind === SyntaxKind.ExportDeclaration
62446264
? node
62456265
: undefined;
6246-
}
6266+
}
62476267

62486268
function getImportMetaIfNecessary(sourceFile: SourceFile) {
62496269
return sourceFile.flags & NodeFlags.PossiblyContainsImportMeta ?
62506270
walkTreeForExternalModuleIndicators(sourceFile) :
62516271
undefined;
6252-
}
6272+
}
62536273

62546274
function walkTreeForExternalModuleIndicators(node: Node): Node | undefined {
62556275
return isImportMeta(node) ? node : forEachChild(node, walkTreeForExternalModuleIndicators);
6256-
}
6276+
}
62576277

62586278
function isImportMeta(node: Node): boolean {
62596279
return isMetaProperty(node) && node.keywordToken === SyntaxKind.ImportKeyword && node.name.escapedText === "meta";
@@ -6468,7 +6488,7 @@ namespace ts {
64686488
break;
64696489
}
64706490
nextJSDocToken();
6471-
}
6491+
}
64726492
removeLeadingNewlines(comments);
64736493
removeTrailingWhitespace(comments);
64746494
return createJSDocComment();
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
//// [tests/cases/compiler/exportDefaultClodule.ts] ////
2+
3+
//// [a.ts]
4+
// https://github.com/Microsoft/TypeScript/issues/3792
5+
export default class A {
6+
A1: string
7+
}
8+
export default namespace A {
9+
export const A2 = 32;
10+
}
11+
12+
//// [b.ts]
13+
import A from "./a";
14+
15+
const a = new A();
16+
const a2 = A.A2;
17+
18+
//// [a.js]
19+
"use strict";
20+
exports.__esModule = true;
21+
// https://github.com/Microsoft/TypeScript/issues/3792
22+
var A = /** @class */ (function () {
23+
function A() {
24+
}
25+
return A;
26+
}());
27+
exports["default"] = A;
28+
(function (A) {
29+
A.A2 = 32;
30+
})(A = exports.A || (exports.A = {}));
31+
//// [b.js]
32+
"use strict";
33+
exports.__esModule = true;
34+
var a_1 = require("./a");
35+
var a = new a_1["default"]();
36+
var a2 = a_1["default"].A2;
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
=== tests/cases/compiler/a.ts ===
2+
// https://github.com/Microsoft/TypeScript/issues/3792
3+
export default class A {
4+
>A : Symbol(A, Decl(a.ts, 0, 0), Decl(a.ts, 3, 1))
5+
6+
A1: string
7+
>A1 : Symbol(A.A1, Decl(a.ts, 1, 24))
8+
}
9+
export default namespace A {
10+
>A : Symbol(A, Decl(a.ts, 0, 0), Decl(a.ts, 3, 1))
11+
12+
export const A2 = 32;
13+
>A2 : Symbol(A2, Decl(a.ts, 5, 16))
14+
}
15+
16+
=== tests/cases/compiler/b.ts ===
17+
import A from "./a";
18+
>A : Symbol(A, Decl(b.ts, 0, 6))
19+
20+
const a = new A();
21+
>a : Symbol(a, Decl(b.ts, 2, 5))
22+
>A : Symbol(A, Decl(b.ts, 0, 6))
23+
24+
const a2 = A.A2;
25+
>a2 : Symbol(a2, Decl(b.ts, 3, 5))
26+
>A.A2 : Symbol(A.A2, Decl(a.ts, 5, 16))
27+
>A : Symbol(A, Decl(b.ts, 0, 6))
28+
>A2 : Symbol(A.A2, Decl(a.ts, 5, 16))
29+
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
=== tests/cases/compiler/a.ts ===
2+
// https://github.com/Microsoft/TypeScript/issues/3792
3+
export default class A {
4+
>A : A
5+
6+
A1: string
7+
>A1 : string
8+
}
9+
export default namespace A {
10+
>A : typeof A
11+
12+
export const A2 = 32;
13+
>A2 : 32
14+
>32 : 32
15+
}
16+
17+
=== tests/cases/compiler/b.ts ===
18+
import A from "./a";
19+
>A : typeof A
20+
21+
const a = new A();
22+
>a : A
23+
>new A() : A
24+
>A : typeof A
25+
26+
const a2 = A.A2;
27+
>a2 : 32
28+
>A.A2 : 32
29+
>A : typeof A
30+
>A2 : 32
31+
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
tests/cases/compiler/a.ts(2,22): error TS2734: Merged default exports must have the same name.
2+
tests/cases/compiler/a.ts(5,26): error TS2734: Merged default exports must have the same name.
3+
4+
5+
==== tests/cases/compiler/a.ts (2 errors) ====
6+
// https://github.com/Microsoft/TypeScript/issues/3792
7+
export default class A {
8+
~
9+
!!! error TS2734: Merged default exports must have the same name.
10+
A1: string = "init"
11+
}
12+
export default namespace B {
13+
~
14+
!!! error TS2734: Merged default exports must have the same name.
15+
export const A2 = 32;
16+
}
17+
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
//// [a.ts]
2+
// https://github.com/Microsoft/TypeScript/issues/3792
3+
export default class A {
4+
A1: string = "init"
5+
}
6+
export default namespace B {
7+
export const A2 = 32;
8+
}
9+
10+
11+
//// [a.js]
12+
"use strict";
13+
exports.__esModule = true;
14+
// https://github.com/Microsoft/TypeScript/issues/3792
15+
var A = /** @class */ (function () {
16+
function A() {
17+
this.A1 = "init";
18+
}
19+
return A;
20+
}());
21+
exports["default"] = A;
22+
var B;
23+
(function (B) {
24+
B.A2 = 32;
25+
})(B = exports.B || (exports.B = {}));
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
=== tests/cases/compiler/a.ts ===
2+
// https://github.com/Microsoft/TypeScript/issues/3792
3+
export default class A {
4+
>A : Symbol(A, Decl(a.ts, 0, 0), Decl(a.ts, 3, 1))
5+
6+
A1: string = "init"
7+
>A1 : Symbol(A.A1, Decl(a.ts, 1, 24))
8+
}
9+
export default namespace B {
10+
>B : Symbol(A, Decl(a.ts, 0, 0), Decl(a.ts, 3, 1))
11+
12+
export const A2 = 32;
13+
>A2 : Symbol(A2, Decl(a.ts, 5, 16))
14+
}
15+
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
=== tests/cases/compiler/a.ts ===
2+
// https://github.com/Microsoft/TypeScript/issues/3792
3+
export default class A {
4+
>A : A
5+
6+
A1: string = "init"
7+
>A1 : string
8+
>"init" : "init"
9+
}
10+
export default namespace B {
11+
>B : typeof A
12+
13+
export const A2 = 32;
14+
>A2 : 32
15+
>32 : 32
16+
}
17+
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
//// [tests/cases/compiler/exportDefaultConstEnum.ts] ////
2+
3+
//// [a.ts]
4+
// https://github.com/Microsoft/TypeScript/issues/3792
5+
export
6+
default
7+
const
8+
enum
9+
A
10+
{ FOO }
11+
12+
//// [b.ts]
13+
import A from './a';
14+
15+
const x = A.FOO;
16+
17+
//// [a.js]
18+
"use strict";
19+
exports.__esModule = true;
20+
//// [b.js]
21+
"use strict";
22+
exports.__esModule = true;
23+
var x = 0 /* FOO */;

0 commit comments

Comments
 (0)