From 05af8faac627017166752e14d9b0ad050cea4d53 Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Fri, 9 Aug 2019 14:02:18 -0700 Subject: [PATCH] Support tree-shakable imports for --target es2015 (#32742) * Support tree-shakable imports for --target es2015 * Alias external helper imports for --module es2015 --- src/compiler/emitter.ts | 2 +- src/compiler/factory.ts | 76 +++++++++++++++++-- src/compiler/transformers/destructuring.ts | 3 +- src/compiler/transformers/es2015.ts | 6 +- src/compiler/transformers/es2017.ts | 3 +- src/compiler/transformers/es2018.ts | 15 ++-- src/compiler/transformers/generators.ts | 3 +- src/compiler/transformers/module/es2015.ts | 36 ++++----- src/compiler/transformers/module/module.ts | 12 +-- src/compiler/transformers/ts.ts | 9 ++- src/compiler/transformers/utilities.ts | 19 +++-- src/compiler/types.ts | 9 ++- tests/baselines/reference/importHelpersES6.js | 4 +- ...tHelpersWithLocalCollisions(module=amd).js | 32 ++++++++ ...ersWithLocalCollisions(module=commonjs).js | 31 ++++++++ ...lpersWithLocalCollisions(module=es2015).js | 29 +++++++ ...lpersWithLocalCollisions(module=system).js | 42 ++++++++++ .../importHelpersWithLocalCollisions.ts | 21 +++++ 18 files changed, 293 insertions(+), 59 deletions(-) create mode 100644 tests/baselines/reference/importHelpersWithLocalCollisions(module=amd).js create mode 100644 tests/baselines/reference/importHelpersWithLocalCollisions(module=commonjs).js create mode 100644 tests/baselines/reference/importHelpersWithLocalCollisions(module=es2015).js create mode 100644 tests/baselines/reference/importHelpersWithLocalCollisions(module=system).js create mode 100644 tests/cases/compiler/importHelpersWithLocalCollisions.ts diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index 8681a452e2a..a70973825d1 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -1609,7 +1609,7 @@ namespace ts { for (let i = 0; i < numNodes; i++) { const currentNode = bundle ? i < numPrepends ? bundle.prepends[i] : bundle.sourceFiles[i - numPrepends] : node; const sourceFile = isSourceFile(currentNode) ? currentNode : isUnparsedSource(currentNode) ? undefined : currentSourceFile!; - const shouldSkip = printerOptions.noEmitHelpers || (!!sourceFile && getExternalHelpersModuleName(sourceFile) !== undefined); + const shouldSkip = printerOptions.noEmitHelpers || (!!sourceFile && hasRecordedExternalHelpers(sourceFile)); const shouldBundle = (isSourceFile(currentNode) || isUnparsedSource(currentNode)) && !isOwnFileEmit; const helpers = isUnparsedSource(currentNode) ? currentNode.helpers : getSortedEmitHelpers(currentNode); if (helpers) { diff --git a/src/compiler/factory.ts b/src/compiler/factory.ts index 3a8ecb20999..8678ba4c122 100644 --- a/src/compiler/factory.ts +++ b/src/compiler/factory.ts @@ -3628,12 +3628,16 @@ namespace ts { // Helpers - export function getHelperName(name: string) { + /** + * Gets an identifier for the name of an *unscoped* emit helper. + */ + export function getUnscopedHelperName(name: string) { return setEmitFlags(createIdentifier(name), EmitFlags.HelperName | EmitFlags.AdviseOnEmitNode); } export const valuesHelper: UnscopedEmitHelper = { name: "typescript:values", + importName: "__values", scoped: false, text: ` var __values = (this && this.__values) || function(o) { @@ -3653,7 +3657,7 @@ namespace ts { context.requestEmitHelper(valuesHelper); return setTextRange( createCall( - getHelperName("__values"), + getUnscopedHelperName("__values"), /*typeArguments*/ undefined, [expression] ), @@ -3663,6 +3667,7 @@ namespace ts { export const readHelper: UnscopedEmitHelper = { name: "typescript:read", + importName: "__read", scoped: false, text: ` var __read = (this && this.__read) || function (o, n) { @@ -3687,7 +3692,7 @@ namespace ts { context.requestEmitHelper(readHelper); return setTextRange( createCall( - getHelperName("__read"), + getUnscopedHelperName("__read"), /*typeArguments*/ undefined, count !== undefined ? [iteratorRecord, createLiteral(count)] @@ -3699,6 +3704,7 @@ namespace ts { export const spreadHelper: UnscopedEmitHelper = { name: "typescript:spread", + importName: "__spread", scoped: false, text: ` var __spread = (this && this.__spread) || function () { @@ -3712,7 +3718,7 @@ namespace ts { context.requestEmitHelper(spreadHelper); return setTextRange( createCall( - getHelperName("__spread"), + getUnscopedHelperName("__spread"), /*typeArguments*/ undefined, argumentList ), @@ -3722,6 +3728,7 @@ namespace ts { export const spreadArraysHelper: UnscopedEmitHelper = { name: "typescript:spreadArrays", + importName: "__spreadArrays", scoped: false, text: ` var __spreadArrays = (this && this.__spreadArrays) || function () { @@ -3737,7 +3744,7 @@ namespace ts { context.requestEmitHelper(spreadArraysHelper); return setTextRange( createCall( - getHelperName("__spreadArrays"), + getUnscopedHelperName("__spreadArrays"), /*typeArguments*/ undefined, argumentList ), @@ -4863,6 +4870,65 @@ namespace ts { return emitNode && emitNode.externalHelpersModuleName; } + export function hasRecordedExternalHelpers(sourceFile: SourceFile) { + const parseNode = getOriginalNode(sourceFile, isSourceFile); + const emitNode = parseNode && parseNode.emitNode; + return !!emitNode && (!!emitNode.externalHelpersModuleName || !!emitNode.externalHelpers); + } + + export function createExternalHelpersImportDeclarationIfNeeded(sourceFile: SourceFile, compilerOptions: CompilerOptions, hasExportStarsToExportValues?: boolean, hasImportStar?: boolean, hasImportDefault?: boolean) { + if (compilerOptions.importHelpers && isEffectiveExternalModule(sourceFile, compilerOptions)) { + let namedBindings: NamedImportBindings | undefined; + const moduleKind = getEmitModuleKind(compilerOptions); + if (moduleKind >= ModuleKind.ES2015 && moduleKind <= ModuleKind.ESNext) { + // use named imports + const helpers = getEmitHelpers(sourceFile); + if (helpers) { + const helperNames: string[] = []; + for (const helper of helpers) { + if (!helper.scoped) { + const importName = (helper as UnscopedEmitHelper).importName; + if (importName) { + pushIfUnique(helperNames, importName); + } + } + } + if (some(helperNames)) { + helperNames.sort(compareStringsCaseSensitive); + // Alias the imports if the names are used somewhere in the file. + // NOTE: We don't need to care about global import collisions as this is a module. + namedBindings = createNamedImports( + map(helperNames, name => isFileLevelUniqueName(sourceFile, name) + ? createImportSpecifier(/*propertyName*/ undefined, createIdentifier(name)) + : createImportSpecifier(createIdentifier(name), getUnscopedHelperName(name)) + ) + ); + const parseNode = getOriginalNode(sourceFile, isSourceFile); + const emitNode = getOrCreateEmitNode(parseNode); + emitNode.externalHelpers = true; + } + } + } + else { + // use a namespace import + const externalHelpersModuleName = getOrCreateExternalHelpersModuleNameIfNeeded(sourceFile, compilerOptions, hasExportStarsToExportValues, hasImportStar || hasImportDefault); + if (externalHelpersModuleName) { + namedBindings = createNamespaceImport(externalHelpersModuleName); + } + } + if (namedBindings) { + const externalHelpersImportDeclaration = createImportDeclaration( + /*decorators*/ undefined, + /*modifiers*/ undefined, + createImportClause(/*name*/ undefined, namedBindings), + createLiteral(externalHelpersModuleNameText) + ); + addEmitFlags(externalHelpersImportDeclaration, EmitFlags.NeverApplyImportHelper); + return externalHelpersImportDeclaration; + } + } + } + export function getOrCreateExternalHelpersModuleNameIfNeeded(node: SourceFile, compilerOptions: CompilerOptions, hasExportStarsToExportValues?: boolean, hasImportStarOrImportDefault?: boolean) { if (compilerOptions.importHelpers && isEffectiveExternalModule(node, compilerOptions)) { const externalHelpersModuleName = getExternalHelpersModuleName(node); diff --git a/src/compiler/transformers/destructuring.ts b/src/compiler/transformers/destructuring.ts index 8946db3f60b..a12cbd42eb6 100644 --- a/src/compiler/transformers/destructuring.ts +++ b/src/compiler/transformers/destructuring.ts @@ -514,6 +514,7 @@ namespace ts { export const restHelper: UnscopedEmitHelper = { name: "typescript:rest", + importName: "__rest", scoped: false, text: ` var __rest = (this && this.__rest) || function (s, e) { @@ -557,7 +558,7 @@ namespace ts { } } return createCall( - getHelperName("__rest"), + getUnscopedHelperName("__rest"), /*typeArguments*/ undefined, [ value, diff --git a/src/compiler/transformers/es2015.ts b/src/compiler/transformers/es2015.ts index 7623d19dd25..793f6e1b1ef 100644 --- a/src/compiler/transformers/es2015.ts +++ b/src/compiler/transformers/es2015.ts @@ -4339,7 +4339,7 @@ namespace ts { function createExtendsHelper(context: TransformationContext, name: Identifier) { context.requestEmitHelper(extendsHelper); return createCall( - getHelperName("__extends"), + getUnscopedHelperName("__extends"), /*typeArguments*/ undefined, [ name, @@ -4351,7 +4351,7 @@ namespace ts { function createTemplateObjectHelper(context: TransformationContext, cooked: ArrayLiteralExpression, raw: ArrayLiteralExpression) { context.requestEmitHelper(templateObjectHelper); return createCall( - getHelperName("__makeTemplateObject"), + getUnscopedHelperName("__makeTemplateObject"), /*typeArguments*/ undefined, [ cooked, @@ -4362,6 +4362,7 @@ namespace ts { export const extendsHelper: UnscopedEmitHelper = { name: "typescript:extends", + importName: "__extends", scoped: false, priority: 0, text: ` @@ -4383,6 +4384,7 @@ namespace ts { export const templateObjectHelper: UnscopedEmitHelper = { name: "typescript:makeTemplateObject", + importName: "__makeTemplateObject", scoped: false, priority: 0, text: ` diff --git a/src/compiler/transformers/es2017.ts b/src/compiler/transformers/es2017.ts index 9c2d5700d43..98f42b5a564 100644 --- a/src/compiler/transformers/es2017.ts +++ b/src/compiler/transformers/es2017.ts @@ -771,6 +771,7 @@ namespace ts { export const awaiterHelper: UnscopedEmitHelper = { name: "typescript:awaiter", + importName: "__awaiter", scoped: false, priority: 5, text: ` @@ -802,7 +803,7 @@ namespace ts { (generatorFunc.emitNode || (generatorFunc.emitNode = {} as EmitNode)).flags |= EmitFlags.AsyncFunctionBody | EmitFlags.ReuseTempVariableScope; return createCall( - getHelperName("__awaiter"), + getUnscopedHelperName("__awaiter"), /*typeArguments*/ undefined, [ createThis(), diff --git a/src/compiler/transformers/es2018.ts b/src/compiler/transformers/es2018.ts index 63b2ccdb731..c723da3d312 100644 --- a/src/compiler/transformers/es2018.ts +++ b/src/compiler/transformers/es2018.ts @@ -995,6 +995,7 @@ namespace ts { export const assignHelper: UnscopedEmitHelper = { name: "typescript:assign", + importName: "__assign", scoped: false, priority: 1, text: ` @@ -1019,7 +1020,7 @@ namespace ts { } context.requestEmitHelper(assignHelper); return createCall( - getHelperName("__assign"), + getUnscopedHelperName("__assign"), /*typeArguments*/ undefined, attributesSegments ); @@ -1027,6 +1028,7 @@ namespace ts { export const awaitHelper: UnscopedEmitHelper = { name: "typescript:await", + importName: "__await", scoped: false, text: ` var __await = (this && this.__await) || function (v) { return this instanceof __await ? (this.v = v, this) : new __await(v); }` @@ -1034,11 +1036,12 @@ namespace ts { function createAwaitHelper(context: TransformationContext, expression: Expression) { context.requestEmitHelper(awaitHelper); - return createCall(getHelperName("__await"), /*typeArguments*/ undefined, [expression]); + return createCall(getUnscopedHelperName("__await"), /*typeArguments*/ undefined, [expression]); } export const asyncGeneratorHelper: UnscopedEmitHelper = { name: "typescript:asyncGenerator", + importName: "__asyncGenerator", scoped: false, text: ` var __asyncGenerator = (this && this.__asyncGenerator) || function (thisArg, _arguments, generator) { @@ -1062,7 +1065,7 @@ namespace ts { (generatorFunc.emitNode || (generatorFunc.emitNode = {} as EmitNode)).flags |= EmitFlags.AsyncFunctionBody; return createCall( - getHelperName("__asyncGenerator"), + getUnscopedHelperName("__asyncGenerator"), /*typeArguments*/ undefined, [ createThis(), @@ -1074,6 +1077,7 @@ namespace ts { export const asyncDelegator: UnscopedEmitHelper = { name: "typescript:asyncDelegator", + importName: "__asyncDelegator", scoped: false, text: ` var __asyncDelegator = (this && this.__asyncDelegator) || function (o) { @@ -1088,7 +1092,7 @@ namespace ts { context.requestEmitHelper(asyncDelegator); return setTextRange( createCall( - getHelperName("__asyncDelegator"), + getUnscopedHelperName("__asyncDelegator"), /*typeArguments*/ undefined, [expression] ), @@ -1098,6 +1102,7 @@ namespace ts { export const asyncValues: UnscopedEmitHelper = { name: "typescript:asyncValues", + importName: "__asyncValues", scoped: false, text: ` var __asyncValues = (this && this.__asyncValues) || function (o) { @@ -1113,7 +1118,7 @@ namespace ts { context.requestEmitHelper(asyncValues); return setTextRange( createCall( - getHelperName("__asyncValues"), + getUnscopedHelperName("__asyncValues"), /*typeArguments*/ undefined, [expression] ), diff --git a/src/compiler/transformers/generators.ts b/src/compiler/transformers/generators.ts index 154b09dc93d..09b4d146dd6 100644 --- a/src/compiler/transformers/generators.ts +++ b/src/compiler/transformers/generators.ts @@ -3176,7 +3176,7 @@ namespace ts { function createGeneratorHelper(context: TransformationContext, body: FunctionExpression) { context.requestEmitHelper(generatorHelper); return createCall( - getHelperName("__generator"), + getUnscopedHelperName("__generator"), /*typeArguments*/ undefined, [createThis(), body]); } @@ -3242,6 +3242,7 @@ namespace ts { // For examples of how these are used, see the comments in ./transformers/generators.ts export const generatorHelper: UnscopedEmitHelper = { name: "typescript:generator", + importName: "__generator", scoped: false, priority: 6, text: ` diff --git a/src/compiler/transformers/module/es2015.ts b/src/compiler/transformers/module/es2015.ts index 792dd944596..e1123d1248d 100644 --- a/src/compiler/transformers/module/es2015.ts +++ b/src/compiler/transformers/module/es2015.ts @@ -9,7 +9,7 @@ namespace ts { context.enableEmitNotification(SyntaxKind.SourceFile); context.enableSubstitution(SyntaxKind.Identifier); - let currentSourceFile: SourceFile | undefined; + let helperNameSubstitutions: Map | undefined; return chainBundle(transformSourceFile); function transformSourceFile(node: SourceFile) { @@ -18,18 +18,11 @@ namespace ts { } if (isExternalModule(node) || compilerOptions.isolatedModules) { - const externalHelpersModuleName = getOrCreateExternalHelpersModuleNameIfNeeded(node, compilerOptions); - if (externalHelpersModuleName) { + const externalHelpersImportDeclaration = createExternalHelpersImportDeclarationIfNeeded(node, compilerOptions); + if (externalHelpersImportDeclaration) { const statements: Statement[] = []; const statementOffset = addPrologue(statements, node.statements); - const tslibImport = createImportDeclaration( - /*decorators*/ undefined, - /*modifiers*/ undefined, - createImportClause(/*name*/ undefined, createNamespaceImport(externalHelpersModuleName)), - createLiteral(externalHelpersModuleNameText) - ); - addEmitFlags(tslibImport, EmitFlags.NeverApplyImportHelper); - append(statements, tslibImport); + append(statements, externalHelpersImportDeclaration); addRange(statements, visitNodes(node.statements, visitor, isStatement, statementOffset)); return updateSourceFileNode( @@ -74,9 +67,9 @@ namespace ts { */ function onEmitNode(hint: EmitHint, node: Node, emitCallback: (hint: EmitHint, node: Node) => void): void { if (isSourceFile(node)) { - currentSourceFile = node; + helperNameSubstitutions = createMap(); previousOnEmitNode(hint, node, emitCallback); - currentSourceFile = undefined; + helperNameSubstitutions = undefined; } else { previousOnEmitNode(hint, node, emitCallback); @@ -95,21 +88,20 @@ namespace ts { */ function onSubstituteNode(hint: EmitHint, node: Node) { node = previousOnSubstituteNode(hint, node); - if (isIdentifier(node) && hint === EmitHint.Expression) { - return substituteExpressionIdentifier(node); + if (helperNameSubstitutions && isIdentifier(node) && getEmitFlags(node) & EmitFlags.HelperName) { + return substituteHelperName(node); } return node; } - function substituteExpressionIdentifier(node: Identifier): Expression { - if (getEmitFlags(node) & EmitFlags.HelperName) { - const externalHelpersModuleName = getExternalHelpersModuleName(currentSourceFile!); - if (externalHelpersModuleName) { - return createPropertyAccess(externalHelpersModuleName, node); - } + function substituteHelperName(node: Identifier): Expression { + const name = idText(node); + let substitution = helperNameSubstitutions!.get(name); + if (!substitution) { + helperNameSubstitutions!.set(name, substitution = createFileLevelUniqueName(name)); } - return node; + return substitution; } } } diff --git a/src/compiler/transformers/module/module.ts b/src/compiler/transformers/module/module.ts index 2e02e15a2ed..53e4f824008 100644 --- a/src/compiler/transformers/module/module.ts +++ b/src/compiler/transformers/module/module.ts @@ -698,7 +698,7 @@ namespace ts { const promise = createNew(createIdentifier("Promise"), /*typeArguments*/ undefined, [func]); if (compilerOptions.esModuleInterop) { context.requestEmitHelper(importStarHelper); - return createCall(createPropertyAccess(promise, createIdentifier("then")), /*typeArguments*/ undefined, [getHelperName("__importStar")]); + return createCall(createPropertyAccess(promise, createIdentifier("then")), /*typeArguments*/ undefined, [getUnscopedHelperName("__importStar")]); } return promise; } @@ -713,7 +713,7 @@ namespace ts { let requireCall = createCall(createIdentifier("require"), /*typeArguments*/ undefined, arg ? [arg] : []); if (compilerOptions.esModuleInterop) { context.requestEmitHelper(importStarHelper); - requireCall = createCall(getHelperName("__importStar"), /*typeArguments*/ undefined, [requireCall]); + requireCall = createCall(getUnscopedHelperName("__importStar"), /*typeArguments*/ undefined, [requireCall]); } let func: FunctionExpression | ArrowFunction; @@ -753,11 +753,11 @@ namespace ts { } if (getImportNeedsImportStarHelper(node)) { context.requestEmitHelper(importStarHelper); - return createCall(getHelperName("__importStar"), /*typeArguments*/ undefined, [innerExpr]); + return createCall(getUnscopedHelperName("__importStar"), /*typeArguments*/ undefined, [innerExpr]); } if (getImportNeedsImportDefaultHelper(node)) { context.requestEmitHelper(importDefaultHelper); - return createCall(getHelperName("__importDefault"), /*typeArguments*/ undefined, [innerExpr]); + return createCall(getUnscopedHelperName("__importDefault"), /*typeArguments*/ undefined, [innerExpr]); } return innerExpr; } @@ -1793,7 +1793,7 @@ namespace ts { function createExportStarHelper(context: TransformationContext, module: Expression) { const compilerOptions = context.getCompilerOptions(); return compilerOptions.importHelpers - ? createCall(getHelperName("__exportStar"), /*typeArguments*/ undefined, [module, createIdentifier("exports")]) + ? createCall(getUnscopedHelperName("__exportStar"), /*typeArguments*/ undefined, [module, createIdentifier("exports")]) : createCall(createIdentifier("__export"), /*typeArguments*/ undefined, [module]); } @@ -1808,6 +1808,7 @@ namespace ts { // emit helper for `import * as Name from "foo"` export const importStarHelper: UnscopedEmitHelper = { name: "typescript:commonjsimportstar", + importName: "__importStar", scoped: false, text: ` var __importStar = (this && this.__importStar) || function (mod) { @@ -1822,6 +1823,7 @@ var __importStar = (this && this.__importStar) || function (mod) { // emit helper for `import Name from "foo"` export const importDefaultHelper: UnscopedEmitHelper = { name: "typescript:commonjsimportdefault", + importName: "__importDefault", scoped: false, text: ` var __importDefault = (this && this.__importDefault) || function (mod) { diff --git a/src/compiler/transformers/ts.ts b/src/compiler/transformers/ts.ts index db4c1459e5a..8d6c593bfe5 100644 --- a/src/compiler/transformers/ts.ts +++ b/src/compiler/transformers/ts.ts @@ -3282,7 +3282,7 @@ namespace ts { context.requestEmitHelper(decorateHelper); return setTextRange( createCall( - getHelperName("__decorate"), + getUnscopedHelperName("__decorate"), /*typeArguments*/ undefined, argumentsArray ), @@ -3292,6 +3292,7 @@ namespace ts { export const decorateHelper: UnscopedEmitHelper = { name: "typescript:decorate", + importName: "__decorate", scoped: false, priority: 2, text: ` @@ -3306,7 +3307,7 @@ namespace ts { function createMetadataHelper(context: TransformationContext, metadataKey: string, metadataValue: Expression) { context.requestEmitHelper(metadataHelper); return createCall( - getHelperName("__metadata"), + getUnscopedHelperName("__metadata"), /*typeArguments*/ undefined, [ createLiteral(metadataKey), @@ -3317,6 +3318,7 @@ namespace ts { export const metadataHelper: UnscopedEmitHelper = { name: "typescript:metadata", + importName: "__metadata", scoped: false, priority: 3, text: ` @@ -3329,7 +3331,7 @@ namespace ts { context.requestEmitHelper(paramHelper); return setTextRange( createCall( - getHelperName("__param"), + getUnscopedHelperName("__param"), /*typeArguments*/ undefined, [ createLiteral(parameterOffset), @@ -3342,6 +3344,7 @@ namespace ts { export const paramHelper: UnscopedEmitHelper = { name: "typescript:param", + importName: "__param", scoped: false, priority: 4, text: ` diff --git a/src/compiler/transformers/utilities.ts b/src/compiler/transformers/utilities.ts index f5f8a298a49..8a986215107 100644 --- a/src/compiler/transformers/utilities.ts +++ b/src/compiler/transformers/utilities.ts @@ -70,7 +70,8 @@ namespace ts { let hasExportDefault = false; let exportEquals: ExportAssignment | undefined; let hasExportStarsToExportValues = false; - let hasImportStarOrImportDefault = false; + let hasImportStar = false; + let hasImportDefault = false; for (const node of sourceFile.statements) { switch (node.kind) { @@ -80,7 +81,12 @@ namespace ts { // import * as x from "mod" // import { x, y } from "mod" externalImports.push(node); - hasImportStarOrImportDefault = hasImportStarOrImportDefault || getImportNeedsImportStarHelper(node) || getImportNeedsImportDefaultHelper(node); + if (!hasImportStar && getImportNeedsImportStarHelper(node)) { + hasImportStar = true; + } + if (!hasImportDefault && getImportNeedsImportDefaultHelper(node)) { + hasImportDefault = true; + } break; case SyntaxKind.ImportEqualsDeclaration: @@ -183,15 +189,8 @@ namespace ts { } } - const externalHelpersModuleName = getOrCreateExternalHelpersModuleNameIfNeeded(sourceFile, compilerOptions, hasExportStarsToExportValues, hasImportStarOrImportDefault); - const externalHelpersImportDeclaration = externalHelpersModuleName && createImportDeclaration( - /*decorators*/ undefined, - /*modifiers*/ undefined, - createImportClause(/*name*/ undefined, createNamespaceImport(externalHelpersModuleName)), - createLiteral(externalHelpersModuleNameText)); - + const externalHelpersImportDeclaration = createExternalHelpersImportDeclarationIfNeeded(sourceFile, compilerOptions, hasExportStarsToExportValues, hasImportStar, hasImportDefault); if (externalHelpersImportDeclaration) { - addEmitFlags(externalHelpersImportDeclaration, EmitFlags.NeverApplyImportHelper); externalImports.unshift(externalHelpersImportDeclaration); } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 3f1b30feb45..ee7861a336b 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -4760,6 +4760,10 @@ namespace ts { AMD = 2, UMD = 3, System = 4, + + // NOTE: ES module kinds should be contiguous to more easily check whether a module kind is *any* ES module kind. + // Non-ES module kinds should not come between ES2015 (the earliest ES module kind) and ESNext (the last ES + // module kind). ES2015 = 5, ESNext = 99 } @@ -5289,6 +5293,7 @@ namespace ts { tokenSourceMapRanges?: (SourceMapRange | undefined)[]; // The text range to use when emitting source mappings for tokens constantValue?: string | number; // The constant value of an expression externalHelpersModuleName?: Identifier; // The local name for an imported helpers module + externalHelpers?: boolean; helpers?: EmitHelper[]; // Emit helpers for the node startsOnNewLine?: boolean; // If the node should begin on a new line } @@ -5310,7 +5315,7 @@ namespace ts { NoTrailingComments = 1 << 10, // Do not emit trailing comments for this node. NoComments = NoLeadingComments | NoTrailingComments, // Do not emit comments for this node. NoNestedComments = 1 << 11, - HelperName = 1 << 12, + HelperName = 1 << 12, // The Identifier refers to an *unscoped* emit helper (one that is emitted at the top of the file) ExportName = 1 << 13, // Ensure an export prefix is added for an identifier that points to an exported declaration with a local name (see SymbolFlags.ExportHasLocal). LocalName = 1 << 14, // Ensure an export prefix is not added for an identifier that points to an exported declaration. InternalName = 1 << 15, // The name is internal to an ES5 class body function. @@ -5336,6 +5341,8 @@ namespace ts { export interface UnscopedEmitHelper extends EmitHelper { readonly scoped: false; // Indicates whether the helper MUST be emitted in the current scope. + /* @internal */ + readonly importName?: string; // The name of the helper to use when importing via `--importHelpers`. readonly text: string; // ES3-compatible raw script text, or a function yielding such a string } diff --git a/tests/baselines/reference/importHelpersES6.js b/tests/baselines/reference/importHelpersES6.js index bc17939e2cc..9eb0e980c0f 100644 --- a/tests/baselines/reference/importHelpersES6.js +++ b/tests/baselines/reference/importHelpersES6.js @@ -18,10 +18,10 @@ export declare function __awaiter(thisArg: any, _arguments: any, P: Function, ge //// [a.js] -import * as tslib_1 from "tslib"; +import { __decorate } from "tslib"; let A = class A { }; -A = tslib_1.__decorate([ +A = __decorate([ dec ], A); export { A }; diff --git a/tests/baselines/reference/importHelpersWithLocalCollisions(module=amd).js b/tests/baselines/reference/importHelpersWithLocalCollisions(module=amd).js new file mode 100644 index 00000000000..d603b0ade17 --- /dev/null +++ b/tests/baselines/reference/importHelpersWithLocalCollisions(module=amd).js @@ -0,0 +1,32 @@ +//// [tests/cases/compiler/importHelpersWithLocalCollisions.ts] //// + +//// [a.ts] +declare var dec: any, __decorate: any; +@dec export class A { + +} + +const o = { a: 1 }; +const y = { ...o }; + +//// [tslib.d.ts] +export declare function __extends(d: Function, b: Function): void; +export declare function __decorate(decorators: Function[], target: any, key?: string | symbol, desc?: any): any; +export declare function __param(paramIndex: number, decorator: Function): Function; +export declare function __metadata(metadataKey: any, metadataValue: any): Function; +export declare function __awaiter(thisArg: any, _arguments: any, P: Function, generator: Function): any; + + +//// [a.js] +define(["require", "exports", "tslib"], function (require, exports, tslib_1) { + "use strict"; + Object.defineProperty(exports, "__esModule", { value: true }); + let A = class A { + }; + A = tslib_1.__decorate([ + dec + ], A); + exports.A = A; + const o = { a: 1 }; + const y = Object.assign({}, o); +}); diff --git a/tests/baselines/reference/importHelpersWithLocalCollisions(module=commonjs).js b/tests/baselines/reference/importHelpersWithLocalCollisions(module=commonjs).js new file mode 100644 index 00000000000..560bc114c3d --- /dev/null +++ b/tests/baselines/reference/importHelpersWithLocalCollisions(module=commonjs).js @@ -0,0 +1,31 @@ +//// [tests/cases/compiler/importHelpersWithLocalCollisions.ts] //// + +//// [a.ts] +declare var dec: any, __decorate: any; +@dec export class A { + +} + +const o = { a: 1 }; +const y = { ...o }; + +//// [tslib.d.ts] +export declare function __extends(d: Function, b: Function): void; +export declare function __decorate(decorators: Function[], target: any, key?: string | symbol, desc?: any): any; +export declare function __param(paramIndex: number, decorator: Function): Function; +export declare function __metadata(metadataKey: any, metadataValue: any): Function; +export declare function __awaiter(thisArg: any, _arguments: any, P: Function, generator: Function): any; + + +//// [a.js] +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const tslib_1 = require("tslib"); +let A = class A { +}; +A = tslib_1.__decorate([ + dec +], A); +exports.A = A; +const o = { a: 1 }; +const y = Object.assign({}, o); diff --git a/tests/baselines/reference/importHelpersWithLocalCollisions(module=es2015).js b/tests/baselines/reference/importHelpersWithLocalCollisions(module=es2015).js new file mode 100644 index 00000000000..ba22069c306 --- /dev/null +++ b/tests/baselines/reference/importHelpersWithLocalCollisions(module=es2015).js @@ -0,0 +1,29 @@ +//// [tests/cases/compiler/importHelpersWithLocalCollisions.ts] //// + +//// [a.ts] +declare var dec: any, __decorate: any; +@dec export class A { + +} + +const o = { a: 1 }; +const y = { ...o }; + +//// [tslib.d.ts] +export declare function __extends(d: Function, b: Function): void; +export declare function __decorate(decorators: Function[], target: any, key?: string | symbol, desc?: any): any; +export declare function __param(paramIndex: number, decorator: Function): Function; +export declare function __metadata(metadataKey: any, metadataValue: any): Function; +export declare function __awaiter(thisArg: any, _arguments: any, P: Function, generator: Function): any; + + +//// [a.js] +import { __decorate as __decorate_1 } from "tslib"; +let A = class A { +}; +A = __decorate_1([ + dec +], A); +export { A }; +const o = { a: 1 }; +const y = Object.assign({}, o); diff --git a/tests/baselines/reference/importHelpersWithLocalCollisions(module=system).js b/tests/baselines/reference/importHelpersWithLocalCollisions(module=system).js new file mode 100644 index 00000000000..fb10d64b45a --- /dev/null +++ b/tests/baselines/reference/importHelpersWithLocalCollisions(module=system).js @@ -0,0 +1,42 @@ +//// [tests/cases/compiler/importHelpersWithLocalCollisions.ts] //// + +//// [a.ts] +declare var dec: any, __decorate: any; +@dec export class A { + +} + +const o = { a: 1 }; +const y = { ...o }; + +//// [tslib.d.ts] +export declare function __extends(d: Function, b: Function): void; +export declare function __decorate(decorators: Function[], target: any, key?: string | symbol, desc?: any): any; +export declare function __param(paramIndex: number, decorator: Function): Function; +export declare function __metadata(metadataKey: any, metadataValue: any): Function; +export declare function __awaiter(thisArg: any, _arguments: any, P: Function, generator: Function): any; + + +//// [a.js] +System.register(["tslib"], function (exports_1, context_1) { + "use strict"; + var tslib_1, A, o, y; + var __moduleName = context_1 && context_1.id; + return { + setters: [ + function (tslib_1_1) { + tslib_1 = tslib_1_1; + } + ], + execute: function () { + A = class A { + }; + A = tslib_1.__decorate([ + dec + ], A); + exports_1("A", A); + o = { a: 1 }; + y = Object.assign({}, o); + } + }; +}); diff --git a/tests/cases/compiler/importHelpersWithLocalCollisions.ts b/tests/cases/compiler/importHelpersWithLocalCollisions.ts new file mode 100644 index 00000000000..30e67ca069b --- /dev/null +++ b/tests/cases/compiler/importHelpersWithLocalCollisions.ts @@ -0,0 +1,21 @@ +// @importHelpers: true +// @target: es6 +// @module: commonjs, system, amd, es2015 +// @moduleResolution: classic +// @experimentalDecorators: true +// @filename: a.ts +// @noTypesAndSymbols: true +declare var dec: any, __decorate: any; +@dec export class A { + +} + +const o = { a: 1 }; +const y = { ...o }; + +// @filename: tslib.d.ts +export declare function __extends(d: Function, b: Function): void; +export declare function __decorate(decorators: Function[], target: any, key?: string | symbol, desc?: any): any; +export declare function __param(paramIndex: number, decorator: Function): Function; +export declare function __metadata(metadataKey: any, metadataValue: any): Function; +export declare function __awaiter(thisArg: any, _arguments: any, P: Function, generator: Function): any;