diff options
Diffstat (limited to 'packages/bun-error/sourcemap.ts')
-rw-r--r-- | packages/bun-error/sourcemap.ts | 300 |
1 files changed, 300 insertions, 0 deletions
diff --git a/packages/bun-error/sourcemap.ts b/packages/bun-error/sourcemap.ts new file mode 100644 index 000000000..827231490 --- /dev/null +++ b/packages/bun-error/sourcemap.ts @@ -0,0 +1,300 @@ +// Accelerate VLQ decoding with a lookup table +const vlqTable = new Uint8Array(128); +const vlqChars = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; +vlqTable.fill(0xff); +for (let i = 0; i < vlqChars.length; i++) vlqTable[vlqChars.charCodeAt(i)] = i; + +export function parseSourceMap(json) { + if (json.version !== 3) { + throw new Error("Invalid source map"); + } + + if ( + !(json.sources instanceof Array) || + json.sources.some((x) => typeof x !== "string") + ) { + throw new Error("Invalid source map"); + } + + if (typeof json.mappings !== "string") { + throw new Error("Invalid source map"); + } + + const { sources, sourcesContent, names, mappings } = json; + const emptyData = new Int32Array(0); + for (let i = 0; i < sources.length; i++) { + sources[i] = { + name: sources[i], + content: (sourcesContent && sourcesContent[i]) || "", + data: emptyData, + dataLength: 0, + }; + } + const data = decodeMappings(mappings, sources.length); + return { sources, names, data }; +} + +// ripped from https://github.com/evanw/source-map-visualization/blob/gh-pages/code.js#L179 +export function decodeMappings(mappings, sourcesCount) { + const n = mappings.length; + let data = new Int32Array(1024); + let dataLength = 0; + let generatedLine = 0; + let generatedLineStart = 0; + let generatedColumn = 0; + let originalSource = 0; + let originalLine = 0; + let originalColumn = 0; + let originalName = 0; + let needToSortGeneratedColumns = false; + let i = 0; + + function decodeError(text) { + const error = `Invalid VLQ data at index ${i}: ${text}`; + throw new Error(error); + } + + function decodeVLQ() { + let shift = 0; + let vlq = 0; + + // Scan over the input + while (true) { + // Read a byte + if (i >= mappings.length) decodeError("Expected extra data"); + const c = mappings.charCodeAt(i); + if ((c & 0x7f) !== c) decodeError("Invalid character"); + const index = vlqTable[c & 0x7f]; + if (index === 0xff) decodeError("Invalid character"); + i++; + + // Decode the byte + vlq |= (index & 31) << shift; + shift += 5; + + // Stop if there's no continuation bit + if ((index & 32) === 0) break; + } + + // Recover the signed value + return vlq & 1 ? -(vlq >> 1) : vlq >> 1; + } + + while (i < n) { + let c = mappings.charCodeAt(i); + + // Handle a line break + if (c === 59 /* ; */) { + // The generated columns are very rarely out of order. In that case, + // sort them with insertion since they are very likely almost ordered. + if (needToSortGeneratedColumns) { + for (let j = generatedLineStart + 6; j < dataLength; j += 6) { + const genL = data[j]; + const genC = data[j + 1]; + const origS = data[j + 2]; + const origL = data[j + 3]; + const origC = data[j + 4]; + const origN = data[j + 5]; + let k = j - 6; + for (; k >= generatedLineStart && data[k + 1] > genC; k -= 6) { + data[k + 6] = data[k]; + data[k + 7] = data[k + 1]; + data[k + 8] = data[k + 2]; + data[k + 9] = data[k + 3]; + data[k + 10] = data[k + 4]; + data[k + 11] = data[k + 5]; + } + data[k + 6] = genL; + data[k + 7] = genC; + data[k + 8] = origS; + data[k + 9] = origL; + data[k + 10] = origC; + data[k + 11] = origN; + } + } + + generatedLine++; + generatedColumn = 0; + generatedLineStart = dataLength; + needToSortGeneratedColumns = false; + i++; + continue; + } + + // Ignore stray commas + if (c === 44 /* , */) { + i++; + continue; + } + + // Read the generated column + const generatedColumnDelta = decodeVLQ(); + if (generatedColumnDelta < 0) needToSortGeneratedColumns = true; + generatedColumn += generatedColumnDelta; + if (generatedColumn < 0) decodeError("Invalid generated column"); + + // It's valid for a mapping to have 1, 4, or 5 variable-length fields + let isOriginalSourceMissing = true; + let isOriginalNameMissing = true; + if (i < n) { + c = mappings.charCodeAt(i); + if (c === 44 /* , */) { + i++; + } else if (c !== 59 /* ; */) { + isOriginalSourceMissing = false; + + // Read the original source + const originalSourceDelta = decodeVLQ(); + originalSource += originalSourceDelta; + if (originalSource < 0 || originalSource >= sourcesCount) + decodeError("Invalid original source"); + + // Read the original line + const originalLineDelta = decodeVLQ(); + originalLine += originalLineDelta; + if (originalLine < 0) decodeError("Invalid original line"); + + // Read the original column + const originalColumnDelta = decodeVLQ(); + originalColumn += originalColumnDelta; + if (originalColumn < 0) decodeError("Invalid original column"); + + // Check for the optional name index + if (i < n) { + c = mappings.charCodeAt(i); + if (c === 44 /* , */) { + i++; + } else if (c !== 59 /* ; */) { + isOriginalNameMissing = false; + + // Read the optional name index + const originalNameDelta = decodeVLQ(); + originalName += originalNameDelta; + if (originalName < 0) decodeError("Invalid original name"); + + // Handle the next character + if (i < n) { + c = mappings.charCodeAt(i); + if (c === 44 /* , */) { + i++; + } else if (c !== 59 /* ; */) { + decodeError("Invalid character after mapping"); + } + } + } + } + } + } + + // Append the mapping to the typed array + if (dataLength + 6 > data.length) { + const newData = new Int32Array(data.length << 1); + newData.set(data); + data = newData; + } + data[dataLength] = generatedLine; + data[dataLength + 1] = generatedColumn; + if (isOriginalSourceMissing) { + data[dataLength + 2] = -1; + data[dataLength + 3] = -1; + data[dataLength + 4] = -1; + } else { + data[dataLength + 2] = originalSource; + data[dataLength + 3] = originalLine; + data[dataLength + 4] = originalColumn; + } + data[dataLength + 5] = isOriginalNameMissing ? -1 : originalName; + dataLength += 6; + } + + return data.subarray(0, dataLength); +} + +export function remapPosition( + decodedMappings: Int32Array, + line: number, + column: number +) { + if (!(decodedMappings instanceof Int32Array)) { + throw new Error("decodedMappings must be an Int32Array"); + } + + if (!Number.isFinite(line)) { + throw new Error("line must be a finite number"); + } + + if (!Number.isFinite(column)) { + throw new Error("column must be a finite number"); + } + + if (decodedMappings.length === 0 || line < 0 || column < 0) return null; + + const index = indexOfMapping(decodedMappings, line, column); + if (index === -1) return null; + + return [decodedMappings[index + 3], decodedMappings[index + 4]]; +} + +async function fetchRemoteSourceMap(file: string, signal) { + const response = await globalThis.fetch(file + ".map", { + signal, + headers: { + Accept: "application/json", + "Mappings-Only": "1", + }, + }); + + if (response.ok) { + return await response.json(); + } + + return null; +} + +export var sourceMappings = new Map(); + +export function fetchMappings(file, signal) { + if (sourceMappings.has(file)) { + return sourceMappings.get(file); + } + + return fetchRemoteSourceMap(file, signal).then((json) => { + if (!json) return null; + const { data } = parseSourceMap(json); + sourceMappings.set(file, data); + return data; + }); +} + +function indexOfMapping(mappings: Int32Array, line: number, column: number) { + // the array is [generatedLine, generatedColumn, sourceIndex, sourceLine, sourceColumn, nameIndex] + // 0 - generated line + var count = mappings.length / 6; + var index = 0; + while (count > 0) { + var step = (count / 2) | 0; + var i = index + step; + // this multiply is slow but it's okay for now + var j = i * 6; + if ( + mappings[j] < line || + (mappings[j] == line && mappings[j + 1] <= column) + ) { + index = i + 1; + count -= step + 1; + } else { + count = step; + } + } + + index = index | 0; + + if (index > 0) { + if (mappings[(index - 1) * 6] == line) { + return (index - 1) * 6; + } + } + + return null; +} |