import resolveUri from '@jridgewell/resolve-uri'; import { encode } from 'sourcemap-codec'; function resolve(input, base) { // The base is always treated as a directory, if it's not empty. // https://github.com/mozilla/source-map/blob/8cb3ee57/lib/util.js#L327 // https://github.com/chromium/chromium/blob/da4adbb3/third_party/blink/renderer/devtools/front_end/sdk/SourceMap.js#L400-L401 if (base && !base.endsWith('/')) base += '/'; return resolveUri(input, base); } /** * Removes everything after the last "/", but leaves the slash. */ function stripFilename(path) { if (!path) return ''; const index = path.lastIndexOf('/'); return path.slice(0, index + 1); } /** * A binary search implementation that returns the index if a match is found, * or the negated index of where the `needle` should be inserted. * * The `comparator` callback receives both the `item` under comparison and the * needle we are searching for. It must return `0` if the `item` is a match, * any negative number if `item` is too small (and we must search after it), or * any positive number if the `item` is too large (and we must search before * it). * * The `len` param allows you to treat contiguous blocks of memory as a single item. Eg, a 5-length * tuple (with values at indices 0-4) would only test index 0. * * If no match is found, then the left-index (the index associated with the item that comes just * before the desired index) is returned. To maintain proper sort order, a splice would happen at * the next index: * * ```js * const array = [1, 3]; * const needle = 2; * const index = binarySearch(array, needle, (item, needle) => item - needle); * * assert.equal(index, 0); * array.splice(index + 1, 0, needle); * assert.deepEqual(array, [1, 2, 3]); * ``` */ function binarySearch(haystack, needle, comparator, low, high, len) { low /= len; high /= len; while (low <= high) { const mid = low + ((high - low) >> 1); const index = mid * len; const cmp = comparator(haystack[index], needle); if (cmp === 0) { return index; } if (cmp < 0) { low = mid + 1; } else { high = mid - 1; } } return (low - 1) * len; } /** * This overly complicated beast is just to record the last tested line/column and the resulting * index, allowing us to skip a few tests if mappings are monotonically increasing. */ function memoizedBinarySearch(haystack, needle, comparator, low, high, len, state, line, column) { const { _lastLine: lastLine, _lastColumn: lastColumn, _lastIndex: lastIndex } = state; if (line === lastLine) { if (column === lastColumn) { return lastIndex; } if (column >= lastColumn) { low = Math.max(lastIndex, 0); } else { high = lastIndex; } } state._lastLine = line; state._lastColumn = column; return (state._lastIndex = binarySearch(haystack, needle, comparator, low, high, len)); } const ITEM_LENGTH$1 = 1; class DecodedSourceMapImpl { constructor(map, owned) { this._lastIndex = 0; this._lastLine = 0; this._lastColumn = 0; this._mappings = maybeSort(map.mappings, owned); } encodedMappings() { return encode(this._mappings); } decodedMappings() { return this._mappings; } map(fn) { const mapOut = []; const mappings = this._mappings; for (let i = 0; i < mappings.length; i++) { const line = mappings[i]; const lineOut = []; mapOut.push(lineOut); for (let j = 0; j < line.length; j++) { const seg = line[j]; const { length } = seg; let segOut; if (length === 4) segOut = fn(i, seg[0], seg[1], seg[2], seg[3], -1); else if (length === 5) segOut = fn(i, seg[0], seg[1], seg[2], seg[3], seg[4]); else segOut = fn(i, seg[0], -1, -1, -1, -1); if (segOut != null) lineOut.push(segOut); } } return mapOut; } traceSegment(line, column) { const mappings = this._mappings; // It's common for parent source maps to have pointers to lines that have no // mapping (like a "//# sourceMappingURL=") at the end of the child file. if (line >= mappings.length) return null; const segments = mappings[line]; const index = memoizedBinarySearch(segments, column, searchComparator$1, 0, segments.length - ITEM_LENGTH$1, ITEM_LENGTH$1, this, line, column); // we come before any mapped segment if (index < 0) return null; return segments[index]; } } function maybeSort(mappings, owned) { const unsortedIndex = nextUnsortedSegmentLine(mappings, 0); if (unsortedIndex === mappings.length) return mappings; // If we own the array (meaning we parsed it from JSON), then we're free to directly mutate it. If // not, we do not want to modify the consumer's input array. if (!owned) mappings = mappings.slice(); for (let i = unsortedIndex; i < mappings.length; i = nextUnsortedSegmentLine(mappings, i + 1)) { mappings[i] = sortSegments(mappings[i], owned); } return mappings; } function nextUnsortedSegmentLine(mappings, start) { for (let i = start; i < mappings.length; i++) { if (!isSorted(mappings[i])) return i; } return mappings.length; } function isSorted(line) { for (let j = 1; j < line.length; j++) { if (line[j][0] < line[j - 1][0]) { return false; } } return true; } function sortSegments(line, owned) { if (!owned) line = line.slice(); return line.sort(sortComparator$1); } function sortComparator$1(a, b) { return a[0] - b[0]; } function searchComparator$1(segment, needle) { return segment[0] - needle; } const base64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='; const base64Index = new Uint8Array(128); for (let i = 0; i < base64.length; i++) { base64Index[base64.charCodeAt(i)] = i; } const ITEM_LENGTH = 5; function decode(encoded, lines) { let generatedColumn = 0; // 0 is used as a "not found" marker (because TypedArrays are 0 by default, so we don't need to // manually set any missing VLQ numbers), so these start at 1. Public APIs will decrement the // values so they are correct before returning to consumers. let sourcesIndex = 1; let sourceLine = 1; let sourceColumn = 1; let namesIndex = 1; let lineSorted = true; // count tracks the number of segments we have stored. let count = 0; // lastLineStart tracks the `count` of the segment that started the current line, so that we may // sort the line after it's complete. let lastLineStart = 0; let decoded = new Uint32Array(1000); // The first line starts with segment 0. If there are no mappings on this line, then the next // line will also start with segment 0 (meaning the first line ends with segment 0). lines.push(0); for (let pos = 0; pos < encoded.length;) { switch (encoded.charCodeAt(pos)) { // Commas separate segements on a line. case 44: // ',' pos++; continue; // Semicolons separates lines. case 59: // ';' lines.push(count); if (!lineSorted) easySort(decoded, lastLineStart, count); lineSorted = true; lastLineStart = count; // generatedColumn is reset when the next line starts, per the spec. generatedColumn = 0; pos++; continue; default: // Ensure that we have at least 5 items left in the decoded buffer, so we can push this // segment on. decoded = reserve(decoded, count, ITEM_LENGTH); // Segments are guaranteed to have at least the generatedColumn VLQ. pos = decodeInteger(encoded, pos, decoded, count); if (lineSorted) lineSorted = decoded[count] < 0; generatedColumn = decoded[count] += generatedColumn; count++; if (!hasMoreMappings(encoded, pos)) { count += 4; continue; } // If there are more VLQ, then we're guaranteed to have sourcesIndex, sourceLine, and // sourceColumn. pos = decodeInteger(encoded, pos, decoded, count); sourcesIndex = decoded[count] += sourcesIndex; count++; pos = decodeInteger(encoded, pos, decoded, count); sourceLine = decoded[count] += sourceLine; count++; pos = decodeInteger(encoded, pos, decoded, count); sourceColumn = decoded[count] += sourceColumn; count++; if (!hasMoreMappings(encoded, pos)) { count += 1; continue; } // Finally, namesIndex. pos = decodeInteger(encoded, pos, decoded, count); namesIndex = decoded[count] += namesIndex; count++; } } // Cap the lines, so that we can always look at index and index+1 for the start and end indices. lines.push(count); if (!lineSorted) easySort(decoded, lastLineStart, count); return decoded.subarray(0, count); } function reserve(buf, pos, count) { if (buf.length > pos + count) return buf; const swap = new Uint32Array(buf.length * 2); swap.set(buf); return swap; } function hasMoreMappings(encoded, pos) { if (pos === encoded.length) return false; const c = encoded.charCodeAt(pos); return c !== 44 /* ',' */ && c !== 59 /* ';' */; } function decodeInteger(encoded, pos, state, index) { let value = 0; let shift = 0; let integer = 0; // VLQ is a variable-length quantity (duh). The low 5 bits encode are integer data, with the 6th // bit being the "continue" bit signaling more data in the VLQ. do { const c = encoded.charCodeAt(pos++); integer = base64Index[c]; value |= (integer & 0b011111) << shift; shift += 5; } while (integer & 0b100000); // The lowest bit encodes whether the VLQ was originally negative. const shouldNegate = value & 1; value >>>= 1; // Nomally, -x produces a negative value like you would expect. But what does `-0` represent? In // VLQ, it should represent -2,147,483,648 (the smallest 32bit signed int). But -0 is just 0 in // JS, so we have to bitwise-OR with -0x80000000. This won't affect any other value, eg // `-1 | -0x80000000` is still `-1`. if (shouldNegate) value = -0x80000000 | -value; state[index] = value; return pos; } function easySort(state, start, end) { // This isn't a fast algorithm, but I believe it's exceedingly rare for a mapping to be unsorted. const segments = []; for (let i = start; i < end; i += ITEM_LENGTH) { segments.push(state.slice(i, i + ITEM_LENGTH)); } segments.sort(sortComparator); for (let i = start, j = 0; i < end; i += ITEM_LENGTH, j++) { state.set(segments[j], i); } } function sortComparator(a, b) { return a[0] - b[0]; } class EncodedSourceMapImpl { constructor(map) { this._lastIndex = 0; this._lastLine = 0; this._lastColumn = 0; this._lineIndices = []; this._encoded = map.mappings; this._mappings = decode(this._encoded, this._lineIndices); } encodedMappings() { return this._encoded; } decodedMappings() { return this.map(segmentify); } map(fn) { const { _mappings: mappings, _lineIndices: lineIndices } = this; const mapOut = []; let lineOut = []; let generatedLine = 0; let lineIndex = lineIndices[generatedLine + 1]; for (let i = 0; i < mappings.length;) { while (i < lineIndex) { const segOut = fn(generatedLine, mappings[i + 0], mappings[i + 1] - 1, mappings[i + 2] - 1, mappings[i + 3] - 1, mappings[i + 4] - 1); if (segOut != null) lineOut.push(segOut); i += ITEM_LENGTH; } do { mapOut.push(lineOut); lineOut = []; generatedLine++; lineIndex = lineIndices[generatedLine + 1]; } while (i === lineIndex); } return mapOut; } traceSegment(line, column) { const { _mappings: mappings, _lineIndices: lineIndices } = this; // It's common for parent source maps to have pointers to lines that have no // mapping (like a "//# sourceMappingURL=") at the end of the child file. if (line >= lineIndices.length - 1) return null; const index = memoizedBinarySearch(mappings, column, searchComparator, lineIndices[line], lineIndices[line + 1] - 1, ITEM_LENGTH, this, line, column); // we come before any mapped segment if (index < 0) return null; return segmentify(line, mappings[index + 0], mappings[index + 1] - 1, mappings[index + 2] - 1, mappings[index + 3] - 1, mappings[index + 4] - 1); } } function segmentify(_genLine, genCol, source, line, col, name) { // If the sourcesIndex is -1, then the VLQ segment didn't specify 2-5 values. if (source === -1) return [genCol]; // If the namesIndex is -1, then the VLQ segment didn't specify 5th value. if (name === -1) return [genCol, source, line, col]; return [genCol, source, line, col, name]; } function searchComparator(column, needle) { return column - needle; } const INVALID_MAPPING = Object.freeze({ source: null, line: null, column: null, name: null, }); class TraceMap { constructor(map, mapUrl) { const isString = typeof map === 'string'; const parsed = isString ? JSON.parse(map) : map; const { version, file, names, sourceRoot, sources, sourcesContent } = parsed; this.version = version; this.file = file; this.names = names; this.sourceRoot = sourceRoot; this.sources = sources; this.sourcesContent = sourcesContent; const from = resolve(sourceRoot || '', stripFilename(mapUrl)); this.resolvedSources = sources.map((s) => resolve(s || '', from)); if (typeof parsed.mappings === 'string') { this._impl = new EncodedSourceMapImpl(parsed); } else { this._impl = new DecodedSourceMapImpl(parsed, isString); } } /** * Returns the encoded (VLQ string) form of the SourceMap's mappings field. */ encodedMappings() { return this._impl.encodedMappings(); } /** * Returns the decoded (array of lines of segments) form of the SourceMap's mappings field. */ decodedMappings() { return this._impl.decodedMappings(); } /** * Similar to Array.p.map, maps each segment into a new segment. Passes -1 for any values that do * not exist in the SourceMapSegment. Both generatedLine and generatedColumn are 0-based. */ map(fn) { return this._impl.map(fn); } /** * A low-level API to find the segment associated with a generated line/column (think, from a * stack trace). Line and column here are 0-based, unlike `originalPositionFor`. */ traceSegment(line, column) { return this._impl.traceSegment(line, column); } /** * A higher-level API to find the source/line/column associated with a generated line/column * (think, from a stack trace). Line is 1-based, but column is 0-based, due to legacy behavior in * `source-map` library. */ originalPositionFor({ line, column }) { if (line < 1) throw new Error('`line` must be greater than 0 (lines start at line 1)'); if (column < 0) { throw new Error('`column` must be greater than or equal to 0 (columns start at column 0)'); } const segment = this.traceSegment(line - 1, column); if (segment == null) return INVALID_MAPPING; if (segment.length == 1) return INVALID_MAPPING; const { names, resolvedSources } = this; return { source: resolvedSources[segment[1]], line: segment[2] + 1, column: segment[3], name: segment.length === 5 ? names[segment[4]] : null, }; } } export { TraceMap, TraceMap as default }; //# sourceMappingURL=trace-mapping.mjs.map