Skip to content

Instantly share code, notes, and snippets.

@mingshun
Last active March 2, 2023 23:51
Show Gist options
  • Save mingshun/1021892c6954a847c6b662f49771bbbe to your computer and use it in GitHub Desktop.
Save mingshun/1021892c6954a847c6b662f49771bbbe to your computer and use it in GitHub Desktop.
SourceMap Parser in Typescript
class SourceMapConsumer {
private readonly base64Table: string = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
private readonly mappingItems: MappingItem[];
constructor(raw: string) {
this.mappingItems = this.parseSourcemap(raw);
}
originalPositionFor(position: Position): MappingItem | null {
for (const item of this.mappingItems) {
if (item.originalLine === position.line && item.originalColumn === position.column) {
return {
source: item.source,
generatedLine: item.generatedLine,
generatedColumn: item.generatedColumn,
originalLine: item.originalLine,
originalColumn: item.originalColumn,
name: item.name,
};
}
}
return null;
}
generatedPositionFor(position: Position): MappingItem | null {
for (const item of this.mappingItems) {
if (item.generatedLine === position.line && item.generatedColumn === position.column) {
return {
source: item.source,
generatedLine: item.generatedLine,
generatedColumn: item.generatedColumn,
originalLine: item.originalLine,
originalColumn: item.originalColumn,
name: item.name,
};
}
}
return null;
}
private parseSourcemap(raw: string): MappingItem[] {
const items: MappingItem[] = [];
const sourceMap: SourceMap = JSON.parse(raw);
const lines = sourceMap.mappings.split(";");
for (let i in lines) {
if (lines[i] === "") {
continue;
}
const locations = lines[i].split(",");
for (let j in locations) {
const lastItem = items.length > 0 ? items[items.length - 1] : {
source: sourceMap.sources[0],
generatedLine: 1,
generatedColumn: 1,
originalLine: 1,
originalColumn: 1,
name: sourceMap.names[0],
};
if (+j === 0) {
lastItem.generatedColumn = 1;
lastItem.originalColumn = 1;
}
const vlq = this.decodeBase64VLQ(locations[j]);
items.push({
source: this.findItem(sourceMap.sources, lastItem.source, vlq[1]),
generatedLine: +i + 1,
generatedColumn: lastItem.generatedColumn + vlq[0],
originalLine: lastItem.originalLine + vlq[2],
originalColumn: lastItem.generatedColumn + vlq[3],
name: this.findItem(sourceMap.names, lastItem.name, vlq[4]),
});
}
}
return items;
}
private findItem(arr: string[], lastValue: string, delta: number | undefined): string {
if (arr.length === 0) {
return "";
};
if (delta === undefined) {
return "";
}
return arr[arr.indexOf(lastValue) + delta];
}
private decodeBase64VLQ(encoded: string): number[] {
const vlqs: number[][] = [];
let groupIndex = 0;
let digitIndex = 0;
for (let i = 0; i < encoded.length; i++) {
const digit = this.base64Table.indexOf(encoded[i]);
if (vlqs[groupIndex] === undefined) {
vlqs[groupIndex] = [];
}
vlqs[groupIndex][digitIndex] = digit;
if (((digit >> 5) & 1) === 1) {
digitIndex++;
} else {
groupIndex++;
digitIndex = 0;
}
}
return vlqs.map(e => this.decodeVLQ(e));
}
private decodeVLQ(encoded: number[]): number {
let result = 0;
const sign = (encoded[0] & 1) === 1 ? -1 : 1;
result = (encoded[0] >> 1) & 15;
for (let i = 1; i < encoded.length; i++) {
result += (encoded[i] & 31) << (4 + 5 * (i - 1))
}
return sign * result;
}
}
interface SourceMap {
version: number;
file: string;
sourceRoot: string;
sources: string[];
names: string[];
mappings: string;
}
interface MappingItem {
source: string;
generatedLine: number;
generatedColumn: number;
originalLine: number;
originalColumn: number;
name: string;
}
interface Position {
line: number,
column: number,
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment