mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-02-06 11:54:44 -06:00
* Remove SymbolWriter, give methods to EmitTextWriter * Unification of writers is done-ish * Make node builder support more flags * Write out mixins like we used to * Accept prototype-free baselines * Use instantiated constraint when building mapped type nodes * Accept better mapped type baselines * Report inaccessible this in node builder * Turns out there was a bug in our codefix, too * Symbol display builder usage falling * Replace signatureToString with a nodeBuilder solution * Replace the last internal usages of the symbol writer * Accept semicolon additions * Accept updated symbol baseline output * Start using node builder for some LS operations * Remove those pesky trailing semicolons on signatures * Get signature printing much closer to old output * Parameter lists should not be indented by default, especially when single-line * Signatures up to snuff * Type quickinfo emit is up to snuff * Start of symbol writer replacement, needs a bit more for full compat * Slightly mor accurate to old behavior * Replicate qualified name type argument output correctly * Bring back the old symbol baselines * Mostly identical to old symbol emit now * Perfectly matches old behavior thus far * Replace another usage of the symbol builder * Another usage removed * Another usage removed * Remove final uses of symbol display builder * Remove implementation and types for unused symbol display builder * Cleanup in the checker * monomorphize new emitter code * Replace emitWithSuffix * Push space character to interface with writer * List emit * Fix lack of usage of emitExpression * writeList, not printList * Remove listy writes and replace with new printer calls * Move ListFormat into types.ts * Remove most new XToString functions in favor of node builder functions * Accept API breaks * Add getSymbolDisplayBuilder polyfill * Accept updated API baseline * Move definition to make diff easier to read * Reinternalize some things * Remove trailign whitespace * Reorder for zero diff * Remove newline * Make shim mor eperfectly imitate old behavior * Style feedback * Rename reset to clear to maintain backcompat with SymbolWriter * Fix quickfix * Keep EmitTextWriter internal * Remove EmitTextWriter from public API * Mimic default name declaration emit fix * Fix tests broken by merge * use isFunctionLike * Cleanup, sync TypeFormat and NodeBuilder flags * Reorder Node initialization so pos and end are first, so a TextRange hidden class is made first to reduce future polymorphism * Use variable instead of ternary * Write helper for emitNodeWithWriter * Emitter cleanup * Cleanup whitespace, comment * Reuse printer * Raise error if display parts writer uses rawWrite * Hide writer parameter through different function instead of overload, rename function in emitter * Make less printer * fix lint
514 lines
21 KiB
TypeScript
514 lines
21 KiB
TypeScript
/// <reference path="checker.ts"/>
|
|
|
|
/* @internal */
|
|
namespace ts {
|
|
export interface SourceMapWriter {
|
|
/**
|
|
* Initialize the SourceMapWriter for a new output file.
|
|
*
|
|
* @param filePath The path to the generated output file.
|
|
* @param sourceMapFilePath The path to the output source map file.
|
|
* @param sourceFileOrBundle The input source file or bundle for the program.
|
|
*/
|
|
initialize(filePath: string, sourceMapFilePath: string, sourceFileOrBundle: SourceFile | Bundle): void;
|
|
|
|
/**
|
|
* Reset the SourceMapWriter to an empty state.
|
|
*/
|
|
reset(): void;
|
|
|
|
/**
|
|
* Set the current source file.
|
|
*
|
|
* @param sourceFile The source file.
|
|
*/
|
|
setSourceFile(sourceFile: SourceMapSource): void;
|
|
|
|
/**
|
|
* Emits a mapping.
|
|
*
|
|
* If the position is synthetic (undefined or a negative value), no mapping will be
|
|
* created.
|
|
*
|
|
* @param pos The position.
|
|
*/
|
|
emitPos(pos: number): void;
|
|
|
|
/**
|
|
* Emits a node with possible leading and trailing source maps.
|
|
*
|
|
* @param hint The current emit context
|
|
* @param node The node to emit.
|
|
* @param emitCallback The callback used to emit the node.
|
|
*/
|
|
emitNodeWithSourceMap(hint: EmitHint, node: Node, emitCallback: (hint: EmitHint, node: Node) => void): void;
|
|
|
|
/**
|
|
* Emits a token of a node node with possible leading and trailing source maps.
|
|
*
|
|
* @param node The node containing the token.
|
|
* @param token The token to emit.
|
|
* @param tokenStartPos The start pos of the token.
|
|
* @param emitCallback The callback used to emit the token.
|
|
*/
|
|
emitTokenWithSourceMap(node: Node, token: SyntaxKind, writer: (s: string) => void, tokenStartPos: number, emitCallback: (token: SyntaxKind, writer: (s: string) => void, tokenStartPos: number) => number): number;
|
|
|
|
/**
|
|
* Gets the text for the source map.
|
|
*/
|
|
getText(): string;
|
|
|
|
/**
|
|
* Gets the SourceMappingURL for the source map.
|
|
*/
|
|
getSourceMappingURL(): string;
|
|
|
|
/**
|
|
* Gets test data for source maps.
|
|
*/
|
|
getSourceMapData(): SourceMapData;
|
|
}
|
|
|
|
// Used for initialize lastEncodedSourceMapSpan and reset lastEncodedSourceMapSpan when updateLastEncodedAndRecordedSpans
|
|
const defaultLastEncodedSourceMapSpan: SourceMapSpan = {
|
|
emittedLine: 1,
|
|
emittedColumn: 1,
|
|
sourceLine: 1,
|
|
sourceColumn: 1,
|
|
sourceIndex: 0
|
|
};
|
|
|
|
export function createSourceMapWriter(host: EmitHost, writer: EmitTextWriter): SourceMapWriter {
|
|
const compilerOptions = host.getCompilerOptions();
|
|
const extendedDiagnostics = compilerOptions.extendedDiagnostics;
|
|
let currentSource: SourceMapSource;
|
|
let currentSourceText: string;
|
|
let sourceMapDir: string; // The directory in which sourcemap will be
|
|
|
|
// Current source map file and its index in the sources list
|
|
let sourceMapSourceIndex: number;
|
|
|
|
// Last recorded and encoded spans
|
|
let lastRecordedSourceMapSpan: SourceMapSpan;
|
|
let lastEncodedSourceMapSpan: SourceMapSpan;
|
|
let lastEncodedNameIndex: number;
|
|
|
|
// Source map data
|
|
let sourceMapData: SourceMapData;
|
|
let disabled: boolean = !(compilerOptions.sourceMap || compilerOptions.inlineSourceMap);
|
|
|
|
return {
|
|
initialize,
|
|
reset,
|
|
getSourceMapData: () => sourceMapData,
|
|
setSourceFile,
|
|
emitPos,
|
|
emitNodeWithSourceMap,
|
|
emitTokenWithSourceMap,
|
|
getText,
|
|
getSourceMappingURL,
|
|
};
|
|
|
|
/**
|
|
* Skips trivia such as comments and white-space that can optionally overriden by the source map source
|
|
*/
|
|
function skipSourceTrivia(pos: number): number {
|
|
return currentSource.skipTrivia ? currentSource.skipTrivia(pos) : skipTrivia(currentSourceText, pos);
|
|
}
|
|
|
|
/**
|
|
* Initialize the SourceMapWriter for a new output file.
|
|
*
|
|
* @param filePath The path to the generated output file.
|
|
* @param sourceMapFilePath The path to the output source map file.
|
|
* @param sourceFileOrBundle The input source file or bundle for the program.
|
|
*/
|
|
function initialize(filePath: string, sourceMapFilePath: string, sourceFileOrBundle: SourceFile | Bundle) {
|
|
if (disabled) {
|
|
return;
|
|
}
|
|
|
|
if (sourceMapData) {
|
|
reset();
|
|
}
|
|
|
|
currentSource = undefined;
|
|
currentSourceText = undefined;
|
|
|
|
// Current source map file and its index in the sources list
|
|
sourceMapSourceIndex = -1;
|
|
|
|
// Last recorded and encoded spans
|
|
lastRecordedSourceMapSpan = undefined;
|
|
lastEncodedSourceMapSpan = defaultLastEncodedSourceMapSpan;
|
|
lastEncodedNameIndex = 0;
|
|
|
|
// Initialize source map data
|
|
sourceMapData = {
|
|
sourceMapFilePath,
|
|
jsSourceMappingURL: !compilerOptions.inlineSourceMap ? getBaseFileName(normalizeSlashes(sourceMapFilePath)) : undefined,
|
|
sourceMapFile: getBaseFileName(normalizeSlashes(filePath)),
|
|
sourceMapSourceRoot: compilerOptions.sourceRoot || "",
|
|
sourceMapSources: [],
|
|
inputSourceFileNames: [],
|
|
sourceMapNames: [],
|
|
sourceMapMappings: "",
|
|
sourceMapSourcesContent: compilerOptions.inlineSources ? [] : undefined,
|
|
sourceMapDecodedMappings: []
|
|
};
|
|
|
|
// Normalize source root and make sure it has trailing "/" so that it can be used to combine paths with the
|
|
// relative paths of the sources list in the sourcemap
|
|
sourceMapData.sourceMapSourceRoot = ts.normalizeSlashes(sourceMapData.sourceMapSourceRoot);
|
|
if (sourceMapData.sourceMapSourceRoot.length && sourceMapData.sourceMapSourceRoot.charCodeAt(sourceMapData.sourceMapSourceRoot.length - 1) !== CharacterCodes.slash) {
|
|
sourceMapData.sourceMapSourceRoot += directorySeparator;
|
|
}
|
|
|
|
if (compilerOptions.mapRoot) {
|
|
sourceMapDir = normalizeSlashes(compilerOptions.mapRoot);
|
|
if (sourceFileOrBundle.kind === SyntaxKind.SourceFile) { // emitting single module file
|
|
// For modules or multiple emit files the mapRoot will have directory structure like the sources
|
|
// So if src\a.ts and src\lib\b.ts are compiled together user would be moving the maps into mapRoot\a.js.map and mapRoot\lib\b.js.map
|
|
sourceMapDir = getDirectoryPath(getSourceFilePathInNewDir(sourceFileOrBundle, host, sourceMapDir));
|
|
}
|
|
|
|
if (!isRootedDiskPath(sourceMapDir) && !isUrl(sourceMapDir)) {
|
|
// The relative paths are relative to the common directory
|
|
sourceMapDir = combinePaths(host.getCommonSourceDirectory(), sourceMapDir);
|
|
sourceMapData.jsSourceMappingURL = getRelativePathToDirectoryOrUrl(
|
|
getDirectoryPath(normalizePath(filePath)), // get the relative sourceMapDir path based on jsFilePath
|
|
combinePaths(sourceMapDir, sourceMapData.jsSourceMappingURL), // this is where user expects to see sourceMap
|
|
host.getCurrentDirectory(),
|
|
host.getCanonicalFileName,
|
|
/*isAbsolutePathAnUrl*/ true);
|
|
}
|
|
else {
|
|
sourceMapData.jsSourceMappingURL = combinePaths(sourceMapDir, sourceMapData.jsSourceMappingURL);
|
|
}
|
|
}
|
|
else {
|
|
sourceMapDir = getDirectoryPath(normalizePath(filePath));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Reset the SourceMapWriter to an empty state.
|
|
*/
|
|
function reset() {
|
|
if (disabled) {
|
|
return;
|
|
}
|
|
|
|
currentSource = undefined;
|
|
sourceMapDir = undefined;
|
|
sourceMapSourceIndex = undefined;
|
|
lastRecordedSourceMapSpan = undefined;
|
|
lastEncodedSourceMapSpan = undefined;
|
|
lastEncodedNameIndex = undefined;
|
|
sourceMapData = undefined;
|
|
}
|
|
|
|
// Encoding for sourcemap span
|
|
function encodeLastRecordedSourceMapSpan() {
|
|
if (!lastRecordedSourceMapSpan || lastRecordedSourceMapSpan === lastEncodedSourceMapSpan) {
|
|
return;
|
|
}
|
|
|
|
let prevEncodedEmittedColumn = lastEncodedSourceMapSpan.emittedColumn;
|
|
// Line/Comma delimiters
|
|
if (lastEncodedSourceMapSpan.emittedLine === lastRecordedSourceMapSpan.emittedLine) {
|
|
// Emit comma to separate the entry
|
|
if (sourceMapData.sourceMapMappings) {
|
|
sourceMapData.sourceMapMappings += ",";
|
|
}
|
|
}
|
|
else {
|
|
// Emit line delimiters
|
|
for (let encodedLine = lastEncodedSourceMapSpan.emittedLine; encodedLine < lastRecordedSourceMapSpan.emittedLine; encodedLine++) {
|
|
sourceMapData.sourceMapMappings += ";";
|
|
}
|
|
prevEncodedEmittedColumn = 1;
|
|
}
|
|
|
|
// 1. Relative Column 0 based
|
|
sourceMapData.sourceMapMappings += base64VLQFormatEncode(lastRecordedSourceMapSpan.emittedColumn - prevEncodedEmittedColumn);
|
|
|
|
// 2. Relative sourceIndex
|
|
sourceMapData.sourceMapMappings += base64VLQFormatEncode(lastRecordedSourceMapSpan.sourceIndex - lastEncodedSourceMapSpan.sourceIndex);
|
|
|
|
// 3. Relative sourceLine 0 based
|
|
sourceMapData.sourceMapMappings += base64VLQFormatEncode(lastRecordedSourceMapSpan.sourceLine - lastEncodedSourceMapSpan.sourceLine);
|
|
|
|
// 4. Relative sourceColumn 0 based
|
|
sourceMapData.sourceMapMappings += base64VLQFormatEncode(lastRecordedSourceMapSpan.sourceColumn - lastEncodedSourceMapSpan.sourceColumn);
|
|
|
|
// 5. Relative namePosition 0 based
|
|
if (lastRecordedSourceMapSpan.nameIndex >= 0) {
|
|
Debug.assert(false, "We do not support name index right now, Make sure to update updateLastEncodedAndRecordedSpans when we start using this");
|
|
sourceMapData.sourceMapMappings += base64VLQFormatEncode(lastRecordedSourceMapSpan.nameIndex - lastEncodedNameIndex);
|
|
lastEncodedNameIndex = lastRecordedSourceMapSpan.nameIndex;
|
|
}
|
|
|
|
lastEncodedSourceMapSpan = lastRecordedSourceMapSpan;
|
|
sourceMapData.sourceMapDecodedMappings.push(lastEncodedSourceMapSpan);
|
|
}
|
|
|
|
/**
|
|
* Emits a mapping.
|
|
*
|
|
* If the position is synthetic (undefined or a negative value), no mapping will be
|
|
* created.
|
|
*
|
|
* @param pos The position.
|
|
*/
|
|
function emitPos(pos: number) {
|
|
if (disabled || positionIsSynthesized(pos)) {
|
|
return;
|
|
}
|
|
|
|
if (extendedDiagnostics) {
|
|
performance.mark("beforeSourcemap");
|
|
}
|
|
|
|
const sourceLinePos = getLineAndCharacterOfPosition(currentSource, pos);
|
|
|
|
// Convert the location to be one-based.
|
|
sourceLinePos.line++;
|
|
sourceLinePos.character++;
|
|
|
|
const emittedLine = writer.getLine();
|
|
const emittedColumn = writer.getColumn();
|
|
|
|
// If this location wasn't recorded or the location in source is going backwards, record the span
|
|
if (!lastRecordedSourceMapSpan ||
|
|
lastRecordedSourceMapSpan.emittedLine !== emittedLine ||
|
|
lastRecordedSourceMapSpan.emittedColumn !== emittedColumn ||
|
|
(lastRecordedSourceMapSpan.sourceIndex === sourceMapSourceIndex &&
|
|
(lastRecordedSourceMapSpan.sourceLine > sourceLinePos.line ||
|
|
(lastRecordedSourceMapSpan.sourceLine === sourceLinePos.line && lastRecordedSourceMapSpan.sourceColumn > sourceLinePos.character)))) {
|
|
|
|
// Encode the last recordedSpan before assigning new
|
|
encodeLastRecordedSourceMapSpan();
|
|
|
|
// New span
|
|
lastRecordedSourceMapSpan = {
|
|
emittedLine,
|
|
emittedColumn,
|
|
sourceLine: sourceLinePos.line,
|
|
sourceColumn: sourceLinePos.character,
|
|
sourceIndex: sourceMapSourceIndex
|
|
};
|
|
}
|
|
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;
|
|
}
|
|
|
|
if (extendedDiagnostics) {
|
|
performance.mark("afterSourcemap");
|
|
performance.measure("Source Map", "beforeSourcemap", "afterSourcemap");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Emits a node with possible leading and trailing source maps.
|
|
*
|
|
* @param hint A hint as to the intended usage of the node.
|
|
* @param node The node to emit.
|
|
* @param emitCallback The callback used to emit the node.
|
|
*/
|
|
function emitNodeWithSourceMap(hint: EmitHint, node: Node, emitCallback: (hint: EmitHint, node: Node) => void) {
|
|
if (disabled) {
|
|
return emitCallback(hint, node);
|
|
}
|
|
|
|
if (node) {
|
|
const emitNode = node.emitNode;
|
|
const emitFlags = emitNode && emitNode.flags;
|
|
const range = emitNode && emitNode.sourceMapRange;
|
|
const { pos, end } = range || node;
|
|
let source = range && range.source;
|
|
const oldSource = currentSource;
|
|
if (source === oldSource) source = undefined;
|
|
|
|
if (source) setSourceFile(source);
|
|
|
|
if (node.kind !== SyntaxKind.NotEmittedStatement
|
|
&& (emitFlags & EmitFlags.NoLeadingSourceMap) === 0
|
|
&& pos >= 0) {
|
|
emitPos(skipSourceTrivia(pos));
|
|
}
|
|
|
|
if (source) setSourceFile(oldSource);
|
|
|
|
if (emitFlags & EmitFlags.NoNestedSourceMaps) {
|
|
disabled = true;
|
|
emitCallback(hint, node);
|
|
disabled = false;
|
|
}
|
|
else {
|
|
emitCallback(hint, node);
|
|
}
|
|
|
|
if (source) setSourceFile(source);
|
|
|
|
if (node.kind !== SyntaxKind.NotEmittedStatement
|
|
&& (emitFlags & EmitFlags.NoTrailingSourceMap) === 0
|
|
&& end >= 0) {
|
|
emitPos(end);
|
|
}
|
|
|
|
if (source) setSourceFile(oldSource);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Emits a token of a 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 pos of the token.
|
|
* @param emitCallback The callback used to emit the token.
|
|
*/
|
|
function emitTokenWithSourceMap(node: Node, token: SyntaxKind, writer: (s: string) => void, tokenPos: number, emitCallback: (token: SyntaxKind, writer: (s: string) => void, tokenStartPos: number) => number) {
|
|
if (disabled) {
|
|
return emitCallback(token, writer, tokenPos);
|
|
}
|
|
|
|
const emitNode = node && node.emitNode;
|
|
const emitFlags = emitNode && emitNode.flags;
|
|
const range = emitNode && emitNode.tokenSourceMapRanges && emitNode.tokenSourceMapRanges[token];
|
|
|
|
tokenPos = skipSourceTrivia(range ? range.pos : tokenPos);
|
|
if ((emitFlags & EmitFlags.NoTokenLeadingSourceMaps) === 0 && tokenPos >= 0) {
|
|
emitPos(tokenPos);
|
|
}
|
|
|
|
tokenPos = emitCallback(token, writer, tokenPos);
|
|
|
|
if (range) tokenPos = range.end;
|
|
if ((emitFlags & EmitFlags.NoTokenTrailingSourceMaps) === 0 && tokenPos >= 0) {
|
|
emitPos(tokenPos);
|
|
}
|
|
|
|
return tokenPos;
|
|
}
|
|
|
|
/**
|
|
* Set the current source file.
|
|
*
|
|
* @param sourceFile The source file.
|
|
*/
|
|
function setSourceFile(sourceFile: SourceMapSource) {
|
|
if (disabled) {
|
|
return;
|
|
}
|
|
|
|
currentSource = sourceFile;
|
|
currentSourceText = currentSource.text;
|
|
|
|
// Add the file to tsFilePaths
|
|
// If sourceroot option: Use the relative path corresponding to the common directory path
|
|
// otherwise source locations relative to map file location
|
|
const sourcesDirectoryPath = compilerOptions.sourceRoot ? host.getCommonSourceDirectory() : sourceMapDir;
|
|
|
|
const source = getRelativePathToDirectoryOrUrl(sourcesDirectoryPath,
|
|
currentSource.fileName,
|
|
host.getCurrentDirectory(),
|
|
host.getCanonicalFileName,
|
|
/*isAbsolutePathAnUrl*/ true);
|
|
|
|
sourceMapSourceIndex = sourceMapData.sourceMapSources.indexOf(source);
|
|
if (sourceMapSourceIndex === -1) {
|
|
sourceMapSourceIndex = sourceMapData.sourceMapSources.length;
|
|
sourceMapData.sourceMapSources.push(source);
|
|
|
|
// The one that can be used from program to get the actual source file
|
|
sourceMapData.inputSourceFileNames.push(currentSource.fileName);
|
|
|
|
if (compilerOptions.inlineSources) {
|
|
sourceMapData.sourceMapSourcesContent.push(currentSource.text);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gets the text for the source map.
|
|
*/
|
|
function getText() {
|
|
if (disabled) {
|
|
return;
|
|
}
|
|
|
|
encodeLastRecordedSourceMapSpan();
|
|
|
|
return JSON.stringify({
|
|
version: 3,
|
|
file: sourceMapData.sourceMapFile,
|
|
sourceRoot: sourceMapData.sourceMapSourceRoot,
|
|
sources: sourceMapData.sourceMapSources,
|
|
names: sourceMapData.sourceMapNames,
|
|
mappings: sourceMapData.sourceMapMappings,
|
|
sourcesContent: sourceMapData.sourceMapSourcesContent,
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 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());
|
|
return sourceMapData.jsSourceMappingURL = `data:application/json;base64,${base64SourceMapText}`;
|
|
}
|
|
else {
|
|
return sourceMapData.jsSourceMappingURL;
|
|
}
|
|
}
|
|
}
|
|
|
|
const base64Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
|
|
function base64FormatEncode(inValue: number) {
|
|
if (inValue < 64) {
|
|
return base64Chars.charAt(inValue);
|
|
}
|
|
|
|
throw TypeError(inValue + ": not a 64 based value");
|
|
}
|
|
|
|
function base64VLQFormatEncode(inValue: number) {
|
|
// Add a new least significant bit that has the sign of the value.
|
|
// if negative number the least significant bit that gets added to the number has value 1
|
|
// else least significant bit value that gets added is 0
|
|
// eg. -1 changes to binary : 01 [1] => 3
|
|
// +1 changes to binary : 01 [0] => 2
|
|
if (inValue < 0) {
|
|
inValue = ((-inValue) << 1) + 1;
|
|
}
|
|
else {
|
|
inValue = inValue << 1;
|
|
}
|
|
|
|
// Encode 5 bits at a time starting from least significant bits
|
|
let encodedStr = "";
|
|
do {
|
|
let currentDigit = inValue & 31; // 11111
|
|
inValue = inValue >> 5;
|
|
if (inValue > 0) {
|
|
// There are still more digits to decode, set the msb (6th bit)
|
|
currentDigit = currentDigit | 32;
|
|
}
|
|
encodedStr = encodedStr + base64FormatEncode(currentDigit);
|
|
} while (inValue > 0);
|
|
|
|
return encodedStr;
|
|
}
|
|
} |