aboutsummaryrefslogtreecommitdiff
path: root/packages/bun-debug-adapter-protocol/debugger/sourcemap.ts
diff options
context:
space:
mode:
authorGravatar Ashcon Partovi <ashcon@partovi.net> 2023-08-24 22:53:34 -0700
committerGravatar GitHub <noreply@github.com> 2023-08-24 22:53:34 -0700
commit1480889205d49cf7221a36608a8896b452967cea (patch)
treee1427e4041cf19ef1e8e8e0f58cfbbceb4cbbf74 /packages/bun-debug-adapter-protocol/debugger/sourcemap.ts
parentf269432d90826ad3e5b66c7685a6e826e0fb05e2 (diff)
downloadbun-1480889205d49cf7221a36608a8896b452967cea.tar.gz
bun-1480889205d49cf7221a36608a8896b452967cea.tar.zst
bun-1480889205d49cf7221a36608a8896b452967cea.zip
Improved support for `debug-adapter-protocol` (#4186)
* 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>
Diffstat (limited to 'packages/bun-debug-adapter-protocol/debugger/sourcemap.ts')
-rw-r--r--packages/bun-debug-adapter-protocol/debugger/sourcemap.ts187
1 files changed, 187 insertions, 0 deletions
diff --git a/packages/bun-debug-adapter-protocol/debugger/sourcemap.ts b/packages/bun-debug-adapter-protocol/debugger/sourcemap.ts
new file mode 100644
index 000000000..adb6dc57d
--- /dev/null
+++ b/packages/bun-debug-adapter-protocol/debugger/sourcemap.ts
@@ -0,0 +1,187 @@
+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);
+}