UNPKG

@tscc/tscc

Version:

A typescript transpiler and bundler that wires up tsickle and closure compiler seamlessly

194 lines (193 loc) 7.6 kB
"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; }