Last active
March 2, 2023 23:51
-
-
Save mingshun/1021892c6954a847c6b662f49771bbbe to your computer and use it in GitHub Desktop.
SourceMap Parser in Typescript
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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