Skip to content

Commit

Permalink
Cherry-pick PR #49814 into release-4.8 (#50578)
Browse files Browse the repository at this point in the history
Component commits:
e2d22fa Unify default import resolution across specifier target codepaths

b341699 Merge main into branch

cf010b5 Use differing type aliases, per request

Co-authored-by: Wesley Wigham <wewigham@microsoft.com>
  • Loading branch information
typescript-bot and weswigham committed Sep 1, 2022
1 parent 565a444 commit 4b7a7b5
Show file tree
Hide file tree
Showing 15 changed files with 1,348 additions and 62 deletions.
164 changes: 102 additions & 62 deletions src/compiler/checker.ts
Expand Up @@ -2798,44 +2798,66 @@ namespace ts {
function getTargetOfImportClause(node: ImportClause, dontResolveAlias: boolean): Symbol | undefined {
const moduleSymbol = resolveExternalModuleName(node, node.parent.moduleSpecifier);
if (moduleSymbol) {
let exportDefaultSymbol: Symbol | undefined;
if (isShorthandAmbientModuleSymbol(moduleSymbol)) {
exportDefaultSymbol = moduleSymbol;
}
else {
exportDefaultSymbol = resolveExportByName(moduleSymbol, InternalSymbolName.Default, node, dontResolveAlias);
}

const file = moduleSymbol.declarations?.find(isSourceFile);
const hasDefaultOnly = isOnlyImportedAsDefault(node.parent.moduleSpecifier);
const hasSyntheticDefault = canHaveSyntheticDefault(file, moduleSymbol, dontResolveAlias, node.parent.moduleSpecifier);
if (!exportDefaultSymbol && !hasSyntheticDefault && !hasDefaultOnly) {
if (hasExportAssignmentSymbol(moduleSymbol)) {
const compilerOptionName = moduleKind >= ModuleKind.ES2015 ? "allowSyntheticDefaultImports" : "esModuleInterop";
const exportEqualsSymbol = moduleSymbol.exports!.get(InternalSymbolName.ExportEquals);
const exportAssignment = exportEqualsSymbol!.valueDeclaration;
const err = error(node.name, Diagnostics.Module_0_can_only_be_default_imported_using_the_1_flag, symbolToString(moduleSymbol), compilerOptionName);

if (exportAssignment) {
addRelatedInfo(err, createDiagnosticForNode(
exportAssignment,
Diagnostics.This_module_is_declared_with_export_and_can_only_be_used_with_a_default_import_when_using_the_0_flag,
compilerOptionName
));
}
}
else {
reportNonDefaultExport(moduleSymbol, node);
return getTargetofModuleDefault(moduleSymbol, node, dontResolveAlias);
}
}

function getTargetofModuleDefault(moduleSymbol: Symbol, node: ImportClause | ImportOrExportSpecifier, dontResolveAlias: boolean) {
let exportDefaultSymbol: Symbol | undefined;
if (isShorthandAmbientModuleSymbol(moduleSymbol)) {
exportDefaultSymbol = moduleSymbol;
}
else {
exportDefaultSymbol = resolveExportByName(moduleSymbol, InternalSymbolName.Default, node, dontResolveAlias);
}

const file = moduleSymbol.declarations?.find(isSourceFile);
const specifier = getModuleSpecifierForImportOrExport(node);
if (!specifier) {
return exportDefaultSymbol;
}
const hasDefaultOnly = isOnlyImportedAsDefault(specifier);
const hasSyntheticDefault = canHaveSyntheticDefault(file, moduleSymbol, dontResolveAlias, specifier);
if (!exportDefaultSymbol && !hasSyntheticDefault && !hasDefaultOnly) {
if (hasExportAssignmentSymbol(moduleSymbol)) {
const compilerOptionName = moduleKind >= ModuleKind.ES2015 ? "allowSyntheticDefaultImports" : "esModuleInterop";
const exportEqualsSymbol = moduleSymbol.exports!.get(InternalSymbolName.ExportEquals);
const exportAssignment = exportEqualsSymbol!.valueDeclaration;
const err = error(node.name, Diagnostics.Module_0_can_only_be_default_imported_using_the_1_flag, symbolToString(moduleSymbol), compilerOptionName);

if (exportAssignment) {
addRelatedInfo(err, createDiagnosticForNode(
exportAssignment,
Diagnostics.This_module_is_declared_with_export_and_can_only_be_used_with_a_default_import_when_using_the_0_flag,
compilerOptionName
));
}
}
else if (hasSyntheticDefault || hasDefaultOnly) {
// per emit behavior, a synthetic default overrides a "real" .default member if `__esModule` is not present
const resolved = resolveExternalModuleSymbol(moduleSymbol, dontResolveAlias) || resolveSymbol(moduleSymbol, dontResolveAlias);
markSymbolOfAliasDeclarationIfTypeOnly(node, moduleSymbol, resolved, /*overwriteTypeOnly*/ false);
return resolved;
else if (isImportClause(node)) {
reportNonDefaultExport(moduleSymbol, node);
}
markSymbolOfAliasDeclarationIfTypeOnly(node, exportDefaultSymbol, /*finalTarget*/ undefined, /*overwriteTypeOnly*/ false);
return exportDefaultSymbol;
else {
errorNoModuleMemberSymbol(moduleSymbol, moduleSymbol, node, isImportOrExportSpecifier(node) && node.propertyName || node.name);
}
}
else if (hasSyntheticDefault || hasDefaultOnly) {
// per emit behavior, a synthetic default overrides a "real" .default member if `__esModule` is not present
const resolved = resolveExternalModuleSymbol(moduleSymbol, dontResolveAlias) || resolveSymbol(moduleSymbol, dontResolveAlias);
markSymbolOfAliasDeclarationIfTypeOnly(node, moduleSymbol, resolved, /*overwriteTypeOnly*/ false);
return resolved;
}
markSymbolOfAliasDeclarationIfTypeOnly(node, exportDefaultSymbol, /*finalTarget*/ undefined, /*overwriteTypeOnly*/ false);
return exportDefaultSymbol;
}

function getModuleSpecifierForImportOrExport(node: ImportEqualsDeclaration | ImportClause | NamespaceImport | ImportOrExportSpecifier): Expression | undefined {
switch (node.kind) {
case SyntaxKind.ImportClause: return node.parent.moduleSpecifier;
case SyntaxKind.ImportEqualsDeclaration: return isExternalModuleReference(node.moduleReference) ? node.moduleReference.expression : undefined;
case SyntaxKind.NamespaceImport: return node.parent.parent.moduleSpecifier;
case SyntaxKind.ImportSpecifier: return node.parent.parent.parent.moduleSpecifier;
case SyntaxKind.ExportSpecifier: return node.parent.parent.moduleSpecifier;
default: return Debug.assertNever(node);
}
}

Expand Down Expand Up @@ -2969,38 +2991,42 @@ namespace ts {
combineValueAndTypeSymbols(symbolFromVariable, symbolFromModule) :
symbolFromModule || symbolFromVariable;
if (!symbol) {
const moduleName = getFullyQualifiedName(moduleSymbol, node);
const declarationName = declarationNameToString(name);
const suggestion = getSuggestedSymbolForNonexistentModule(name, targetSymbol);
if (suggestion !== undefined) {
const suggestionName = symbolToString(suggestion);
const diagnostic = error(name, Diagnostics._0_has_no_exported_member_named_1_Did_you_mean_2, moduleName, declarationName, suggestionName);
if (suggestion.valueDeclaration) {
addRelatedInfo(diagnostic,
createDiagnosticForNode(suggestion.valueDeclaration, Diagnostics._0_is_declared_here, suggestionName)
);
}
}
else {
if (moduleSymbol.exports?.has(InternalSymbolName.Default)) {
error(
name,
Diagnostics.Module_0_has_no_exported_member_1_Did_you_mean_to_use_import_1_from_0_instead,
moduleName,
declarationName
);
}
else {
reportNonExportedMember(node, name, declarationName, moduleSymbol, moduleName);
}
}
errorNoModuleMemberSymbol(moduleSymbol, targetSymbol, node, name);
}
return symbol;
}
}
}

function reportNonExportedMember(node: ImportDeclaration | ExportDeclaration | VariableDeclaration, name: Identifier, declarationName: string, moduleSymbol: Symbol, moduleName: string): void {
function errorNoModuleMemberSymbol(moduleSymbol: Symbol, targetSymbol: Symbol, node: Node, name: Identifier) {
const moduleName = getFullyQualifiedName(moduleSymbol, node);
const declarationName = declarationNameToString(name);
const suggestion = getSuggestedSymbolForNonexistentModule(name, targetSymbol);
if (suggestion !== undefined) {
const suggestionName = symbolToString(suggestion);
const diagnostic = error(name, Diagnostics._0_has_no_exported_member_named_1_Did_you_mean_2, moduleName, declarationName, suggestionName);
if (suggestion.valueDeclaration) {
addRelatedInfo(diagnostic,
createDiagnosticForNode(suggestion.valueDeclaration, Diagnostics._0_is_declared_here, suggestionName)
);
}
}
else {
if (moduleSymbol.exports?.has(InternalSymbolName.Default)) {
error(
name,
Diagnostics.Module_0_has_no_exported_member_1_Did_you_mean_to_use_import_1_from_0_instead,
moduleName,
declarationName
);
}
else {
reportNonExportedMember(node, name, declarationName, moduleSymbol, moduleName);
}
}
}

function reportNonExportedMember(node: Node, name: Identifier, declarationName: string, moduleSymbol: Symbol, moduleName: string): void {
const localSymbol = moduleSymbol.valueDeclaration?.locals?.get(name.escapedText);
const exports = moduleSymbol.exports;
if (localSymbol) {
Expand All @@ -3025,7 +3051,7 @@ namespace ts {
}
}

function reportInvalidImportEqualsExportMember(node: ImportDeclaration | ExportDeclaration | VariableDeclaration, name: Identifier, declarationName: string, moduleName: string) {
function reportInvalidImportEqualsExportMember(node: Node, name: Identifier, declarationName: string, moduleName: string) {
if (moduleKind >= ModuleKind.ES2015) {
const message = getESModuleInterop(compilerOptions) ? Diagnostics._0_can_only_be_imported_by_using_a_default_import :
Diagnostics._0_can_only_be_imported_by_turning_on_the_esModuleInterop_flag_and_using_a_default_import;
Expand All @@ -3046,6 +3072,13 @@ namespace ts {
}

function getTargetOfImportSpecifier(node: ImportSpecifier | BindingElement, dontResolveAlias: boolean): Symbol | undefined {
if (isImportSpecifier(node) && idText(node.propertyName || node.name) === InternalSymbolName.Default) {
const specifier = getModuleSpecifierForImportOrExport(node);
const moduleSymbol = specifier && resolveExternalModuleName(node, specifier);
if (moduleSymbol) {
return getTargetofModuleDefault(moduleSymbol, node, dontResolveAlias);
}
}
const root = isBindingElement(node) ? getRootDeclaration(node) as VariableDeclaration : node.parent.parent.parent;
const commonJSPropertyAccess = getCommonJSPropertyAccess(root);
const resolved = getExternalModuleMember(root, commonJSPropertyAccess || node, dontResolveAlias);
Expand All @@ -3070,6 +3103,13 @@ namespace ts {
}

function getTargetOfExportSpecifier(node: ExportSpecifier, meaning: SymbolFlags, dontResolveAlias?: boolean) {
if (idText(node.propertyName || node.name) === InternalSymbolName.Default) {
const specifier = getModuleSpecifierForImportOrExport(node);
const moduleSymbol = specifier && resolveExternalModuleName(node, specifier);
if (moduleSymbol) {
return getTargetofModuleDefault(moduleSymbol, node, !!dontResolveAlias);
}
}
const resolved = node.parent.parent.moduleSpecifier ?
getExternalModuleMember(node.parent.parent, node, dontResolveAlias) :
resolveEntityName(node.propertyName || node.name, meaning, /*ignoreErrors*/ false, dontResolveAlias);
Expand Down
67 changes: 67 additions & 0 deletions tests/baselines/reference/esModuleInteropDefaultImports.errors.txt
@@ -0,0 +1,67 @@
tests/cases/compiler/b.ts(15,1): error TS2349: This expression is not callable.
Type 'typeof import("tests/cases/compiler/mod")' has no call signatures.
tests/cases/compiler/b.ts(16,1): error TS2349: This expression is not callable.
Type 'typeof import("tests/cases/compiler/mod")' has no call signatures.
tests/cases/compiler/b.ts(17,1): error TS2349: This expression is not callable.
Type 'typeof import("tests/cases/compiler/mod")' has no call signatures.
tests/cases/compiler/b.ts(18,1): error TS2349: This expression is not callable.
Type 'typeof import("tests/cases/compiler/mod")' has no call signatures.
tests/cases/compiler/b.ts(19,6): error TS2349: This expression is not callable.
Type 'typeof import("tests/cases/compiler/mod")' has no call signatures.
tests/cases/compiler/b.ts(20,6): error TS2349: This expression is not callable.
Type 'typeof import("tests/cases/compiler/mod")' has no call signatures.


==== tests/cases/compiler/mod.ts (0 errors) ====
declare function fun(): void;
export default fun;
==== tests/cases/compiler/a.ts (0 errors) ====
import mod = require("./mod");
export = mod;
==== tests/cases/compiler/b.ts (6 errors) ====
import a from "./a";
import { default as b } from "./a";
import c, { default as d } from "./a";
import * as self from "./b";
export { default } from "./a";
export { default as def } from "./a";

a === b;
b === c;
c === d;
d === self.default;
self.default === self.def;

// should all fail
a();
~
!!! error TS2349: This expression is not callable.
!!! error TS2349: Type 'typeof import("tests/cases/compiler/mod")' has no call signatures.
b();
~
!!! error TS2349: This expression is not callable.
!!! error TS2349: Type 'typeof import("tests/cases/compiler/mod")' has no call signatures.
c();
~
!!! error TS2349: This expression is not callable.
!!! error TS2349: Type 'typeof import("tests/cases/compiler/mod")' has no call signatures.
d();
~
!!! error TS2349: This expression is not callable.
!!! error TS2349: Type 'typeof import("tests/cases/compiler/mod")' has no call signatures.
self.default();
~~~~~~~
!!! error TS2349: This expression is not callable.
!!! error TS2349: Type 'typeof import("tests/cases/compiler/mod")' has no call signatures.
self.def();
~~~
!!! error TS2349: This expression is not callable.
!!! error TS2349: Type 'typeof import("tests/cases/compiler/mod")' has no call signatures.

// should all work
a.default();
b.default();
c.default();
d.default();
self.default.default();
self.def.default();
103 changes: 103 additions & 0 deletions tests/baselines/reference/esModuleInteropDefaultImports.js
@@ -0,0 +1,103 @@
//// [tests/cases/compiler/esModuleInteropDefaultImports.ts] ////

//// [mod.ts]
declare function fun(): void;
export default fun;
//// [a.ts]
import mod = require("./mod");
export = mod;
//// [b.ts]
import a from "./a";
import { default as b } from "./a";
import c, { default as d } from "./a";
import * as self from "./b";
export { default } from "./a";
export { default as def } from "./a";

a === b;
b === c;
c === d;
d === self.default;
self.default === self.def;

// should all fail
a();
b();
c();
d();
self.default();
self.def();

// should all work
a.default();
b.default();
c.default();
d.default();
self.default.default();
self.def.default();

//// [mod.js]
"use strict";
exports.__esModule = true;
exports["default"] = fun;
//// [a.js]
"use strict";
var mod = require("./mod");
module.exports = mod;
//// [b.js]
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
exports.__esModule = true;
exports.def = exports["default"] = void 0;
var a_1 = __importDefault(require("./a"));
var a_2 = __importDefault(require("./a"));
var a_3 = __importDefault(require("./a"));
var self = __importStar(require("./b"));
var a_4 = require("./a");
__createBinding(exports, a_4, "default");
var a_5 = require("./a");
__createBinding(exports, a_5, "default", "def");
a_1["default"] === a_2["default"];
a_2["default"] === a_3["default"];
a_3["default"] === a_3["default"];
a_3["default"] === self["default"];
self["default"] === self.def;
// should all fail
(0, a_1["default"])();
(0, a_2["default"])();
(0, a_3["default"])();
(0, a_3["default"])();
self["default"]();
self.def();
// should all work
a_1["default"]["default"]();
a_2["default"]["default"]();
a_3["default"]["default"]();
a_3["default"]["default"]();
self["default"]["default"]();
self.def["default"]();

0 comments on commit 4b7a7b5

Please sign in to comment.