mirror of
https://github.com/microsoft/TypeScript.git
synced 2025-11-18 17:21:48 +00:00
Support importHelpers with module:preserve (#59852)
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
Reference in New Issue
Block a user