From 3305baf5eb11e496d309754a896eb075906260f0 Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Fri, 2 Feb 2018 18:19:28 -0800 Subject: [PATCH] Collect import helper needs during module info gathering (#21567) (#21586) * Collect import helper needs during module info gathering * Add tests for other forms that trigger import helpers --- src/compiler/factory.ts | 4 +- src/compiler/transformers/module/module.ts | 25 +------- src/compiler/transformers/utilities.ts | 30 ++++++++- .../reference/esModuleInteropTslibHelpers.js | 64 +++++++++++++++++++ .../esModuleInteropTslibHelpers.symbols | 45 +++++++++++++ .../esModuleInteropTslibHelpers.types | 61 ++++++++++++++++++ .../compiler/esModuleInteropTslibHelpers.ts | 21 ++++++ 7 files changed, 224 insertions(+), 26 deletions(-) create mode 100644 tests/baselines/reference/esModuleInteropTslibHelpers.js create mode 100644 tests/baselines/reference/esModuleInteropTslibHelpers.symbols create mode 100644 tests/baselines/reference/esModuleInteropTslibHelpers.types create mode 100644 tests/cases/compiler/esModuleInteropTslibHelpers.ts diff --git a/src/compiler/factory.ts b/src/compiler/factory.ts index c622ddfb2bf..05559da37b1 100644 --- a/src/compiler/factory.ts +++ b/src/compiler/factory.ts @@ -4293,7 +4293,7 @@ namespace ts { return emitNode && emitNode.externalHelpersModuleName; } - export function getOrCreateExternalHelpersModuleNameIfNeeded(node: SourceFile, compilerOptions: CompilerOptions, hasExportStarsToExportValues?: boolean) { + export function getOrCreateExternalHelpersModuleNameIfNeeded(node: SourceFile, compilerOptions: CompilerOptions, hasExportStarsToExportValues?: boolean, hasImportStarOrImportDefault?: boolean) { if (compilerOptions.importHelpers && isEffectiveExternalModule(node, compilerOptions)) { const externalHelpersModuleName = getExternalHelpersModuleName(node); if (externalHelpersModuleName) { @@ -4301,7 +4301,7 @@ namespace ts { } const moduleKind = getEmitModuleKind(compilerOptions); - let create = hasExportStarsToExportValues + let create = (hasExportStarsToExportValues || (compilerOptions.esModuleInterop && hasImportStarOrImportDefault)) && moduleKind !== ModuleKind.System && moduleKind !== ModuleKind.ES2015 && moduleKind !== ModuleKind.ESNext; diff --git a/src/compiler/transformers/module/module.ts b/src/compiler/transformers/module/module.ts index aabc2bc45c1..2c75964d616 100644 --- a/src/compiler/transformers/module/module.ts +++ b/src/compiler/transformers/module/module.ts @@ -690,36 +690,15 @@ 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; } - const nameCount = getNamedImportCount(node); - const hasDefault = nameCount > 0 ? containsDefaultReference(node.importClause.namedBindings) : undefined; - - if (getNamespaceDeclarationNode(node) || (nameCount > 1 && hasDefault)) { + if (getImportNeedsImportStarHelper(node)) { context.requestEmitHelper(importStarHelper); return createCall(getHelperName("__importStar"), /*typeArguments*/ undefined, [innerExpr]); } - if (isDefaultImport(node) || (nameCount === 1 && hasDefault)) { + if (getImportNeedsImportDefaultHelper(node)) { context.requestEmitHelper(importDefaultHelper); return createCall(getHelperName("__importDefault"), /*typeArguments*/ undefined, [innerExpr]); } diff --git a/src/compiler/transformers/utilities.ts b/src/compiler/transformers/utilities.ts index 15e91d755ca..9267a239bb4 100644 --- a/src/compiler/transformers/utilities.ts +++ b/src/compiler/transformers/utilities.ts @@ -15,6 +15,32 @@ namespace ts { hasExportStarsToExportValues: boolean; // whether this module contains export* } + 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; + } + + export function getImportNeedsImportStarHelper(node: ImportDeclaration) { + return !!getNamespaceDeclarationNode(node) || (getNamedImportCount(node) > 1 && containsDefaultReference(node.importClause.namedBindings)); + } + + export function getImportNeedsImportDefaultHelper(node: ImportDeclaration) { + return isDefaultImport(node) || (getNamedImportCount(node) === 1 && containsDefaultReference(node.importClause.namedBindings)); + } + export function collectExternalModuleInfo(sourceFile: SourceFile, resolver: EmitResolver, compilerOptions: CompilerOptions): ExternalModuleInfo { const externalImports: (ImportDeclaration | ImportEqualsDeclaration | ExportDeclaration)[] = []; const exportSpecifiers = createMultiMap(); @@ -24,6 +50,7 @@ namespace ts { let hasExportDefault = false; let exportEquals: ExportAssignment = undefined; let hasExportStarsToExportValues = false; + let hasImportStarOrImportDefault = false; for (const node of sourceFile.statements) { switch (node.kind) { @@ -33,6 +60,7 @@ namespace ts { // import * as x from "mod" // import { x, y } from "mod" externalImports.push(node); + hasImportStarOrImportDefault = getImportNeedsImportStarHelper(node) || getImportNeedsImportDefaultHelper(node); break; case SyntaxKind.ImportEqualsDeclaration: @@ -135,7 +163,7 @@ namespace ts { } } - const externalHelpersModuleName = getOrCreateExternalHelpersModuleNameIfNeeded(sourceFile, compilerOptions, hasExportStarsToExportValues); + const externalHelpersModuleName = getOrCreateExternalHelpersModuleNameIfNeeded(sourceFile, compilerOptions, hasExportStarsToExportValues, hasImportStarOrImportDefault); const externalHelpersImportDeclaration = externalHelpersModuleName && createImportDeclaration( /*decorators*/ undefined, /*modifiers*/ undefined, diff --git a/tests/baselines/reference/esModuleInteropTslibHelpers.js b/tests/baselines/reference/esModuleInteropTslibHelpers.js new file mode 100644 index 00000000000..a844bd40cfb --- /dev/null +++ b/tests/baselines/reference/esModuleInteropTslibHelpers.js @@ -0,0 +1,64 @@ +//// [tests/cases/compiler/esModuleInteropTslibHelpers.ts] //// + +//// [refs.d.ts] +declare module "path"; +//// [file.ts] +import path from "path"; +path.resolve("", "../"); +export class Foo { } +//// [file2.ts] +import * as path from "path"; +path.resolve("", "../"); +export class Foo2 { } +//// [file3.ts] +import {default as resolve} from "path"; +resolve("", "../"); +export class Foo3 { } +//// [file4.ts] +import {Bar, default as resolve} from "path"; +resolve("", "../"); +export { Bar } + +//// [file.js] +"use strict"; +exports.__esModule = true; +var tslib_1 = require("tslib"); +var path_1 = tslib_1.__importDefault(require("path")); +path_1["default"].resolve("", "../"); +var Foo = /** @class */ (function () { + function Foo() { + } + return Foo; +}()); +exports.Foo = Foo; +//// [file2.js] +"use strict"; +exports.__esModule = true; +var tslib_1 = require("tslib"); +var path = tslib_1.__importStar(require("path")); +path.resolve("", "../"); +var Foo2 = /** @class */ (function () { + function Foo2() { + } + return Foo2; +}()); +exports.Foo2 = Foo2; +//// [file3.js] +"use strict"; +exports.__esModule = true; +var tslib_1 = require("tslib"); +var path_1 = tslib_1.__importDefault(require("path")); +path_1["default"]("", "../"); +var Foo3 = /** @class */ (function () { + function Foo3() { + } + return Foo3; +}()); +exports.Foo3 = Foo3; +//// [file4.js] +"use strict"; +exports.__esModule = true; +var tslib_1 = require("tslib"); +var path_1 = tslib_1.__importStar(require("path")); +exports.Bar = path_1.Bar; +path_1["default"]("", "../"); diff --git a/tests/baselines/reference/esModuleInteropTslibHelpers.symbols b/tests/baselines/reference/esModuleInteropTslibHelpers.symbols new file mode 100644 index 00000000000..d6bf19071bc --- /dev/null +++ b/tests/baselines/reference/esModuleInteropTslibHelpers.symbols @@ -0,0 +1,45 @@ +=== tests/cases/compiler/refs.d.ts === +declare module "path"; +No type information for this code.=== tests/cases/compiler/file.ts === +import path from "path"; +>path : Symbol(path, Decl(file.ts, 0, 6)) + +path.resolve("", "../"); +>path : Symbol(path, Decl(file.ts, 0, 6)) + +export class Foo { } +>Foo : Symbol(Foo, Decl(file.ts, 1, 24)) + +=== tests/cases/compiler/file2.ts === +import * as path from "path"; +>path : Symbol(path, Decl(file2.ts, 0, 6)) + +path.resolve("", "../"); +>path : Symbol(path, Decl(file2.ts, 0, 6)) + +export class Foo2 { } +>Foo2 : Symbol(Foo2, Decl(file2.ts, 1, 24)) + +=== tests/cases/compiler/file3.ts === +import {default as resolve} from "path"; +>default : Symbol(resolve, Decl(file3.ts, 0, 8)) +>resolve : Symbol(resolve, Decl(file3.ts, 0, 8)) + +resolve("", "../"); +>resolve : Symbol(resolve, Decl(file3.ts, 0, 8)) + +export class Foo3 { } +>Foo3 : Symbol(Foo3, Decl(file3.ts, 1, 19)) + +=== tests/cases/compiler/file4.ts === +import {Bar, default as resolve} from "path"; +>Bar : Symbol(Bar, Decl(file4.ts, 0, 8)) +>default : Symbol(resolve, Decl(file4.ts, 0, 12)) +>resolve : Symbol(resolve, Decl(file4.ts, 0, 12)) + +resolve("", "../"); +>resolve : Symbol(resolve, Decl(file4.ts, 0, 12)) + +export { Bar } +>Bar : Symbol(Bar, Decl(file4.ts, 2, 8)) + diff --git a/tests/baselines/reference/esModuleInteropTslibHelpers.types b/tests/baselines/reference/esModuleInteropTslibHelpers.types new file mode 100644 index 00000000000..e0d6ac24f8f --- /dev/null +++ b/tests/baselines/reference/esModuleInteropTslibHelpers.types @@ -0,0 +1,61 @@ +=== tests/cases/compiler/refs.d.ts === +declare module "path"; +No type information for this code.=== tests/cases/compiler/file.ts === +import path from "path"; +>path : any + +path.resolve("", "../"); +>path.resolve("", "../") : any +>path.resolve : any +>path : any +>resolve : any +>"" : "" +>"../" : "../" + +export class Foo { } +>Foo : Foo + +=== tests/cases/compiler/file2.ts === +import * as path from "path"; +>path : any + +path.resolve("", "../"); +>path.resolve("", "../") : any +>path.resolve : any +>path : any +>resolve : any +>"" : "" +>"../" : "../" + +export class Foo2 { } +>Foo2 : Foo2 + +=== tests/cases/compiler/file3.ts === +import {default as resolve} from "path"; +>default : any +>resolve : any + +resolve("", "../"); +>resolve("", "../") : any +>resolve : any +>"" : "" +>"../" : "../" + +export class Foo3 { } +>Foo3 : Foo3 + +=== tests/cases/compiler/file4.ts === +import {Bar, default as resolve} from "path"; +>Bar : any +>default : any +>resolve : any + +resolve("", "../"); +>resolve("", "../") : any +>resolve : any +>"" : "" +>"../" : "../" + +export { Bar } +>Bar : any + diff --git a/tests/cases/compiler/esModuleInteropTslibHelpers.ts b/tests/cases/compiler/esModuleInteropTslibHelpers.ts new file mode 100644 index 00000000000..a485ae8d2af --- /dev/null +++ b/tests/cases/compiler/esModuleInteropTslibHelpers.ts @@ -0,0 +1,21 @@ +// @esModuleInterop: true +// @importHelpers: true +// @noEmitHelpers: true +// @filename: refs.d.ts +declare module "path"; +// @filename: file.ts +import path from "path"; +path.resolve("", "../"); +export class Foo { } +// @filename: file2.ts +import * as path from "path"; +path.resolve("", "../"); +export class Foo2 { } +// @filename: file3.ts +import {default as resolve} from "path"; +resolve("", "../"); +export class Foo3 { } +// @filename: file4.ts +import {Bar, default as resolve} from "path"; +resolve("", "../"); +export { Bar } \ No newline at end of file