diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index 24174483fdc6d..ab2c9cbac6fd8 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -1100,7 +1100,7 @@ namespace ts { bundleFileInfo.sources.prologues = prologues; } - // Store helpes + // Store helpers const helpers = getHelpersFromBundledSourceFiles(bundle); if (helpers) { if (!bundleFileInfo.sources) bundleFileInfo.sources = {}; diff --git a/src/compiler/factory/emitHelpers.ts b/src/compiler/factory/emitHelpers.ts index 4333272616da6..eff76118bc05b 100644 --- a/src/compiler/factory/emitHelpers.ts +++ b/src/compiler/factory/emitHelpers.ts @@ -897,4 +897,4 @@ namespace ts { && (getEmitFlags(firstSegment.expression) & EmitFlags.HelperName) && firstSegment.expression.escapedText === helperName; } -} \ No newline at end of file +} diff --git a/src/compiler/factory/emitNode.ts b/src/compiler/factory/emitNode.ts index 1da968d027dec..cd1c32b1db85d 100644 --- a/src/compiler/factory/emitNode.ts +++ b/src/compiler/factory/emitNode.ts @@ -193,7 +193,28 @@ namespace ts { */ export function addEmitHelper(node: T, helper: EmitHelper): T { const emitNode = getOrCreateEmitNode(node); - emitNode.helpers = append(emitNode.helpers, helper); + emitNode.helperRequests = append(emitNode.helperRequests, { helper, directlyUsed: true }); + return node; + } + + /** @internal */ + export function addEmitHelperRequests(node: T, requests: EmitHelperRequest[] | undefined): T { + if (some(requests)) { + const emitNode = getOrCreateEmitNode(node); + emitNode.helperRequests = emitNode.helperRequests ?? []; + for (const request of requests) { + const foundRequestIndex: number = findIndex(emitNode.helperRequests, existingRequest => existingRequest.helper === request.helper); + if (foundRequestIndex === -1) { + emitNode.helperRequests = append(emitNode.helperRequests, request); + } + else { + emitNode.helperRequests = replaceElement(emitNode.helperRequests, foundRequestIndex, { + ...emitNode.helperRequests[foundRequestIndex], + directlyUsed: emitNode.helperRequests[foundRequestIndex].directlyUsed || request.directlyUsed + }); + } + } + } return node; } @@ -202,10 +223,7 @@ namespace ts { */ export function addEmitHelpers(node: T, helpers: EmitHelper[] | undefined): T { if (some(helpers)) { - const emitNode = getOrCreateEmitNode(node); - for (const helper of helpers) { - emitNode.helpers = appendIfUnique(emitNode.helpers, helper); - } + return addEmitHelperRequests(node, helpers.map(helper => ({ helper, directlyUsed: true }))); } return node; } @@ -214,9 +232,14 @@ namespace ts { * Removes an EmitHelper from a node. */ export function removeEmitHelper(node: Node, helper: EmitHelper): boolean { - const helpers = node.emitNode?.helpers; - if (helpers) { - return orderedRemoveItem(helpers, helper); + const helperRequests = node.emitNode?.helperRequests; + if (helperRequests) { + const foundRequestIndex = findIndex(helperRequests, request => request.helper === helper); + if (foundRequestIndex === -1) { + return false; + } + orderedRemoveItemAt(helperRequests, foundRequestIndex); + return true; } return false; } @@ -224,8 +247,8 @@ namespace ts { /** * Gets the EmitHelpers of a node. */ - export function getEmitHelpers(node: Node): EmitHelper[] | undefined { - return node.emitNode?.helpers; + export function getEmitHelpers(node: Node, directlyUsedOnly = false): EmitHelper[] | undefined { + return node.emitNode?.helperRequests?.filter(request => !directlyUsedOnly || request.directlyUsed).map(request => request.helper); } /** @@ -233,24 +256,34 @@ namespace ts { */ export function moveEmitHelpers(source: Node, target: Node, predicate: (helper: EmitHelper) => boolean) { const sourceEmitNode = source.emitNode; - const sourceEmitHelpers = sourceEmitNode && sourceEmitNode.helpers; - if (!some(sourceEmitHelpers)) return; + const sourceEmitHelperRequests = sourceEmitNode && sourceEmitNode.helperRequests; + if (!some(sourceEmitHelperRequests)) return; const targetEmitNode = getOrCreateEmitNode(target); - let helpersRemoved = 0; - for (let i = 0; i < sourceEmitHelpers.length; i++) { - const helper = sourceEmitHelpers[i]; - if (predicate(helper)) { - helpersRemoved++; - targetEmitNode.helpers = appendIfUnique(targetEmitNode.helpers, helper); + targetEmitNode.helperRequests = targetEmitNode.helperRequests ?? []; + let requestsRemoved = 0; + for (let i = 0; i < sourceEmitHelperRequests.length; i++) { + const request = sourceEmitHelperRequests[i]; + if (predicate(request.helper)) { + requestsRemoved++; + const foundRequestIndex: number = findIndex(targetEmitNode.helperRequests, targetRequest => request.helper === targetRequest.helper); + if (foundRequestIndex === -1) { + targetEmitNode.helperRequests = append(targetEmitNode.helperRequests, request); + } + else { + targetEmitNode.helperRequests = replaceElement(targetEmitNode.helperRequests, foundRequestIndex, { + ...targetEmitNode.helperRequests[foundRequestIndex], + directlyUsed: request.directlyUsed || targetEmitNode.helperRequests[foundRequestIndex].directlyUsed + }); + } } - else if (helpersRemoved > 0) { - sourceEmitHelpers[i - helpersRemoved] = helper; + else if (requestsRemoved > 0) { + sourceEmitHelperRequests[i - requestsRemoved] = request; } } - if (helpersRemoved > 0) { - sourceEmitHelpers.length -= helpersRemoved; + if (requestsRemoved > 0) { + sourceEmitHelperRequests.length -= requestsRemoved; } } @@ -259,4 +292,4 @@ namespace ts { getOrCreateEmitNode(node).flags |= EmitFlags.IgnoreSourceNewlines; return node; } -} \ No newline at end of file +} diff --git a/src/compiler/factory/nodeFactory.ts b/src/compiler/factory/nodeFactory.ts index 5f3a19a8949ac..e1e1f07aaf688 100644 --- a/src/compiler/factory/nodeFactory.ts +++ b/src/compiler/factory/nodeFactory.ts @@ -6377,7 +6377,7 @@ namespace ts { sourceMapRange, tokenSourceMapRanges, constantValue, - helpers, + helperRequests, startsOnNewLine, } = sourceEmitNode; if (!destEmitNode) destEmitNode = {} as EmitNode; @@ -6389,9 +6389,19 @@ namespace ts { if (sourceMapRange) destEmitNode.sourceMapRange = sourceMapRange; if (tokenSourceMapRanges) destEmitNode.tokenSourceMapRanges = mergeTokenSourceMapRanges(tokenSourceMapRanges, destEmitNode.tokenSourceMapRanges!); if (constantValue !== undefined) destEmitNode.constantValue = constantValue; - if (helpers) { - for (const helper of helpers) { - destEmitNode.helpers = appendIfUnique(destEmitNode.helpers, helper); + if (some(helperRequests)) { + destEmitNode.helperRequests = destEmitNode.helperRequests ?? []; + for (const request of helperRequests) { + const foundRequestIndex: number = findIndex(destEmitNode.helperRequests, destRequest => request.helper === destRequest.helper); + if (foundRequestIndex === -1) { + destEmitNode.helperRequests = append(destEmitNode.helperRequests, request); + } + else { + destEmitNode.helperRequests = replaceElement(destEmitNode.helperRequests, foundRequestIndex, { + ...destEmitNode.helperRequests[foundRequestIndex], + directlyUsed: request.directlyUsed || destEmitNode.helperRequests[foundRequestIndex].directlyUsed + }); + } } } if (startsOnNewLine !== undefined) destEmitNode.startsOnNewLine = startsOnNewLine; diff --git a/src/compiler/factory/utilities.ts b/src/compiler/factory/utilities.ts index 8b18c3152e3ff..14ef8e2ce6860 100644 --- a/src/compiler/factory/utilities.ts +++ b/src/compiler/factory/utilities.ts @@ -407,7 +407,7 @@ namespace ts { const moduleKind = getEmitModuleKind(compilerOptions); if (moduleKind >= ModuleKind.ES2015 && moduleKind <= ModuleKind.ESNext) { // use named imports - const helpers = getEmitHelpers(sourceFile); + const helpers = getEmitHelpers(sourceFile, /*directlyUsedOnly*/ true); if (helpers) { const helperNames: string[] = []; for (const helper of helpers) { diff --git a/src/compiler/transformer.ts b/src/compiler/transformer.ts index efca6f7f187d3..ca80759fdd802 100644 --- a/src/compiler/transformer.ts +++ b/src/compiler/transformer.ts @@ -140,7 +140,7 @@ namespace ts { * @param host The emit host object used to interact with the file system. * @param options Compiler options to surface in the `TransformationContext`. * @param nodes An array of nodes to transform. - * @param transforms An array of `TransformerFactory` callbacks. + * @param transformers An array of `TransformerFactory` callbacks. * @param allowDtsFiles A value indicating whether to allow the transformation of .d.ts files. */ export function transformNodes(resolver: EmitResolver | undefined, host: EmitHost | undefined, factory: NodeFactory, options: CompilerOptions, nodes: readonly T[], transformers: readonly TransformerFactory[], allowDtsFiles: boolean): TransformationResult { @@ -155,7 +155,7 @@ namespace ts { let lexicalEnvironmentFlagsStack: LexicalEnvironmentFlags[] = []; let lexicalEnvironmentStackOffset = 0; let lexicalEnvironmentSuspended = false; - let emitHelpers: EmitHelper[] | undefined; + let emitHelperRequests: EmitHelperRequest[] | undefined; let onSubstituteNode: TransformationContext["onSubstituteNode"] = noEmitSubstitution; let onEmitNode: TransformationContext["onEmitNode"] = noEmitNotification; let state = TransformationState.Uninitialized; @@ -180,6 +180,7 @@ namespace ts { addInitializationStatement, requestEmitHelper, readEmitHelpers, + readEmitHelperRequests, enableSubstitution, enableEmitNotification, isSubstitutionEnabled, @@ -469,23 +470,31 @@ namespace ts { return lexicalEnvironmentFlags; } - function requestEmitHelper(helper: EmitHelper): void { + function requestEmitHelper(helper: EmitHelper, directlyUsed = true): void { Debug.assert(state > TransformationState.Uninitialized, "Cannot modify the transformation context during initialization."); Debug.assert(state < TransformationState.Completed, "Cannot modify the transformation context after transformation has completed."); Debug.assert(!helper.scoped, "Cannot request a scoped emit helper."); if (helper.dependencies) { for (const h of helper.dependencies) { - requestEmitHelper(h); + requestEmitHelper(h, /*directlyUsed*/ false); } } - emitHelpers = append(emitHelpers, helper); + emitHelperRequests = append(emitHelperRequests, { helper, directlyUsed }); + } + + function readEmitHelperRequests(): EmitHelperRequest[] | undefined { + Debug.assert(state > TransformationState.Uninitialized, "Cannot modify the transformation context during initialization."); + Debug.assert(state < TransformationState.Completed, "Cannot modify the transformation context after transformation has completed."); + const helperRequests = emitHelperRequests; + emitHelperRequests = undefined; + return helperRequests; } function readEmitHelpers(): EmitHelper[] | undefined { Debug.assert(state > TransformationState.Uninitialized, "Cannot modify the transformation context during initialization."); Debug.assert(state < TransformationState.Completed, "Cannot modify the transformation context after transformation has completed."); - const helpers = emitHelpers; - emitHelpers = undefined; + const helpers = emitHelperRequests?.map(({helper}) => helper); + emitHelperRequests = undefined; return helpers; } @@ -503,7 +512,7 @@ namespace ts { lexicalEnvironmentFunctionDeclarationsStack = undefined!; onSubstituteNode = undefined!; onEmitNode = undefined!; - emitHelpers = undefined; + emitHelperRequests = undefined; // Prevent further use of the transformation result. state = TransformationState.Disposed; @@ -530,6 +539,7 @@ namespace ts { onEmitNode: noop, onSubstituteNode: notImplemented, readEmitHelpers: notImplemented, + readEmitHelperRequests: notImplemented, requestEmitHelper: noop, resumeLexicalEnvironment: noop, startLexicalEnvironment: noop, diff --git a/src/compiler/transformers/classFields.ts b/src/compiler/transformers/classFields.ts index f9e539770dcdc..a015e5eaab233 100644 --- a/src/compiler/transformers/classFields.ts +++ b/src/compiler/transformers/classFields.ts @@ -75,7 +75,7 @@ namespace ts { return node; } const visited = visitEachChild(node, visitor, context); - addEmitHelpers(visited, context.readEmitHelpers()); + addEmitHelperRequests(visited, context.readEmitHelperRequests()); return visited; } diff --git a/src/compiler/transformers/es2015.ts b/src/compiler/transformers/es2015.ts index 6f28a651190e7..f14ed4c3edb4c 100644 --- a/src/compiler/transformers/es2015.ts +++ b/src/compiler/transformers/es2015.ts @@ -293,7 +293,7 @@ namespace ts { currentText = node.text; const visited = visitSourceFile(node); - addEmitHelpers(visited, context.readEmitHelpers()); + addEmitHelperRequests(visited, context.readEmitHelperRequests()); currentSourceFile = undefined!; currentText = undefined!; diff --git a/src/compiler/transformers/es2017.ts b/src/compiler/transformers/es2017.ts index 6c3c8c1332acf..4ebaaa9c003d2 100644 --- a/src/compiler/transformers/es2017.ts +++ b/src/compiler/transformers/es2017.ts @@ -68,7 +68,7 @@ namespace ts { setContextFlag(ContextFlags.NonTopLevel, false); setContextFlag(ContextFlags.HasLexicalThis, !isEffectiveStrictModeSourceFile(node, compilerOptions)); const visited = visitEachChild(node, visitor, context); - addEmitHelpers(visited, context.readEmitHelpers()); + addEmitHelperRequests(visited, context.readEmitHelperRequests()); return visited; } diff --git a/src/compiler/transformers/es2018.ts b/src/compiler/transformers/es2018.ts index 41fcc6c25d9b9..d89ec9bfcd285 100644 --- a/src/compiler/transformers/es2018.ts +++ b/src/compiler/transformers/es2018.ts @@ -111,7 +111,7 @@ namespace ts { currentSourceFile = node; const visited = visitSourceFile(node); - addEmitHelpers(visited, context.readEmitHelpers()); + addEmitHelperRequests(visited, context.readEmitHelperRequests()); currentSourceFile = undefined!; taggedTemplateStringDeclarations = undefined!; diff --git a/src/compiler/transformers/generators.ts b/src/compiler/transformers/generators.ts index 6ae08275e3a02..6c7c0c8553b20 100644 --- a/src/compiler/transformers/generators.ts +++ b/src/compiler/transformers/generators.ts @@ -301,7 +301,7 @@ namespace ts { const visited = visitEachChild(node, visitor, context); - addEmitHelpers(visited, context.readEmitHelpers()); + addEmitHelperRequests(visited, context.readEmitHelperRequests()); return visited; } diff --git a/src/compiler/transformers/jsx.ts b/src/compiler/transformers/jsx.ts index 9814f29b2a28f..8d13b4735e115 100644 --- a/src/compiler/transformers/jsx.ts +++ b/src/compiler/transformers/jsx.ts @@ -76,7 +76,7 @@ namespace ts { currentFileState = {}; currentFileState.importSpecifier = getJSXImplicitImportBase(compilerOptions, node); let visited = visitEachChild(node, visitor, context); - addEmitHelpers(visited, context.readEmitHelpers()); + addEmitHelperRequests(visited, context.readEmitHelperRequests()); let statements: readonly Statement[] = visited.statements; if (currentFileState.filenameDeclaration) { statements = insertStatementAfterCustomPrologue(statements.slice(), factory.createVariableStatement(/*modifiers*/ undefined, factory.createVariableDeclarationList([currentFileState.filenameDeclaration], NodeFlags.Const))); diff --git a/src/compiler/transformers/module/module.ts b/src/compiler/transformers/module/module.ts index d2f4917d50b7b..9355414f250d2 100644 --- a/src/compiler/transformers/module/module.ts +++ b/src/compiler/transformers/module/module.ts @@ -120,7 +120,7 @@ namespace ts { insertStatementsAfterStandardPrologue(statements, endLexicalEnvironment()); const updated = factory.updateSourceFile(node, setTextRange(factory.createNodeArray(statements), node.statements)); - addEmitHelpers(updated, context.readEmitHelpers()); + addEmitHelperRequests(updated, context.readEmitHelperRequests()); return updated; } @@ -207,7 +207,7 @@ namespace ts { ) ); - addEmitHelpers(updated, context.readEmitHelpers()); + addEmitHelperRequests(updated, context.readEmitHelperRequests()); return updated; } @@ -347,7 +347,7 @@ namespace ts { ) ); - addEmitHelpers(updated, context.readEmitHelpers()); + addEmitHelperRequests(updated, context.readEmitHelperRequests()); return updated; } diff --git a/src/compiler/transformers/ts.ts b/src/compiler/transformers/ts.ts index 9cc9c1c3fc92e..bf4a60f3764c4 100644 --- a/src/compiler/transformers/ts.ts +++ b/src/compiler/transformers/ts.ts @@ -117,7 +117,7 @@ namespace ts { currentSourceFile = node; const visited = saveStateAndInvoke(node, visitSourceFile); - addEmitHelpers(visited, context.readEmitHelpers()); + addEmitHelperRequests(visited, context.readEmitHelperRequests()); currentSourceFile = undefined!; return visited; diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 4352e2f2171e3..dfed7ee945204 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -3297,7 +3297,6 @@ namespace ts { | FlowStart | FlowLabel | FlowAssignment - | FlowCall | FlowCondition | FlowSwitchClause | FlowArrayMutation @@ -6539,7 +6538,7 @@ namespace ts { 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 + helperRequests?: EmitHelperRequest[]; // Emit helper requests for the node startsOnNewLine?: boolean; // If the node should begin on a new line } @@ -6586,6 +6585,12 @@ namespace ts { readonly dependencies?: EmitHelper[] } + /*@internal*/ + export interface EmitHelperRequest { + readonly helper: EmitHelper; + readonly directlyUsed: boolean; + } + export interface UnscopedEmitHelper extends EmitHelper { readonly scoped: false; // Indicates whether the helper MUST be emitted in the current scope. /* @internal */ @@ -7510,6 +7515,8 @@ namespace ts { /*@internal*/ getEmitHost(): EmitHost; /*@internal*/ getEmitHelperFactory(): EmitHelperFactory; + /*@internal*/ readEmitHelperRequests(): EmitHelperRequest[] | undefined; + /** Records a request for a non-scoped emit helper in the current context. */ requestEmitHelper(helper: EmitHelper): void; diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index be9c82b1e1d9f..da3c1d3f5876d 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -1872,7 +1872,7 @@ declare namespace ts { Label = 12, Condition = 96 } - export type FlowNode = FlowStart | FlowLabel | FlowAssignment | FlowCall | FlowCondition | FlowSwitchClause | FlowArrayMutation | FlowCall | FlowReduceLabel; + export type FlowNode = FlowStart | FlowLabel | FlowAssignment | FlowCondition | FlowSwitchClause | FlowArrayMutation | FlowCall | FlowReduceLabel; export interface FlowNodeBase { flags: FlowFlags; id?: number; @@ -4331,7 +4331,7 @@ declare namespace ts { /** * Gets the EmitHelpers of a node. */ - function getEmitHelpers(node: Node): EmitHelper[] | undefined; + function getEmitHelpers(node: Node, directlyUsedOnly?: boolean): EmitHelper[] | undefined; /** * Moves matching emit helpers from a source node to a target node. */ diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index f8f86d5b88fb2..2bd55ae3b51a0 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -1872,7 +1872,7 @@ declare namespace ts { Label = 12, Condition = 96 } - export type FlowNode = FlowStart | FlowLabel | FlowAssignment | FlowCall | FlowCondition | FlowSwitchClause | FlowArrayMutation | FlowCall | FlowReduceLabel; + export type FlowNode = FlowStart | FlowLabel | FlowAssignment | FlowCondition | FlowSwitchClause | FlowArrayMutation | FlowCall | FlowReduceLabel; export interface FlowNodeBase { flags: FlowFlags; id?: number; @@ -4331,7 +4331,7 @@ declare namespace ts { /** * Gets the EmitHelpers of a node. */ - function getEmitHelpers(node: Node): EmitHelper[] | undefined; + function getEmitHelpers(node: Node, directlyUsedOnly?: boolean): EmitHelper[] | undefined; /** * Moves matching emit helpers from a source node to a target node. */