Clean up SourceMapWriter and emitter.

This commit is contained in:
Ron Buckton 2016-09-26 13:52:09 -07:00
parent f2de4508df
commit c1ee534974
3 changed files with 141 additions and 614 deletions

View File

@ -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) {

View File

@ -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) {

View File

@ -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);
}
}
}
/**