Revision d020ac6dd13afd85316ea58a3afa66ed11fd32fc authored by csigs on 16 October 2019, 22:10:56 UTC, committed by GitHub on 16 October 2019, 22:10:56 UTC
LEGO: Merge pull request 34526
sourceMapRecorder.ts
namespace Harness.SourceMapRecorder {
interface SourceMapSpanWithDecodeErrors {
sourceMapSpan: ts.Mapping;
decodeErrors: string[] | undefined;
}
namespace SourceMapDecoder {
let sourceMapMappings: string;
let decodingIndex: number;
let mappings: ts.MappingsDecoder | undefined;
export interface DecodedMapping {
sourceMapSpan: ts.Mapping;
error?: string;
}
export function initializeSourceMapDecoding(sourceMap: ts.RawSourceMap) {
decodingIndex = 0;
sourceMapMappings = sourceMap.mappings;
mappings = ts.decodeMappings(sourceMap.mappings);
}
export function decodeNextEncodedSourceMapSpan(): DecodedMapping {
if (!mappings) return ts.Debug.fail("not initialized");
const result = mappings.next();
if (result.done) return { error: mappings.error || "No encoded entry found", sourceMapSpan: mappings.state };
return { sourceMapSpan: result.value };
}
export function hasCompletedDecoding() {
if (!mappings) return ts.Debug.fail("not initialized");
return mappings.pos === sourceMapMappings.length;
}
export function getRemainingDecodeString() {
return sourceMapMappings.substr(decodingIndex);
}
}
namespace SourceMapSpanWriter {
let sourceMapRecorder: Compiler.WriterAggregator;
let sourceMapSources: string[];
let sourceMapNames: string[] | null | undefined;
let jsFile: documents.TextDocument;
let jsLineMap: readonly number[];
let tsCode: string;
let tsLineMap: number[];
let spansOnSingleLine: SourceMapSpanWithDecodeErrors[];
let prevWrittenSourcePos: number;
let nextJsLineToWrite: number;
let spanMarkerContinues: boolean;
export function initializeSourceMapSpanWriter(sourceMapRecordWriter: Compiler.WriterAggregator, sourceMap: ts.RawSourceMap, currentJsFile: documents.TextDocument) {
sourceMapRecorder = sourceMapRecordWriter;
sourceMapSources = sourceMap.sources;
sourceMapNames = sourceMap.names;
jsFile = currentJsFile;
jsLineMap = jsFile.lineStarts;
spansOnSingleLine = [];
prevWrittenSourcePos = 0;
nextJsLineToWrite = 0;
spanMarkerContinues = false;
SourceMapDecoder.initializeSourceMapDecoding(sourceMap);
sourceMapRecorder.WriteLine("===================================================================");
sourceMapRecorder.WriteLine("JsFile: " + sourceMap.file);
sourceMapRecorder.WriteLine("mapUrl: " + ts.tryGetSourceMappingURL(ts.getLineInfo(jsFile.text, jsLineMap)));
sourceMapRecorder.WriteLine("sourceRoot: " + sourceMap.sourceRoot);
sourceMapRecorder.WriteLine("sources: " + sourceMap.sources);
if (sourceMap.sourcesContent) {
sourceMapRecorder.WriteLine("sourcesContent: " + JSON.stringify(sourceMap.sourcesContent));
}
sourceMapRecorder.WriteLine("===================================================================");
}
function getSourceMapSpanString(mapEntry: ts.Mapping, getAbsentNameIndex?: boolean) {
let mapString = "Emitted(" + (mapEntry.generatedLine + 1) + ", " + (mapEntry.generatedCharacter + 1) + ")";
if (ts.isSourceMapping(mapEntry)) {
mapString += " Source(" + (mapEntry.sourceLine + 1) + ", " + (mapEntry.sourceCharacter + 1) + ") + SourceIndex(" + mapEntry.sourceIndex + ")";
if (mapEntry.nameIndex! >= 0 && mapEntry.nameIndex! < sourceMapNames!.length) {
mapString += " name (" + sourceMapNames![mapEntry.nameIndex!] + ")";
}
else {
if ((mapEntry.nameIndex && mapEntry.nameIndex !== -1) || getAbsentNameIndex) {
mapString += " nameIndex (" + mapEntry.nameIndex + ")";
}
}
}
return mapString;
}
export function recordSourceMapSpan(sourceMapSpan: ts.Mapping) {
// verify the decoded span is same as the new span
const decodeResult = SourceMapDecoder.decodeNextEncodedSourceMapSpan();
let decodeErrors: string[] | undefined;
if (typeof decodeResult.error === "string" || !ts.sameMapping(decodeResult.sourceMapSpan, sourceMapSpan)) {
if (decodeResult.error) {
decodeErrors = ["!!^^ !!^^ There was decoding error in the sourcemap at this location: " + decodeResult.error];
}
else {
decodeErrors = ["!!^^ !!^^ The decoded span from sourcemap's mapping entry does not match what was encoded for this span:"];
}
decodeErrors.push("!!^^ !!^^ Decoded span from sourcemap's mappings entry: " + getSourceMapSpanString(decodeResult.sourceMapSpan, /*getAbsentNameIndex*/ true) + " Span encoded by the emitter:" + getSourceMapSpanString(sourceMapSpan, /*getAbsentNameIndex*/ true));
}
if (spansOnSingleLine.length && spansOnSingleLine[0].sourceMapSpan.generatedLine !== sourceMapSpan.generatedLine) {
// On different line from the one that we have been recording till now,
writeRecordedSpans();
spansOnSingleLine = [];
}
spansOnSingleLine.push({ sourceMapSpan, decodeErrors });
}
export function recordNewSourceFileSpan(sourceMapSpan: ts.Mapping, newSourceFileCode: string) {
let continuesLine = false;
if (spansOnSingleLine.length > 0 && spansOnSingleLine[0].sourceMapSpan.generatedCharacter === sourceMapSpan.generatedLine) {
writeRecordedSpans();
spansOnSingleLine = [];
nextJsLineToWrite--; // walk back one line to reprint the line
continuesLine = true;
}
recordSourceMapSpan(sourceMapSpan);
assert.isTrue(spansOnSingleLine.length === 1);
sourceMapRecorder.WriteLine("-------------------------------------------------------------------");
sourceMapRecorder.WriteLine("emittedFile:" + jsFile.file + (continuesLine ? ` (${sourceMapSpan.generatedLine + 1}, ${sourceMapSpan.generatedCharacter + 1})` : ""));
sourceMapRecorder.WriteLine("sourceFile:" + sourceMapSources[spansOnSingleLine[0].sourceMapSpan.sourceIndex!]);
sourceMapRecorder.WriteLine("-------------------------------------------------------------------");
tsLineMap = ts.computeLineStarts(newSourceFileCode);
tsCode = newSourceFileCode;
prevWrittenSourcePos = 0;
}
export function close() {
// Write the lines pending on the single line
writeRecordedSpans();
if (!SourceMapDecoder.hasCompletedDecoding()) {
sourceMapRecorder.WriteLine("!!!! **** There are more source map entries in the sourceMap's mapping than what was encoded");
sourceMapRecorder.WriteLine("!!!! **** Remaining decoded string: " + SourceMapDecoder.getRemainingDecodeString());
}
// write remaining js lines
writeJsFileLines(jsLineMap.length);
}
function getTextOfLine(line: number, lineMap: readonly number[], code: string) {
const startPos = lineMap[line];
const endPos = lineMap[line + 1];
const text = code.substring(startPos, endPos);
return line === 0 ? utils.removeByteOrderMark(text) : text;
}
function writeJsFileLines(endJsLine: number) {
for (; nextJsLineToWrite < endJsLine; nextJsLineToWrite++) {
sourceMapRecorder.Write(">>>" + getTextOfLine(nextJsLineToWrite, jsLineMap, jsFile.text));
}
}
function writeRecordedSpans() {
const markerIds: string[] = [];
function getMarkerId(markerIndex: number) {
let markerId = "";
if (spanMarkerContinues) {
assert.isTrue(markerIndex === 0);
markerId = "1->";
}
else {
markerId = "" + (markerIndex + 1);
if (markerId.length < 2) {
markerId = markerId + " ";
}
markerId += ">";
}
return markerId;
}
let prevEmittedCol!: number;
function iterateSpans(fn: (currentSpan: SourceMapSpanWithDecodeErrors, index: number) => void) {
prevEmittedCol = 0;
for (let i = 0; i < spansOnSingleLine.length; i++) {
fn(spansOnSingleLine[i], i);
prevEmittedCol = spansOnSingleLine[i].sourceMapSpan.generatedCharacter;
}
}
function writeSourceMapIndent(indentLength: number, indentPrefix: string) {
sourceMapRecorder.Write(indentPrefix);
for (let i = 0; i < indentLength; i++) {
sourceMapRecorder.Write(" ");
}
}
function writeSourceMapMarker(currentSpan: SourceMapSpanWithDecodeErrors, index: number, endColumn = currentSpan.sourceMapSpan.generatedCharacter, endContinues = false) {
const markerId = getMarkerId(index);
markerIds.push(markerId);
writeSourceMapIndent(prevEmittedCol, markerId);
for (let i = prevEmittedCol; i < endColumn; i++) {
sourceMapRecorder.Write("^");
}
if (endContinues) {
sourceMapRecorder.Write("->");
}
sourceMapRecorder.WriteLine("");
spanMarkerContinues = endContinues;
}
function writeSourceMapSourceText(currentSpan: SourceMapSpanWithDecodeErrors, index: number) {
const sourcePos = tsLineMap[currentSpan.sourceMapSpan.sourceLine!] + (currentSpan.sourceMapSpan.sourceCharacter!);
let sourceText = "";
if (prevWrittenSourcePos < sourcePos) {
// Position that goes forward, get text
sourceText = tsCode.substring(prevWrittenSourcePos, sourcePos);
}
if (currentSpan.decodeErrors) {
// If there are decode errors, write
for (const decodeError of currentSpan.decodeErrors) {
writeSourceMapIndent(prevEmittedCol, markerIds[index]);
sourceMapRecorder.WriteLine(decodeError);
}
}
const tsCodeLineMap = ts.computeLineStarts(sourceText);
for (let i = 0; i < tsCodeLineMap.length; i++) {
writeSourceMapIndent(prevEmittedCol, i === 0 ? markerIds[index] : " >");
sourceMapRecorder.Write(getTextOfLine(i, tsCodeLineMap, sourceText));
if (i === tsCodeLineMap.length - 1) {
sourceMapRecorder.WriteLine("");
}
}
prevWrittenSourcePos = sourcePos;
}
function writeSpanDetails(currentSpan: SourceMapSpanWithDecodeErrors, index: number) {
sourceMapRecorder.WriteLine(markerIds[index] + getSourceMapSpanString(currentSpan.sourceMapSpan));
}
if (spansOnSingleLine.length) {
const currentJsLine = spansOnSingleLine[0].sourceMapSpan.generatedLine;
// Write js line
writeJsFileLines(currentJsLine + 1);
// Emit markers
iterateSpans(writeSourceMapMarker);
const jsFileText = getTextOfLine(currentJsLine + 1, jsLineMap, jsFile.text);
if (prevEmittedCol < jsFileText.length - 1) {
// There is remaining text on this line that will be part of next source span so write marker that continues
writeSourceMapMarker(/*currentSpan*/ undefined!, spansOnSingleLine.length, /*endColumn*/ jsFileText.length - 1, /*endContinues*/ true); // TODO: GH#18217
}
// Emit Source text
iterateSpans(writeSourceMapSourceText);
// Emit column number etc
iterateSpans(writeSpanDetails);
sourceMapRecorder.WriteLine("---");
}
}
}
export function getSourceMapRecord(sourceMapDataList: readonly ts.SourceMapEmitResult[], program: ts.Program, jsFiles: readonly documents.TextDocument[], declarationFiles: readonly documents.TextDocument[]) {
const sourceMapRecorder = new Compiler.WriterAggregator();
for (let i = 0; i < sourceMapDataList.length; i++) {
const sourceMapData = sourceMapDataList[i];
let prevSourceFile: ts.SourceFile | undefined;
let currentFile: documents.TextDocument;
if (ts.endsWith(sourceMapData.sourceMap.file, ts.Extension.Dts)) {
if (sourceMapDataList.length > jsFiles.length) {
currentFile = declarationFiles[Math.floor(i / 2)]; // When both kinds of source map are present, they alternate js/dts
}
else {
currentFile = declarationFiles[i];
}
}
else {
if (sourceMapDataList.length > jsFiles.length) {
currentFile = jsFiles[Math.floor(i / 2)];
}
else {
currentFile = jsFiles[i];
}
}
SourceMapSpanWriter.initializeSourceMapSpanWriter(sourceMapRecorder, sourceMapData.sourceMap, currentFile);
const mapper = ts.decodeMappings(sourceMapData.sourceMap.mappings);
for (let iterResult = mapper.next(); !iterResult.done; iterResult = mapper.next()) {
const decodedSourceMapping = iterResult.value;
const currentSourceFile = ts.isSourceMapping(decodedSourceMapping)
? program.getSourceFile(sourceMapData.inputSourceFileNames[decodedSourceMapping.sourceIndex])
: undefined;
if (currentSourceFile !== prevSourceFile) {
if (currentSourceFile) {
SourceMapSpanWriter.recordNewSourceFileSpan(decodedSourceMapping, currentSourceFile.text);
}
prevSourceFile = currentSourceFile;
}
else {
SourceMapSpanWriter.recordSourceMapSpan(decodedSourceMapping);
}
}
SourceMapSpanWriter.close(); // If the last spans werent emitted, emit them
}
sourceMapRecorder.Close();
return sourceMapRecorder.lines.join("\r\n");
}
export function getSourceMapRecordWithVFS(fs: vfs.FileSystem, sourceMapFile: string) {
const sourceMapRecorder = new Compiler.WriterAggregator();
let prevSourceFile: documents.TextDocument | undefined;
const files = ts.createMap<documents.TextDocument>();
const sourceMap = ts.tryParseRawSourceMap(fs.readFileSync(sourceMapFile, "utf8"));
if (sourceMap) {
const mapDirectory = ts.getDirectoryPath(sourceMapFile);
const sourceRoot = sourceMap.sourceRoot ? ts.getNormalizedAbsolutePath(sourceMap.sourceRoot, mapDirectory) : mapDirectory;
const generatedAbsoluteFilePath = ts.getNormalizedAbsolutePath(sourceMap.file, mapDirectory);
const sourceFileAbsolutePaths = sourceMap.sources.map(source => ts.getNormalizedAbsolutePath(source, sourceRoot));
const currentFile = getFile(generatedAbsoluteFilePath);
SourceMapSpanWriter.initializeSourceMapSpanWriter(sourceMapRecorder, sourceMap, currentFile);
const mapper = ts.decodeMappings(sourceMap.mappings);
for (let iterResult = mapper.next(); !iterResult.done; iterResult = mapper.next()) {
const decodedSourceMapping = iterResult.value;
const currentSourceFile = ts.isSourceMapping(decodedSourceMapping)
? getFile(sourceFileAbsolutePaths[decodedSourceMapping.sourceIndex])
: undefined;
if (currentSourceFile !== prevSourceFile) {
if (currentSourceFile) {
SourceMapSpanWriter.recordNewSourceFileSpan(decodedSourceMapping, currentSourceFile.text);
}
prevSourceFile = currentSourceFile;
}
else {
SourceMapSpanWriter.recordSourceMapSpan(decodedSourceMapping);
}
}
SourceMapSpanWriter.close(); // If the last spans werent emitted, emit them
}
sourceMapRecorder.Close();
return sourceMapRecorder.lines.join("\r\n");
function getFile(path: string) {
const existing = files.get(path);
if (existing) return existing;
const value = new documents.TextDocument(path, fs.readFileSync(path, "utf8"));
files.set(path, value);
return value;
}
}
}
Computing file changes ...