Skip to content

Create import helpers for named imports if they contain default imports #21550

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 22 additions & 2 deletions src/compiler/transformers/module/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -690,16 +690,36 @@ namespace ts {
return createCall(createPropertyAccess(promiseResolveCall, "then"), /*typeArguments*/ undefined, [func]);
}

function getNamedImportCount(node: ImportDeclaration) {
if (!(node.importClause && node.importClause.namedBindings)) return 0;
const names = node.importClause.namedBindings;
if (!names) return 0;
if (!isNamedImports(names)) return 0;
return names.elements.length;
}

function containsDefaultReference(node: NamedImportBindings) {
if (!node) return false;
if (!isNamedImports(node)) return false;
return some(node.elements, isNamedDefaultReference);
}

function isNamedDefaultReference(e: ImportSpecifier) {
return e.propertyName && e.propertyName.escapedText === InternalSymbolName.Default;
}

function getHelperExpressionForImport(node: ImportDeclaration, innerExpr: Expression) {
if (!compilerOptions.esModuleInterop || getEmitFlags(node) & EmitFlags.NeverApplyImportHelper) {
return innerExpr;
}
if (getNamespaceDeclarationNode(node)) {
const nameCount = getNamedImportCount(node);
const hasDefault = nameCount > 0 ? containsDefaultReference(node.importClause.namedBindings) : undefined;

if (getNamespaceDeclarationNode(node) || (nameCount > 1 && hasDefault)) {
context.requestEmitHelper(importStarHelper);
return createCall(getHelperName("__importStar"), /*typeArguments*/ undefined, [innerExpr]);
}
if (isDefaultImport(node)) {
if (isDefaultImport(node) || (nameCount === 1 && hasDefault)) {
context.requestEmitHelper(importDefaultHelper);
return createCall(getHelperName("__importDefault"), /*typeArguments*/ undefined, [innerExpr]);
}
Expand Down
49 changes: 49 additions & 0 deletions tests/baselines/reference/esModuleInteropNamedDefaultImports.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
//// [tests/cases/compiler/esModuleInteropNamedDefaultImports.ts] ////

//// [mod.ts]
export default class Foo {}
export class Bar {}
//// [idx.ts]
import Foo from "./mod";
import { default as Foo2 } from "./mod";
import { Bar, default as Foo3 } from "./mod";
new Foo();
new Foo2();
new Bar();
new Foo3();

//// [mod.js]
"use strict";
exports.__esModule = true;
var Foo = /** @class */ (function () {
function Foo() {
}
return Foo;
}());
exports["default"] = Foo;
var Bar = /** @class */ (function () {
function Bar() {
}
return Bar;
}());
exports.Bar = Bar;
//// [idx.js]
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
}
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
result["default"] = mod;
return result;
}
exports.__esModule = true;
var mod_1 = __importDefault(require("./mod"));
var mod_2 = __importDefault(require("./mod"));
var mod_3 = __importStar(require("./mod"));
new mod_1["default"]();
new mod_2["default"]();
new mod_3.Bar();
new mod_3["default"]();
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
=== tests/cases/compiler/mod.ts ===
export default class Foo {}
>Foo : Symbol(Foo, Decl(mod.ts, 0, 0))

export class Bar {}
>Bar : Symbol(Bar, Decl(mod.ts, 0, 27))

=== tests/cases/compiler/idx.ts ===
import Foo from "./mod";
>Foo : Symbol(Foo, Decl(idx.ts, 0, 6))

import { default as Foo2 } from "./mod";
>default : Symbol(Foo2, Decl(idx.ts, 1, 8))
>Foo2 : Symbol(Foo2, Decl(idx.ts, 1, 8))

import { Bar, default as Foo3 } from "./mod";
>Bar : Symbol(Bar, Decl(idx.ts, 2, 8))
>default : Symbol(Foo3, Decl(idx.ts, 2, 13))
>Foo3 : Symbol(Foo3, Decl(idx.ts, 2, 13))

new Foo();
>Foo : Symbol(Foo, Decl(idx.ts, 0, 6))

new Foo2();
>Foo2 : Symbol(Foo2, Decl(idx.ts, 1, 8))

new Bar();
>Bar : Symbol(Bar, Decl(idx.ts, 2, 8))

new Foo3();
>Foo3 : Symbol(Foo3, Decl(idx.ts, 2, 13))

36 changes: 36 additions & 0 deletions tests/baselines/reference/esModuleInteropNamedDefaultImports.types
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
=== tests/cases/compiler/mod.ts ===
export default class Foo {}
>Foo : Foo

export class Bar {}
>Bar : Bar

=== tests/cases/compiler/idx.ts ===
import Foo from "./mod";
>Foo : typeof Foo

import { default as Foo2 } from "./mod";
>default : typeof Foo
>Foo2 : typeof Foo

import { Bar, default as Foo3 } from "./mod";
>Bar : typeof Bar
>default : typeof Foo
>Foo3 : typeof Foo

new Foo();
>new Foo() : Foo
>Foo : typeof Foo

new Foo2();
>new Foo2() : Foo
>Foo2 : typeof Foo

new Bar();
>new Bar() : Bar
>Bar : typeof Bar

new Foo3();
>new Foo3() : Foo
>Foo3 : typeof Foo

13 changes: 13 additions & 0 deletions tests/cases/compiler/esModuleInteropNamedDefaultImports.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// @module: commonjs
// @esModuleInterop: true
// @filename: mod.ts
export default class Foo {}
export class Bar {}
// @filename: idx.ts
import Foo from "./mod";
import { default as Foo2 } from "./mod";
import { Bar, default as Foo3 } from "./mod";
new Foo();
new Foo2();
new Bar();
new Foo3();