@tscc/tscc
Version:
A typescript transpiler and bundler that wires up tsickle and closure compiler seamlessly
194 lines (193 loc) • 7.6 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Seeker = exports.splitWithRegex = void 0;
const source_map_1 = require("source-map");
/**
* From a file with sourcemap, splice intervals specified with the third argument
* and translate sourcemaps accordingly.
* @param content
* @param map
* @param spliceIntervals sorted, non-overlapping intervals to splice.
*/
async function spliceSourceMap(content, map, spliceIntervals) {
const consumer = await new source_map_1.SourceMapConsumer(map);
const generator = new source_map_1.SourceMapGenerator({ file: map.file });
const seeker = new Seeker(content, spliceIntervals);
consumer.eachMapping(({ source, generatedLine, generatedColumn, originalLine, originalColumn, name }) => {
// line numbers in mozilla/source-map are 1-based. column numbers are 0-based.
seeker.seek(generatedLine - 1, generatedColumn);
if (seeker.isInInterval())
return;
let transformedLine = seeker.getTransformedLine();
let transformedColumn = seeker.getTransformedColumn();
const mapping = getMapping(source, transformedLine + 1, transformedColumn, originalLine, originalColumn, name);
generator.addMapping(mapping);
});
return generator.toJSON();
}
exports.default = spliceSourceMap;
const placeholderString = undefined;
const placeholderNumber = undefined;
const mapping = {
source: placeholderString,
generated: {
line: placeholderNumber,
column: placeholderNumber
},
original: {
line: placeholderNumber,
column: placeholderNumber
},
name: placeholderString
};
const mappingWithoutOriginal = {
source: placeholderString,
generated: {
line: placeholderNumber,
column: placeholderNumber
}
};
function getMapping(source, generatedLine, generatedColumn, originalLine, originalColumn, name) {
if (typeof originalLine !== 'number' && typeof originalLine !== 'number') {
mappingWithoutOriginal.source = source;
mappingWithoutOriginal.generated.line = generatedLine;
mappingWithoutOriginal.generated.column = generatedColumn;
return mappingWithoutOriginal;
}
mapping.source = source;
mapping.generated.line = generatedLine;
mapping.generated.column = generatedColumn;
mapping.original.line = originalLine;
mapping.original.column = originalColumn;
mapping.name = name;
return mapping;
}
function splitWithRegex(contents, regex) {
const intervals = [];
let prevEnd = 0;
let replacedContent = '';
let execRes;
while ((execRes = regex.exec(contents)) !== null) {
let removeStart = execRes.index;
let removeEnd = removeStart + execRes[0].length;
replacedContent += contents.substring(prevEnd, removeStart);
prevEnd = removeEnd;
intervals.push([removeStart, removeEnd]);
}
replacedContent += contents.substring(prevEnd);
return { contents: replacedContent, intervals };
}
exports.splitWithRegex = splitWithRegex;
class Seeker {
constructor(contents, intervals) {
this.contents = contents;
this.intervals = intervals;
/*************** State machine state *****************/
// Current cursor position descriptors
this.line = 0;
this.column = 0;
this.index = 0;
this.lineStart = 0; // Index of current line's start
this.intervalIndex = -1; // Index of last interval whose start comes before then or at the same point with the current index
this.accLine = 0;
this.accColumn = 0;
}
/************* State machine state end ***************/
/**
* Seeks the last interval that intersects with the interval [0, index], starting from the current interval.
* Returns a contribution of lengths occupied by intervals in [this.Index, index).
* (Beware the parentheses)
*/
seekInterval(index) {
let { intervalIndex, index: prevIndex } = this;
let occupied = 0;
if (intervalIndex === -1)
intervalIndex = 0;
let interval = this.getInterval(intervalIndex);
while (interval && interval[0] <= index) {
if (interval[1] > prevIndex) {
occupied += min(interval[1], index) - max(interval[0], prevIndex);
}
interval = this.getInterval(++intervalIndex);
}
this.intervalIndex = intervalIndex - 1;
return occupied;
}
seekWithinLine(nextColumn) {
let increment = nextColumn - this.column;
let nextIndex = this.index + increment;
// Update column (line and lineStart stays the same)
let lineEnd = this.nextLineBreak();
if (lineEnd !== -1 && nextIndex > lineEnd)
throw new Error('EOL');
if (nextIndex >= this.contents.length)
throw new Error('EOF');
this.column = nextColumn;
// Update intervalIndex, accColumn (accLine stays the same)
let occupied = this.seekInterval(nextIndex);
this.accColumn += increment - occupied;
this.index = nextIndex;
}
nextLine() {
// Update line,column,lineStart
let lineStart = this.nextLineBreak() + 1;
if (lineStart > 0 && this.contents.length <= lineStart)
throw new Error(`EOF`);
this.lineStart = lineStart;
this.line++;
this.column = 0;
let increment = lineStart - this.index;
// Update interavlIndex, accLine, accColumn
// Check if there is an interval containing lineStart - 1 (index of '\n')
let occupied = this.seekInterval(lineStart - 1);
let interval = this.getInterval(this.intervalIndex);
this.index = lineStart; // Setting it after seekInterval call, as it requires prev index.
if (!interval || interval[1] <= lineStart - 1) {
// No interval contains the previous line break character - accLine will be increased.
this.accLine++;
this.accColumn = 0;
// intervalIndex may need to proceed one step further,
// as we are looking for the latest interval s.t. interval[0] <= lineStart
// See test "when interval ends with \n".
let nextInterval = this.getInterval(this.intervalIndex + 1);
if (nextInterval && nextInterval[0] === lineStart)
this.intervalIndex++;
}
else {
// This interval contains the line break character.
// In order to get # of occupied positions before the lineStart, we need to increase
// occupied by 1, since the line break character is occupied by the current interval.
this.accColumn += increment - occupied - 1;
}
}
seek(nextLine, nextColumn) {
while (nextLine > this.line) {
this.nextLine();
}
this.seekWithinLine(nextColumn);
}
// Querying methods
isInInterval() {
let currentInterval = this.getInterval(this.intervalIndex);
return currentInterval && currentInterval[1] > this.index;
}
getTransformedLine() {
return this.accLine;
}
getTransformedColumn() {
return this.accColumn;
}
nextLineBreak() {
return this.contents.indexOf('\n', this.index) || this.contents.length;
}
getInterval(intervalIndex) {
return this.intervals[intervalIndex];
}
}
exports.Seeker = Seeker;
function max(a, b) {
return a > b ? a : b;
}
function min(a, b) {
return a > b ? b : a;
}