Support importHelpers with module:preserve (#59852)

This commit is contained in:
Ron Buckton
2024-09-05 17:48:39 -04:00
committed by GitHub
parent d514dab3f6
commit f39fe7dbe6
8 changed files with 309 additions and 58 deletions
+49 -47
View File
@@ -40,6 +40,7 @@ import {
Expression,
ExpressionStatement,
externalHelpersModuleNameText,
filter,
first,
firstOrUndefined,
ForInitializer,
@@ -131,7 +132,6 @@ import {
MultiplicativeOperator,
MultiplicativeOperatorOrHigher,
Mutable,
NamedImportBindings,
Node,
NodeArray,
NodeFactory,
@@ -173,6 +173,7 @@ import {
Token,
TransformFlags,
TypeNode,
UnscopedEmitHelper,
WrappedExpression,
} from "../_namespaces/ts.js";
@@ -688,26 +689,29 @@ export function hasRecordedExternalHelpers(sourceFile: SourceFile) {
/** @internal */
export function createExternalHelpersImportDeclarationIfNeeded(nodeFactory: NodeFactory, helperFactory: EmitHelperFactory, 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) || getImpliedNodeFormatForEmitWorker(sourceFile, compilerOptions) === ModuleKind.ESNext) {
// use named imports
const helpers = getEmitHelpers(sourceFile);
const impliedModuleKind = getImpliedNodeFormatForEmitWorker(sourceFile, compilerOptions);
const helpers = getImportedHelpers(sourceFile);
if (
(moduleKind >= ModuleKind.ES2015 && moduleKind <= ModuleKind.ESNext) ||
impliedModuleKind === ModuleKind.ESNext ||
impliedModuleKind === undefined && moduleKind === ModuleKind.Preserve
) {
// When we emit as an ES module, generate an `import` declaration that uses named imports for helpers.
// If we cannot determine the implied module kind under `module: preserve` we assume ESM.
if (helpers) {
const helperNames: string[] = [];
for (const helper of helpers) {
if (!helper.scoped) {
const importName = helper.importName;
if (importName) {
pushIfUnique(helperNames, importName);
}
const importName = helper.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 = nodeFactory.createNamedImports(
const namedBindings = nodeFactory.createNamedImports(
map(helperNames, name =>
isFileLevelUniqueName(sourceFile, name)
? nodeFactory.createImportSpecifier(/*isTypeOnly*/ false, /*propertyName*/ undefined, nodeFactory.createIdentifier(name))
@@ -716,55 +720,53 @@ export function createExternalHelpersImportDeclarationIfNeeded(nodeFactory: Node
const parseNode = getOriginalNode(sourceFile, isSourceFile);
const emitNode = getOrCreateEmitNode(parseNode);
emitNode.externalHelpers = true;
const externalHelpersImportDeclaration = nodeFactory.createImportDeclaration(
/*modifiers*/ undefined,
nodeFactory.createImportClause(/*isTypeOnly*/ false, /*name*/ undefined, namedBindings),
nodeFactory.createStringLiteral(externalHelpersModuleNameText),
/*attributes*/ undefined,
);
addInternalEmitFlags(externalHelpersImportDeclaration, InternalEmitFlags.NeverApplyImportHelper);
return externalHelpersImportDeclaration;
}
}
}
else {
// use a namespace import
const externalHelpersModuleName = getOrCreateExternalHelpersModuleNameIfNeeded(nodeFactory, sourceFile, compilerOptions, hasExportStarsToExportValues, hasImportStar || hasImportDefault);
// When we emit to a non-ES module, generate a synthetic `import tslib = require("tslib")` to be further transformed.
const externalHelpersModuleName = getOrCreateExternalHelpersModuleNameIfNeeded(nodeFactory, sourceFile, compilerOptions, helpers, hasExportStarsToExportValues, hasImportStar || hasImportDefault);
if (externalHelpersModuleName) {
namedBindings = nodeFactory.createNamespaceImport(externalHelpersModuleName);
const externalHelpersImportDeclaration = nodeFactory.createImportEqualsDeclaration(
/*modifiers*/ undefined,
/*isTypeOnly*/ false,
externalHelpersModuleName,
nodeFactory.createExternalModuleReference(nodeFactory.createStringLiteral(externalHelpersModuleNameText)),
);
addInternalEmitFlags(externalHelpersImportDeclaration, InternalEmitFlags.NeverApplyImportHelper);
return externalHelpersImportDeclaration;
}
}
if (namedBindings) {
const externalHelpersImportDeclaration = nodeFactory.createImportDeclaration(
/*modifiers*/ undefined,
nodeFactory.createImportClause(/*isTypeOnly*/ false, /*name*/ undefined, namedBindings),
nodeFactory.createStringLiteral(externalHelpersModuleNameText),
/*attributes*/ undefined,
);
addInternalEmitFlags(externalHelpersImportDeclaration, InternalEmitFlags.NeverApplyImportHelper);
return externalHelpersImportDeclaration;
}
}
}
function getOrCreateExternalHelpersModuleNameIfNeeded(factory: NodeFactory, node: SourceFile, compilerOptions: CompilerOptions, hasExportStarsToExportValues?: boolean, hasImportStarOrImportDefault?: boolean) {
if (compilerOptions.importHelpers && isEffectiveExternalModule(node, compilerOptions)) {
const externalHelpersModuleName = getExternalHelpersModuleName(node);
if (externalHelpersModuleName) {
return externalHelpersModuleName;
}
function getImportedHelpers(sourceFile: SourceFile) {
return filter(getEmitHelpers(sourceFile), helper => !helper.scoped);
}
let create = (hasExportStarsToExportValues || (getESModuleInterop(compilerOptions) && hasImportStarOrImportDefault))
function getOrCreateExternalHelpersModuleNameIfNeeded(factory: NodeFactory, node: SourceFile, compilerOptions: CompilerOptions, helpers: UnscopedEmitHelper[] | undefined, hasExportStarsToExportValues?: boolean, hasImportStarOrImportDefault?: boolean) {
const externalHelpersModuleName = getExternalHelpersModuleName(node);
if (externalHelpersModuleName) {
return externalHelpersModuleName;
}
const create = some(helpers)
|| (hasExportStarsToExportValues || (getESModuleInterop(compilerOptions) && hasImportStarOrImportDefault))
&& getEmitModuleFormatOfFileWorker(node, compilerOptions) < ModuleKind.System;
if (!create) {
const helpers = getEmitHelpers(node);
if (helpers) {
for (const helper of helpers) {
if (!helper.scoped) {
create = true;
break;
}
}
}
}
if (create) {
const parseNode = getOriginalNode(node, isSourceFile);
const emitNode = getOrCreateEmitNode(parseNode);
return emitNode.externalHelpersModuleName || (emitNode.externalHelpersModuleName = factory.createUniqueName(externalHelpersModuleNameText));
}
if (create) {
const parseNode = getOriginalNode(node, isSourceFile);
const emitNode = getOrCreateEmitNode(parseNode);
return emitNode.externalHelpersModuleName || (emitNode.externalHelpersModuleName = factory.createUniqueName(externalHelpersModuleNameText));
}
}
@@ -16,8 +16,10 @@ import {
getEmitFlags,
getEmitModuleKind,
getEmitScriptTarget,
getExternalHelpersModuleName,
getExternalModuleNameLiteral,
getIsolatedModules,
getNodeId,
hasSyntacticModifier,
Identifier,
idText,
@@ -36,6 +38,7 @@ import {
ModuleKind,
Node,
NodeFlags,
NodeId,
ScriptTarget,
setOriginalNode,
setTextRange,
@@ -46,6 +49,7 @@ import {
SyntaxKind,
TransformationContext,
VariableStatement,
visitArray,
visitEachChild,
visitNodes,
VisitResult,
@@ -68,6 +72,7 @@ export function transformECMAScriptModule(context: TransformationContext): (x: S
context.enableEmitNotification(SyntaxKind.SourceFile);
context.enableSubstitution(SyntaxKind.Identifier);
const noSubstitution = new Set<NodeId>();
let helperNameSubstitutions: Map<string, Identifier> | undefined;
let currentSourceFile: SourceFile | undefined;
let importRequireStatements: [ImportDeclaration, VariableStatement] | undefined;
@@ -106,8 +111,7 @@ export function transformECMAScriptModule(context: TransformationContext): (x: S
if (externalHelpersImportDeclaration) {
const statements: Statement[] = [];
const statementOffset = factory.copyPrologue(node.statements, statements);
append(statements, externalHelpersImportDeclaration);
addRange(statements, visitArray([externalHelpersImportDeclaration], visitor, isStatement));
addRange(statements, visitNodes(node.statements, visitor, isStatement, statementOffset));
return factory.updateSourceFile(
node,
@@ -318,7 +322,9 @@ export function transformECMAScriptModule(context: TransformationContext): (x: S
if ((isExternalModule(node) || getIsolatedModules(compilerOptions)) && compilerOptions.importHelpers) {
helperNameSubstitutions = new Map<string, Identifier>();
}
currentSourceFile = node;
previousOnEmitNode(hint, node, emitCallback);
currentSourceFile = undefined;
helperNameSubstitutions = undefined;
}
else {
@@ -338,7 +344,10 @@ export function transformECMAScriptModule(context: TransformationContext): (x: S
*/
function onSubstituteNode(hint: EmitHint, node: Node) {
node = previousOnSubstituteNode(hint, node);
if (helperNameSubstitutions && isIdentifier(node) && getEmitFlags(node) & EmitFlags.HelperName) {
if (node.id && noSubstitution.has(node.id)) {
return node;
}
if (isIdentifier(node) && getEmitFlags(node) & EmitFlags.HelperName) {
return substituteHelperName(node);
}
@@ -346,11 +355,19 @@ export function transformECMAScriptModule(context: TransformationContext): (x: S
}
function substituteHelperName(node: Identifier): Expression {
const name = idText(node);
let substitution = helperNameSubstitutions!.get(name);
if (!substitution) {
helperNameSubstitutions!.set(name, substitution = factory.createUniqueName(name, GeneratedIdentifierFlags.Optimistic | GeneratedIdentifierFlags.FileLevel));
const externalHelpersModuleName = currentSourceFile && getExternalHelpersModuleName(currentSourceFile);
if (externalHelpersModuleName) {
noSubstitution.add(getNodeId(node));
return factory.createPropertyAccessExpression(externalHelpersModuleName, node);
}
return substitution;
if (helperNameSubstitutions) {
const name = idText(node);
let substitution = helperNameSubstitutions.get(name);
if (!substitution) {
helperNameSubstitutions.set(name, substitution = factory.createUniqueName(name, GeneratedIdentifierFlags.Optimistic | GeneratedIdentifierFlags.FileLevel));
}
return substitution;
}
return node;
}
}
+1 -1
View File
@@ -104,7 +104,7 @@ export function getOriginalNodeId(node: Node) {
/** @internal */
export interface ExternalModuleInfo {
externalImports: (ImportDeclaration | ImportEqualsDeclaration | ExportDeclaration)[]; // imports of other external modules
externalHelpersImportDeclaration: ImportDeclaration | undefined; // import of external helpers
externalHelpersImportDeclaration: ImportDeclaration | ImportEqualsDeclaration | undefined; // import of external helpers
exportSpecifiers: IdentifierNameMap<ExportSpecifier[]>; // file-local export specifiers by name (no reexports)
exportedBindings: Identifier[][]; // exported names of local declarations
exportedNames: ModuleExportName[] | undefined; // all exported names in the module, both local and reexported, excluding the names of locally exported function declarations
@@ -45,9 +45,9 @@ export = foo;
//// [main.cjs]
import * as tslib_1 from "tslib";
const tslib_1 = require("tslib");
function foo(args) {
const { bar } = args, extraArgs = __rest(args, ["bar"]);
const { bar } = args, extraArgs = tslib_1.__rest(args, ["bar"]);
return extraArgs;
}
module.exports = foo;
@@ -0,0 +1,97 @@
//// [tests/cases/compiler/modulePreserveImportHelpers.ts] ////
//// [a.mts]
declare var dec: any
@dec()
export class A {}
//// [b.cts]
declare var dec: any
@dec()
class B {}
export {};
//// [c.ts]
declare var dec: any
@dec()
export class C {}
//// [package.json]
{
"type": "module"
}
//// [package.json]
{
"name": "tslib",
"main": "tslib.js",
"types": "tslib.d.ts"
}
//// [tslib.d.ts]
export declare function __esDecorate(...args: any[]): any;
export declare function __runInitializers(...args: any[]): any;
//// [a.mjs]
import { __esDecorate, __runInitializers } from "tslib";
let A = (() => {
let _classDecorators = [dec()];
let _classDescriptor;
let _classExtraInitializers = [];
let _classThis;
var A = class {
static { _classThis = this; }
static {
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0;
__esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
A = _classThis = _classDescriptor.value;
if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
__runInitializers(_classThis, _classExtraInitializers);
}
};
return A = _classThis;
})();
export { A };
//// [b.cjs]
const tslib_1 = require("tslib");
let B = (() => {
let _classDecorators = [dec()];
let _classDescriptor;
let _classExtraInitializers = [];
let _classThis;
var B = class {
static { _classThis = this; }
static {
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0;
tslib_1.__esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
B = _classThis = _classDescriptor.value;
if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
tslib_1.__runInitializers(_classThis, _classExtraInitializers);
}
};
return B = _classThis;
})();
//// [c.js]
import { __esDecorate, __runInitializers } from "tslib";
let C = (() => {
let _classDecorators = [dec()];
let _classDescriptor;
let _classExtraInitializers = [];
let _classThis;
var C = class {
static { _classThis = this; }
static {
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0;
__esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
C = _classThis = _classDescriptor.value;
if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
__runInitializers(_classThis, _classExtraInitializers);
}
};
return C = _classThis;
})();
export { C };
@@ -0,0 +1,43 @@
//// [tests/cases/compiler/modulePreserveImportHelpers.ts] ////
=== /a.mts ===
declare var dec: any
>dec : Symbol(dec, Decl(a.mts, 0, 11))
@dec()
>dec : Symbol(dec, Decl(a.mts, 0, 11))
export class A {}
>A : Symbol(A, Decl(a.mts, 0, 20))
=== /b.cts ===
declare var dec: any
>dec : Symbol(dec, Decl(b.cts, 0, 11))
@dec()
>dec : Symbol(dec, Decl(b.cts, 0, 11))
class B {}
>B : Symbol(B, Decl(b.cts, 0, 20))
export {};
=== /c.ts ===
declare var dec: any
>dec : Symbol(dec, Decl(c.ts, 0, 11))
@dec()
>dec : Symbol(dec, Decl(c.ts, 0, 11))
export class C {}
>C : Symbol(C, Decl(c.ts, 0, 20))
=== /node_modules/tslib/tslib.d.ts ===
export declare function __esDecorate(...args: any[]): any;
>__esDecorate : Symbol(__esDecorate, Decl(tslib.d.ts, --, --))
>args : Symbol(args, Decl(tslib.d.ts, --, --))
export declare function __runInitializers(...args: any[]): any;
>__runInitializers : Symbol(__runInitializers, Decl(tslib.d.ts, --, --))
>args : Symbol(args, Decl(tslib.d.ts, --, --))
@@ -0,0 +1,53 @@
//// [tests/cases/compiler/modulePreserveImportHelpers.ts] ////
=== /a.mts ===
declare var dec: any
>dec : any
@dec()
>dec() : any
>dec : any
export class A {}
>A : A
> : ^
=== /b.cts ===
declare var dec: any
>dec : any
@dec()
>dec() : any
>dec : any
class B {}
>B : B
> : ^
export {};
=== /c.ts ===
declare var dec: any
>dec : any
@dec()
>dec() : any
>dec : any
export class C {}
>C : C
> : ^
=== /node_modules/tslib/tslib.d.ts ===
export declare function __esDecorate(...args: any[]): any;
>__esDecorate : (...args: any[]) => any
> : ^^^^ ^^ ^^^^^
>args : any[]
> : ^^^^^
export declare function __runInitializers(...args: any[]): any;
>__runInitializers : (...args: any[]) => any
> : ^^^^ ^^ ^^^^^
>args : any[]
> : ^^^^^
@@ -0,0 +1,39 @@
// @module: preserve
// @target: es2022
// @importHelpers: true
// @Filename: /a.mts
declare var dec: any
@dec()
export class A {}
// @Filename: /b.cts
declare var dec: any
@dec()
class B {}
export {};
// @Filename: /c.ts
declare var dec: any
@dec()
export class C {}
// @filename: /package.json
{
"type": "module"
}
// @filename: /node_modules/tslib/package.json
{
"name": "tslib",
"main": "tslib.js",
"types": "tslib.d.ts"
}
// @filename: /node_modules/tslib/tslib.d.ts
export declare function __esDecorate(...args: any[]): any;
export declare function __runInitializers(...args: any[]): any;