diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index 5487057bbca..8457ea41d02 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -203,10 +203,8 @@ const _super = (function (geti, seti) { const sourceMap = createSourceMapWriter(host, writer); const { - emitStart, - emitEnd, - emitTokenStart, - emitTokenEnd + emitNodeWithSourceMap, + emitTokenWithSourceMap } = sourceMap; const comments = createCommentWriter(host, writer, sourceMap); @@ -244,10 +242,8 @@ const _super = (function (geti, seti) { // Extract helpers from the result const { - isSubstitutionEnabled, - isEmitNotificationEnabled, - onSubstituteNode, - onEmitNode + emitNodeWithSubstitution, + emitNodeWithNotification } = transformed; performance.mark("beforePrint"); @@ -448,101 +444,6 @@ const _super = (function (geti, seti) { emitNodeWithSubstitution(node, /*isExpression*/ true, emitExpressionWorker); } - /** - * Emits a node with possible emit notification. - */ - // TODO(rbuckton): Move this into transformer.ts - function emitNodeWithNotification(node: Node, emitCallback: (node: Node) => void) { - if (node) { - if (isEmitNotificationEnabled(node)) { - onEmitNode(node, emitCallback); - } - else { - emitCallback(node); - } - } - } - - /** - * Emits a node with possible source maps. - */ - // TODO(rbuckton): Move this into sourcemap.ts - function emitNodeWithSourceMap(node: Node, emitCallback: (node: Node) => void) { - if (node) { - emitStart(/*range*/ node, /*contextNode*/ node, shouldSkipLeadingSourceMapForNode, shouldSkipSourceMapForChildren, getSourceMapRange); - emitCallback(node); - emitEnd(/*range*/ node, /*contextNode*/ node, shouldSkipTrailingSourceMapForNode, shouldSkipSourceMapForChildren, getSourceMapRange); - } - } - - /** - * Determines whether to skip leading comment emit for a node. - * - * We do not emit comments for NotEmittedStatement nodes or any node that has - * NodeEmitFlags.NoLeadingComments. - * - * @param node A Node. - */ - // TODO(rbuckton): Move this into comments.ts - function shouldSkipLeadingCommentsForNode(node: Node) { - return isNotEmittedStatement(node) - || (getEmitFlags(node) & EmitFlags.NoLeadingComments) !== 0; - } - - /** - * Determines whether to skip source map emit for the start position of a node. - * - * We do not emit source maps for NotEmittedStatement nodes or any node that - * has NodeEmitFlags.NoLeadingSourceMap. - * - * @param node A Node. - */ - // TODO(rbuckton): Move this into sourcemap.ts - function shouldSkipLeadingSourceMapForNode(node: Node) { - return isNotEmittedStatement(node) - || (getEmitFlags(node) & EmitFlags.NoLeadingSourceMap) !== 0; - } - - /** - * Determines whether to skip source map emit for the end position of a node. - * - * We do not emit source maps for NotEmittedStatement nodes or any node that - * has NodeEmitFlags.NoTrailingSourceMap. - * - * @param node A Node. - */ - // TODO(rbuckton): Move this into sourcemap.ts - function shouldSkipTrailingSourceMapForNode(node: Node) { - return isNotEmittedStatement(node) - || (getEmitFlags(node) & EmitFlags.NoTrailingSourceMap) !== 0; - } - - /** - * Determines whether to skip source map emit for a node and its children. - * - * We do not emit source maps for a node that has NodeEmitFlags.NoNestedSourceMaps. - */ - // TODO(rbuckton): Move this into sourcemap.ts - function shouldSkipSourceMapForChildren(node: Node) { - return (getEmitFlags(node) & EmitFlags.NoNestedSourceMaps) !== 0; - } - - /** - * Emits a node with possible substitution. - */ - // TODO(rbuckton): Move this into transformer.ts - function emitNodeWithSubstitution(node: Node, isExpression: boolean, emitCallback: (node: Node) => void) { - if (isSubstitutionEnabled(node) && (getEmitFlags(node) & EmitFlags.NoSubstitution) === 0) { - const substitute = onSubstituteNode(node, isExpression); - if (substitute !== node) { - emitCallback(substitute); - return; - } - } - - emitCallback(node); - } - /** * Emits a node. * @@ -585,7 +486,7 @@ const _super = (function (geti, seti) { case SyntaxKind.StringKeyword: case SyntaxKind.SymbolKeyword: case SyntaxKind.GlobalKeyword: - return writeTokenNode(node); + return emitTokenNode(node); // Parse tree nodes @@ -832,7 +733,7 @@ const _super = (function (geti, seti) { case SyntaxKind.SuperKeyword: case SyntaxKind.TrueKeyword: case SyntaxKind.ThisKeyword: - return writeTokenNode(node); + return emitTokenNode(node); // Expressions case SyntaxKind.ArrayLiteralExpression: @@ -2129,7 +2030,7 @@ const _super = (function (geti, seti) { // "comment1" is not considered to be leading comment for node.initializer // but rather a trailing comment on the previous node. const initializer = node.initializer; - if (!shouldSkipLeadingCommentsForNode(initializer)) { + if ((getEmitFlags(initializer) & EmitFlags.NoLeadingComments) === 0) { const commentRange = getCommentRange(initializer); emitTrailingCommentsOfPosition(commentRange.pos); } @@ -2537,17 +2438,7 @@ const _super = (function (geti, seti) { } function writeToken(token: SyntaxKind, pos: number, contextNode?: Node) { - const tokenStartPos = emitTokenStart(token, pos, contextNode, shouldSkipLeadingSourceMapForToken, getTokenSourceMapRange); - const tokenEndPos = writeTokenText(token, tokenStartPos); - return emitTokenEnd(token, tokenEndPos, contextNode, shouldSkipTrailingSourceMapForToken, getTokenSourceMapRange); - } - - function shouldSkipLeadingSourceMapForToken(contextNode: Node) { - return (getEmitFlags(contextNode) & EmitFlags.NoTokenLeadingSourceMaps) !== 0; - } - - function shouldSkipTrailingSourceMapForToken(contextNode: Node) { - return (getEmitFlags(contextNode) & EmitFlags.NoTokenTrailingSourceMaps) !== 0; + return emitTokenWithSourceMap(contextNode, token, pos, writeTokenText); } function writeTokenText(token: SyntaxKind, pos?: number) { @@ -2556,12 +2447,12 @@ const _super = (function (geti, seti) { return positionIsSynthesized(pos) ? -1 : pos + tokenString.length; } - function writeTokenNode(node: Node) { - if (node) { - emitStart(/*range*/ node, /*contextNode*/ node, shouldSkipLeadingSourceMapForNode, shouldSkipSourceMapForChildren, getSourceMapRange); - writeTokenText(node.kind); - emitEnd(/*range*/ node, /*contextNode*/ node, shouldSkipTrailingSourceMapForNode, shouldSkipSourceMapForChildren, getSourceMapRange); - } + function emitTokenNode(node: Node) { + emitNodeWithSourceMap(node, emitTokenNodeWorker); + } + + function emitTokenNodeWorker(node: Node) { + writeTokenText(node.kind); } function increaseIndentIf(value: boolean, valueToWriteWhenNotIndenting?: string) { diff --git a/src/compiler/sourcemap.ts b/src/compiler/sourcemap.ts index eb369be39d7..3517c54b407 100644 --- a/src/compiler/sourcemap.ts +++ b/src/compiler/sourcemap.ts @@ -18,11 +18,6 @@ namespace ts { */ reset(): void; - /** - * Gets test data for source maps. - */ - getSourceMapData(): SourceMapData; - /** * Set the current source file. * @@ -41,123 +36,22 @@ namespace ts { emitPos(pos: number): void; /** - * Emits a mapping for the start of a range. + * Emits a node with possible leading and trailing source maps. * - * If the range's start position is synthetic (undefined or a negative value), no mapping - * will be created. Any trivia at the start position in the original source will be - * skipped. - * - * @param range The range to emit. + * @param node The node to emit. + * @param emitCallback The callback used to emit the node. */ - emitStart(range: TextRange): void; + emitNodeWithSourceMap(node: Node, emitCallback: (node: Node) => void): void; /** - * Emits a mapping for the start of a range. - * - * If the node's start position is synthetic (undefined or a negative value), no mapping - * will be created. Any trivia at the start position in the original source will be - * skipped. - * - * @param range The range to emit. - * @param contextNode The node for the current range. - * @param ignoreNodeCallback A callback used to determine whether to skip source map - * emit for the start position of this node. - * @param ignoreChildrenCallback A callback used to determine whether to skip source - * map emit for all children of this node. - * @param getTextRangeCallbackCallback A callback used to get a custom source map - * range for this node. - */ - emitStart(range: TextRange, contextNode: Node, ignoreNodeCallback: (node: Node) => boolean, ignoreChildrenCallback: (node: Node) => boolean, getTextRangeCallbackCallback: (node: Node) => TextRange): void; - - /** - * Emits a mapping for the end of a range. - * - * If the range's end position is synthetic (undefined or a negative value), no mapping - * will be created. - * - * @param range The range to emit. - */ - emitEnd(range: TextRange): void; - - /** - * Emits a mapping for the end of a range. - * - * If the node's end position is synthetic (undefined or a negative value), no mapping - * will be created. - * - * @param range The range to emit. - * @param contextNode The node for the current range. - * @param ignoreNodeCallback A callback used to determine whether to skip source map - * emit for the end position of this node. - * @param ignoreChildrenCallback A callback used to determine whether to skip source - * map emit for all children of this node. - * @param getTextRangeCallbackCallback A callback used to get a custom source map - * range for this node. - */ - emitEnd(range: TextRange, contextNode: Node, ignoreNodeCallback: (node: Node) => boolean, ignoreChildrenCallback: (node: Node) => boolean, getTextRangeCallbackCallback: (node: Node) => TextRange): void; - - /** - * Emits a mapping for the start position of a token. - * - * If the token's start position is synthetic (undefined or a negative value), no mapping - * will be created. Any trivia at the start position in the original source will be - * skipped. + * Emits a token of a node node with possible leading and trailing source maps. * + * @param node The node containing the token. * @param token The token to emit. - * @param tokenStartPos The start position of the token. - * @returns The start position of the token, following any trivia. + * @param tokenStartPos The start pos of the token. + * @param emitCallback The callback used to emit the token. */ - emitTokenStart(token: SyntaxKind, tokenStartPos: number): number; - - /** - * Emits a mapping for the start position of a token. - * - * If the token's start position is synthetic (undefined or a negative value), no mapping - * will be created. Any trivia at the start position in the original source will be - * skipped. - * - * @param token The token to emit. - * @param tokenStartPos The start position of the token. - * @param contextNode The node containing this token. - * @param ignoreTokenCallback A callback used to determine whether to skip source map - * emit for the start position of this token. - * @param getTokenTextRangeCallback A callback used to get a custom source - * map range for this node. - * @returns The start position of the token, following any trivia. - */ - emitTokenStart(token: SyntaxKind, tokenStartPos: number, contextNode: Node, ignoreTokenCallback: (node: Node, token: SyntaxKind) => boolean, getTokenTextRangeCallback: (node: Node, token: SyntaxKind) => TextRange): number; - - /** - * Emits a mapping for the end position of a token. - * - * If the token's end position is synthetic (undefined or a negative value), no mapping - * will be created. - * - * @param token The token to emit. - * @param tokenEndPos The end position of the token. - * @returns The end position of the token. - */ - emitTokenEnd(token: SyntaxKind, tokenEndPos: number): number; - - /** - * Emits a mapping for the end position of a token. - * - * If the token's end position is synthetic (undefined or a negative value), no mapping - * will be created. - * - * @param token The token to emit. - * @param tokenEndPos The end position of the token. - * @param contextNode The node containing this token. - * @param ignoreTokenCallback A callback used to determine whether to skip source map - * emit for the end position of this token. - * @param getTokenTextRangeCallback A callback used to get a custom source - * map range for this node. - * @returns The end position of the token. - */ - emitTokenEnd(token: SyntaxKind, tokenEndPos: number, contextNode: Node, ignoreTokenCallback: (node: Node, token: SyntaxKind) => boolean, getTokenTextRangeCallback: (node: Node, token: SyntaxKind) => TextRange): number; - - /*@deprecated*/ changeEmitSourcePos(): void; - /*@deprecated*/ stopOverridingSpan(): void; + emitTokenWithSourceMap(node: Node, token: SyntaxKind, tokenStartPos: number, emitCallback: (token: SyntaxKind, tokenStartPos: number) => number): number; /** * Gets the text for the source map. @@ -168,44 +62,11 @@ namespace ts { * Gets the SourceMappingURL for the source map. */ getSourceMappingURL(): string; - } - export function createSourceMapWriter(host: EmitHost, writer: EmitTextWriter): SourceMapWriter { - const compilerOptions = host.getCompilerOptions(); - if (compilerOptions.sourceMap || compilerOptions.inlineSourceMap) { - if (compilerOptions.extendedDiagnostics) { - return createSourceMapWriterWithExtendedDiagnostics(host, writer); - } - - return createSourceMapWriterWorker(host, writer); - } - else { - return getNullSourceMapWriter(); - } - } - - let nullSourceMapWriter: SourceMapWriter; - - export function getNullSourceMapWriter(): SourceMapWriter { - if (nullSourceMapWriter === undefined) { - nullSourceMapWriter = { - initialize(filePath: string, sourceMapFilePath: string, sourceFiles: SourceFile[], isBundledEmit: boolean): void { }, - reset(): void { }, - getSourceMapData(): SourceMapData { return undefined; }, - setSourceFile(sourceFile: SourceFile): void { }, - emitPos(pos: number): void { }, - emitStart(range: TextRange, contextNode?: Node, ignoreNodeCallback?: (node: Node) => boolean, ignoreChildrenCallback?: (node: Node) => boolean, getTextRangeCallback?: (node: Node) => TextRange): void { }, - emitEnd(range: TextRange, contextNode?: Node, ignoreNodeCallback?: (node: Node) => boolean, ignoreChildrenCallback?: (node: Node) => boolean, getTextRangeCallback?: (node: Node) => TextRange): void { }, - emitTokenStart(token: SyntaxKind, pos: number, contextNode?: Node, ignoreTokenCallback?: (node: Node) => boolean, getTokenTextRangeCallback?: (node: Node, token: SyntaxKind) => TextRange): number { return -1; }, - emitTokenEnd(token: SyntaxKind, end: number, contextNode?: Node, ignoreTokenCallback?: (node: Node) => boolean, getTokenTextRangeCallback?: (node: Node, token: SyntaxKind) => TextRange): number { return -1; }, - changeEmitSourcePos(): void { }, - stopOverridingSpan(): void { }, - getText(): string { return undefined; }, - getSourceMappingURL(): string { return undefined; } - }; - } - - return nullSourceMapWriter; + /** + * Gets test data for source maps. + */ + getSourceMapData(): SourceMapData; } // Used for initialize lastEncodedSourceMapSpan and reset lastEncodedSourceMapSpan when updateLastEncodedAndRecordedSpans @@ -217,14 +78,12 @@ namespace ts { sourceIndex: 0 }; - function createSourceMapWriterWorker(host: EmitHost, writer: EmitTextWriter): SourceMapWriter { + export function createSourceMapWriter(host: EmitHost, writer: EmitTextWriter): SourceMapWriter { const compilerOptions = host.getCompilerOptions(); const extendedDiagnostics = compilerOptions.extendedDiagnostics; let currentSourceFile: SourceFile; let currentSourceText: string; let sourceMapDir: string; // The directory in which sourcemap will be - let stopOverridingSpan = false; - let modifyLastSourcePos = false; // Current source map file and its index in the sources list let sourceMapSourceIndex: number; @@ -236,13 +95,7 @@ namespace ts { // Source map data let sourceMapData: SourceMapData; - - // This keeps track of the number of times `disable` has been called without a - // corresponding call to `enable`. As long as this value is non-zero, mappings will not - // be recorded. - // This is primarily used to provide a better experience when debugging binding - // patterns and destructuring assignments for simple expressions. - let disableDepth: number; + let disabled: boolean = !(compilerOptions.sourceMap || compilerOptions.inlineSourceMap); return { initialize, @@ -250,12 +103,8 @@ namespace ts { getSourceMapData: () => sourceMapData, setSourceFile, emitPos, - emitStart, - emitEnd, - emitTokenStart, - emitTokenEnd, - changeEmitSourcePos, - stopOverridingSpan: () => stopOverridingSpan = true, + emitNodeWithSourceMap, + emitTokenWithSourceMap, getText, getSourceMappingURL, }; @@ -269,13 +118,16 @@ namespace ts { * @param isBundledEmit A value indicating whether the generated output file is a bundle. */ function initialize(filePath: string, sourceMapFilePath: string, sourceFiles: SourceFile[], isBundledEmit: boolean) { + if (disabled) { + return; + } + if (sourceMapData) { reset(); } currentSourceFile = undefined; currentSourceText = undefined; - disableDepth = 0; // Current source map file and its index in the sources list sourceMapSourceIndex = -1; @@ -338,6 +190,10 @@ namespace ts { * Reset the SourceMapWriter to an empty state. */ function reset() { + if (disabled) { + return; + } + currentSourceFile = undefined; sourceMapDir = undefined; sourceMapSourceIndex = undefined; @@ -345,64 +201,6 @@ namespace ts { lastEncodedSourceMapSpan = undefined; lastEncodedNameIndex = undefined; sourceMapData = undefined; - disableDepth = 0; - } - - /** - * Re-enables the recording of mappings. - */ - function enable() { - if (disableDepth > 0) { - disableDepth--; - } - } - - /** - * Disables the recording of mappings. - */ - function disable() { - disableDepth++; - } - - function updateLastEncodedAndRecordedSpans() { - if (modifyLastSourcePos) { - // Reset the source pos - modifyLastSourcePos = false; - - // Change Last recorded Map with last encoded emit line and character - lastRecordedSourceMapSpan.emittedLine = lastEncodedSourceMapSpan.emittedLine; - lastRecordedSourceMapSpan.emittedColumn = lastEncodedSourceMapSpan.emittedColumn; - - // Pop sourceMapDecodedMappings to remove last entry - sourceMapData.sourceMapDecodedMappings.pop(); - - // Point the lastEncodedSourceMapSpace to the previous encoded sourceMapSpan - // If the list is empty which indicates that we are at the beginning of the file, - // we have to reset it to default value (same value when we first initialize sourceMapWriter) - lastEncodedSourceMapSpan = sourceMapData.sourceMapDecodedMappings.length ? - sourceMapData.sourceMapDecodedMappings[sourceMapData.sourceMapDecodedMappings.length - 1] : - defaultLastEncodedSourceMapSpan; - - // TODO: Update lastEncodedNameIndex - // Since we dont support this any more, lets not worry about it right now. - // When we start supporting nameIndex, we will get back to this - - // Change the encoded source map - const sourceMapMappings = sourceMapData.sourceMapMappings; - let lenthToSet = sourceMapMappings.length - 1; - for (; lenthToSet >= 0; lenthToSet--) { - const currentChar = sourceMapMappings.charAt(lenthToSet); - if (currentChar === ",") { - // Separator for the entry found - break; - } - if (currentChar === ";" && lenthToSet !== 0 && sourceMapMappings.charAt(lenthToSet - 1) !== ";") { - // Last line separator found - break; - } - } - sourceMapData.sourceMapMappings = sourceMapMappings.substr(0, Math.max(0, lenthToSet)); - } } // Encoding for sourcemap span @@ -459,7 +257,7 @@ namespace ts { * @param pos The position. */ function emitPos(pos: number) { - if (positionIsSynthesized(pos) || disableDepth > 0) { + if (disabled || positionIsSynthesized(pos)) { return; } @@ -495,209 +293,89 @@ namespace ts { sourceColumn: sourceLinePos.character, sourceIndex: sourceMapSourceIndex }; - - stopOverridingSpan = false; } - else if (!stopOverridingSpan) { + else { // Take the new pos instead since there is no change in emittedLine and column since last location lastRecordedSourceMapSpan.sourceLine = sourceLinePos.line; lastRecordedSourceMapSpan.sourceColumn = sourceLinePos.character; lastRecordedSourceMapSpan.sourceIndex = sourceMapSourceIndex; } - updateLastEncodedAndRecordedSpans(); - if (extendedDiagnostics) { performance.mark("afterSourcemap"); performance.measure("Source Map", "beforeSourcemap", "afterSourcemap"); } } - function getStartPosPastDecorators(range: TextRange) { - const rangeHasDecorators = !!(range as Node).decorators; - return skipTrivia(currentSourceText, rangeHasDecorators ? (range as Node).decorators.end : range.pos); - } - /** - * Emits a mapping for the start of a range. + * Emits a node with possible leading and trailing source maps. * - * If the range's start position is synthetic (undefined or a negative value), no mapping - * will be created. Any trivia at the start position in the original source will be - * skipped. - * - * @param range The range to emit.0 + * @param node The node to emit. + * @param emitCallback The callback used to emit the node. */ - function emitStart(range: TextRange): void; - /** - * Emits a mapping for the start of a range. - * - * If the node's start position is synthetic (undefined or a negative value), no mapping - * will be created. Any trivia at the start position in the original source will be - * skipped. - * - * @param range The range to emit. - * @param contextNode The node for the current range. - * @param ignoreNodeCallback A callback used to determine whether to skip source map - * emit for the start position of this node. - * @param ignoreChildrenCallback A callback used to determine whether to skip source - * map emit for all children of this node. - * @param getTextRangeCallbackCallback A callback used to get a custom source map - * range for this node. - */ - function emitStart(range: TextRange, contextNode: Node, ignoreNodeCallback: (node: Node) => boolean, ignoreChildrenCallback: (node: Node) => boolean, getTextRangeCallback: (node: Node) => TextRange): void; - function emitStart(range: TextRange, contextNode?: Node, ignoreNodeCallback?: (node: Node) => boolean, ignoreChildrenCallback?: (node: Node) => boolean, getTextRangeCallback?: (node: Node) => TextRange) { - if (contextNode) { - if (!ignoreNodeCallback(contextNode)) { - range = getTextRangeCallback(contextNode) || range; - emitPos(getStartPosPastDecorators(range)); - } - - if (ignoreChildrenCallback(contextNode)) { - disable(); - } + function emitNodeWithSourceMap(node: Node, emitCallback: (node: Node) => void) { + if (disabled) { + return emitCallback(node); } - else { - emitPos(getStartPosPastDecorators(range)); + + if (node) { + const emitNode = node.emitNode; + const emitFlags = emitNode && emitNode.flags; + const { pos, end } = emitNode && emitNode.sourceMapRange || node; + + if (node.kind !== SyntaxKind.NotEmittedStatement + && (emitFlags & EmitFlags.NoLeadingSourceMap) === 0 + && pos >= 0) { + emitPos(skipTrivia(currentSourceText, pos)); + } + + if (emitFlags & EmitFlags.NoNestedSourceMaps) { + disabled = true; + emitCallback(node); + disabled = false; + } + else { + emitCallback(node); + } + + if (node.kind !== SyntaxKind.NotEmittedStatement + && (emitFlags & EmitFlags.NoTrailingSourceMap) === 0 + && end >= 0) { + emitPos(end); + } } } /** - * Emits a mapping for the end of a range. - * - * If the range's end position is synthetic (undefined or a negative value), no mapping - * will be created. - * - * @param range The range to emit. - */ - function emitEnd(range: TextRange): void; - /** - * Emits a mapping for the end of a range. - * - * If the node's end position is synthetic (undefined or a negative value), no mapping - * will be created. - * - * @param range The range to emit. - * @param contextNode The node for the current range. - * @param ignoreNodeCallback A callback used to determine whether to skip source map - * emit for the end position of this node. - * @param ignoreChildrenCallback A callback used to determine whether to skip source - * map emit for all children of this node. - * @param getTextRangeCallbackCallback A callback used to get a custom source map - * range for this node. - */ - function emitEnd(range: TextRange, contextNode: Node, ignoreNodeCallback: (node: Node) => boolean, ignoreChildrenCallback: (node: Node) => boolean, getTextRangeCallback: (node: Node) => TextRange): void; - function emitEnd(range: TextRange, contextNode?: Node, ignoreNodeCallback?: (node: Node) => boolean, ignoreChildrenCallback?: (node: Node) => boolean, getTextRangeCallback?: (node: Node) => TextRange) { - if (contextNode) { - if (ignoreChildrenCallback(contextNode)) { - enable(); - } - - if (!ignoreNodeCallback(contextNode)) { - range = getTextRangeCallback(contextNode) || range; - emitPos(range.end); - } - } - else { - emitPos(range.end); - } - - stopOverridingSpan = false; - } - - /** - * Emits a mapping for the start position of a token. - * - * If the token's start position is synthetic (undefined or a negative value), no mapping - * will be created. Any trivia at the start position in the original source will be - * skipped. + * Emits a token of a node node with possible leading and trailing source maps. * + * @param node The node containing the token. * @param token The token to emit. - * @param tokenStartPos The start position of the token. - * @returns The start position of the token, following any trivia. + * @param tokenStartPos The start pos of the token. + * @param emitCallback The callback used to emit the token. */ - function emitTokenStart(token: SyntaxKind, tokenStartPos: number): number; - /** - * Emits a mapping for the start position of a token. - * - * If the token's start position is synthetic (undefined or a negative value), no mapping - * will be created. Any trivia at the start position in the original source will be - * skipped. - * - * @param token The token to emit. - * @param tokenStartPos The start position of the token. - * @param contextNode The node containing this token. - * @param ignoreTokenCallback A callback used to determine whether to skip source map - * emit for the start position of this token. - * @param getTokenTextRangeCallback A callback used to get a custom source - * map range for this node. - * @returns The start position of the token, following any trivia. - */ - function emitTokenStart(token: SyntaxKind, tokenStartPos: number, contextNode: Node, ignoreTokenCallback: (node: Node, token: SyntaxKind) => boolean, getTokenTextRangeCallback: (node: Node, token: SyntaxKind) => TextRange): number; - function emitTokenStart(token: SyntaxKind, tokenStartPos: number, contextNode?: Node, ignoreTokenCallback?: (node: Node, token: SyntaxKind) => boolean, getTokenTextRangeCallback?: (node: Node, token: SyntaxKind) => TextRange): number { - if (contextNode) { - if (ignoreTokenCallback(contextNode, token)) { - return skipTrivia(currentSourceText, tokenStartPos); - } - - const range = getTokenTextRangeCallback(contextNode, token); - if (range) { - tokenStartPos = range.pos; - } + function emitTokenWithSourceMap(node: Node, token: SyntaxKind, tokenPos: number, emitCallback: (token: SyntaxKind, tokenStartPos: number) => number) { + if (disabled) { + return emitCallback(token, tokenPos); } - tokenStartPos = skipTrivia(currentSourceText, tokenStartPos); - emitPos(tokenStartPos); - return tokenStartPos; - } + const emitNode = node && node.emitNode; + const emitFlags = emitNode && emitNode.flags; + const range = emitNode && emitNode.tokenSourceMapRanges && emitNode.tokenSourceMapRanges[token]; - /** - * Emits a mapping for the end position of a token. - * - * If the token's end position is synthetic (undefined or a negative value), no mapping - * will be created. - * - * @param token The token to emit. - * @param tokenEndPos The end position of the token. - * @returns The end position of the token. - */ - function emitTokenEnd(token: SyntaxKind, tokenEndPos: number): number; - /** - * Emits a mapping for the end position of a token. - * - * If the token's end position is synthetic (undefined or a negative value), no mapping - * will be created. - * - * @param token The token to emit. - * @param tokenEndPos The end position of the token. - * @param contextNode The node containing this token. - * @param ignoreTokenCallback A callback used to determine whether to skip source map - * emit for the end position of this token. - * @param getTokenTextRangeCallback A callback used to get a custom source - * map range for this node. - * @returns The end position of the token. - */ - function emitTokenEnd(token: SyntaxKind, tokenEndPos: number, contextNode: Node, ignoreTokenCallback: (node: Node, token: SyntaxKind) => boolean, getTokenTextRangeCallback: (node: Node, token: SyntaxKind) => TextRange): number; - function emitTokenEnd(token: SyntaxKind, tokenEndPos: number, contextNode?: Node, ignoreTokenCallback?: (node: Node, token: SyntaxKind) => boolean, getTokenTextRangeCallback?: (node: Node, token: SyntaxKind) => TextRange): number { - if (contextNode) { - if (ignoreTokenCallback(contextNode, token)) { - return tokenEndPos; - } - - const range = getTokenTextRangeCallback(contextNode, token); - if (range) { - tokenEndPos = range.end; - } + tokenPos = skipTrivia(currentSourceText, range ? range.pos : tokenPos); + if ((emitFlags & EmitFlags.NoTokenLeadingSourceMaps) === 0 && tokenPos >= 0) { + emitPos(tokenPos); } - emitPos(tokenEndPos); - return tokenEndPos; - } + tokenPos = emitCallback(token, tokenPos); + if (range) tokenPos = range.end; + if ((emitFlags & EmitFlags.NoTokenTrailingSourceMaps) === 0 && tokenPos >= 0) { + emitPos(tokenPos); + } - // @deprecated - function changeEmitSourcePos() { - Debug.assert(!modifyLastSourcePos); - modifyLastSourcePos = true; + return tokenPos; } /** @@ -706,6 +384,10 @@ namespace ts { * @param sourceFile The source file. */ function setSourceFile(sourceFile: SourceFile) { + if (disabled) { + return; + } + currentSourceFile = sourceFile; currentSourceText = currentSourceFile.text; @@ -738,6 +420,10 @@ namespace ts { * Gets the text for the source map. */ function getText() { + if (disabled) { + return; + } + encodeLastRecordedSourceMapSpan(); return stringify({ @@ -755,6 +441,10 @@ namespace ts { * Gets the SourceMappingURL for the source map. */ function getSourceMappingURL() { + if (disabled) { + return; + } + if (compilerOptions.inlineSourceMap) { // Encode the sourceMap into the sourceMap url const base64SourceMapText = convertToBase64(getText()); @@ -766,61 +456,6 @@ namespace ts { } } - function createSourceMapWriterWithExtendedDiagnostics(host: EmitHost, writer: EmitTextWriter): SourceMapWriter { - const { - initialize, - reset, - getSourceMapData, - setSourceFile, - emitPos, - emitStart, - emitEnd, - emitTokenStart, - emitTokenEnd, - changeEmitSourcePos, - stopOverridingSpan, - getText, - getSourceMappingURL, - } = createSourceMapWriterWorker(host, writer); - return { - initialize, - reset, - getSourceMapData, - setSourceFile, - emitPos(pos: number): void { - performance.mark("sourcemapStart"); - emitPos(pos); - performance.measure("sourceMapTime", "sourcemapStart"); - }, - emitStart(range: TextRange, contextNode?: Node, ignoreNodeCallback?: (node: Node) => boolean, ignoreChildrenCallback?: (node: Node) => boolean, getTextRangeCallback?: (node: Node) => TextRange): void { - performance.mark("emitSourcemap:emitStart"); - emitStart(range, contextNode, ignoreNodeCallback, ignoreChildrenCallback, getTextRangeCallback); - performance.measure("sourceMapTime", "emitSourcemap:emitStart"); - }, - emitEnd(range: TextRange, contextNode?: Node, ignoreNodeCallback?: (node: Node) => boolean, ignoreChildrenCallback?: (node: Node) => boolean, getTextRangeCallback?: (node: Node) => TextRange): void { - performance.mark("emitSourcemap:emitEnd"); - emitEnd(range, contextNode, ignoreNodeCallback, ignoreChildrenCallback, getTextRangeCallback); - performance.measure("sourceMapTime", "emitSourcemap:emitEnd"); - }, - emitTokenStart(token: SyntaxKind, tokenStartPos: number, contextNode?: Node, ignoreTokenCallback?: (node: Node) => boolean, getTokenTextRangeCallback?: (node: Node, token: SyntaxKind) => TextRange): number { - performance.mark("emitSourcemap:emitTokenStart"); - tokenStartPos = emitTokenStart(token, tokenStartPos, contextNode, ignoreTokenCallback, getTokenTextRangeCallback); - performance.measure("sourceMapTime", "emitSourcemap:emitTokenStart"); - return tokenStartPos; - }, - emitTokenEnd(token: SyntaxKind, tokenEndPos: number, contextNode?: Node, ignoreTokenCallback?: (node: Node) => boolean, getTokenTextRangeCallback?: (node: Node, token: SyntaxKind) => TextRange): number { - performance.mark("emitSourcemap:emitTokenEnd"); - tokenEndPos = emitTokenEnd(token, tokenEndPos, contextNode, ignoreTokenCallback, getTokenTextRangeCallback); - performance.measure("sourceMapTime", "emitSourcemap:emitTokenEnd"); - return tokenEndPos; - }, - changeEmitSourcePos, - stopOverridingSpan, - getText, - getSourceMappingURL, - }; - } - const base64Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; function base64FormatEncode(inValue: number) { diff --git a/src/compiler/transformer.ts b/src/compiler/transformer.ts index 8c6fae458f0..5695bea7ae9 100644 --- a/src/compiler/transformer.ts +++ b/src/compiler/transformer.ts @@ -31,33 +31,21 @@ namespace ts { getSourceFiles(): SourceFile[]; /** - * Determines whether expression substitutions are enabled for the provided node. - */ - isSubstitutionEnabled(node: Node): boolean; - - /** - * Determines whether before/after emit notifications should be raised in the pretty - * printer when it emits a node. - */ - isEmitNotificationEnabled(node: Node): boolean; - - /** - * Hook used by transformers to substitute expressions just before they - * are emitted by the pretty printer. + * Emits the substitute for a node, if one is available; otherwise, emits the node. * * @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. */ - onSubstituteNode(node: Node, isExpression: boolean): Node; + emitNodeWithSubstitution(node: Node, isExpression: boolean, emitCallback: (node: Node) => void): void; /** - * Hook used to allow transformers to capture state before or after - * the printer emits a node. + * Emits a node with possible notification. * * @param node The node to emit. * @param emitCallback A callback used to emit the node. */ - onEmitNode(node: Node, emitCallback: (node: Node) => void): void; + emitNodeWithNotification(node: Node, emitCallback: (node: Node) => void): void; /** * Reset transient transformation properties on parse tree nodes. @@ -302,10 +290,10 @@ namespace ts { hoistFunctionDeclaration, startLexicalEnvironment, endLexicalEnvironment, - onSubstituteNode, + onSubstituteNode: (node, isExpression) => node, enableSubstitution, isSubstitutionEnabled, - onEmitNode, + onEmitNode: (node, emitCallback) => emitCallback(node), enableEmitNotification, isEmitNotificationEnabled }; @@ -321,10 +309,8 @@ namespace ts { return { getSourceFiles: () => transformed, - isSubstitutionEnabled, - isEmitNotificationEnabled, - onSubstituteNode: context.onSubstituteNode, - onEmitNode: context.onEmitNode, + 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 @@ -362,18 +348,29 @@ namespace ts { * Determines whether expression substitutions are enabled for the provided node. */ function isSubstitutionEnabled(node: Node) { - return (enabledSyntaxKindFeatures[node.kind] & SyntaxKindFeatureFlags.Substitution) !== 0; + return (enabledSyntaxKindFeatures[node.kind] & SyntaxKindFeatureFlags.Substitution) !== 0 + && (getEmitFlags(node) & EmitFlags.NoSubstitution) === 0; } /** - * Default hook for node substitutions. + * Emits a node with possible substitution. * - * @param node The node to substitute. - * @param isExpression A value indicating whether the node is to be used in an expression - * position. + * @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 onSubstituteNode(node: Node, isExpression: boolean) { - return node; + function emitNodeWithSubstitution(node: Node, isExpression: boolean, emitCallback: (node: Node) => void) { + if (node) { + if (isSubstitutionEnabled(node)) { + const substitute = context.onSubstituteNode(node, isExpression); + if (substitute && substitute !== node) { + emitCallback(substitute); + return; + } + } + + emitCallback(node); + } } /** @@ -393,13 +390,17 @@ namespace ts { } /** - * Default hook for node emit. - * - * @param node The node to emit. - * @param emit A callback used to emit the node in the printer. + * Emits a node with possible emit notification. */ - function onEmitNode(node: Node, emit: (node: Node) => void) { - emit(node); + function emitNodeWithNotification(node: Node, emitCallback: (node: Node) => void) { + if (node) { + if (isEmitNotificationEnabled(node)) { + context.onEmitNode(node, emitCallback); + } + else { + emitCallback(node); + } + } } /**