From 96fd7c6f16f31410dd045727fa703c4c0ea89b31 Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Wed, 7 Feb 2018 20:02:37 -0800 Subject: [PATCH] Reduce stack depth from transforms --- src/compiler/emitter.ts | 79 +++++++++++++++++----- src/compiler/sourcemap.ts | 24 ------- src/compiler/transformer.ts | 73 ++++++++++++++++++-- src/compiler/transformers/es2015.ts | 24 +++++-- src/compiler/transformers/es2017.ts | 34 ++++++---- src/compiler/transformers/es5.ts | 11 ++- src/compiler/transformers/esnext.ts | 29 ++++---- src/compiler/transformers/module/es2015.ts | 20 +++--- src/compiler/transformers/module/module.ts | 24 ++++--- src/compiler/transformers/module/system.ts | 25 +++---- src/compiler/transformers/ts.ts | 44 ++++++++---- src/compiler/types.ts | 8 +++ 12 files changed, 263 insertions(+), 132 deletions(-) diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index 2dc9d127496..0460a4df42b 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -105,7 +105,9 @@ namespace ts { hasGlobalName: resolver.hasGlobalName, // transform hooks - onEmitNode: transform.emitNodeWithNotification, + onEmitNode: transform.useEmitNodeWithNotification ? transform.emitNodeWithNotification : undefined, + onBeforeEmitNode: transform.useEmitNodeWithNotification ? undefined : transform.beforeEmitNode, + onAfterEmitNode: transform.useEmitNodeWithNotification ? undefined : transform.afterEmitNode, substituteNode: transform.substituteNode, // sourcemap hooks @@ -260,11 +262,15 @@ namespace ts { } const enum PipelinePhase { + BeforeEmit, LeadingComments, LeadingSourceMap, Emit, TrailingSourceMap, - TrailingComments + TrailingComments, + AfterEmit, + + Start = BeforeEmit } export function createPrinter(printerOptions: PrinterOptions = {}, handlers: PrintHandlers = {}): Printer { @@ -275,6 +281,8 @@ namespace ts { onEmitSourceMapOfToken, onEmitSourceMapOfPosition, onEmitNode, + onBeforeEmitNode, + onAfterEmitNode, onEmitHelpers, onSetSourceFile, substituteNode, @@ -284,6 +292,8 @@ namespace ts { onAfterEmitToken } = handlers; + Debug.assert(!(onEmitNode && (onBeforeEmitNode || onAfterEmitNode)), "Do not provide 'onEmitNode' of either of 'onBeforeEmitNode' or 'onAfterEmitNode' are provided."); + const newLine = getNewLineCharacter(printerOptions); const comments = createCommentWriter(printerOptions, onEmitSourceMapOfPosition); const { @@ -428,7 +438,13 @@ namespace ts { setSourceFile(sourceFile); } - pipelineEmitWithNotification(hint, node); + // NOTE: this has been manually inlined to reduce overall stack depth + if (onEmitNode) { + onEmitNode(hint, node, pipelineEmit); + } + else { + pipelineEmitWorker(hint, node, PipelinePhase.Start); + } } function setSourceFile(sourceFile: SourceFile) { @@ -456,33 +472,49 @@ namespace ts { } function emit(node: Node | undefined) { - pipelineEmitWithNotification(EmitHint.Unspecified, node); + // NOTE: this has been manually inlined to reduce overall stack depth + if (onEmitNode) { + onEmitNode(EmitHint.Unspecified, node, pipelineEmit); + } + else { + pipelineEmitWorker(EmitHint.Unspecified, node, PipelinePhase.Start); + } } function emitIdentifierName(node: Identifier | undefined) { - pipelineEmitWithNotification(EmitHint.IdentifierName, node); + // NOTE: this has been manually inlined to reduce overall stack depth + if (onEmitNode) { + onEmitNode(EmitHint.IdentifierName, node, pipelineEmit); + } + else { + pipelineEmitWorker(EmitHint.IdentifierName, node, PipelinePhase.Start); + } } function emitExpression(node: Expression | undefined) { - pipelineEmitWithNotification(EmitHint.Expression, node); - } - - function pipelineEmitWithNotification(hint: EmitHint, node: Node) { + // NOTE: this has been manually inlined to reduce overall stack depth if (onEmitNode) { - onEmitNode(hint, node, pipelineEmitWithComments); + onEmitNode(EmitHint.Expression, node, pipelineEmit); } else { - pipelineEmitWithComments(hint, node); + pipelineEmitWorker(EmitHint.Expression, node, PipelinePhase.Start); } } - function pipelineEmitWithComments(hint: EmitHint, node: Node) { - pipelineEmit(hint, node, PipelinePhase.LeadingComments); + function pipelineEmit(hint: EmitHint, node: Node) { + pipelineEmitWorker(hint, node, PipelinePhase.Start); } - function pipelineEmit(hint: EmitHint, node: Node | undefined, start: PipelinePhase) { + function pipelineEmitWorker(hint: EmitHint, node: Node | undefined, phase: PipelinePhase) { if (!node) return; - switch (start) { + switch (phase) { + case PipelinePhase.BeforeEmit: + if (onBeforeEmitNode) { + onBeforeEmitNode(hint, node); + } + + // falls through + case PipelinePhase.LeadingComments: node = trySubstituteNode(hint, node); if (hint !== EmitHint.SourceFile) { @@ -514,7 +546,12 @@ namespace ts { emitTrailingCommentsOfNode(node); } - break; + // falls through + + case PipelinePhase.AfterEmit: + if (onAfterEmitNode) { + onAfterEmitNode(hint, node); + } } } @@ -1260,7 +1297,15 @@ namespace ts { } writePunctuation("["); - pipelineEmitWithNotification(EmitHint.MappedTypeParameter, node.typeParameter); + + // NOTE: this has been manually inlined to reduce overall stack depth + if (onEmitNode) { + onEmitNode(EmitHint.MappedTypeParameter, node.typeParameter, pipelineEmit); + } + else { + pipelineEmitWorker(EmitHint.MappedTypeParameter, node.typeParameter, PipelinePhase.Start); + } + writePunctuation("]"); emit(node.questionToken); diff --git a/src/compiler/sourcemap.ts b/src/compiler/sourcemap.ts index dc374ea09e4..b0d353ddad1 100644 --- a/src/compiler/sourcemap.ts +++ b/src/compiler/sourcemap.ts @@ -37,15 +37,6 @@ namespace ts { emitLeadingSourceMapOfNode(node: Node): void; emitTrailingSourceMapOfNode(node: Node): void; - /** - * Emits a node with possible leading and trailing source maps. - * - * @param hint The current emit context - * @param node The node to emit. - * @param emitCallback The callback used to emit the node. - */ - emitNodeWithSourceMap(hint: EmitHint, node: Node, emitCallback: (hint: EmitHint, node: Node) => void): void; - /** * Emits a token of a node node with possible leading and trailing source maps. * @@ -108,7 +99,6 @@ namespace ts { emitPos, emitLeadingSourceMapOfNode, emitTrailingSourceMapOfNode, - emitNodeWithSourceMap, emitTokenWithSourceMap, getText, getSourceMappingURL, @@ -317,20 +307,6 @@ namespace ts { } } - /** - * Emits a node with possible leading and trailing source maps. - * - * @param hint A hint as to the intended usage of the node. - * @param node The node to emit. - * @param emitCallback The callback used to emit the node. - */ - function emitNodeWithSourceMap(hint: EmitHint, node: Node, emitCallback: (hint: EmitHint, node: Node) => void) { - if (!node) return; - emitLeadingSourceMapOfNode(node); - emitCallback(hint, node); - emitTrailingSourceMapOfNode(node); - } - function emitLeadingSourceMapOfNode(node: Node) { if (!node) return; diff --git a/src/compiler/transformer.ts b/src/compiler/transformer.ts index 341c0c9cd45..4cb636b8aaf 100644 --- a/src/compiler/transformer.ts +++ b/src/compiler/transformer.ts @@ -82,6 +82,13 @@ namespace ts { return transformers; } + const defaultOnSubstituteNode: TransformationContext["onSubstituteNode"] = (_, node) => node; + const defaultOnEmitNode: TransformationContext["onEmitNode"] = (hint, node, callback) => callback(hint, node); + // NOTE: not using `noop` here to avoid deoptimizations due to inlining and argument/parameter + // count mismatches. Deoptimization for this would be severely negative as these functions are + // used in a hot path. + const defaultOnBeforeOrAfterEmitNode: TransformationContext["onBeforeEmitNode"] = (_hint, _node) => { /*empty*/ }; + /** * Transforms an array of SourceFiles by passing them through each transformer. * @@ -101,8 +108,10 @@ namespace ts { let lexicalEnvironmentStackOffset = 0; let lexicalEnvironmentSuspended = false; let emitHelpers: EmitHelper[]; - let onSubstituteNode: TransformationContext["onSubstituteNode"] = (_, node) => node; - let onEmitNode: TransformationContext["onEmitNode"] = (hint, node, callback) => callback(hint, node); + let onSubstituteNode: TransformationContext["onSubstituteNode"] = defaultOnSubstituteNode; + let onEmitNode: TransformationContext["onEmitNode"]; + let onBeforeEmitNode: TransformationContext["onBeforeEmitNode"]; + let onAfterEmitNode: TransformationContext["onBeforeEmitNode"]; let state = TransformationState.Uninitialized; // The transformation context is provided to each transformer as part of transformer @@ -129,12 +138,32 @@ namespace ts { Debug.assert(value !== undefined, "Value must not be 'undefined'"); onSubstituteNode = value; }, - get onEmitNode() { return onEmitNode; }, + get onEmitNode() { + // for backwards compatibility, we push any current before/after events into the current callback. + if (onBeforeEmitNode || onAfterEmitNode) { + onEmitNode = wrapNotification(onEmitNode || defaultOnEmitNode, onBeforeEmitNode, onAfterEmitNode); + onBeforeEmitNode = undefined; + onAfterEmitNode = undefined; + } + return onEmitNode || defaultOnEmitNode; + }, set onEmitNode(value) { Debug.assert(state < TransformationState.Initialized, "Cannot modify transformation hooks after initialization has completed."); Debug.assert(value !== undefined, "Value must not be 'undefined'"); onEmitNode = value; - } + }, + get onBeforeEmitNode() { return onBeforeEmitNode || defaultOnBeforeOrAfterEmitNode; }, + set onBeforeEmitNode(value) { + Debug.assert(state < TransformationState.Initialized, "Cannot modify transformation hooks after initialization has completed."); + Debug.assert(value !== undefined, "Value must not be 'undefined'"); + onBeforeEmitNode = value; + }, + get onAfterEmitNode() { return onAfterEmitNode || defaultOnBeforeOrAfterEmitNode; }, + set onAfterEmitNode(value) { + Debug.assert(state < TransformationState.Initialized, "Cannot modify transformation hooks after initialization has completed."); + Debug.assert(value !== undefined, "Value must not be 'undefined'"); + onAfterEmitNode = value; + }, }; // Ensure the parse tree is clean before applying transformations @@ -159,11 +188,21 @@ namespace ts { performance.mark("afterTransform"); performance.measure("transformTime", "beforeTransform", "afterTransform"); + // for backwards compatibility, we push any current before/after events into the current callback. + if (onEmitNode && (onBeforeEmitNode || onAfterEmitNode)) { + onEmitNode = wrapNotification(onEmitNode, onBeforeEmitNode, onAfterEmitNode); + onBeforeEmitNode = undefined; + onAfterEmitNode = undefined; + } + return { transformed, substituteNode, + useEmitNodeWithNotification: onEmitNode !== undefined, emitNodeWithNotification, - dispose + beforeEmitNode: onBeforeEmitNode ? beforeEmitNode : undefined, + afterEmitNode: onAfterEmitNode ? afterEmitNode : undefined, + dispose, }; function transformRoot(node: T) { @@ -215,6 +254,14 @@ namespace ts { || (getEmitFlags(node) & EmitFlags.AdviseOnEmitNode) !== 0; } + function wrapNotification(onEmitNode: TransformationContext["onEmitNode"], onBeforeEmitNode: TransformationContext["onBeforeEmitNode"], onAfterEmitNode: TransformationContext["onAfterEmitNode"]): TransformationContext["onEmitNode"] { + return (hint, node, emitCallback) => { + if (onBeforeEmitNode) onBeforeEmitNode(hint, node); + onEmitNode(hint, node, emitCallback); + if (onAfterEmitNode) onAfterEmitNode(hint, node); + }; + } + /** * Emits a node with possible emit notification. * @@ -226,7 +273,7 @@ namespace ts { Debug.assert(state < TransformationState.Disposed, "Cannot invoke TransformationResult callbacks after the result is disposed."); if (node) { if (isEmitNotificationEnabled(node)) { - onEmitNode(hint, node, emitCallback); + if (onEmitNode) onEmitNode(hint, node, emitCallback); } else { emitCallback(hint, node); @@ -234,6 +281,20 @@ namespace ts { } } + function beforeEmitNode(hint: EmitHint, node: Node) { + Debug.assert(state < TransformationState.Disposed, "Cannot invoke TransformationResult callbacks after the result is disposed."); + if (node && onBeforeEmitNode && isEmitNotificationEnabled(node)) { + onBeforeEmitNode(hint, node); + } + } + + function afterEmitNode(hint: EmitHint, node: Node) { + Debug.assert(state < TransformationState.Disposed, "Cannot invoke TransformationResult callbacks after the result is disposed."); + if (node && onAfterEmitNode && isEmitNotificationEnabled(node)) { + onAfterEmitNode(hint, node); + } + } + /** * Records a hoisted variable declaration for the provided name within a lexical environment. */ diff --git a/src/compiler/transformers/es2015.ts b/src/compiler/transformers/es2015.ts index 0956c1075ef..5a455cb733d 100644 --- a/src/compiler/transformers/es2015.ts +++ b/src/compiler/transformers/es2015.ts @@ -272,12 +272,15 @@ namespace ts { const compilerOptions = context.getCompilerOptions(); const resolver = context.getEmitResolver(); const previousOnSubstituteNode = context.onSubstituteNode; - const previousOnEmitNode = context.onEmitNode; - context.onEmitNode = onEmitNode; + const previousOnBeforeEmitNode = context.onBeforeEmitNode; + const previousOnAfterEmitNode = context.onAfterEmitNode; context.onSubstituteNode = onSubstituteNode; + context.onBeforeEmitNode = onBeforeEmitNode; + context.onAfterEmitNode = onAfterEmitNode; let currentSourceFile: SourceFile; let currentText: string; + const hierarchyFactsStack: HierarchyFacts[] = []; let hierarchyFacts: HierarchyFacts; let taggedTemplateStringDeclarations: VariableDeclaration[]; @@ -3840,9 +3843,8 @@ namespace ts { * * @param hint A hint as to the intended usage of the node. * @param node The node to be printed. - * @param emitCallback The callback used to emit the node. */ - function onEmitNode(hint: EmitHint, node: Node, emitCallback: (hint: EmitHint, node: Node) => void) { + function onBeforeEmitNode(hint: EmitHint, node: Node) { if (enabledSubstitutions & ES2015SubstitutionFlags.CapturedThis && isFunctionLike(node)) { // If we are tracking a captured `this`, keep track of the enclosing function. const ancestorFacts = enterSubtree( @@ -3850,11 +3852,19 @@ namespace ts { getEmitFlags(node) & EmitFlags.CapturesThis ? HierarchyFacts.FunctionIncludes | HierarchyFacts.CapturesThis : HierarchyFacts.FunctionIncludes); - previousOnEmitNode(hint, node, emitCallback); + hierarchyFactsStack.push(ancestorFacts); + } + + previousOnBeforeEmitNode(hint, node); + } + + function onAfterEmitNode(hint: EmitHint, node: Node) { + previousOnAfterEmitNode(hint, node); + + if (enabledSubstitutions & ES2015SubstitutionFlags.CapturedThis && isFunctionLike(node)) { + const ancestorFacts = hierarchyFactsStack.pop(); exitSubtree(ancestorFacts, HierarchyFacts.None, HierarchyFacts.None); - return; } - previousOnEmitNode(hint, node, emitCallback); } /** diff --git a/src/compiler/transformers/es2017.ts b/src/compiler/transformers/es2017.ts index 905686c8861..098fcba0c34 100644 --- a/src/compiler/transformers/es2017.ts +++ b/src/compiler/transformers/es2017.ts @@ -32,15 +32,18 @@ namespace ts { * just-in-time substitution for `super` expressions inside of async methods. */ let enclosingSuperContainerFlags: NodeCheckFlags = 0; + const enclosingSuperContainerFlagsStack: NodeCheckFlags[] = []; let enclosingFunctionParameterNames: UnderscoreEscapedMap; // Save the previous transformation hooks. - const previousOnEmitNode = context.onEmitNode; + const previousOnBeforeEmitNode = context.onBeforeEmitNode; + const previousOnAfterEmitNode = context.onAfterEmitNode; const previousOnSubstituteNode = context.onSubstituteNode; // Set new transformation hooks. - context.onEmitNode = onEmitNode; + context.onBeforeEmitNode = onBeforeEmitNode; + context.onAfterEmitNode = onAfterEmitNode; context.onSubstituteNode = onSubstituteNode; return transformSourceFile; @@ -503,22 +506,23 @@ namespace ts { * * @param hint A hint as to the intended usage of the node. * @param node The node to emit. - * @param emit A callback used to emit the node in the printer. */ - function onEmitNode(hint: EmitHint, node: Node, emitCallback: (hint: EmitHint, node: Node) => void): void { - // If we need to support substitutions for `super` in an async method, - // we should track it here. + + function onBeforeEmitNode(hint: EmitHint, node: Node) { if (enabledSubstitutions & ES2017SubstitutionFlags.AsyncMethodsWithSuper && isSuperContainer(node)) { - const superContainerFlags = resolver.getNodeCheckFlags(node) & (NodeCheckFlags.AsyncMethodWithSuper | NodeCheckFlags.AsyncMethodWithSuperBinding); - if (superContainerFlags !== enclosingSuperContainerFlags) { - const savedEnclosingSuperContainerFlags = enclosingSuperContainerFlags; - enclosingSuperContainerFlags = superContainerFlags; - previousOnEmitNode(hint, node, emitCallback); - enclosingSuperContainerFlags = savedEnclosingSuperContainerFlags; - return; - } + enclosingSuperContainerFlagsStack.push(enclosingSuperContainerFlags); + enclosingSuperContainerFlags = resolver.getNodeCheckFlags(node) & (NodeCheckFlags.AsyncMethodWithSuper | NodeCheckFlags.AsyncMethodWithSuperBinding); + } + + previousOnBeforeEmitNode(hint, node); + } + + function onAfterEmitNode(hint: EmitHint, node: Node) { + previousOnAfterEmitNode(hint, node); + + if (enabledSubstitutions & ES2017SubstitutionFlags.AsyncMethodsWithSuper && isSuperContainer(node)) { + enclosingSuperContainerFlags = enclosingSuperContainerFlagsStack.pop(); } - previousOnEmitNode(hint, node, emitCallback); } /** diff --git a/src/compiler/transformers/es5.ts b/src/compiler/transformers/es5.ts index bc6ee13520f..4c17f9f6de9 100644 --- a/src/compiler/transformers/es5.ts +++ b/src/compiler/transformers/es5.ts @@ -12,11 +12,11 @@ namespace ts { const compilerOptions = context.getCompilerOptions(); // enable emit notification only if using --jsx preserve or react-native - let previousOnEmitNode: (hint: EmitHint, node: Node, emitCallback: (hint: EmitHint, node: Node) => void) => void; + let previousOnBeforeEmitNode: TransformationContext["onBeforeEmitNode"]; let noSubstitution: boolean[]; if (compilerOptions.jsx === JsxEmit.Preserve || compilerOptions.jsx === JsxEmit.ReactNative) { - previousOnEmitNode = context.onEmitNode; - context.onEmitNode = onEmitNode; + previousOnBeforeEmitNode = context.onBeforeEmitNode; + context.onBeforeEmitNode = onBeforeEmitNode; context.enableEmitNotification(SyntaxKind.JsxOpeningElement); context.enableEmitNotification(SyntaxKind.JsxClosingElement); context.enableEmitNotification(SyntaxKind.JsxSelfClosingElement); @@ -43,9 +43,8 @@ namespace ts { * * @param hint A hint as to the intended usage of the node. * @param node The node to emit. - * @param emitCallback A callback used to emit the node. */ - function onEmitNode(hint: EmitHint, node: Node, emitCallback: (emitContext: EmitHint, node: Node) => void) { + function onBeforeEmitNode(hint: EmitHint, node: Node) { switch (node.kind) { case SyntaxKind.JsxOpeningElement: case SyntaxKind.JsxClosingElement: @@ -55,7 +54,7 @@ namespace ts { break; } - previousOnEmitNode(hint, node, emitCallback); + previousOnBeforeEmitNode(hint, node); } /** diff --git a/src/compiler/transformers/esnext.ts b/src/compiler/transformers/esnext.ts index 424371ad311..a9791793102 100644 --- a/src/compiler/transformers/esnext.ts +++ b/src/compiler/transformers/esnext.ts @@ -20,8 +20,10 @@ namespace ts { const compilerOptions = context.getCompilerOptions(); const languageVersion = getEmitScriptTarget(compilerOptions); - const previousOnEmitNode = context.onEmitNode; - context.onEmitNode = onEmitNode; + const previousOnBeforeEmitNode = context.onBeforeEmitNode; + const previousOnAfterEmitNode = context.onAfterEmitNode; + context.onBeforeEmitNode = onBeforeEmitNode; + context.onAfterEmitNode = onAfterEmitNode; const previousOnSubstituteNode = context.onSubstituteNode; context.onSubstituteNode = onSubstituteNode; @@ -29,6 +31,7 @@ namespace ts { let enabledSubstitutions: ESNextSubstitutionFlags; let enclosingFunctionFlags: FunctionFlags; let enclosingSuperContainerFlags: NodeCheckFlags = 0; + const enclosingSuperContainerFlagsStack: NodeCheckFlags[] = []; return transformSourceFile; @@ -744,21 +747,23 @@ namespace ts { * @param node The node to be printed. * @param emitCallback The callback used to emit the node. */ - function onEmitNode(hint: EmitHint, node: Node, emitCallback: (hint: EmitHint, node: Node) => void) { + function onBeforeEmitNode(hint: EmitHint, node: Node) { // If we need to support substitutions for `super` in an async method, // we should track it here. if (enabledSubstitutions & ESNextSubstitutionFlags.AsyncMethodsWithSuper && isSuperContainer(node)) { - const superContainerFlags = resolver.getNodeCheckFlags(node) & (NodeCheckFlags.AsyncMethodWithSuper | NodeCheckFlags.AsyncMethodWithSuperBinding); - if (superContainerFlags !== enclosingSuperContainerFlags) { - const savedEnclosingSuperContainerFlags = enclosingSuperContainerFlags; - enclosingSuperContainerFlags = superContainerFlags; - previousOnEmitNode(hint, node, emitCallback); - enclosingSuperContainerFlags = savedEnclosingSuperContainerFlags; - return; - } + enclosingSuperContainerFlagsStack.push(enclosingSuperContainerFlags); + enclosingSuperContainerFlags = resolver.getNodeCheckFlags(node) & (NodeCheckFlags.AsyncMethodWithSuper | NodeCheckFlags.AsyncMethodWithSuperBinding); } - previousOnEmitNode(hint, node, emitCallback); + previousOnBeforeEmitNode(hint, node); + } + + function onAfterEmitNode(hint: EmitHint, node: Node) { + previousOnAfterEmitNode(hint, node); + + if (enabledSubstitutions & ESNextSubstitutionFlags.AsyncMethodsWithSuper && isSuperContainer(node)) { + enclosingSuperContainerFlags = enclosingSuperContainerFlagsStack.pop(); + } } /** diff --git a/src/compiler/transformers/module/es2015.ts b/src/compiler/transformers/module/es2015.ts index 4f218e4fdcf..d2f8216323f 100644 --- a/src/compiler/transformers/module/es2015.ts +++ b/src/compiler/transformers/module/es2015.ts @@ -5,9 +5,11 @@ namespace ts { export function transformES2015Module(context: TransformationContext) { const compilerOptions = context.getCompilerOptions(); - const previousOnEmitNode = context.onEmitNode; + const previousOnBeforeEmitNode = context.onBeforeEmitNode; + const previousOnAfterEmitNode = context.onAfterEmitNode; const previousOnSubstituteNode = context.onSubstituteNode; - context.onEmitNode = onEmitNode; + context.onBeforeEmitNode = onBeforeEmitNode; + context.onAfterEmitNode = onAfterEmitNode; context.onSubstituteNode = onSubstituteNode; context.enableEmitNotification(SyntaxKind.SourceFile); context.enableSubstitution(SyntaxKind.Identifier); @@ -73,16 +75,18 @@ namespace ts { * * @param hint A hint as to the intended usage of the node. * @param node The node to emit. - * @param emit A callback used to emit the node in the printer. */ - function onEmitNode(hint: EmitHint, node: Node, emitCallback: (hint: EmitHint, node: Node) => void): void { + function onBeforeEmitNode(hint: EmitHint, node: Node): void { if (isSourceFile(node)) { currentSourceFile = node; - previousOnEmitNode(hint, node, emitCallback); - currentSourceFile = undefined; } - else { - previousOnEmitNode(hint, node, emitCallback); + previousOnBeforeEmitNode(hint, node); + } + + function onAfterEmitNode(hint: EmitHint, node: Node): void { + previousOnAfterEmitNode(hint, node); + if (isSourceFile(node)) { + currentSourceFile = undefined; } } diff --git a/src/compiler/transformers/module/module.ts b/src/compiler/transformers/module/module.ts index 2c75964d616..e0fd57a4a3a 100644 --- a/src/compiler/transformers/module/module.ts +++ b/src/compiler/transformers/module/module.ts @@ -30,10 +30,12 @@ namespace ts { const host = context.getEmitHost(); const languageVersion = getEmitScriptTarget(compilerOptions); const moduleKind = getEmitModuleKind(compilerOptions); + const previousOnBeforeEmitNode = context.onBeforeEmitNode; + const previousOnAfterEmitNode = context.onAfterEmitNode; const previousOnSubstituteNode = context.onSubstituteNode; - const previousOnEmitNode = context.onEmitNode; + context.onBeforeEmitNode = onBeforeEmitNode; + context.onAfterEmitNode = onAfterEmitNode; context.onSubstituteNode = onSubstituteNode; - context.onEmitNode = onEmitNode; context.enableSubstitution(SyntaxKind.Identifier); // Substitutes expression identifiers with imported/exported symbols. context.enableSubstitution(SyntaxKind.BinaryExpression); // Substitutes assignments to exported symbols. context.enableSubstitution(SyntaxKind.PrefixUnaryExpression); // Substitutes updates to exported symbols. @@ -1455,23 +1457,25 @@ namespace ts { * * @param hint A hint as to the intended usage of the node. * @param node The node to emit. - * @param emit A callback used to emit the node in the printer. */ - function onEmitNode(hint: EmitHint, node: Node, emitCallback: (hint: EmitHint, node: Node) => void): void { - if (node.kind === SyntaxKind.SourceFile) { - currentSourceFile = node; + function onBeforeEmitNode(hint: EmitHint, node: Node): void { + if (isSourceFile(node)) { + currentSourceFile = node; currentModuleInfo = moduleInfoMap[getOriginalNodeId(currentSourceFile)]; noSubstitution = []; + } - previousOnEmitNode(hint, node, emitCallback); + previousOnBeforeEmitNode(hint, node); + } + function onAfterEmitNode(hint: EmitHint, node: Node): void { + previousOnAfterEmitNode(hint, node); + + if (isSourceFile(node)) { currentSourceFile = undefined; currentModuleInfo = undefined; noSubstitution = undefined; } - else { - previousOnEmitNode(hint, node, emitCallback); - } } // diff --git a/src/compiler/transformers/module/system.ts b/src/compiler/transformers/module/system.ts index 2e6f6d84b51..36263337ba9 100644 --- a/src/compiler/transformers/module/system.ts +++ b/src/compiler/transformers/module/system.ts @@ -19,10 +19,12 @@ namespace ts { const compilerOptions = context.getCompilerOptions(); const resolver = context.getEmitResolver(); const host = context.getEmitHost(); + const previousOnBeforeEmitNode = context.onBeforeEmitNode; + const previousOnAfterEmitNode = context.onAfterEmitNode; const previousOnSubstituteNode = context.onSubstituteNode; - const previousOnEmitNode = context.onEmitNode; + context.onBeforeEmitNode = onBeforeEmitNode; + context.onAfterEmitNode = onAfterEmitNode; context.onSubstituteNode = onSubstituteNode; - context.onEmitNode = onEmitNode; context.enableSubstitution(SyntaxKind.Identifier); // Substitutes expression identifiers for imported symbols. context.enableSubstitution(SyntaxKind.ShorthandPropertyAssignment); // Substitutes expression identifiers for imported symbols context.enableSubstitution(SyntaxKind.BinaryExpression); // Substitutes assignments to exported symbols. @@ -1581,30 +1583,29 @@ namespace ts { * * @param hint A hint as to the intended usage of the node. * @param node The node to emit. - * @param emitCallback A callback used to emit the node in the printer. */ - function onEmitNode(hint: EmitHint, node: Node, emitCallback: (hint: EmitHint, node: Node) => void): void { - if (node.kind === SyntaxKind.SourceFile) { + function onBeforeEmitNode(hint: EmitHint, node: Node): void { + if (isSourceFile(node)) { const id = getOriginalNodeId(node); - currentSourceFile = node; + currentSourceFile = node; moduleInfo = moduleInfoMap[id]; exportFunction = exportFunctionsMap[id]; noSubstitution = noSubstitutionMap[id]; - if (noSubstitution) { delete noSubstitutionMap[id]; } + } + previousOnBeforeEmitNode(hint, node); + } - previousOnEmitNode(hint, node, emitCallback); - + function onAfterEmitNode(hint: EmitHint, node: Node) { + previousOnAfterEmitNode(hint, node); + if (isSourceFile(node)) { currentSourceFile = undefined; moduleInfo = undefined; exportFunction = undefined; noSubstitution = undefined; } - else { - previousOnEmitNode(hint, node, emitCallback); - } } // diff --git a/src/compiler/transformers/ts.ts b/src/compiler/transformers/ts.ts index 0e70b3099c0..8ef4deecf2f 100644 --- a/src/compiler/transformers/ts.ts +++ b/src/compiler/transformers/ts.ts @@ -50,11 +50,13 @@ namespace ts { const moduleKind = getEmitModuleKind(compilerOptions); // Save the previous transformation hooks. - const previousOnEmitNode = context.onEmitNode; + const previousOnBeforeEmitNode = context.onBeforeEmitNode; + const previousOnAfterEmitNode = context.onAfterEmitNode; const previousOnSubstituteNode = context.onSubstituteNode; // Set new transformation hooks. - context.onEmitNode = onEmitNode; + context.onBeforeEmitNode = onBeforeEmitNode; + context.onAfterEmitNode = onAfterEmitNode; context.onSubstituteNode = onSubstituteNode; // Enable substitution for property/element access to emit const enum values. @@ -85,6 +87,7 @@ namespace ts { * just-in-time substitution while printing an expression identifier. */ let applicableSubstitutions: TypeScriptSubstitutionFlags; + const applicableSubstitutionsStack: TypeScriptSubstitutionFlags[] = []; /** * Tracks what computed name expressions originating from elided names must be inlined @@ -3389,28 +3392,39 @@ namespace ts { * * @param hint A hint as to the intended usage of the node. * @param node The node to emit. - * @param emit A callback used to emit the node in the printer. */ - function onEmitNode(hint: EmitHint, node: Node, emitCallback: (hint: EmitHint, node: Node) => void): void { - const savedApplicableSubstitutions = applicableSubstitutions; - const savedCurrentSourceFile = currentSourceFile; - + function onBeforeEmitNode(hint: EmitHint, node: Node) { if (isSourceFile(node)) { currentSourceFile = node; } - if (enabledSubstitutions & TypeScriptSubstitutionFlags.NamespaceExports && isTransformedModuleDeclaration(node)) { - applicableSubstitutions |= TypeScriptSubstitutionFlags.NamespaceExports; + const requiresNamespaceExports = enabledSubstitutions & TypeScriptSubstitutionFlags.NamespaceExports && isTransformedModuleDeclaration(node); + const requiresNonQualifiedEnumMembers = enabledSubstitutions & TypeScriptSubstitutionFlags.NonQualifiedEnumMembers && isTransformedEnumDeclaration(node); + if (requiresNamespaceExports || requiresNonQualifiedEnumMembers) { + applicableSubstitutionsStack.push(applicableSubstitutions); + if (requiresNamespaceExports) { + applicableSubstitutions |= TypeScriptSubstitutionFlags.NamespaceExports; + } + + if (requiresNonQualifiedEnumMembers) { + applicableSubstitutions |= TypeScriptSubstitutionFlags.NonQualifiedEnumMembers; + } } - if (enabledSubstitutions & TypeScriptSubstitutionFlags.NonQualifiedEnumMembers && isTransformedEnumDeclaration(node)) { - applicableSubstitutions |= TypeScriptSubstitutionFlags.NonQualifiedEnumMembers; + previousOnBeforeEmitNode(hint, node); + } + + function onAfterEmitNode(hint: EmitHint, node: Node) { + previousOnAfterEmitNode(hint, node); + + if (isSourceFile(node)) { + currentSourceFile = undefined; } - previousOnEmitNode(hint, node, emitCallback); - - applicableSubstitutions = savedApplicableSubstitutions; - currentSourceFile = savedCurrentSourceFile; + if (enabledSubstitutions & TypeScriptSubstitutionFlags.NamespaceExports && isTransformedModuleDeclaration(node) || + enabledSubstitutions & TypeScriptSubstitutionFlags.NonQualifiedEnumMembers && isTransformedEnumDeclaration(node)) { + applicableSubstitutions = applicableSubstitutionsStack.pop(); + } } /** diff --git a/src/compiler/types.ts b/src/compiler/types.ts index b8fcbaa3151..de374381dd1 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -4817,6 +4817,8 @@ namespace ts { * before returning the `NodeTransformer` callback. */ onEmitNode: (hint: EmitHint, node: Node, emitCallback: (hint: EmitHint, node: Node) => void) => void; + /*@internal*/ onBeforeEmitNode?: (hint: EmitHint, node: Node) => void; + /*@internal*/ onAfterEmitNode?: (hint: EmitHint, node: Node) => void; } export interface TransformationResult { @@ -4847,6 +4849,10 @@ namespace ts { * Clean up EmitNode entries on any parse-tree nodes. */ dispose(): void; + + /*@internal*/ useEmitNodeWithNotification: boolean; + /*@internal*/ beforeEmitNode?(hint: EmitHint, node: Node): void; + /*@internal*/ afterEmitNode?(hint: EmitHint, node: Node): void; } /** @@ -4942,6 +4948,8 @@ namespace ts { * ``` */ substituteNode?(hint: EmitHint, node: Node): Node; + /*@internal*/ onBeforeEmitNode?: (hint: EmitHint, node: Node) => void; + /*@internal*/ onAfterEmitNode?: (hint: EmitHint, node: Node) => void; /*@internal*/ onEmitLeadingSourceMapOfNode?: (node: Node) => void; /*@internal*/ onEmitTrailingSourceMapOfNode?: (node: Node) => void; /*@internal*/ onEmitSourceMapOfToken?: (node: Node, token: SyntaxKind, writer: (s: string) => void, pos: number, emitCallback: (token: SyntaxKind, writer: (s: string) => void, pos: number) => number) => number;