Skip to content

Commit e84b051

Browse files
committed
Overhaul allowSyntheticDefaultExports to be safer
1 parent e910603 commit e84b051

28 files changed

+236
-252
lines changed

src/compiler/checker.ts

+68-17
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ namespace ts {
6464
const languageVersion = getEmitScriptTarget(compilerOptions);
6565
const modulekind = getEmitModuleKind(compilerOptions);
6666
const noUnusedIdentifiers = !!compilerOptions.noUnusedLocals || !!compilerOptions.noUnusedParameters;
67-
const allowSyntheticDefaultImports = typeof compilerOptions.allowSyntheticDefaultImports !== "undefined" ? compilerOptions.allowSyntheticDefaultImports : modulekind === ModuleKind.System;
67+
const allowSyntheticDefaultImports = typeof compilerOptions.allowSyntheticDefaultImports !== "undefined" ? compilerOptions.allowSyntheticDefaultImports : (modulekind && modulekind < ModuleKind.ES2015);
6868
const strictNullChecks = getStrictOptionValue(compilerOptions, "strictNullChecks");
6969
const strictFunctionTypes = getStrictOptionValue(compilerOptions, "strictFunctionTypes");
7070
const noImplicitAny = getStrictOptionValue(compilerOptions, "noImplicitAny");
@@ -1431,6 +1431,43 @@ namespace ts {
14311431
return getSymbolOfPartOfRightHandSideOfImportEquals(<EntityName>node.moduleReference, dontResolveAlias);
14321432
}
14331433

1434+
function resolveExportByName(moduleSymbol: Symbol, name: __String, dontResolveAlias: boolean) {
1435+
const exportValue = moduleSymbol.exports.get(InternalSymbolName.ExportEquals);
1436+
return exportValue
1437+
? getPropertyOfType(getTypeOfSymbol(exportValue), name)
1438+
: resolveSymbol(moduleSymbol.exports.get(name), dontResolveAlias);
1439+
}
1440+
1441+
function canHaveSyntheticDefault(file: SourceFile | undefined, moduleSymbol: Symbol, dontResolveAlias: boolean) {
1442+
if (!allowSyntheticDefaultImports) {
1443+
return false;
1444+
}
1445+
// Declaration files (and ambient modules)
1446+
if (!file || file.isDeclarationFile) {
1447+
// Definitely cannot have a synthetic default if they have a default member specified
1448+
if (resolveExportByName(moduleSymbol, InternalSymbolName.Default, dontResolveAlias)) {
1449+
return false;
1450+
}
1451+
// It _might_ still be incorrect to assume there is no __esModule marker on the import at runtime, even if there is no `default` member
1452+
// So we check a bit more,
1453+
if (resolveExportByName(moduleSymbol, escapeLeadingUnderscores("__esModule"), dontResolveAlias)) {
1454+
// If there is an `__esModule` specified in the declaration (meaning someone explicitly added it or wrote it in their code),
1455+
// it definitely is a module and does not have a synthetic default
1456+
return false;
1457+
}
1458+
// There are _many_ declaration files not written with esmodules in mind that still get compiled into a format with __esModule set
1459+
// Meaning there may be no default at runtime - however to be on the permissive side, we allow access to a synthetic default member
1460+
// as there is no marker to indicate if the accompanying JS has `__esModule` or not, or is even native esm
1461+
return true;
1462+
}
1463+
// TypeScript files never have a synthetic default (as they are always emitted with an __esModule marker) _unless_ they contain an export= statement
1464+
if (!isSourceFileJavaScript(file)) {
1465+
return hasExportAssignmentSymbol(moduleSymbol);
1466+
}
1467+
// JS files have a synthetic default if they do not contain ES2015+ module syntax (export = is not valid in js) _and_ do not have an __esModule marker
1468+
return !file.externalModuleIndicator && !resolveExportByName(moduleSymbol, escapeLeadingUnderscores("__esModule"), dontResolveAlias);
1469+
}
1470+
14341471
function getTargetOfImportClause(node: ImportClause, dontResolveAlias: boolean): Symbol {
14351472
const moduleSymbol = resolveExternalModuleName(node, (<ImportDeclaration>node.parent).moduleSpecifier);
14361473

@@ -1440,22 +1477,26 @@ namespace ts {
14401477
exportDefaultSymbol = moduleSymbol;
14411478
}
14421479
else {
1443-
const exportValue = moduleSymbol.exports.get("export=" as __String);
1444-
exportDefaultSymbol = exportValue
1445-
? getPropertyOfType(getTypeOfSymbol(exportValue), "default" as __String)
1446-
: resolveSymbol(moduleSymbol.exports.get("default" as __String), dontResolveAlias);
1480+
exportDefaultSymbol = resolveExportByName(moduleSymbol, InternalSymbolName.Default, dontResolveAlias);
14471481
}
14481482

1449-
if (!exportDefaultSymbol && !allowSyntheticDefaultImports) {
1483+
const file = forEach(moduleSymbol.declarations, sourceFileOrUndefined);
1484+
const hasSyntheticDefault = canHaveSyntheticDefault(file, moduleSymbol, dontResolveAlias);
1485+
if (!exportDefaultSymbol && !hasSyntheticDefault) {
14501486
error(node.name, Diagnostics.Module_0_has_no_default_export, symbolToString(moduleSymbol));
14511487
}
1452-
else if (!exportDefaultSymbol && allowSyntheticDefaultImports) {
1488+
else if (!exportDefaultSymbol && hasSyntheticDefault) {
1489+
// per emit behavior, a synthetic default overrides a "real" .default member if `__esModule` is not present
14531490
return resolveExternalModuleSymbol(moduleSymbol, dontResolveAlias) || resolveSymbol(moduleSymbol, dontResolveAlias);
14541491
}
14551492
return exportDefaultSymbol;
14561493
}
14571494
}
14581495

1496+
function sourceFileOrUndefined(d: Declaration) {
1497+
return isSourceFile(d) ? d : undefined;
1498+
}
1499+
14591500
function getTargetOfNamespaceImport(node: NamespaceImport, dontResolveAlias: boolean): Symbol {
14601501
const moduleSpecifier = (<ImportDeclaration>node.parent.parent).moduleSpecifier;
14611502
return resolveESModuleSymbol(resolveExternalModuleName(node, moduleSpecifier), moduleSpecifier, dontResolveAlias);
@@ -1851,8 +1892,11 @@ namespace ts {
18511892
error(moduleReferenceExpression, Diagnostics.Module_0_resolves_to_a_non_module_entity_and_cannot_be_imported_using_this_construct, symbolToString(moduleSymbol));
18521893
return symbol;
18531894
}
1854-
const referenaceParent = moduleReferenceExpression.parent;
1855-
if (referenaceParent.kind === SyntaxKind.ImportDeclaration && getNamespaceDeclarationNode(referenaceParent as ImportDeclaration)) {
1895+
const referenceParent = moduleReferenceExpression.parent;
1896+
if (
1897+
(referenceParent.kind === SyntaxKind.ImportDeclaration && getNamespaceDeclarationNode(referenceParent as ImportDeclaration)) ||
1898+
isImportCall(referenceParent)
1899+
) {
18561900
const type = getTypeOfSymbol(symbol);
18571901
let sigs = getSignaturesOfStructuredType(type, SignatureKind.Call);
18581902
if (!sigs || !sigs.length) {
@@ -1864,7 +1908,7 @@ namespace ts {
18641908
result.declarations = symbol.declarations ? symbol.declarations.slice() : [];
18651909
result.parent = symbol.parent;
18661910
result.target = symbol;
1867-
result.originatingImport = referenaceParent as ImportDeclaration;
1911+
result.originatingImport = referenceParent as ImportDeclaration | ImportCall;
18681912
if (symbol.valueDeclaration) result.valueDeclaration = symbol.valueDeclaration;
18691913
if (symbol.constEnumOnlyModule) result.constEnumOnlyModule = true;
18701914
if (symbol.members) result.members = cloneMap(symbol.members);
@@ -8993,7 +9037,7 @@ namespace ts {
89939037
// Check if we should issue an extra diagnostic to produce a quickfix for a slightly incorrect import statement
89949038
if (headMessage && errorNode && !answer && source.symbol) {
89959039
const links = getSymbolLinks(source.symbol);
8996-
if (links.originatingImport) {
9040+
if (links.originatingImport && !isImportCall(links.originatingImport)) {
89979041
const helpfulRetry = checkTypeRelatedTo(getTypeOfSymbol(links.target), target, relation, /*errorNode*/ undefined);
89989042
if (helpfulRetry) {
89999043
// Likely an incorrect import. Issue a helpful diagnostic to produce a quickfix to change the import
@@ -16814,10 +16858,16 @@ namespace ts {
1681416858
}
1681516859

1681616860
function invocationErrorRecovery(apparentType: Type, kind: SignatureKind) {
16817-
if (apparentType.symbol && getSymbolLinks(apparentType.symbol).originatingImport) {
16861+
if (!apparentType.symbol) {
16862+
return;
16863+
}
16864+
const importNode = getSymbolLinks(apparentType.symbol).originatingImport;
16865+
// Create a diagnostic on the originating import if possible onto which we can attach a quickfix
16866+
// An import call expression cannot be rewritten into another form to correct the error - the only solution is to use `.default` at the use-site
16867+
if (importNode && !isImportCall(importNode)) {
1681816868
const sigs = getSignaturesOfType(getTypeOfSymbol(getSymbolLinks(apparentType.symbol).target), kind);
1681916869
if (!sigs || !sigs.length) return;
16820-
error(getSymbolLinks(apparentType.symbol).originatingImport, Diagnostics.Import_is_called_or_constructed_which_is_not_valid_ES2015_module_usage_and_will_fail_at_runtime);
16870+
error(importNode, Diagnostics.Import_is_called_or_constructed_which_is_not_valid_ES2015_module_usage_and_will_fail_at_runtime);
1682116871
}
1682216872
}
1682316873

@@ -17126,25 +17176,26 @@ namespace ts {
1712617176
if (moduleSymbol) {
1712717177
const esModuleSymbol = resolveESModuleSymbol(moduleSymbol, specifier, /*dontRecursivelyResolve*/ true);
1712817178
if (esModuleSymbol) {
17129-
return createPromiseReturnType(node, getTypeWithSyntheticDefaultImportType(getTypeOfSymbol(esModuleSymbol), esModuleSymbol));
17179+
return createPromiseReturnType(node, getTypeWithSyntheticDefaultImportType(getTypeOfSymbol(esModuleSymbol), esModuleSymbol, moduleSymbol));
1713017180
}
1713117181
}
1713217182
return createPromiseReturnType(node, anyType);
1713317183
}
1713417184

17135-
function getTypeWithSyntheticDefaultImportType(type: Type, symbol: Symbol): Type {
17185+
function getTypeWithSyntheticDefaultImportType(type: Type, symbol: Symbol, originalSymbol: Symbol): Type {
1713617186
if (allowSyntheticDefaultImports && type && type !== unknownType) {
1713717187
const synthType = type as SyntheticDefaultModuleType;
1713817188
if (!synthType.syntheticType) {
17139-
if (!getPropertyOfType(type, InternalSymbolName.Default)) {
17189+
const hasSyntheticDefault = canHaveSyntheticDefault(forEach(originalSymbol.declarations, sourceFileOrUndefined), originalSymbol, /*dontResolveAlias*/ false);
17190+
if (hasSyntheticDefault) {
1714017191
const memberTable = createSymbolTable();
1714117192
const newSymbol = createSymbol(SymbolFlags.Alias, InternalSymbolName.Default);
1714217193
newSymbol.target = resolveSymbol(symbol);
1714317194
memberTable.set(InternalSymbolName.Default, newSymbol);
1714417195
const anonymousSymbol = createSymbol(SymbolFlags.TypeLiteral, InternalSymbolName.Type);
1714517196
const defaultContainingObject = createAnonymousType(anonymousSymbol, memberTable, emptyArray, emptyArray, /*stringIndexInfo*/ undefined, /*numberIndexInfo*/ undefined);
1714617197
anonymousSymbol.type = defaultContainingObject;
17147-
synthType.syntheticType = getIntersectionType([type, defaultContainingObject]);
17198+
synthType.syntheticType = (type.flags & TypeFlags.StructuredType && type.symbol.flags & (SymbolFlags.Module | SymbolFlags.Variable)) ? getSpreadType(type, defaultContainingObject, anonymousSymbol, /*propegatedFlags*/ 0) : defaultContainingObject;
1714817199
}
1714917200
else {
1715017201
synthType.syntheticType = type;

src/compiler/types.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -3102,7 +3102,7 @@ namespace ts {
31023102
bindingElement?: BindingElement; // Binding element associated with property symbol
31033103
exportsSomeValue?: boolean; // True if module exports some value (not just types)
31043104
enumKind?: EnumKind; // Enum declaration classification
3105-
originatingImport?: ImportDeclaration; // Import declaration which produced the symbol, present if the symbol is poisoned
3105+
originatingImport?: ImportDeclaration | ImportCall; // Import declaration which produced the symbol, present if the symbol is poisoned
31063106
}
31073107

31083108
/* @internal */

tests/baselines/reference/allowSyntheticDefaultImports1.js

+1-10
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,12 @@
44
import Namespace from "./b";
55
export var x = new Namespace.Foo();
66

7-
//// [b.ts]
7+
//// [b.d.ts]
88
export class Foo {
99
member: string;
1010
}
1111

1212

13-
//// [b.js]
14-
"use strict";
15-
exports.__esModule = true;
16-
var Foo = /** @class */ (function () {
17-
function Foo() {
18-
}
19-
return Foo;
20-
}());
21-
exports.Foo = Foo;
2213
//// [a.js]
2314
"use strict";
2415
var __importDefault = (this && this.__importDefault) || function (mod) {

tests/baselines/reference/allowSyntheticDefaultImports1.symbols

+5-5
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,15 @@ import Namespace from "./b";
44

55
export var x = new Namespace.Foo();
66
>x : Symbol(x, Decl(a.ts, 1, 10))
7-
>Namespace.Foo : Symbol(Namespace.Foo, Decl(b.ts, 0, 0))
7+
>Namespace.Foo : Symbol(Namespace.Foo, Decl(b.d.ts, 0, 0))
88
>Namespace : Symbol(Namespace, Decl(a.ts, 0, 6))
9-
>Foo : Symbol(Namespace.Foo, Decl(b.ts, 0, 0))
9+
>Foo : Symbol(Namespace.Foo, Decl(b.d.ts, 0, 0))
1010

11-
=== tests/cases/compiler/b.ts ===
11+
=== tests/cases/compiler/b.d.ts ===
1212
export class Foo {
13-
>Foo : Symbol(Foo, Decl(b.ts, 0, 0))
13+
>Foo : Symbol(Foo, Decl(b.d.ts, 0, 0))
1414

1515
member: string;
16-
>member : Symbol(Foo.member, Decl(b.ts, 0, 18))
16+
>member : Symbol(Foo.member, Decl(b.d.ts, 0, 18))
1717
}
1818

tests/baselines/reference/allowSyntheticDefaultImports1.types

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ export var x = new Namespace.Foo();
99
>Namespace : typeof Namespace
1010
>Foo : typeof Namespace.Foo
1111

12-
=== tests/cases/compiler/b.ts ===
12+
=== tests/cases/compiler/b.d.ts ===
1313
export class Foo {
1414
>Foo : Foo
1515

tests/baselines/reference/allowSyntheticDefaultImports2.js

+1-18
Original file line numberDiff line numberDiff line change
@@ -4,28 +4,11 @@
44
import Namespace from "./b";
55
export var x = new Namespace.Foo();
66

7-
//// [b.ts]
7+
//// [b.d.ts]
88
export class Foo {
99
member: string;
1010
}
1111

12-
//// [b.js]
13-
System.register([], function (exports_1, context_1) {
14-
"use strict";
15-
var __moduleName = context_1 && context_1.id;
16-
var Foo;
17-
return {
18-
setters: [],
19-
execute: function () {
20-
Foo = /** @class */ (function () {
21-
function Foo() {
22-
}
23-
return Foo;
24-
}());
25-
exports_1("Foo", Foo);
26-
}
27-
};
28-
});
2912
//// [a.js]
3013
System.register(["./b"], function (exports_1, context_1) {
3114
"use strict";

tests/baselines/reference/allowSyntheticDefaultImports2.symbols

+5-5
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,14 @@ import Namespace from "./b";
44

55
export var x = new Namespace.Foo();
66
>x : Symbol(x, Decl(a.ts, 1, 10))
7-
>Namespace.Foo : Symbol(Namespace.Foo, Decl(b.ts, 0, 0))
7+
>Namespace.Foo : Symbol(Namespace.Foo, Decl(b.d.ts, 0, 0))
88
>Namespace : Symbol(Namespace, Decl(a.ts, 0, 6))
9-
>Foo : Symbol(Namespace.Foo, Decl(b.ts, 0, 0))
9+
>Foo : Symbol(Namespace.Foo, Decl(b.d.ts, 0, 0))
1010

11-
=== tests/cases/compiler/b.ts ===
11+
=== tests/cases/compiler/b.d.ts ===
1212
export class Foo {
13-
>Foo : Symbol(Foo, Decl(b.ts, 0, 0))
13+
>Foo : Symbol(Foo, Decl(b.d.ts, 0, 0))
1414

1515
member: string;
16-
>member : Symbol(Foo.member, Decl(b.ts, 0, 18))
16+
>member : Symbol(Foo.member, Decl(b.d.ts, 0, 18))
1717
}

tests/baselines/reference/allowSyntheticDefaultImports2.types

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ export var x = new Namespace.Foo();
99
>Namespace : typeof Namespace
1010
>Foo : typeof Namespace.Foo
1111

12-
=== tests/cases/compiler/b.ts ===
12+
=== tests/cases/compiler/b.d.ts ===
1313
export class Foo {
1414
>Foo : Foo
1515

tests/baselines/reference/es6ExportEqualsInterop.errors.txt

+1-31
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,6 @@
11
tests/cases/compiler/main.ts(15,1): error TS2693: 'z1' only refers to a type, but is being used as a value here.
22
tests/cases/compiler/main.ts(21,4): error TS2339: Property 'a' does not exist on type '() => any'.
33
tests/cases/compiler/main.ts(23,4): error TS2339: Property 'a' does not exist on type 'typeof Foo'.
4-
tests/cases/compiler/main.ts(27,8): error TS1192: Module '"interface"' has no default export.
5-
tests/cases/compiler/main.ts(28,8): error TS1192: Module '"variable"' has no default export.
6-
tests/cases/compiler/main.ts(29,8): error TS1192: Module '"interface-variable"' has no default export.
7-
tests/cases/compiler/main.ts(30,8): error TS1192: Module '"module"' has no default export.
8-
tests/cases/compiler/main.ts(31,8): error TS1192: Module '"interface-module"' has no default export.
9-
tests/cases/compiler/main.ts(32,8): error TS1192: Module '"variable-module"' has no default export.
10-
tests/cases/compiler/main.ts(33,8): error TS1192: Module '"function"' has no default export.
11-
tests/cases/compiler/main.ts(34,8): error TS1192: Module '"function-module"' has no default export.
12-
tests/cases/compiler/main.ts(35,8): error TS1192: Module '"class"' has no default export.
13-
tests/cases/compiler/main.ts(36,8): error TS1192: Module '"class-module"' has no default export.
144
tests/cases/compiler/main.ts(39,21): error TS2497: Module '"interface"' resolves to a non-module entity and cannot be imported using this construct.
155
tests/cases/compiler/main.ts(45,21): error TS2497: Module '"function"' resolves to a non-module entity and cannot be imported using this construct.
166
tests/cases/compiler/main.ts(47,21): error TS2497: Module '"class"' resolves to a non-module entity and cannot be imported using this construct.
@@ -41,7 +31,7 @@ tests/cases/compiler/main.ts(105,15): error TS2498: Module '"class"' uses 'expor
4131
tests/cases/compiler/main.ts(106,15): error TS2498: Module '"class-module"' uses 'export =' and cannot be used with 'export *'.
4232

4333

44-
==== tests/cases/compiler/main.ts (41 errors) ====
34+
==== tests/cases/compiler/main.ts (31 errors) ====
4535
/// <reference path="modules.d.ts"/>
4636

4737
// import-equals
@@ -75,35 +65,15 @@ tests/cases/compiler/main.ts(106,15): error TS2498: Module '"class-module"' uses
7565

7666
// default import
7767
import x1 from "interface";
78-
~~
79-
!!! error TS1192: Module '"interface"' has no default export.
8068
import x2 from "variable";
81-
~~
82-
!!! error TS1192: Module '"variable"' has no default export.
8369
import x3 from "interface-variable";
84-
~~
85-
!!! error TS1192: Module '"interface-variable"' has no default export.
8670
import x4 from "module";
87-
~~
88-
!!! error TS1192: Module '"module"' has no default export.
8971
import x5 from "interface-module";
90-
~~
91-
!!! error TS1192: Module '"interface-module"' has no default export.
9272
import x6 from "variable-module";
93-
~~
94-
!!! error TS1192: Module '"variable-module"' has no default export.
9573
import x7 from "function";
96-
~~
97-
!!! error TS1192: Module '"function"' has no default export.
9874
import x8 from "function-module";
99-
~~
100-
!!! error TS1192: Module '"function-module"' has no default export.
10175
import x9 from "class";
102-
~~
103-
!!! error TS1192: Module '"class"' has no default export.
10476
import x0 from "class-module";
105-
~~
106-
!!! error TS1192: Module '"class-module"' has no default export.
10777

10878
// namespace import
10979
import * as y1 from "interface";

0 commit comments

Comments
 (0)