Skip to content

Commit f39fe7d

Browse files
authored
Support importHelpers with module:preserve (#59852)
1 parent d514dab commit f39fe7d

File tree

8 files changed

+309
-58
lines changed

8 files changed

+309
-58
lines changed

src/compiler/factory/utilities.ts

Lines changed: 49 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ import {
4040
Expression,
4141
ExpressionStatement,
4242
externalHelpersModuleNameText,
43+
filter,
4344
first,
4445
firstOrUndefined,
4546
ForInitializer,
@@ -131,7 +132,6 @@ import {
131132
MultiplicativeOperator,
132133
MultiplicativeOperatorOrHigher,
133134
Mutable,
134-
NamedImportBindings,
135135
Node,
136136
NodeArray,
137137
NodeFactory,
@@ -173,6 +173,7 @@ import {
173173
Token,
174174
TransformFlags,
175175
TypeNode,
176+
UnscopedEmitHelper,
176177
WrappedExpression,
177178
} from "../_namespaces/ts.js";
178179

@@ -688,26 +689,29 @@ export function hasRecordedExternalHelpers(sourceFile: SourceFile) {
688689
/** @internal */
689690
export function createExternalHelpersImportDeclarationIfNeeded(nodeFactory: NodeFactory, helperFactory: EmitHelperFactory, sourceFile: SourceFile, compilerOptions: CompilerOptions, hasExportStarsToExportValues?: boolean, hasImportStar?: boolean, hasImportDefault?: boolean) {
690691
if (compilerOptions.importHelpers && isEffectiveExternalModule(sourceFile, compilerOptions)) {
691-
let namedBindings: NamedImportBindings | undefined;
692692
const moduleKind = getEmitModuleKind(compilerOptions);
693-
if ((moduleKind >= ModuleKind.ES2015 && moduleKind <= ModuleKind.ESNext) || getImpliedNodeFormatForEmitWorker(sourceFile, compilerOptions) === ModuleKind.ESNext) {
694-
// use named imports
695-
const helpers = getEmitHelpers(sourceFile);
693+
const impliedModuleKind = getImpliedNodeFormatForEmitWorker(sourceFile, compilerOptions);
694+
const helpers = getImportedHelpers(sourceFile);
695+
if (
696+
(moduleKind >= ModuleKind.ES2015 && moduleKind <= ModuleKind.ESNext) ||
697+
impliedModuleKind === ModuleKind.ESNext ||
698+
impliedModuleKind === undefined && moduleKind === ModuleKind.Preserve
699+
) {
700+
// When we emit as an ES module, generate an `import` declaration that uses named imports for helpers.
701+
// If we cannot determine the implied module kind under `module: preserve` we assume ESM.
696702
if (helpers) {
697703
const helperNames: string[] = [];
698704
for (const helper of helpers) {
699-
if (!helper.scoped) {
700-
const importName = helper.importName;
701-
if (importName) {
702-
pushIfUnique(helperNames, importName);
703-
}
705+
const importName = helper.importName;
706+
if (importName) {
707+
pushIfUnique(helperNames, importName);
704708
}
705709
}
706710
if (some(helperNames)) {
707711
helperNames.sort(compareStringsCaseSensitive);
708712
// Alias the imports if the names are used somewhere in the file.
709713
// NOTE: We don't need to care about global import collisions as this is a module.
710-
namedBindings = nodeFactory.createNamedImports(
714+
const namedBindings = nodeFactory.createNamedImports(
711715
map(helperNames, name =>
712716
isFileLevelUniqueName(sourceFile, name)
713717
? nodeFactory.createImportSpecifier(/*isTypeOnly*/ false, /*propertyName*/ undefined, nodeFactory.createIdentifier(name))
@@ -716,55 +720,53 @@ export function createExternalHelpersImportDeclarationIfNeeded(nodeFactory: Node
716720
const parseNode = getOriginalNode(sourceFile, isSourceFile);
717721
const emitNode = getOrCreateEmitNode(parseNode);
718722
emitNode.externalHelpers = true;
723+
724+
const externalHelpersImportDeclaration = nodeFactory.createImportDeclaration(
725+
/*modifiers*/ undefined,
726+
nodeFactory.createImportClause(/*isTypeOnly*/ false, /*name*/ undefined, namedBindings),
727+
nodeFactory.createStringLiteral(externalHelpersModuleNameText),
728+
/*attributes*/ undefined,
729+
);
730+
addInternalEmitFlags(externalHelpersImportDeclaration, InternalEmitFlags.NeverApplyImportHelper);
731+
return externalHelpersImportDeclaration;
719732
}
720733
}
721734
}
722735
else {
723-
// use a namespace import
724-
const externalHelpersModuleName = getOrCreateExternalHelpersModuleNameIfNeeded(nodeFactory, sourceFile, compilerOptions, hasExportStarsToExportValues, hasImportStar || hasImportDefault);
736+
// When we emit to a non-ES module, generate a synthetic `import tslib = require("tslib")` to be further transformed.
737+
const externalHelpersModuleName = getOrCreateExternalHelpersModuleNameIfNeeded(nodeFactory, sourceFile, compilerOptions, helpers, hasExportStarsToExportValues, hasImportStar || hasImportDefault);
725738
if (externalHelpersModuleName) {
726-
namedBindings = nodeFactory.createNamespaceImport(externalHelpersModuleName);
739+
const externalHelpersImportDeclaration = nodeFactory.createImportEqualsDeclaration(
740+
/*modifiers*/ undefined,
741+
/*isTypeOnly*/ false,
742+
externalHelpersModuleName,
743+
nodeFactory.createExternalModuleReference(nodeFactory.createStringLiteral(externalHelpersModuleNameText)),
744+
);
745+
addInternalEmitFlags(externalHelpersImportDeclaration, InternalEmitFlags.NeverApplyImportHelper);
746+
return externalHelpersImportDeclaration;
727747
}
728748
}
729-
if (namedBindings) {
730-
const externalHelpersImportDeclaration = nodeFactory.createImportDeclaration(
731-
/*modifiers*/ undefined,
732-
nodeFactory.createImportClause(/*isTypeOnly*/ false, /*name*/ undefined, namedBindings),
733-
nodeFactory.createStringLiteral(externalHelpersModuleNameText),
734-
/*attributes*/ undefined,
735-
);
736-
addInternalEmitFlags(externalHelpersImportDeclaration, InternalEmitFlags.NeverApplyImportHelper);
737-
return externalHelpersImportDeclaration;
738-
}
739749
}
740750
}
741751

742-
function getOrCreateExternalHelpersModuleNameIfNeeded(factory: NodeFactory, node: SourceFile, compilerOptions: CompilerOptions, hasExportStarsToExportValues?: boolean, hasImportStarOrImportDefault?: boolean) {
743-
if (compilerOptions.importHelpers && isEffectiveExternalModule(node, compilerOptions)) {
744-
const externalHelpersModuleName = getExternalHelpersModuleName(node);
745-
if (externalHelpersModuleName) {
746-
return externalHelpersModuleName;
747-
}
752+
function getImportedHelpers(sourceFile: SourceFile) {
753+
return filter(getEmitHelpers(sourceFile), helper => !helper.scoped);
754+
}
755+
756+
function getOrCreateExternalHelpersModuleNameIfNeeded(factory: NodeFactory, node: SourceFile, compilerOptions: CompilerOptions, helpers: UnscopedEmitHelper[] | undefined, hasExportStarsToExportValues?: boolean, hasImportStarOrImportDefault?: boolean) {
757+
const externalHelpersModuleName = getExternalHelpersModuleName(node);
758+
if (externalHelpersModuleName) {
759+
return externalHelpersModuleName;
760+
}
748761

749-
let create = (hasExportStarsToExportValues || (getESModuleInterop(compilerOptions) && hasImportStarOrImportDefault))
762+
const create = some(helpers)
763+
|| (hasExportStarsToExportValues || (getESModuleInterop(compilerOptions) && hasImportStarOrImportDefault))
750764
&& getEmitModuleFormatOfFileWorker(node, compilerOptions) < ModuleKind.System;
751-
if (!create) {
752-
const helpers = getEmitHelpers(node);
753-
if (helpers) {
754-
for (const helper of helpers) {
755-
if (!helper.scoped) {
756-
create = true;
757-
break;
758-
}
759-
}
760-
}
761-
}
762765

763-
if (create) {
764-
const parseNode = getOriginalNode(node, isSourceFile);
765-
const emitNode = getOrCreateEmitNode(parseNode);
766-
return emitNode.externalHelpersModuleName || (emitNode.externalHelpersModuleName = factory.createUniqueName(externalHelpersModuleNameText));
767-
}
766+
if (create) {
767+
const parseNode = getOriginalNode(node, isSourceFile);
768+
const emitNode = getOrCreateEmitNode(parseNode);
769+
return emitNode.externalHelpersModuleName || (emitNode.externalHelpersModuleName = factory.createUniqueName(externalHelpersModuleNameText));
768770
}
769771
}
770772

src/compiler/transformers/module/esnextAnd2015.ts

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,10 @@ import {
1616
getEmitFlags,
1717
getEmitModuleKind,
1818
getEmitScriptTarget,
19+
getExternalHelpersModuleName,
1920
getExternalModuleNameLiteral,
2021
getIsolatedModules,
22+
getNodeId,
2123
hasSyntacticModifier,
2224
Identifier,
2325
idText,
@@ -36,6 +38,7 @@ import {
3638
ModuleKind,
3739
Node,
3840
NodeFlags,
41+
NodeId,
3942
ScriptTarget,
4043
setOriginalNode,
4144
setTextRange,
@@ -46,6 +49,7 @@ import {
4649
SyntaxKind,
4750
TransformationContext,
4851
VariableStatement,
52+
visitArray,
4953
visitEachChild,
5054
visitNodes,
5155
VisitResult,
@@ -68,6 +72,7 @@ export function transformECMAScriptModule(context: TransformationContext): (x: S
6872
context.enableEmitNotification(SyntaxKind.SourceFile);
6973
context.enableSubstitution(SyntaxKind.Identifier);
7074

75+
const noSubstitution = new Set<NodeId>();
7176
let helperNameSubstitutions: Map<string, Identifier> | undefined;
7277
let currentSourceFile: SourceFile | undefined;
7378
let importRequireStatements: [ImportDeclaration, VariableStatement] | undefined;
@@ -106,8 +111,7 @@ export function transformECMAScriptModule(context: TransformationContext): (x: S
106111
if (externalHelpersImportDeclaration) {
107112
const statements: Statement[] = [];
108113
const statementOffset = factory.copyPrologue(node.statements, statements);
109-
append(statements, externalHelpersImportDeclaration);
110-
114+
addRange(statements, visitArray([externalHelpersImportDeclaration], visitor, isStatement));
111115
addRange(statements, visitNodes(node.statements, visitor, isStatement, statementOffset));
112116
return factory.updateSourceFile(
113117
node,
@@ -318,7 +322,9 @@ export function transformECMAScriptModule(context: TransformationContext): (x: S
318322
if ((isExternalModule(node) || getIsolatedModules(compilerOptions)) && compilerOptions.importHelpers) {
319323
helperNameSubstitutions = new Map<string, Identifier>();
320324
}
325+
currentSourceFile = node;
321326
previousOnEmitNode(hint, node, emitCallback);
327+
currentSourceFile = undefined;
322328
helperNameSubstitutions = undefined;
323329
}
324330
else {
@@ -338,19 +344,30 @@ export function transformECMAScriptModule(context: TransformationContext): (x: S
338344
*/
339345
function onSubstituteNode(hint: EmitHint, node: Node) {
340346
node = previousOnSubstituteNode(hint, node);
341-
if (helperNameSubstitutions && isIdentifier(node) && getEmitFlags(node) & EmitFlags.HelperName) {
347+
if (node.id && noSubstitution.has(node.id)) {
348+
return node;
349+
}
350+
if (isIdentifier(node) && getEmitFlags(node) & EmitFlags.HelperName) {
342351
return substituteHelperName(node);
343352
}
344353

345354
return node;
346355
}
347356

348357
function substituteHelperName(node: Identifier): Expression {
349-
const name = idText(node);
350-
let substitution = helperNameSubstitutions!.get(name);
351-
if (!substitution) {
352-
helperNameSubstitutions!.set(name, substitution = factory.createUniqueName(name, GeneratedIdentifierFlags.Optimistic | GeneratedIdentifierFlags.FileLevel));
358+
const externalHelpersModuleName = currentSourceFile && getExternalHelpersModuleName(currentSourceFile);
359+
if (externalHelpersModuleName) {
360+
noSubstitution.add(getNodeId(node));
361+
return factory.createPropertyAccessExpression(externalHelpersModuleName, node);
362+
}
363+
if (helperNameSubstitutions) {
364+
const name = idText(node);
365+
let substitution = helperNameSubstitutions.get(name);
366+
if (!substitution) {
367+
helperNameSubstitutions.set(name, substitution = factory.createUniqueName(name, GeneratedIdentifierFlags.Optimistic | GeneratedIdentifierFlags.FileLevel));
368+
}
369+
return substitution;
353370
}
354-
return substitution;
371+
return node;
355372
}
356373
}

src/compiler/transformers/utilities.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ export function getOriginalNodeId(node: Node) {
104104
/** @internal */
105105
export interface ExternalModuleInfo {
106106
externalImports: (ImportDeclaration | ImportEqualsDeclaration | ExportDeclaration)[]; // imports of other external modules
107-
externalHelpersImportDeclaration: ImportDeclaration | undefined; // import of external helpers
107+
externalHelpersImportDeclaration: ImportDeclaration | ImportEqualsDeclaration | undefined; // import of external helpers
108108
exportSpecifiers: IdentifierNameMap<ExportSpecifier[]>; // file-local export specifiers by name (no reexports)
109109
exportedBindings: Identifier[][]; // exported names of local declarations
110110
exportedNames: ModuleExportName[] | undefined; // all exported names in the module, both local and reexported, excluding the names of locally exported function declarations

tests/baselines/reference/importHelpersVerbatimModuleSyntax.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,9 @@ export = foo;
4545

4646

4747
//// [main.cjs]
48-
import * as tslib_1 from "tslib";
48+
const tslib_1 = require("tslib");
4949
function foo(args) {
50-
const { bar } = args, extraArgs = __rest(args, ["bar"]);
50+
const { bar } = args, extraArgs = tslib_1.__rest(args, ["bar"]);
5151
return extraArgs;
5252
}
5353
module.exports = foo;
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
//// [tests/cases/compiler/modulePreserveImportHelpers.ts] ////
2+
3+
//// [a.mts]
4+
declare var dec: any
5+
6+
@dec()
7+
export class A {}
8+
9+
//// [b.cts]
10+
declare var dec: any
11+
12+
@dec()
13+
class B {}
14+
export {};
15+
16+
//// [c.ts]
17+
declare var dec: any
18+
19+
@dec()
20+
export class C {}
21+
22+
//// [package.json]
23+
{
24+
"type": "module"
25+
}
26+
27+
//// [package.json]
28+
{
29+
"name": "tslib",
30+
"main": "tslib.js",
31+
"types": "tslib.d.ts"
32+
}
33+
34+
//// [tslib.d.ts]
35+
export declare function __esDecorate(...args: any[]): any;
36+
export declare function __runInitializers(...args: any[]): any;
37+
38+
39+
//// [a.mjs]
40+
import { __esDecorate, __runInitializers } from "tslib";
41+
let A = (() => {
42+
let _classDecorators = [dec()];
43+
let _classDescriptor;
44+
let _classExtraInitializers = [];
45+
let _classThis;
46+
var A = class {
47+
static { _classThis = this; }
48+
static {
49+
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0;
50+
__esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
51+
A = _classThis = _classDescriptor.value;
52+
if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
53+
__runInitializers(_classThis, _classExtraInitializers);
54+
}
55+
};
56+
return A = _classThis;
57+
})();
58+
export { A };
59+
//// [b.cjs]
60+
const tslib_1 = require("tslib");
61+
let B = (() => {
62+
let _classDecorators = [dec()];
63+
let _classDescriptor;
64+
let _classExtraInitializers = [];
65+
let _classThis;
66+
var B = class {
67+
static { _classThis = this; }
68+
static {
69+
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0;
70+
tslib_1.__esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
71+
B = _classThis = _classDescriptor.value;
72+
if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
73+
tslib_1.__runInitializers(_classThis, _classExtraInitializers);
74+
}
75+
};
76+
return B = _classThis;
77+
})();
78+
//// [c.js]
79+
import { __esDecorate, __runInitializers } from "tslib";
80+
let C = (() => {
81+
let _classDecorators = [dec()];
82+
let _classDescriptor;
83+
let _classExtraInitializers = [];
84+
let _classThis;
85+
var C = class {
86+
static { _classThis = this; }
87+
static {
88+
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0;
89+
__esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
90+
C = _classThis = _classDescriptor.value;
91+
if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
92+
__runInitializers(_classThis, _classExtraInitializers);
93+
}
94+
};
95+
return C = _classThis;
96+
})();
97+
export { C };

0 commit comments

Comments
 (0)