mirror of
https://github.com/oven-sh/bun
synced 2026-02-02 15:08:46 +00:00
* Improve support for \`debug-adapter-protocol\` * More improvements, fix formatting in debug console * Fix attaching * Prepare for source maps * Start of source map support, breakpoints work * Source map support * add some package.jsons * wip * Update package.json * More fixes * Make source maps safer if exception occurs * Check bun version if it fails * Fix console.log formatting * Fix source maps partly * More source map fixes * Prepare for extension * watch mode with dap * Improve preview code * Prepare for extension 2 --------- Co-authored-by: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com>
188 lines
4.6 KiB
TypeScript
188 lines
4.6 KiB
TypeScript
import type { LineRange, MappedPosition } from "source-map-js";
|
|
import { SourceMapConsumer } from "source-map-js";
|
|
|
|
export type LocationRequest = {
|
|
line?: number;
|
|
column?: number;
|
|
url?: string;
|
|
};
|
|
|
|
export type Location = {
|
|
line: number; // 0-based
|
|
column: number; // 0-based
|
|
} & (
|
|
| {
|
|
verified: true;
|
|
}
|
|
| {
|
|
verified?: false;
|
|
message?: string;
|
|
}
|
|
);
|
|
|
|
export interface SourceMap {
|
|
generatedLocation(request: LocationRequest): Location;
|
|
originalLocation(request: LocationRequest): Location;
|
|
}
|
|
|
|
class ActualSourceMap implements SourceMap {
|
|
#sourceMap: SourceMapConsumer;
|
|
#sources: string[];
|
|
|
|
constructor(sourceMap: SourceMapConsumer) {
|
|
this.#sourceMap = sourceMap;
|
|
this.#sources = (sourceMap as any)._absoluteSources;
|
|
}
|
|
|
|
#getSource(url?: string): string {
|
|
const sources = this.#sources;
|
|
if (!sources.length) {
|
|
return "";
|
|
}
|
|
if (sources.length === 1 || !url) {
|
|
return sources[0];
|
|
}
|
|
for (const source of sources) {
|
|
if (url.endsWith(source)) {
|
|
return source;
|
|
}
|
|
}
|
|
return "";
|
|
}
|
|
|
|
generatedLocation(request: LocationRequest): Location {
|
|
const { line, column, url } = request;
|
|
let lineRange: LineRange;
|
|
try {
|
|
const source = this.#getSource(url);
|
|
lineRange = this.#sourceMap.generatedPositionFor({
|
|
line: lineTo1BasedLine(line),
|
|
column: columnToColumn(column),
|
|
source,
|
|
});
|
|
} catch (error) {
|
|
return {
|
|
line: lineToLine(line),
|
|
column: columnToColumn(column),
|
|
verified: false,
|
|
message: unknownToError(error),
|
|
};
|
|
}
|
|
if (!locationIsValid(lineRange)) {
|
|
return {
|
|
line: lineToLine(line),
|
|
column: columnToColumn(column),
|
|
verified: false,
|
|
};
|
|
}
|
|
const { line: gline, column: gcolumn } = lineRange;
|
|
return {
|
|
line: lineToLine(gline),
|
|
column: columnToColumn(gcolumn),
|
|
verified: true,
|
|
};
|
|
}
|
|
|
|
originalLocation(request: LocationRequest): Location {
|
|
const { line, column } = request;
|
|
let mappedPosition: MappedPosition;
|
|
try {
|
|
mappedPosition = this.#sourceMap.originalPositionFor({
|
|
line: lineTo1BasedLine(line),
|
|
column: columnToColumn(column),
|
|
});
|
|
} catch (error) {
|
|
return {
|
|
line: lineToLine(line),
|
|
column: columnToColumn(column),
|
|
verified: false,
|
|
message: unknownToError(error),
|
|
};
|
|
}
|
|
if (!locationIsValid(mappedPosition)) {
|
|
return {
|
|
line: lineToLine(line),
|
|
column: columnToColumn(column),
|
|
verified: false,
|
|
};
|
|
}
|
|
const { line: oline, column: ocolumn } = mappedPosition;
|
|
return {
|
|
line: lineTo0BasedLine(oline),
|
|
column: columnToColumn(ocolumn),
|
|
verified: true,
|
|
};
|
|
}
|
|
}
|
|
|
|
class NoopSourceMap implements SourceMap {
|
|
generatedLocation(request: LocationRequest): Location {
|
|
const { line, column } = request;
|
|
return {
|
|
line: lineToLine(line),
|
|
column: columnToColumn(column),
|
|
verified: true,
|
|
};
|
|
}
|
|
|
|
originalLocation(request: LocationRequest): Location {
|
|
const { line, column } = request;
|
|
return {
|
|
line: lineToLine(line),
|
|
column: columnToColumn(column),
|
|
verified: true,
|
|
};
|
|
}
|
|
}
|
|
|
|
const defaultSourceMap = new NoopSourceMap();
|
|
|
|
export function SourceMap(url?: string): SourceMap {
|
|
if (!url || !url.startsWith("data:")) {
|
|
return defaultSourceMap;
|
|
}
|
|
try {
|
|
const [_, base64] = url.split(",", 2);
|
|
const decoded = Buffer.from(base64, "base64url").toString("utf8");
|
|
const schema = JSON.parse(decoded);
|
|
const sourceMap = new SourceMapConsumer(schema);
|
|
return new ActualSourceMap(sourceMap);
|
|
} catch (error) {
|
|
console.warn("Failed to parse source map URL", url);
|
|
}
|
|
return defaultSourceMap;
|
|
}
|
|
|
|
function lineTo1BasedLine(line?: number): number {
|
|
return numberIsValid(line) ? line + 1 : 1;
|
|
}
|
|
|
|
function lineTo0BasedLine(line?: number): number {
|
|
return numberIsValid(line) ? line - 1 : 0;
|
|
}
|
|
|
|
function lineToLine(line?: number): number {
|
|
return numberIsValid(line) ? line : 0;
|
|
}
|
|
|
|
function columnToColumn(column?: number): number {
|
|
return numberIsValid(column) ? column : 0;
|
|
}
|
|
|
|
function locationIsValid(location: Location): location is Location {
|
|
const { line, column } = location;
|
|
return numberIsValid(line) && numberIsValid(column);
|
|
}
|
|
|
|
function numberIsValid(number?: number): number is number {
|
|
return typeof number === "number" && isFinite(number) && number >= 0;
|
|
}
|
|
|
|
function unknownToError(error: unknown): string {
|
|
if (error instanceof Error) {
|
|
const { message } = error;
|
|
return message;
|
|
}
|
|
return String(error);
|
|
}
|