diff --git a/src/compiler/comments.ts b/src/compiler/comments.ts index f9f8e74d4ca..116a8e849a3 100644 --- a/src/compiler/comments.ts +++ b/src/compiler/comments.ts @@ -5,7 +5,7 @@ namespace ts { export interface CommentWriter { reset(): void; setSourceFile(sourceFile: SourceFile): void; - emitNodeWithComments(node: Node, emitCallback: (node: Node) => void): void; + emitNodeWithComments(emitContext: EmitContext, node: Node, emitCallback: (emitContext: EmitContext, node: Node) => void): void; emitBodyWithDetachedComments(node: Node, detachedRange: TextRange, emitCallback: (node: Node) => void): void; emitTrailingCommentsOfPosition(pos: number): void; } @@ -34,9 +34,9 @@ namespace ts { emitTrailingCommentsOfPosition, }; - function emitNodeWithComments(node: Node, emitCallback: (node: Node) => void) { + function emitNodeWithComments(emitContext: EmitContext, node: Node, emitCallback: (emitContext: EmitContext, node: Node) => void) { if (disabled) { - emitCallback(node); + emitCallback(emitContext, node); return; } @@ -46,10 +46,12 @@ namespace ts { if ((pos < 0 && end < 0) || (pos === end)) { // Both pos and end are synthesized, so just emit the node without comments. if (emitFlags & EmitFlags.NoNestedComments) { - disableCommentsAndEmit(node, emitCallback); + disabled = true; + emitCallback(emitContext, node); + disabled = false; } else { - emitCallback(node); + emitCallback(emitContext, node); } } else { @@ -91,10 +93,12 @@ namespace ts { } if (emitFlags & EmitFlags.NoNestedComments) { - disableCommentsAndEmit(node, emitCallback); + disabled = true; + emitCallback(emitContext, node); + disabled = false; } else { - emitCallback(node); + emitCallback(emitContext, node); } if (extendedDiagnostics) { @@ -137,8 +141,10 @@ namespace ts { performance.measure("commentTime", "preEmitBodyWithDetachedComments"); } - if (emitFlags & EmitFlags.NoNestedComments) { - disableCommentsAndEmit(node, emitCallback); + if (emitFlags & EmitFlags.NoNestedComments && !disabled) { + disabled = true; + emitCallback(node); + disabled = false; } else { emitCallback(node); @@ -284,17 +290,6 @@ namespace ts { detachedCommentsInfo = undefined; } - function disableCommentsAndEmit(node: Node, emitCallback: (node: Node) => void): void { - if (disabled) { - emitCallback(node); - } - else { - disabled = true; - emitCallback(node); - disabled = false; - } - } - function hasDetachedComments(pos: number) { return detachedCommentsInfo !== undefined && lastOrUndefined(detachedCommentsInfo).nodePos === pos; } diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index 8457ea41d02..03f785dc7de 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -229,33 +229,27 @@ const _super = (function (geti, seti) { let isOwnFileEmit: boolean; let emitSkipped = false; - performance.mark("beforeTransform"); + const sourceFiles = getSourceFilesToEmit(host, targetSourceFile); // Transform the source files - const transformed = transformFiles( - resolver, - host, - getSourceFilesToEmit(host, targetSourceFile), - transformers); - - performance.measure("transformTime", "beforeTransform"); - - // Extract helpers from the result + performance.mark("beforeTransform"); const { + transformed, emitNodeWithSubstitution, emitNodeWithNotification - } = transformed; - - performance.mark("beforePrint"); + } = transformFiles(resolver, host, sourceFiles, transformers); + performance.measure("transformTime", "beforeTransform"); // Emit each output file - forEachTransformedEmitFile(host, transformed.getSourceFiles(), emitFile); - - // Clean up after transformation - transformed.dispose(); - + performance.mark("beforePrint"); + forEachTransformedEmitFile(host, transformed, emitFile); performance.measure("printTime", "beforePrint"); + // Clean up emit nodes on parse tree + for (const sourceFile of sourceFiles) { + disposeEmitNodes(sourceFile); + } + return { emitSkipped, diagnostics: emitterDiagnostics.getDiagnostics(), @@ -346,111 +340,137 @@ const _super = (function (geti, seti) { currentFileIdentifiers = node.identifiers; sourceMap.setSourceFile(node); comments.setSourceFile(node); - emitNodeWithNotification(node, emitWithSubstitution); + pipelineEmitWithNotification(EmitContext.SourceFile, node); } /** * Emits a node. */ function emit(node: Node) { - emitNodeWithNotification(node, emitWithComments); - } - - /** - * Emits a node with comments. - * - * NOTE: Do not call this method directly. It is part of the emit pipeline - * and should only be called indirectly from emit. - */ - function emitWithComments(node: Node) { - emitNodeWithComments(node, emitWithSourceMap); - } - - /** - * Emits a node with source maps. - * - * NOTE: Do not call this method directly. It is part of the emit pipeline - * and should only be called indirectly from emitWithComments. - */ - function emitWithSourceMap(node: Node) { - emitNodeWithSourceMap(node, emitWithSubstitution); - } - - /** - * Emits a node with possible substitution. - * - * NOTE: Do not call this method directly. It is part of the emit pipeline - * and should only be called indirectly from emitWithSourceMap or - * emitIdentifierNameWithComments. - */ - function emitWithSubstitution(node: Node) { - emitNodeWithSubstitution(node, /*isExpression*/ false, emitWorker); + pipelineEmitWithNotification(EmitContext.Unspecified, node); } /** * Emits an IdentifierName. */ function emitIdentifierName(node: Identifier) { - if (node) { - emitNodeWithNotification(node, emitIdentifierNameWithComments); - } - } - - /** - * Emits an IdentifierName with possible comments. - * - * NOTE: Do not call this method directly. It is part of the emit pipeline - * and should only be called indirectly from emitIdentifierName. - */ - function emitIdentifierNameWithComments(node: Identifier) { - emitNodeWithComments(node, emitWithSubstitution); + pipelineEmitWithNotification(EmitContext.IdentifierName, node); } /** * Emits an expression node. */ function emitExpression(node: Expression) { - emitNodeWithNotification(node, emitExpressionWithComments); + pipelineEmitWithNotification(EmitContext.Expression, node); } /** - * Emits an expression with comments. + * Emits a node with possible notification. * - * NOTE: Do not call this method directly. It is part of the emitExpression pipeline - * and should only be called indirectly from emitExpression. + * NOTE: Do not call this method directly. It is part of the emit pipeline + * and should only be called from printSourceFile, emit, emitExpression, or + * emitIdentifierName. */ - function emitExpressionWithComments(node: Expression) { - emitNodeWithComments(node, emitExpressionWithSourceMap); + function pipelineEmitWithNotification(emitContext: EmitContext, node: Node) { + emitNodeWithNotification(emitContext, node, pipelineEmitWithComments); } /** - * Emits an expression with possible source maps. + * Emits a node with comments. * - * NOTE: Do not call this method directly. It is part of the emitExpression pipeline - * and should only be called indirectly from emitExpressionWithComments. + * NOTE: Do not call this method directly. It is part of the emit pipeline + * and should only be called indirectly from pipelineEmitWithNotification. */ - function emitExpressionWithSourceMap(node: Expression) { - emitNodeWithSourceMap(node, emitExpressionWithSubstitution); + function pipelineEmitWithComments(emitContext: EmitContext, node: Node) { + // Do not emit comments for SourceFile + if (emitContext === EmitContext.SourceFile) { + pipelineEmitWithSourceMap(emitContext, node); + return; + } + + emitNodeWithComments(emitContext, node, pipelineEmitWithSourceMap); } /** - * Emits an expression with possible substitution. + * Emits a node with source maps. * - * NOTE: Do not call this method directly. It is part of the emitExpression pipeline - * and should only be called indirectly from emitExpressionWithSourceMap or - * from emitWorker. + * NOTE: Do not call this method directly. It is part of the emit pipeline + * and should only be called indirectly from pipelineEmitWithComments. */ - function emitExpressionWithSubstitution(node: Expression) { - emitNodeWithSubstitution(node, /*isExpression*/ true, emitExpressionWorker); + function pipelineEmitWithSourceMap(emitContext: EmitContext, node: Node) { + // Do not emit source mappings for SourceFile or IdentifierName + if (emitContext === EmitContext.SourceFile + || emitContext === EmitContext.IdentifierName) { + pipelineEmitWithSubstitution(emitContext, node); + return; + } + + emitNodeWithSourceMap(emitContext, node, pipelineEmitWithSubstitution); + } + + /** + * Emits a node with possible substitution. + * + * NOTE: Do not call this method directly. It is part of the emit pipeline + * and should only be called indirectly from pipelineEmitWithSourceMap or + * pipelineEmitInUnspecifiedContext (when pickign a more specific context). + */ + function pipelineEmitWithSubstitution(emitContext: EmitContext, node: Node) { + emitNodeWithSubstitution(emitContext, node, pipelineEmitForContext); } /** * Emits a node. * * NOTE: Do not call this method directly. It is part of the emit pipeline - * and should only be called indirectly from emitNodeWithSubstitution. + * and should only be called indirectly from pipelineEmitWithSubstitution. */ - function emitWorker(node: Node): void { + function pipelineEmitForContext(emitContext: EmitContext, node: Node): void { + switch (emitContext) { + case EmitContext.SourceFile: return pipelineEmitInSourceFileContext(node); + case EmitContext.IdentifierName: return pipelineEmitInIdentifierNameContext(node); + case EmitContext.Unspecified: return pipelineEmitInUnspecifiedContext(node); + case EmitContext.Expression: return pipelineEmitInExpressionContext(node); + } + } + + /** + * Emits a node in the SourceFile EmitContext. + * + * NOTE: Do not call this method directly. It is part of the emit pipeline + * and should only be called indirectly from pipelineEmitForContext. + */ + function pipelineEmitInSourceFileContext(node: Node): void { + const kind = node.kind; + switch (kind) { + // Top-level nodes + case SyntaxKind.SourceFile: + return emitSourceFile(node); + } + } + + /** + * Emits a node in the IdentifierName EmitContext. + * + * NOTE: Do not call this method directly. It is part of the emit pipeline + * and should only be called indirectly from pipelineEmitForContext. + */ + function pipelineEmitInIdentifierNameContext(node: Node): void { + const kind = node.kind; + switch (kind) { + // Identifiers + case SyntaxKind.Identifier: + return emitIdentifier(node); + } + } + + /** + * Emits a node in the Unspecified EmitContext. + * + * NOTE: Do not call this method directly. It is part of the emit pipeline + * and should only be called indirectly from pipelineEmitForContext. + */ + function pipelineEmitInUnspecifiedContext(node: Node): void { const kind = node.kind; switch (kind) { // Pseudo-literals @@ -486,7 +506,8 @@ const _super = (function (geti, seti) { case SyntaxKind.StringKeyword: case SyntaxKind.SymbolKeyword: case SyntaxKind.GlobalKeyword: - return emitTokenNode(node); + writeTokenText(kind); + return; // Parse tree nodes @@ -691,27 +712,24 @@ const _super = (function (geti, seti) { case SyntaxKind.EnumMember: return emitEnumMember(node); - // Top-level nodes - case SyntaxKind.SourceFile: - return emitSourceFile(node); - // JSDoc nodes (ignored) - // Transformation nodes (ignored) } + // If the node is an expression, try to emit it as an expression with + // substitution. if (isExpression(node)) { - return emitExpressionWithSubstitution(node); + return pipelineEmitWithSubstitution(EmitContext.Expression, node); } } /** - * Emits an expression. + * Emits a node in the Expression EmitContext. * - * NOTE: Do not call this method directly. It is part of the emitExpression pipeline - * and should only be called indirectly from emitExpressionWithNotification. + * NOTE: Do not call this method directly. It is part of the emit pipeline + * and should only be called indirectly from pipelineEmitForContext. */ - function emitExpressionWorker(node: Node) { + function pipelineEmitInExpressionContext(node: Node): void { const kind = node.kind; switch (kind) { // Literals @@ -733,7 +751,8 @@ const _super = (function (geti, seti) { case SyntaxKind.SuperKeyword: case SyntaxKind.TrueKeyword: case SyntaxKind.ThisKeyword: - return emitTokenNode(node); + writeTokenText(kind); + return; // Expressions case SyntaxKind.ArrayLiteralExpression: @@ -2444,15 +2463,7 @@ const _super = (function (geti, seti) { function writeTokenText(token: SyntaxKind, pos?: number) { const tokenString = tokenToString(token); write(tokenString); - return positionIsSynthesized(pos) ? -1 : pos + tokenString.length; - } - - function emitTokenNode(node: Node) { - emitNodeWithSourceMap(node, emitTokenNodeWorker); - } - - function emitTokenNodeWorker(node: Node) { - writeTokenText(node.kind); + return pos < 0 ? pos : pos + tokenString.length; } function increaseIndentIf(value: boolean, valueToWriteWhenNotIndenting?: string) { diff --git a/src/compiler/factory.ts b/src/compiler/factory.ts index c0565f6e91d..b6fc2093288 100644 --- a/src/compiler/factory.ts +++ b/src/compiler/factory.ts @@ -2665,6 +2665,139 @@ namespace ts { return destRanges; } + /** + * Clears any EmitNode entries from parse-tree nodes. + * @param sourceFile A source file. + */ + export function disposeEmitNodes(sourceFile: SourceFile) { + // During transformation we may need to annotate a parse tree node with transient + // transformation properties. As parse tree nodes live longer than transformation + // nodes, we need to make sure we reclaim any memory allocated for custom ranges + // from these nodes to ensure we do not hold onto entire subtrees just for position + // information. We also need to reset these nodes to a pre-transformation state + // for incremental parsing scenarios so that we do not impact later emit. + sourceFile = getSourceFileOfNode(getParseTreeNode(sourceFile)); + const emitNode = sourceFile && sourceFile.emitNode; + const annotatedNodes = emitNode && emitNode.annotatedNodes; + if (annotatedNodes) { + for (const node of annotatedNodes) { + node.emitNode = undefined; + } + } + } + + /** + * Associates a node with the current transformation, initializing + * various transient transformation properties. + * + * @param node The node. + */ + function getOrCreateEmitNode(node: Node) { + if (!node.emitNode) { + if (isParseTreeNode(node)) { + // To avoid holding onto transformation artifacts, we keep track of any + // parse tree node we are annotating. This allows us to clean them up after + // all transformations have completed. + if (node.kind === SyntaxKind.SourceFile) { + return node.emitNode = { annotatedNodes: [node] }; + } + + const sourceFile = getSourceFileOfNode(node); + getOrCreateEmitNode(sourceFile).annotatedNodes.push(node); + } + + node.emitNode = {}; + } + + return node.emitNode; + } + + /** + * Gets flags that control emit behavior of a node. + * + * @param node The node. + */ + export function getEmitFlags(node: Node) { + const emitNode = node.emitNode; + return emitNode && emitNode.flags; + } + + /** + * Sets flags that control emit behavior of a node. + * + * @param node The node. + * @param emitFlags The NodeEmitFlags for the node. + */ + export function setEmitFlags(node: T, emitFlags: EmitFlags) { + getOrCreateEmitNode(node).flags = emitFlags; + return node; + } + + /** + * Sets a custom text range to use when emitting source maps. + * + * @param node The node. + * @param range The text range. + */ + export function setSourceMapRange(node: T, range: TextRange) { + getOrCreateEmitNode(node).sourceMapRange = range; + return node; + } + + /** + * Sets the TextRange to use for source maps for a token of a node. + * + * @param node The node. + * @param token The token. + * @param range The text range. + */ + export function setTokenSourceMapRange(node: T, token: SyntaxKind, range: TextRange) { + const emitNode = getOrCreateEmitNode(node); + const tokenSourceMapRanges = emitNode.tokenSourceMapRanges || (emitNode.tokenSourceMapRanges = createMap()); + tokenSourceMapRanges[token] = range; + return node; + } + + /** + * Sets a custom text range to use when emitting comments. + */ + export function setCommentRange(node: T, range: TextRange) { + getOrCreateEmitNode(node).commentRange = range; + return node; + } + + /** + * Gets a custom text range to use when emitting comments. + * + * @param node The node. + */ + export function getCommentRange(node: Node) { + const emitNode = node.emitNode; + return (emitNode && emitNode.commentRange) || node; + } + + /** + * Gets a custom text range to use when emitting source maps. + * + * @param node The node. + */ + export function getSourceMapRange(node: Node) { + const emitNode = node.emitNode; + return (emitNode && emitNode.sourceMapRange) || node; + } + + /** + * Gets the TextRange to use for source maps for a token of a node. + * + * @param node The node. + * @param token The token. + */ + export function getTokenSourceMapRange(node: Node, token: SyntaxKind) { + const emitNode = node.emitNode; + const tokenSourceMapRanges = emitNode && emitNode.tokenSourceMapRanges; + return tokenSourceMapRanges && tokenSourceMapRanges[token]; + } + export function setTextRange(node: T, location: TextRange): T { if (location) { node.pos = location.pos; @@ -2706,7 +2839,7 @@ namespace ts { return undefined; } - /** + /** * Get the name of a target module from an import/export declaration as should be written in the emitted output. * The emitted output name can be different from the input if: * 1. The module has a /// diff --git a/src/compiler/sourcemap.ts b/src/compiler/sourcemap.ts index 3517c54b407..d6e6501c598 100644 --- a/src/compiler/sourcemap.ts +++ b/src/compiler/sourcemap.ts @@ -38,10 +38,11 @@ namespace ts { /** * Emits a node with possible leading and trailing source maps. * + * @param emitContext The current emit context * @param node The node to emit. * @param emitCallback The callback used to emit the node. */ - emitNodeWithSourceMap(node: Node, emitCallback: (node: Node) => void): void; + emitNodeWithSourceMap(emitContext: EmitContext, node: Node, emitCallback: (emitContext: EmitContext, node: Node) => void): void; /** * Emits a token of a node node with possible leading and trailing source maps. @@ -313,9 +314,9 @@ namespace ts { * @param node The node to emit. * @param emitCallback The callback used to emit the node. */ - function emitNodeWithSourceMap(node: Node, emitCallback: (node: Node) => void) { + function emitNodeWithSourceMap(emitContext: EmitContext, node: Node, emitCallback: (emitContext: EmitContext, node: Node) => void) { if (disabled) { - return emitCallback(node); + return emitCallback(emitContext, node); } if (node) { @@ -331,11 +332,11 @@ namespace ts { if (emitFlags & EmitFlags.NoNestedSourceMaps) { disabled = true; - emitCallback(node); + emitCallback(emitContext, node); disabled = false; } else { - emitCallback(node); + emitCallback(emitContext, node); } if (node.kind !== SyntaxKind.NotEmittedStatement diff --git a/src/compiler/transformer.ts b/src/compiler/transformer.ts index 5695bea7ae9..92407af2bb9 100644 --- a/src/compiler/transformer.ts +++ b/src/compiler/transformer.ts @@ -28,29 +28,25 @@ namespace ts { /** * Gets the transformed source files. */ - getSourceFiles(): SourceFile[]; + transformed: SourceFile[]; /** * Emits the substitute for a node, if one is available; otherwise, emits the node. * + * @param emitContext The current emit context. * @param node The node to substitute. - * @param isExpression A value indicating whether the node is in an expression context. * @param emitCallback A callback used to emit the node or its substitute. */ - emitNodeWithSubstitution(node: Node, isExpression: boolean, emitCallback: (node: Node) => void): void; + emitNodeWithSubstitution(emitContext: EmitContext, node: Node, emitCallback: (emitContext: EmitContext, node: Node) => void): void; /** * Emits a node with possible notification. * + * @param emitContext The current emit context. * @param node The node to emit. * @param emitCallback A callback used to emit the node. */ - emitNodeWithNotification(node: Node, emitCallback: (node: Node) => void): void; - - /** - * Reset transient transformation properties on parse tree nodes. - */ - dispose(): void; + emitNodeWithNotification(emitContext: EmitContext, node: Node, emitCallback: (emitContext: EmitContext, node: Node) => void): void; } export interface TransformationContext extends LexicalEnvironment { @@ -82,7 +78,7 @@ namespace ts { * Hook used by transformers to substitute expressions just before they * are emitted by the pretty printer. */ - onSubstituteNode?: (node: Node, isExpression: boolean) => Node; + onSubstituteNode?: (emitContext: EmitContext, node: Node) => Node; /** * Enables before/after emit notifications in the pretty printer for the provided @@ -100,7 +96,7 @@ namespace ts { * Hook used to allow transformers to capture state before or after * the printer emits a node. */ - onEmitNode?: (node: Node, emit: (node: Node) => void) => void; + onEmitNode?: (emitContext: EmitContext, node: Node, emitCallback: (emitContext: EmitContext, node: Node) => void) => void; } /* @internal */ @@ -129,139 +125,6 @@ namespace ts { return transformers; } - /** - * Clears any EmitNode entries from parse-tree nodes. - * @param sourceFile A source file. - */ - export function disposeEmitNodes(sourceFile: SourceFile) { - // During transformation we may need to annotate a parse tree node with transient - // transformation properties. As parse tree nodes live longer than transformation - // nodes, we need to make sure we reclaim any memory allocated for custom ranges - // from these nodes to ensure we do not hold onto entire subtrees just for position - // information. We also need to reset these nodes to a pre-transformation state - // for incremental parsing scenarios so that we do not impact later emit. - sourceFile = getSourceFileOfNode(getParseTreeNode(sourceFile)); - const emitNode = sourceFile && sourceFile.emitNode; - const annotatedNodes = emitNode && emitNode.annotatedNodes; - if (annotatedNodes) { - for (const node of annotatedNodes) { - node.emitNode = undefined; - } - } - } - - /** - * Associates a node with the current transformation, initializing - * various transient transformation properties. - * - * @param node The node. - */ - function getOrCreateEmitNode(node: Node) { - if (!node.emitNode) { - if (isParseTreeNode(node)) { - // To avoid holding onto transformation artifacts, we keep track of any - // parse tree node we are annotating. This allows us to clean them up after - // all transformations have completed. - if (node.kind === SyntaxKind.SourceFile) { - return node.emitNode = { annotatedNodes: [node] }; - } - - const sourceFile = getSourceFileOfNode(node); - getOrCreateEmitNode(sourceFile).annotatedNodes.push(node); - } - - node.emitNode = {}; - } - - return node.emitNode; - } - - /** - * Gets flags that control emit behavior of a node. - * - * @param node The node. - */ - export function getEmitFlags(node: Node) { - const emitNode = node.emitNode; - return emitNode && emitNode.flags; - } - - /** - * Sets flags that control emit behavior of a node. - * - * @param node The node. - * @param emitFlags The NodeEmitFlags for the node. - */ - export function setEmitFlags(node: T, emitFlags: EmitFlags) { - getOrCreateEmitNode(node).flags = emitFlags; - return node; - } - - /** - * Sets a custom text range to use when emitting source maps. - * - * @param node The node. - * @param range The text range. - */ - export function setSourceMapRange(node: T, range: TextRange) { - getOrCreateEmitNode(node).sourceMapRange = range; - return node; - } - - /** - * Sets the TextRange to use for source maps for a token of a node. - * - * @param node The node. - * @param token The token. - * @param range The text range. - */ - export function setTokenSourceMapRange(node: T, token: SyntaxKind, range: TextRange) { - const emitNode = getOrCreateEmitNode(node); - const tokenSourceMapRanges = emitNode.tokenSourceMapRanges || (emitNode.tokenSourceMapRanges = createMap()); - tokenSourceMapRanges[token] = range; - return node; - } - - /** - * Sets a custom text range to use when emitting comments. - */ - export function setCommentRange(node: T, range: TextRange) { - getOrCreateEmitNode(node).commentRange = range; - return node; - } - - /** - * Gets a custom text range to use when emitting comments. - * - * @param node The node. - */ - export function getCommentRange(node: Node) { - const emitNode = node.emitNode; - return (emitNode && emitNode.commentRange) || node; - } - - /** - * Gets a custom text range to use when emitting source maps. - * - * @param node The node. - */ - export function getSourceMapRange(node: Node) { - const emitNode = node.emitNode; - return (emitNode && emitNode.sourceMapRange) || node; - } - - /** - * Gets the TextRange to use for source maps for a token of a node. - * - * @param node The node. - * @param token The token. - */ - export function getTokenSourceMapRange(node: Node, token: SyntaxKind) { - const emitNode = node.emitNode; - const tokenSourceMapRanges = emitNode && emitNode.tokenSourceMapRanges; - return tokenSourceMapRanges && tokenSourceMapRanges[token]; - } - /** * Transforms an array of SourceFiles by passing them through each transformer. * @@ -290,10 +153,10 @@ namespace ts { hoistFunctionDeclaration, startLexicalEnvironment, endLexicalEnvironment, - onSubstituteNode: (node, isExpression) => node, + onSubstituteNode: (emitContext, node) => node, enableSubstitution, isSubstitutionEnabled, - onEmitNode: (node, emitCallback) => emitCallback(node), + onEmitNode: (node, emitContext, emitCallback) => emitCallback(node, emitContext), enableEmitNotification, isEmitNotificationEnabled }; @@ -308,20 +171,9 @@ namespace ts { lexicalEnvironmentDisabled = true; return { - getSourceFiles: () => transformed, + transformed, emitNodeWithSubstitution, - emitNodeWithNotification, - dispose() { - // During transformation we may need to annotate a parse tree node with transient - // transformation properties. As parse tree nodes live longer than transformation - // nodes, we need to make sure we reclaim any memory allocated for custom ranges - // from these nodes to ensure we do not hold onto entire subtrees just for position - // information. We also need to reset these nodes to a pre-transformation state - // for incremental parsing scenarios so that we do not impact later emit. - for (const sourceFile of sourceFiles) { - disposeEmitNodes(sourceFile); - } - } + emitNodeWithNotification }; /** @@ -355,21 +207,21 @@ namespace ts { /** * Emits a node with possible substitution. * + * @param emitContext The current emit context. * @param node The node to emit. - * @param isExpression Whether the node represents an expression. * @param emitCallback The callback used to emit the node or its substitute. */ - function emitNodeWithSubstitution(node: Node, isExpression: boolean, emitCallback: (node: Node) => void) { + function emitNodeWithSubstitution(emitContext: EmitContext, node: Node, emitCallback: (emitContext: EmitContext, node: Node) => void) { if (node) { if (isSubstitutionEnabled(node)) { - const substitute = context.onSubstituteNode(node, isExpression); + const substitute = context.onSubstituteNode(emitContext, node); if (substitute && substitute !== node) { - emitCallback(substitute); + emitCallback(emitContext, substitute); return; } } - emitCallback(node); + emitCallback(emitContext, node); } } @@ -391,14 +243,18 @@ namespace ts { /** * Emits a node with possible emit notification. + * + * @param emitContext The current emit context. + * @param node The node to emit. + * @param emitCallback The callback used to emit the node. */ - function emitNodeWithNotification(node: Node, emitCallback: (node: Node) => void) { + function emitNodeWithNotification(emitContext: EmitContext, node: Node, emitCallback: (emitContext: EmitContext, node: Node) => void) { if (node) { if (isEmitNotificationEnabled(node)) { - context.onEmitNode(node, emitCallback); + context.onEmitNode(emitContext, node, emitCallback); } else { - emitCallback(node); + emitCallback(emitContext, node); } } } diff --git a/src/compiler/transformers/es6.ts b/src/compiler/transformers/es6.ts index eda0d03c704..589478f1f6f 100644 --- a/src/compiler/transformers/es6.ts +++ b/src/compiler/transformers/es6.ts @@ -2852,7 +2852,7 @@ namespace ts { * * @param node The node to be printed. */ - function onEmitNode(node: Node, emit: (node: Node) => void) { + function onEmitNode(emitContext: EmitContext, node: Node, emitCallback: (emitContext: EmitContext, node: Node) => void) { const savedUseCapturedThis = useCapturedThis; if (enabledSubstitutions & ES6SubstitutionFlags.CapturedThis && isFunctionLike(node)) { @@ -2861,7 +2861,7 @@ namespace ts { useCapturedThis = (getEmitFlags(node) & EmitFlags.CapturesThis) !== 0; } - previousOnEmitNode(node, emit); + previousOnEmitNode(emitContext, node, emitCallback); useCapturedThis = savedUseCapturedThis; } @@ -2902,10 +2902,10 @@ namespace ts { * @param isExpression A value indicating whether the node is to be used in an expression * position. */ - function onSubstituteNode(node: Node, isExpression: boolean) { - node = previousOnSubstituteNode(node, isExpression); + function onSubstituteNode(emitContext: EmitContext, node: Node) { + node = previousOnSubstituteNode(emitContext, node); - if (isExpression) { + if (emitContext === EmitContext.Expression) { return substituteExpression(node); } diff --git a/src/compiler/transformers/generators.ts b/src/compiler/transformers/generators.ts index d6a30a81d61..58c18fdbc5d 100644 --- a/src/compiler/transformers/generators.ts +++ b/src/compiler/transformers/generators.ts @@ -1883,9 +1883,9 @@ namespace ts { return -1; } - function onSubstituteNode(node: Node, isExpression: boolean): Node { - node = previousOnSubstituteNode(node, isExpression); - if (isExpression) { + function onSubstituteNode(emitContext: EmitContext, node: Node): Node { + node = previousOnSubstituteNode(emitContext, node); + if (emitContext === EmitContext.Expression) { return substituteExpression(node); } return node; diff --git a/src/compiler/transformers/module/module.ts b/src/compiler/transformers/module/module.ts index 286ef967288..8bcf8f5fb25 100644 --- a/src/compiler/transformers/module/module.ts +++ b/src/compiler/transformers/module/module.ts @@ -822,14 +822,14 @@ namespace ts { return node.name ? getSynthesizedClone(node.name) : getGeneratedNameForNode(node); } - function onEmitNode(node: Node, emit: (node: Node) => void): void { + function onEmitNode(emitContext: EmitContext, node: Node, emitCallback: (emitContext: EmitContext, node: Node) => void): void { if (node.kind === SyntaxKind.SourceFile) { bindingNameExportSpecifiersMap = bindingNameExportSpecifiersForFileMap[getOriginalNodeId(node)]; - previousOnEmitNode(node, emit); + previousOnEmitNode(emitContext, node, emitCallback); bindingNameExportSpecifiersMap = undefined; } else { - previousOnEmitNode(node, emit); + previousOnEmitNode(emitContext, node, emitCallback); } } @@ -840,9 +840,9 @@ namespace ts { * @param isExpression A value indicating whether the node is to be used in an expression * position. */ - function onSubstituteNode(node: Node, isExpression: boolean) { - node = previousOnSubstituteNode(node, isExpression); - if (isExpression) { + function onSubstituteNode(emitContext: EmitContext, node: Node) { + node = previousOnSubstituteNode(emitContext, node); + if (emitContext === EmitContext.Expression) { return substituteExpression(node); } else if (isShorthandPropertyAssignment(node)) { diff --git a/src/compiler/transformers/module/system.ts b/src/compiler/transformers/module/system.ts index 2a14f341272..7e5b36d239c 100644 --- a/src/compiler/transformers/module/system.ts +++ b/src/compiler/transformers/module/system.ts @@ -986,14 +986,14 @@ namespace ts { // Substitutions // - function onEmitNode(node: Node, emit: (node: Node) => void): void { + function onEmitNode(emitContext: EmitContext, node: Node, emitCallback: (emitContext: EmitContext, node: Node) => void): void { if (node.kind === SyntaxKind.SourceFile) { exportFunctionForFile = exportFunctionForFileMap[getOriginalNodeId(node)]; - previousOnEmitNode(node, emit); + previousOnEmitNode(emitContext, node, emitCallback); exportFunctionForFile = undefined; } else { - previousOnEmitNode(node, emit); + previousOnEmitNode(emitContext, node, emitCallback); } } @@ -1004,9 +1004,9 @@ namespace ts { * @param isExpression A value indicating whether the node is to be used in an expression * position. */ - function onSubstituteNode(node: Node, isExpression: boolean) { - node = previousOnSubstituteNode(node, isExpression); - if (isExpression) { + function onSubstituteNode(emitContext: EmitContext, node: Node) { + node = previousOnSubstituteNode(emitContext, node); + if (emitContext === EmitContext.Expression) { return substituteExpression(node); } diff --git a/src/compiler/transformers/ts.ts b/src/compiler/transformers/ts.ts index e1892ff5bf4..225ab130d62 100644 --- a/src/compiler/transformers/ts.ts +++ b/src/compiler/transformers/ts.ts @@ -3231,7 +3231,7 @@ namespace ts { * @param node The node to emit. * @param emit A callback used to emit the node in the printer. */ - function onEmitNode(node: Node, emit: (node: Node) => void): void { + function onEmitNode(emitContext: EmitContext, node: Node, emitCallback: (emitContext: EmitContext, node: Node) => void): void { const savedApplicableSubstitutions = applicableSubstitutions; const savedCurrentSuperContainer = currentSuperContainer; // If we need to support substitutions for `super` in an async method, @@ -3248,7 +3248,7 @@ namespace ts { applicableSubstitutions |= TypeScriptSubstitutionFlags.NonQualifiedEnumMembers; } - previousOnEmitNode(node, emit); + previousOnEmitNode(emitContext, node, emitCallback); applicableSubstitutions = savedApplicableSubstitutions; currentSuperContainer = savedCurrentSuperContainer; @@ -3261,9 +3261,9 @@ namespace ts { * @param isExpression A value indicating whether the node is to be used in an expression * position. */ - function onSubstituteNode(node: Node, isExpression: boolean) { - node = previousOnSubstituteNode(node, isExpression); - if (isExpression) { + function onSubstituteNode(emitContext: EmitContext, node: Node) { + node = previousOnSubstituteNode(emitContext, node); + if (emitContext === EmitContext.Expression) { return substituteExpression(node); } else if (isShorthandPropertyAssignment(node)) { diff --git a/src/compiler/types.ts b/src/compiler/types.ts index c18ebdd7200..74e7e741be8 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -3189,6 +3189,14 @@ namespace ts { CustomPrologue = 1 << 23, // Treat the statement as if it were a prologue directive (NOTE: Prologue directives are *not* transformed). } + /* @internal */ + export const enum EmitContext { + SourceFile, // Emitting a SourceFile + Expression, // Emitting an Expression + IdentifierName, // Emitting an IdentifierName + Unspecified, // Emitting an otherwise unspecified node + } + /** Additional context provided to `visitEachChild` */ /* @internal */ export interface LexicalEnvironment {