aboutsummaryrefslogtreecommitdiff
path: root/packages/bun-debug-adapter-protocol/debugger
diff options
context:
space:
mode:
Diffstat (limited to 'packages/bun-debug-adapter-protocol/debugger')
-rw-r--r--packages/bun-debug-adapter-protocol/debugger/__snapshots__/preview.test.ts.snap143
-rw-r--r--packages/bun-debug-adapter-protocol/debugger/adapter.ts1692
-rw-r--r--packages/bun-debug-adapter-protocol/debugger/capabilities.ts271
-rw-r--r--packages/bun-debug-adapter-protocol/debugger/fixtures/preview.js99
-rw-r--r--packages/bun-debug-adapter-protocol/debugger/fixtures/with-sourcemap.js36
-rw-r--r--packages/bun-debug-adapter-protocol/debugger/fixtures/with-sourcemap.ts46
-rw-r--r--packages/bun-debug-adapter-protocol/debugger/fixtures/without-sourcemap.js20
-rw-r--r--packages/bun-debug-adapter-protocol/debugger/preview.test.ts62
-rw-r--r--packages/bun-debug-adapter-protocol/debugger/preview.ts110
-rw-r--r--packages/bun-debug-adapter-protocol/debugger/sourcemap.test.ts31
-rw-r--r--packages/bun-debug-adapter-protocol/debugger/sourcemap.ts187
11 files changed, 0 insertions, 2697 deletions
diff --git a/packages/bun-debug-adapter-protocol/debugger/__snapshots__/preview.test.ts.snap b/packages/bun-debug-adapter-protocol/debugger/__snapshots__/preview.test.ts.snap
deleted file mode 100644
index 0acc17575..000000000
--- a/packages/bun-debug-adapter-protocol/debugger/__snapshots__/preview.test.ts.snap
+++ /dev/null
@@ -1,143 +0,0 @@
-// Bun Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`remoteObjectToString 1`] = `"undefined"`;
-
-exports[`remoteObjectToString 2`] = `"null"`;
-
-exports[`remoteObjectToString 3`] = `"true"`;
-
-exports[`remoteObjectToString 4`] = `"false"`;
-
-exports[`remoteObjectToString 5`] = `"0"`;
-
-exports[`remoteObjectToString 6`] = `"1"`;
-
-exports[`remoteObjectToString 7`] = `"3.141592653589793"`;
-
-exports[`remoteObjectToString 8`] = `"-2.718281828459045"`;
-
-exports[`remoteObjectToString 9`] = `"NaN"`;
-
-exports[`remoteObjectToString 10`] = `"Infinity"`;
-
-exports[`remoteObjectToString 11`] = `"-Infinity"`;
-
-exports[`remoteObjectToString 12`] = `"0n"`;
-
-exports[`remoteObjectToString 13`] = `"1n"`;
-
-exports[`remoteObjectToString 14`] = `"10000000000000n"`;
-
-exports[`remoteObjectToString 15`] = `"-10000000000000n"`;
-
-exports[`remoteObjectToString 16`] = `""""`;
-
-exports[`remoteObjectToString 17`] = `"" ""`;
-
-exports[`remoteObjectToString 18`] = `""Hello""`;
-
-exports[`remoteObjectToString 19`] = `""Hello World""`;
-
-exports[`remoteObjectToString 20`] = `"Array(0)"`;
-
-exports[`remoteObjectToString 21`] = `"Array(3) [1, 2, 3]"`;
-
-exports[`remoteObjectToString 22`] = `"Array(4) ["a", 1, null, undefined]"`;
-
-exports[`remoteObjectToString 23`] = `"Array(2) [1, Array]"`;
-
-exports[`remoteObjectToString 24`] = `"Array(1) [Array]"`;
-
-exports[`remoteObjectToString 25`] = `"{}"`;
-
-exports[`remoteObjectToString 26`] = `"{a: 1}"`;
-
-exports[`remoteObjectToString 27`] = `"{a: 1, b: 2, c: 3}"`;
-
-exports[`remoteObjectToString 28`] = `"{a: Object}"`;
-
-exports[`remoteObjectToString 29`] = `
-"ƒ() {
-}"
-`;
-
-exports[`remoteObjectToString 30`] = `
-"ƒ namedFunction() {
-}"
-`;
-
-exports[`remoteObjectToString 31`] = `
-"class {
-}"
-`;
-
-exports[`remoteObjectToString 32`] = `
-"class namedClass {
-}"
-`;
-
-exports[`remoteObjectToString 33`] = `
-"class namedClass {
- a() {
- }
- b = 1;
- c = [
- null,
- undefined,
- "a",
- {
- a: 1,
- b: 2,
- c: 3
- }
- ];
-}"
-`;
-
-exports[`remoteObjectToString 34`] = `"Wed Dec 31 1969 16:00:00 GMT-0800 (Pacific Standard Time)"`;
-
-exports[`remoteObjectToString 35`] = `"Invalid Date"`;
-
-exports[`remoteObjectToString 36`] = `"/(?:)/"`;
-
-exports[`remoteObjectToString 37`] = `"/abc/"`;
-
-exports[`remoteObjectToString 38`] = `"/abc/g"`;
-
-exports[`remoteObjectToString 39`] = `"/abc/"`;
-
-exports[`remoteObjectToString 40`] = `"Set(0)"`;
-
-exports[`remoteObjectToString 41`] = `"Set(3) [1, 2, 3]"`;
-
-exports[`remoteObjectToString 42`] = `"WeakSet(0)"`;
-
-exports[`remoteObjectToString 43`] = `"WeakSet(3) [{a: 1}, {b: 2}, {c: 3}]"`;
-
-exports[`remoteObjectToString 44`] = `"Map(0)"`;
-
-exports[`remoteObjectToString 45`] = `"Map(3) {"a" => 1, "b" => 2, "c" => 3}"`;
-
-exports[`remoteObjectToString 46`] = `"WeakMap(0)"`;
-
-exports[`remoteObjectToString 47`] = `"WeakMap(3) {{a: 1} => 1, {b: 2} => 2, {c: 3} => 3}"`;
-
-exports[`remoteObjectToString 48`] = `"Symbol()"`;
-
-exports[`remoteObjectToString 49`] = `"Symbol(namedSymbol)"`;
-
-exports[`remoteObjectToString 50`] = `"Error"`;
-
-exports[`remoteObjectToString 51`] = `"TypeError: This is a TypeError"`;
-
-exports[`remoteObjectToString 52`] = `"Headers {append: ƒ, delete: ƒ, get: ƒ, getAll: ƒ, has: ƒ, …}"`;
-
-exports[`remoteObjectToString 53`] = `"Headers {a: "1", append: ƒ, b: "2", delete: ƒ, get: ƒ, …}"`;
-
-exports[`remoteObjectToString 54`] = `"Request {arrayBuffer: ƒ, blob: ƒ, body: null, bodyUsed: false, cache: "default", …}"`;
-
-exports[`remoteObjectToString 55`] = `"Request {arrayBuffer: ƒ, blob: ƒ, body: ReadableStream, bodyUsed: false, cache: "default", …}"`;
-
-exports[`remoteObjectToString 56`] = `"Response {arrayBuffer: ƒ, blob: ƒ, body: null, bodyUsed: false, clone: ƒ, …}"`;
-
-exports[`remoteObjectToString 57`] = `"Response {arrayBuffer: ƒ, blob: ƒ, body: ReadableStream, bodyUsed: false, clone: ƒ, …}"`;
diff --git a/packages/bun-debug-adapter-protocol/debugger/adapter.ts b/packages/bun-debug-adapter-protocol/debugger/adapter.ts
deleted file mode 100644
index 9dc55fe38..000000000
--- a/packages/bun-debug-adapter-protocol/debugger/adapter.ts
+++ /dev/null
@@ -1,1692 +0,0 @@
-import type { DAP } from "..";
-// @ts-ignore: FIXME - there is something wrong with the types
-import type { JSC, InspectorListener } from "../../bun-inspector-protocol";
-import { WebSocketInspector } from "../../bun-inspector-protocol";
-import type { ChildProcess } from "node:child_process";
-import { spawn, spawnSync } from "node:child_process";
-import capabilities from "./capabilities";
-import { Location, SourceMap } from "./sourcemap";
-import { remoteObjectToString } from "./preview";
-import { compare, parse } from "semver";
-
-type InitializeRequest = DAP.InitializeRequest & {
- supportsConfigurationDoneRequest?: boolean;
-};
-
-type LaunchRequest = DAP.LaunchRequest & {
- runtime?: string;
- program?: string;
- cwd?: string;
- args?: string[];
- env?: Record<string, string>;
- inheritEnv?: boolean;
- watch?: boolean | "hot";
-};
-
-type AttachRequest = DAP.AttachRequest & {
- url?: string;
-};
-
-type Source = DAP.Source & {
- scriptId: string;
- sourceMap: SourceMap;
-} & (
- | {
- sourceId: string;
- path: string;
- sourceReference?: undefined;
- }
- | {
- sourceId: number;
- path?: undefined;
- sourceReference: number;
- }
- );
-
-type Breakpoint = DAP.Breakpoint & {
- id: number;
- breakpointId: string;
- source: Source;
-};
-
-type FunctionBreakpoint = DAP.Breakpoint & {
- id: number;
- name: string;
-};
-
-type StackFrame = DAP.StackFrame & {
- scriptId: string;
- callFrameId: string;
- source?: Source;
- scopes?: Scope[];
-};
-
-type Scope = DAP.Scope & {
- source?: Source;
-};
-
-type Variable = DAP.Variable & {
- objectId?: string;
- type: JSC.Runtime.RemoteObject["type"] | JSC.Runtime.RemoteObject["subtype"];
-};
-
-type IDebugAdapter = {
- [E in keyof DAP.EventMap]?: (event: DAP.EventMap[E]) => void;
-} & {
- [R in keyof DAP.RequestMap]?: (
- request: DAP.RequestMap[R],
- ) => void | DAP.ResponseMap[R] | Promise<void | DAP.ResponseMap[R]>;
-};
-
-export type DebugAdapterOptions = {
- sendToAdapter(message: DAP.Request | DAP.Response | DAP.Event): Promise<void>;
-};
-
-// This adapter only support single-threaded debugging,
-// which means that there is only one thread at a time.
-const threadId = 1;
-
-export class DebugAdapter implements IDebugAdapter, InspectorListener {
- #sendToAdapter: DebugAdapterOptions["sendToAdapter"];
- #inspector: WebSocketInspector;
- #sourceId: number;
- #pendingSources: Map<string, ((source: Source) => void)[]>;
- #sources: Map<string | number, Source>;
- #stackFrames: StackFrame[];
- #stopped?: DAP.StoppedEvent["reason"];
- #breakpointId: number;
- #breakpoints: Breakpoint[];
- #functionBreakpoints: Map<string, FunctionBreakpoint>;
- #variables: (Variable | Variable[])[];
- #process?: ChildProcess;
- #initialized?: InitializeRequest;
- #launched?: LaunchRequest;
- #connected?: boolean;
- #terminated?: boolean;
- #url?: URL;
-
- constructor({ sendToAdapter }: DebugAdapterOptions) {
- this.#inspector = new WebSocketInspector({ listener: this });
- this.#sendToAdapter = sendToAdapter;
- this.#sourceId = 1;
- this.#pendingSources = new Map();
- this.#sources = new Map();
- this.#stackFrames = [];
- this.#stopped = undefined;
- this.#breakpointId = 1;
- this.#breakpoints = [];
- this.#functionBreakpoints = new Map();
- this.#variables = [{ name: "", value: "", type: undefined, variablesReference: 0 }];
- }
-
- async accept(message: DAP.Request | DAP.Response | DAP.Event): Promise<void> {
- const { type } = message;
-
- switch (type) {
- case "request":
- return this.#acceptRequest(message);
- }
-
- throw new Error(`Not supported: ${type}`);
- }
-
- async #acceptRequest(request: DAP.Request): Promise<void> {
- const { seq, command, arguments: args } = request;
-
- let response;
- try {
- if (!(command! in this)) {
- throw new Error(`Not supported: ${command}`);
- }
- response = await this[command as keyof this](args);
- } catch (error) {
- console.error(error);
- const { message } = unknownToError(error);
- return this.#sendToAdapter({
- type: "response",
- success: false,
- message,
- request_seq: seq,
- seq: 0,
- command,
- });
- }
-
- return this.#sendToAdapter({
- type: "response",
- success: true,
- request_seq: seq,
- seq: 0,
- command,
- body: response,
- });
- }
-
- async #send<M extends keyof JSC.RequestMap & keyof JSC.ResponseMap>(
- method: M,
- params?: JSC.RequestMap[M],
- ): Promise<JSC.ResponseMap[M]> {
- return this.#inspector.send(method, params);
- }
-
- async #emit<E extends keyof DAP.EventMap>(name: E, body?: DAP.EventMap[E]): Promise<void> {
- await this.#sendToAdapter({
- type: "event",
- seq: 0,
- event: name,
- body,
- });
- }
-
- initialize(request: InitializeRequest): DAP.InitializeResponse {
- const { clientID, supportsConfigurationDoneRequest } = (this.#initialized = request);
-
- this.#send("Inspector.enable");
- this.#send("Runtime.enable");
- this.#send("Console.enable");
- this.#send("Debugger.enable");
- this.#send("Debugger.setAsyncStackTraceDepth", { depth: 200 });
- this.#send("Debugger.setPauseOnDebuggerStatements", { enabled: true });
- this.#send("Debugger.setBlackboxBreakpointEvaluations", { blackboxBreakpointEvaluations: true });
- this.#send("Debugger.setBreakpointsActive", { active: true });
-
- // If the client will not send a `configurationDone` request, then we need to
- // tell the debugger that everything is ready.
- if (!supportsConfigurationDoneRequest && clientID !== "vscode") {
- this.#send("Inspector.initialized");
- }
-
- // Tell the client what capabilities this adapter supports.
- return capabilities;
- }
-
- configurationDone(): void {
- // If the client requested that `noDebug` mode be enabled,
- // then we need to disable all breakpoints and pause on statements.
- if (this.#launched?.noDebug) {
- this.#send("Debugger.setBreakpointsActive", { active: false });
- this.#send("Debugger.setPauseOnExceptions", { state: "none" });
- this.#send("Debugger.setPauseOnDebuggerStatements", { enabled: false });
- this.#send("Debugger.setPauseOnMicrotasks", { enabled: false });
- this.#send("Debugger.setPauseForInternalScripts", { shouldPause: false });
- this.#send("Debugger.setPauseOnAssertions", { enabled: false });
- }
-
- // Tell the debugger that everything is ready.
- this.#send("Inspector.initialized");
- }
-
- async launch(request: DAP.LaunchRequest): Promise<void> {
- this.#launched = request;
-
- try {
- await this.#launch(request);
- } catch (error) {
- // Some clients, like VSCode, will show a system-level popup when a `launch` request fails.
- // Instead, we want to show the error as a sidebar notification.
- const { message } = unknownToError(error);
- this.#emit("output", {
- category: "stderr",
- output: `Failed to start debugger.\n${message}`,
- });
- this.#emit("terminated");
- }
- }
-
- async #launch(request: LaunchRequest): Promise<void> {
- if (this.#process?.exitCode === null) {
- throw new Error("Another program is already running. Did you terminate the last session?");
- }
-
- const { program, runtime = "bun", args = [], cwd, env = {}, inheritEnv = true, watch = true } = request;
- if (!program) {
- throw new Error("No program specified. Did you set the 'program' property in your launch.json?");
- }
-
- if (!isJavaScript(program)) {
- throw new Error("Program must be a JavaScript or TypeScript file.");
- }
-
- const argz = ["--inspect-wait=0", ...args];
- if (watch) {
- argz.push(watch === "hot" ? "--hot" : "--watch");
- }
- console.log(argz);
-
- const subprocess = spawn(runtime, [...argz, program], {
- stdio: ["ignore", "pipe", "pipe", "pipe"],
- cwd,
- env: inheritEnv ? { ...process.env, ...env } : env,
- });
-
- subprocess.on("spawn", () => {
- this.#process = subprocess;
- this.#emit("process", {
- name: program,
- systemProcessId: subprocess.pid,
- isLocalProcess: true,
- startMethod: "launch",
- });
- });
-
- subprocess.on("exit", code => {
- this.#emit("exited", {
- exitCode: code ?? -1,
- });
- this.#process = undefined;
- });
-
- const stdout: string[] = [];
- subprocess.stdout!.on("data", data => {
- if (!this.#url) {
- const text = data.toString();
- stdout.push(text);
- const url = (this.#url = parseUrlMaybe(text));
- this.#inspector.start(url);
- } else if (stdout.length) {
- stdout.length = 0;
- }
- });
-
- const stderr: string[] = [];
- subprocess.stderr!.on("data", data => {
- if (!this.#url) {
- stderr.push(data.toString());
- } else if (stderr.length) {
- stderr.length = 0;
- }
- });
-
- const start = new Promise<undefined>(resolve => {
- subprocess.on("spawn", () => resolve(undefined));
- });
-
- const exitOrError = new Promise<number | string | Error>(resolve => {
- subprocess.on("exit", (code, signal) => resolve(code ?? signal ?? -1));
- subprocess.on("error", resolve);
- });
-
- const reason = await Promise.race([start, exitOrError]);
-
- if (reason instanceof Error) {
- const { message } = reason;
- throw new Error(`Program could not be started.\n${message}`);
- }
-
- if (reason !== undefined) {
- throw new Error(`Program exited with code ${reason} before the debugger could attached.`);
- }
-
- for (let retries = 0; !this.#url && retries < 10; retries++) {
- await new Promise(resolve => setTimeout(resolve, 100 * retries));
- }
-
- if (this.#url) {
- return;
- }
-
- if (subprocess.exitCode === null && !subprocess.kill() && !subprocess.kill("SIGKILL")) {
- this.#emit("output", {
- category: "debug console",
- output: `Failed to kill process ${subprocess.pid}\n`,
- });
- }
-
- const { stdout: version } = spawnSync(runtime, ["--version"], { stdio: "pipe", encoding: "utf-8" });
-
- if (parse(version, true) && compare("0.8.0", version, true)) {
- throw new Error(
- `Bun v${version.trim()} does not have debugger support. Please upgrade to v0.8 or later by running: \`bun upgrade\``,
- );
- }
-
- for (const message of stderr) {
- this.#emit("output", {
- category: "stderr",
- output: message,
- source: {
- path: program,
- },
- });
- }
-
- for (const message of stdout) {
- this.#emit("output", {
- category: "stdout",
- output: message,
- source: {
- path: program,
- },
- });
- }
-
- throw new Error("Program started, but the debugger could not be attached.");
- }
-
- attach(request: AttachRequest): void {
- const { url } = request;
- this.#inspector.start(parseUrl(url));
- }
-
- terminate(): void {
- this.#terminated = true;
- this.#process?.kill();
- }
-
- disconnect(request: DAP.DisconnectRequest): void {
- const { terminateDebuggee } = request;
-
- if (terminateDebuggee) {
- this.terminate();
- }
-
- this.close();
- }
-
- async source(request: DAP.SourceRequest): Promise<DAP.SourceResponse> {
- const { source } = request;
-
- const { scriptId } = await this.#getSource(sourceToId(source));
- const { scriptSource } = await this.#send("Debugger.getScriptSource", { scriptId });
-
- return {
- content: scriptSource,
- };
- }
-
- async threads(request: DAP.ThreadsRequest): Promise<DAP.ThreadsResponse> {
- return {
- threads: [
- {
- id: threadId,
- name: "Main Thread",
- },
- ],
- };
- }
-
- async pause(): Promise<void> {
- await this.#send("Debugger.pause");
- this.#stopped = "pause";
- }
-
- async continue(): Promise<void> {
- await this.#send("Debugger.resume");
- this.#stopped = undefined;
- }
-
- async next(): Promise<void> {
- await this.#send("Debugger.stepNext");
- this.#stopped = "step";
- }
-
- async stepIn(): Promise<void> {
- await this.#send("Debugger.stepInto");
- this.#stopped = "step";
- }
-
- async stepOut(): Promise<void> {
- await this.#send("Debugger.stepOut");
- this.#stopped = "step";
- }
-
- async breakpointLocations(request: DAP.BreakpointLocationsRequest): Promise<DAP.BreakpointLocationsResponse> {
- const { line, endLine, column, endColumn, source: source0 } = request;
- const source = await this.#getSource(sourceToId(source0));
-
- const [start, end] = await Promise.all([
- this.#generatedLocation(source, line, column),
- this.#generatedLocation(source, endLine ?? line + 1, endColumn),
- ]);
-
- const { locations } = await this.#send("Debugger.getBreakpointLocations", {
- start,
- end,
- });
-
- return {
- breakpoints: locations.map(location => this.#originalLocation(source, location)),
- };
- }
-
- #generatedLocation(source: Source, line?: number, column?: number): JSC.Debugger.Location {
- const { sourceMap, scriptId, path } = source;
- const { line: gline, column: gcolumn } = sourceMap.generatedLocation({
- line: this.#lineTo0BasedLine(line),
- column: this.#columnTo0BasedColumn(column),
- url: path,
- });
-
- return {
- scriptId,
- lineNumber: gline,
- columnNumber: gcolumn,
- };
- }
-
- #lineTo0BasedLine(line?: number): number {
- if (!numberIsValid(line)) {
- return 0;
- }
- if (this.#initialized?.linesStartAt1) {
- return line - 1;
- }
- return line;
- }
-
- #columnTo0BasedColumn(column?: number): number {
- if (!numberIsValid(column)) {
- return 0;
- }
- if (this.#initialized?.columnsStartAt1) {
- return column - 1;
- }
- return column;
- }
-
- #originalLocation(
- source: Source,
- line?: number | JSC.Debugger.Location,
- column?: number,
- ): { line: number; column: number } {
- if (typeof line === "object") {
- const { lineNumber, columnNumber } = line;
- line = lineNumber;
- column = columnNumber;
- }
-
- const { sourceMap } = source;
- const { line: oline, column: ocolumn } = sourceMap.originalLocation({ line, column });
-
- return {
- line: this.#lineFrom0BasedLine(oline),
- column: this.#columnFrom0BasedColumn(ocolumn),
- };
- }
-
- #lineFrom0BasedLine(line?: number): number {
- if (this.#initialized?.linesStartAt1) {
- return numberIsValid(line) ? line + 1 : 1;
- }
- return numberIsValid(line) ? line : 0;
- }
-
- #columnFrom0BasedColumn(column?: number): number {
- if (this.#initialized?.columnsStartAt1) {
- return numberIsValid(column) ? column + 1 : 1;
- }
- return numberIsValid(column) ? column : 0;
- }
-
- async setBreakpoints(request: DAP.SetBreakpointsRequest): Promise<DAP.SetBreakpointsResponse> {
- const { source: source0, breakpoints: requests } = request;
- const sourceId = sourceToId(source0);
- const source = await this.#getSource(sourceId);
-
- const oldBreakpoints = this.#getBreakpoints(sourceId);
-
- const breakpoints = await Promise.all(
- requests!.map(async ({ line, column, ...options }) => {
- const breakpoint = this.#getBreakpoint(sourceId, line, column);
- if (breakpoint) {
- return breakpoint;
- }
-
- const location = this.#generatedLocation(source, line, column);
- try {
- const { breakpointId, actualLocation } = await this.#send("Debugger.setBreakpoint", {
- location,
- options: breakpointOptions(options),
- });
-
- const originalLocation = this.#originalLocation(source, actualLocation);
- return this.#addBreakpoint({
- id: this.#breakpointId++,
- breakpointId,
- source,
- verified: true,
- ...originalLocation,
- });
- } catch (error) {
- const { message } = unknownToError(error);
- // If there was an error setting the breakpoint,
- // mark it as unverified and add a message.
- const breakpointId = this.#breakpointId++;
- return this.#addBreakpoint({
- id: breakpointId,
- breakpointId: `${breakpointId}`,
- line,
- column,
- source,
- verified: false,
- message,
- });
- }
- }),
- );
-
- await Promise.all(
- oldBreakpoints.map(async ({ breakpointId }) => {
- const isRemoved = !breakpoints.filter(({ breakpointId: id }) => breakpointId === id).length;
- if (isRemoved) {
- await this.#send("Debugger.removeBreakpoint", {
- breakpointId,
- });
- this.#removeBreakpoint(breakpointId);
- }
- }),
- );
-
- return {
- breakpoints,
- };
- }
-
- #getBreakpoints(sourceId: string | number): Breakpoint[] {
- const breakpoints: Breakpoint[] = [];
-
- for (const breakpoint of this.#breakpoints.values()) {
- const { source } = breakpoint;
- if (sourceId === sourceToId(source)) {
- breakpoints.push(breakpoint);
- }
- }
-
- return breakpoints;
- }
-
- #getBreakpoint(sourceId: string | number, line?: number, column?: number): Breakpoint | undefined {
- for (const breakpoint of this.#getBreakpoints(sourceId)) {
- if (isSameLocation(breakpoint, { line, column })) {
- return breakpoint;
- }
- }
- return undefined;
- }
-
- #addBreakpoint(breakpoint: Breakpoint): Breakpoint {
- this.#breakpoints.push(breakpoint);
-
- this.#emit("breakpoint", {
- reason: "changed",
- breakpoint,
- });
-
- return breakpoint;
- }
-
- #removeBreakpoint(breakpointId: string): void {
- const breakpoint = this.#breakpoints.find(({ breakpointId: id }) => id === breakpointId);
- if (!breakpoint) {
- return;
- }
-
- this.#breakpoints = this.#breakpoints.filter(({ breakpointId: id }) => id !== breakpointId);
- this.#emit("breakpoint", {
- reason: "removed",
- breakpoint,
- });
- }
-
- async setFunctionBreakpoints(
- request: DAP.SetFunctionBreakpointsRequest,
- ): Promise<DAP.SetFunctionBreakpointsResponse> {
- const { breakpoints: requests } = request;
-
- const oldBreakpoints = this.#getFunctionBreakpoints();
-
- const breakpoints = await Promise.all(
- requests.map(async ({ name, ...options }) => {
- const breakpoint = this.#getFunctionBreakpoint(name);
- if (breakpoint) {
- return breakpoint;
- }
-
- try {
- await this.#send("Debugger.addSymbolicBreakpoint", {
- symbol: name,
- caseSensitive: true,
- isRegex: false,
- options: breakpointOptions(options),
- });
- } catch (error) {
- const { message } = unknownToError(error);
- return this.#addFunctionBreakpoint({
- id: this.#breakpointId++,
- name,
- verified: false,
- message,
- });
- }
-
- return this.#addFunctionBreakpoint({
- id: this.#breakpointId++,
- name,
- verified: true,
- });
- }),
- );
-
- await Promise.all(
- oldBreakpoints.map(async ({ name }) => {
- const isRemoved = !breakpoints.filter(({ name: n }) => name === n).length;
- if (isRemoved) {
- await this.#send("Debugger.removeSymbolicBreakpoint", {
- symbol: name,
- caseSensitive: true,
- isRegex: false,
- });
- this.#removeFunctionBreakpoint(name);
- }
- }),
- );
-
- return {
- breakpoints,
- };
- }
-
- #getFunctionBreakpoints(): FunctionBreakpoint[] {
- return [...this.#functionBreakpoints.values()];
- }
-
- #getFunctionBreakpoint(name: string): FunctionBreakpoint | undefined {
- return this.#functionBreakpoints.get(name);
- }
-
- #addFunctionBreakpoint(breakpoint: FunctionBreakpoint): FunctionBreakpoint {
- const { name } = breakpoint;
- this.#functionBreakpoints.set(name, breakpoint);
- this.#emit("breakpoint", {
- reason: "changed",
- breakpoint,
- });
- return breakpoint;
- }
-
- #removeFunctionBreakpoint(name: string): void {
- const breakpoint = this.#functionBreakpoints.get(name);
- if (!breakpoint || !this.#functionBreakpoints.delete(name)) {
- return;
- }
- this.#emit("breakpoint", {
- reason: "removed",
- breakpoint,
- });
- }
-
- async setExceptionBreakpoints(request: DAP.SetExceptionBreakpointsRequest): Promise<void> {
- const { filters, filterOptions } = request;
-
- const filterIds = [...filters];
- if (filterOptions) {
- filterIds.push(...filterOptions.map(({ filterId }) => filterId));
- }
-
- await this.#send("Debugger.setPauseOnExceptions", {
- state: exceptionFiltersToPauseOnExceptionsState(filterIds),
- });
- }
-
- async evaluate(request: DAP.EvaluateRequest): Promise<DAP.EvaluateResponse> {
- const { expression, frameId, context } = request;
- const callFrameId = this.#getCallFrameId(frameId);
-
- const { result, wasThrown } = await this.#evaluate(expression, callFrameId);
- const { className } = result;
-
- if (context === "hover" && wasThrown && (className === "SyntaxError" || className === "ReferenceError")) {
- return {
- result: "",
- variablesReference: 0,
- };
- }
-
- const { name, value, ...variable } = this.#addVariable(result);
- return {
- ...variable,
- result: value,
- };
- }
-
- async #evaluate(expression: string, callFrameId?: string): Promise<JSC.Runtime.EvaluateResponse> {
- const method = callFrameId ? "Debugger.evaluateOnCallFrame" : "Runtime.evaluate";
-
- return this.#send(method, {
- callFrameId,
- expression: sanitizeExpression(expression),
- generatePreview: true,
- emulateUserGesture: true,
- doNotPauseOnExceptionsAndMuteConsole: true,
- includeCommandLineAPI: true,
- });
- }
-
- restart(): void {
- this.initialize(this.#initialized!);
- this.configurationDone();
-
- this.#emit("output", {
- category: "debug console",
- output: "Debugger reloaded.\n",
- });
- }
-
- ["Inspector.connected"](): void {
- if (this.#connected) {
- this.restart();
- return;
- }
-
- this.#connected = true;
-
- this.#emit("output", {
- category: "debug console",
- output: "Debugger attached.\n",
- });
-
- this.#emit("initialized");
- }
-
- ["Inspector.disconnected"](error?: Error): void {
- if (this.#connected && this.#process?.exitCode === null) {
- this.#url = undefined;
- return;
- }
-
- this.#emit("output", {
- category: "debug console",
- output: "Debugger detached.\n",
- });
-
- if (error && !this.#terminated) {
- const { message } = error;
- this.#emit("output", {
- category: "stderr",
- output: `${message}\n`,
- });
- }
-
- this.#emit("terminated");
- this.#reset();
- }
-
- async ["Debugger.scriptParsed"](event: JSC.Debugger.ScriptParsedEvent): Promise<void> {
- const { url, scriptId, sourceMapURL } = event;
-
- // If no url is present, the script is from a `evaluate` request.
- if (!url) {
- return;
- }
-
- // Sources can be retrieved in two ways:
- // 1. If it has a `path`, the client retrieves the source from the file system.
- // 2. If it has a `sourceReference`, the client sends a `source` request.
- // Moreover, the code is usually shown in a read-only editor.
- const isUserCode = url.startsWith("/");
- const sourceMap = SourceMap(sourceMapURL);
- const name = sourceName(url);
- const presentationHint = sourcePresentationHint(url);
-
- if (isUserCode) {
- this.#addSource({
- sourceId: url,
- scriptId,
- name,
- path: url,
- presentationHint,
- sourceMap,
- });
- return;
- }
-
- const sourceReference = this.#sourceId++;
- this.#addSource({
- sourceId: sourceReference,
- scriptId,
- name,
- sourceReference,
- presentationHint,
- sourceMap,
- });
- }
-
- ["Debugger.scriptFailedToParse"](event: JSC.Debugger.ScriptFailedToParseEvent): void {
- const { url, errorMessage, errorLine } = event;
-
- this.#emit("output", {
- category: "stderr",
- output: errorMessage,
- line: this.#lineFrom0BasedLine(errorLine),
- source: {
- path: url || undefined,
- },
- });
- }
-
- ["Debugger.paused"](event: JSC.Debugger.PausedEvent): void {
- const { reason, callFrames, asyncStackTrace, data } = event;
-
- if (reason === "PauseOnNextStatement") {
- for (const { functionName } of callFrames) {
- if (functionName === "module code") {
- this.#send("Debugger.resume");
- return;
- }
- }
- }
-
- this.#stackFrames.length = 0;
- this.#stopped ||= stoppedReason(reason);
- for (const callFrame of callFrames) {
- this.#addStackFrame(callFrame);
- }
- if (asyncStackTrace) {
- this.#addAsyncStackTrace(asyncStackTrace);
- }
-
- let hitBreakpointIds: number[] | undefined;
- // Depending on the reason, the `data` property is set to the reason
- // why the execution was paused. For example, if the reason is "breakpoint",
- // the `data` property is set to the breakpoint ID.
- if (data) {
- if (reason === "exception") {
- const remoteObject = data as JSC.Runtime.RemoteObject;
- }
-
- if (reason === "FunctionCall") {
- const { name } = data as { name: string };
- const breakpoint = this.#getFunctionBreakpoint(name);
- if (breakpoint) {
- const { id } = breakpoint;
- hitBreakpointIds = [id];
- }
- }
-
- if (reason === "Breakpoint") {
- const { breakpointId: hitBreakpointId } = data as { breakpointId: string };
- for (const { id, breakpointId } of this.#breakpoints.values()) {
- if (breakpointId === hitBreakpointId) {
- hitBreakpointIds = [id];
- break;
- }
- }
- }
- }
-
- this.#emit("stopped", {
- threadId,
- reason: this.#stopped,
- hitBreakpointIds,
- });
- }
-
- ["Debugger.resumed"](event: JSC.Debugger.ResumedEvent): void {
- this.#stackFrames.length = 0;
- this.#stopped = undefined;
- this.#emit("continued", {
- threadId,
- });
- }
-
- ["Console.messageAdded"](event: JSC.Console.MessageAddedEvent): void {
- const { message } = event;
- const { type, level, text, parameters, line, column, stackTrace } = message;
-
- let output: string;
- let variablesReference: number | undefined;
-
- if (parameters?.length) {
- output = "";
-
- const variables = parameters.map((parameter, i) => {
- const variable = this.#addVariable(parameter, { name: `${i}` });
-
- const { value } = variable;
- output += value + " ";
-
- return variable;
- });
-
- if (variables.length === 1) {
- const [{ variablesReference: reference }] = variables;
- variablesReference = reference;
- } else {
- variablesReference = this.#setVariable(variables);
- }
- } else {
- output = text;
- }
-
- if (!output.endsWith("\n")) {
- output += "\n";
- }
-
- const color = consoleLevelToAnsiColor(level);
- if (color) {
- output = `${color}${output}`;
- }
-
- if (variablesReference) {
- variablesReference = this.#setVariable([
- {
- name: "",
- value: "",
- type: undefined,
- variablesReference,
- },
- ]);
- }
-
- let source: Source | undefined;
- if (stackTrace) {
- const { callFrames } = stackTrace;
- if (callFrames.length) {
- const { scriptId } = callFrames.at(-1)!;
- source = this.#getSourceIfPresent(scriptId);
- }
- }
-
- let location: Location | {} = {};
- if (source) {
- location = this.#originalLocation(source, line, column);
- }
-
- this.#emit("output", {
- category: "debug console",
- group: consoleMessageGroup(type),
- output,
- variablesReference,
- source,
- ...location,
- });
- }
-
- #addSource(source: Source): Source {
- const { sourceId, scriptId, path, sourceReference } = source;
-
- const oldSource = this.#getSourceIfPresent(sourceId);
- if (oldSource) {
- const { scriptId, path: oldPath } = oldSource;
- // For now, the script ID will always change.
- // Could that not be the case in the future?
- this.#sources.delete(scriptId);
-
- // If the path changed or the source has a source reference,
- // the old source should be marked as removed.
- if (path !== oldPath || sourceReference) {
- this.#emit("loadedSource", {
- reason: "removed",
- source: oldSource,
- });
- }
- }
-
- this.#sources.set(sourceId, source);
- this.#sources.set(scriptId, source);
-
- this.#emit("loadedSource", {
- // If the reason is "changed", the source will be retrieved using
- // the `source` command, which is why it cannot be set when `path` is present.
- reason: oldSource && !path ? "changed" : "new",
- source,
- });
-
- if (!path) {
- return source;
- }
-
- // If there are any pending requests for this source by its path,
- // resolve them now that the source has been loaded.
- const resolves = this.#pendingSources.get(path);
- if (resolves) {
- this.#pendingSources.delete(path);
- for (const resolve of resolves) {
- resolve(source);
- }
- }
-
- return source;
- }
-
- loadedSources(): DAP.LoadedSourcesResponse {
- const sources = new Map();
-
- // Since there are duplicate keys for each source,
- // (e.g. scriptId, path, sourceReference, etc.) it needs to be deduped.
- for (const source of this.#sources.values()) {
- const { sourceId } = source;
- sources.set(sourceId, source);
- }
-
- return {
- sources: [...sources.values()],
- };
- }
-
- #getSourceIfPresent(sourceId: string | number): Source | undefined {
- return this.#sources.get(sourceId);
- }
-
- async #getSource(sourceId: string | number): Promise<Source> {
- const source = this.#getSourceIfPresent(sourceId);
-
- if (source) {
- return source;
- }
-
- // If the source does not have a path or is a builtin module,
- // it cannot be retrieved from the file system.
- if (typeof sourceId === "number" || !sourceId.startsWith("/")) {
- throw new Error(`Source not found: ${sourceId}`);
- }
-
- // If the source is not present, it may not have been loaded yet.
- // In that case, wait for it to be loaded.
- let resolves = this.#pendingSources.get(sourceId);
- if (!resolves) {
- this.#pendingSources.set(sourceId, (resolves = []));
- }
-
- return new Promise(resolve => {
- resolves!.push(resolve);
- });
- }
-
- async stackTrace(request: DAP.StackTraceRequest): Promise<DAP.StackTraceResponse> {
- const { length } = this.#stackFrames;
- const { startFrame = 0, levels } = request;
- const endFrame = levels ? startFrame + levels : length;
-
- return {
- totalFrames: length,
- stackFrames: this.#stackFrames.slice(startFrame, endFrame),
- };
- }
-
- async scopes(request: DAP.ScopesRequest): Promise<DAP.ScopesResponse> {
- const { frameId } = request;
-
- for (const stackFrame of this.#stackFrames) {
- const { id, scopes } = stackFrame;
- if (id !== frameId || !scopes) {
- continue;
- }
- return {
- scopes,
- };
- }
-
- return {
- scopes: [],
- };
- }
-
- #getCallFrameId(frameId?: number): string | undefined {
- for (const { id, callFrameId } of this.#stackFrames) {
- if (id === frameId) {
- return callFrameId;
- }
- }
- return undefined;
- }
-
- #addStackFrame(callFrame: JSC.Debugger.CallFrame): StackFrame {
- const { callFrameId, functionName, location, scopeChain } = callFrame;
- const { scriptId } = location;
- const source = this.#getSourceIfPresent(scriptId);
-
- let originalLocation: Location;
- if (source) {
- originalLocation = this.#originalLocation(source, location);
- } else {
- const { lineNumber, columnNumber } = location;
- originalLocation = {
- line: this.#lineFrom0BasedLine(lineNumber),
- column: this.#columnFrom0BasedColumn(columnNumber),
- };
- }
-
- const { line, column } = originalLocation;
- const scopes: Scope[] = [];
- const stackFrame: StackFrame = {
- callFrameId,
- scriptId,
- id: this.#stackFrames.length,
- name: functionName || "<anonymous>",
- line,
- column,
- presentationHint: stackFramePresentationHint(source?.path),
- source,
- scopes,
- };
- this.#stackFrames.push(stackFrame);
-
- for (const scope of scopeChain) {
- const { name, type, location, object, empty } = scope;
- if (empty) {
- continue;
- }
-
- const { variablesReference } = this.#addVariable(object);
- const presentationHint = scopePresentationHint(type);
- const title = presentationHint ? titleize(presentationHint) : "Unknown";
- const displayName = name ? `${title}: ${name}` : title;
-
- let originalLocation: Location | undefined;
- if (location) {
- const { scriptId } = location;
- const source = this.#getSourceIfPresent(scriptId);
-
- if (source) {
- originalLocation = this.#originalLocation(source, location);
- } else {
- const { lineNumber, columnNumber } = location;
- originalLocation = {
- line: this.#lineFrom0BasedLine(lineNumber),
- column: this.#columnFrom0BasedColumn(columnNumber),
- };
- }
- }
-
- const { line, column } = originalLocation ?? {};
- scopes.push({
- name: displayName,
- presentationHint,
- expensive: presentationHint === "globals",
- variablesReference,
- line,
- column,
- source,
- });
- }
-
- return stackFrame;
- }
-
- #addAsyncStackTrace(stackTrace: JSC.Console.StackTrace): void {
- const { callFrames, parentStackTrace } = stackTrace;
-
- for (const callFrame of callFrames) {
- this.#addAsyncStackFrame(callFrame);
- }
-
- if (parentStackTrace) {
- this.#addAsyncStackTrace(parentStackTrace);
- }
- }
-
- #addAsyncStackFrame(callFrame: JSC.Console.CallFrame): StackFrame {
- const { scriptId, functionName } = callFrame;
- const callFrameId = callFrameToId(callFrame);
- const source = this.#getSourceIfPresent(scriptId);
-
- let originalLocation: Location;
- if (source) {
- originalLocation = this.#originalLocation(source, callFrame);
- } else {
- const { lineNumber, columnNumber } = callFrame;
- originalLocation = {
- line: this.#lineFrom0BasedLine(lineNumber),
- column: this.#columnFrom0BasedColumn(columnNumber),
- };
- }
-
- const { line, column } = originalLocation;
- const stackFrame: StackFrame = {
- callFrameId,
- scriptId,
- id: this.#stackFrames.length,
- name: functionName || "<anonymous>",
- line,
- column,
- source,
- presentationHint: stackFramePresentationHint(source?.path),
- canRestart: false,
- };
- this.#stackFrames.push(stackFrame);
-
- return stackFrame;
- }
-
- async variables(request: DAP.VariablesRequest): Promise<DAP.VariablesResponse> {
- const { variablesReference, start, count } = request;
- const variable = this.#variables[variablesReference];
-
- let variables: Variable[];
- if (!variable) {
- variables = [];
- } else if (Array.isArray(variable)) {
- variables = variable;
- } else {
- variables = await this.#getVariables(variable, start, count);
- }
-
- return {
- variables: variables.sort(variablesSortBy),
- };
- }
-
- #setVariable(variable: Variable | Variable[]): number {
- const variablesReference = this.#variables.length;
-
- this.#variables.push(variable);
-
- return variablesReference;
- }
-
- #addVariable(remoteObject: JSC.Runtime.RemoteObject, propertyDescriptor?: JSC.Runtime.PropertyDescriptor): Variable {
- const { objectId, type, subtype, size } = remoteObject;
- const variablesReference = objectId ? this.#variables.length : 0;
-
- const variable: Variable = {
- objectId,
- name: propertyDescriptorToName(propertyDescriptor),
- type: subtype || type,
- value: remoteObjectToString(remoteObject),
- variablesReference,
- indexedVariables: isIndexed(subtype) ? size : undefined,
- namedVariables: isNamedIndexed(subtype) ? size : undefined,
- presentationHint: remoteObjectToVariablePresentationHint(remoteObject, propertyDescriptor),
- };
- this.#setVariable(variable);
-
- return variable;
- }
-
- async #getVariables(variable: Variable, offset?: number, count?: number): Promise<Variable[]> {
- const { objectId, type, indexedVariables, namedVariables } = variable;
-
- if (!objectId || type === "symbol") {
- return [];
- }
-
- const { properties, internalProperties } = await this.#send("Runtime.getDisplayableProperties", {
- objectId,
- generatePreview: true,
- });
-
- const variables: Variable[] = [];
- for (const property of properties) {
- variables.push(...this.#getVariable(property));
- }
-
- if (internalProperties) {
- for (const property of internalProperties) {
- variables.push(...this.#getVariable({ ...property, configurable: false }));
- }
- }
-
- const hasEntries = type !== "array" && (indexedVariables || namedVariables);
- if (hasEntries) {
- const { entries } = await this.#send("Runtime.getCollectionEntries", {
- objectId,
- fetchStart: offset,
- fetchCount: count,
- });
-
- let i = 0;
- for (const { key, value } of entries) {
- let name = String(i++);
- if (key) {
- const { value, description } = key;
- name = String(value ?? description);
- }
- variables.push(this.#addVariable(value, { name }));
- }
- }
-
- return variables;
- }
-
- #getVariable(
- propertyDescriptor: JSC.Runtime.PropertyDescriptor | JSC.Runtime.InternalPropertyDescriptor,
- ): Variable[] {
- const { value, get, set, symbol } = propertyDescriptor as JSC.Runtime.PropertyDescriptor;
- const variables: Variable[] = [];
-
- if (value) {
- variables.push(this.#addVariable(value, propertyDescriptor));
- }
-
- if (get) {
- const { type } = get;
- if (type !== "undefined") {
- variables.push(this.#addVariable(get, propertyDescriptor));
- }
- }
-
- if (set) {
- const { type } = set;
- if (type !== "undefined") {
- variables.push(this.#addVariable(set, propertyDescriptor));
- }
- }
-
- if (symbol) {
- variables.push(this.#addVariable(symbol, propertyDescriptor));
- }
-
- return variables;
- }
-
- close(): void {
- this.#terminated = true;
- this.#process?.kill();
- this.#inspector.close();
- this.#reset();
- }
-
- #reset(): void {
- this.#pendingSources.clear();
- this.#sources.clear();
- this.#stackFrames.length = 0;
- this.#stopped = undefined;
- this.#breakpointId = 1;
- this.#breakpoints.length = 0;
- this.#functionBreakpoints.clear();
- this.#variables.length = 1;
- this.#launched = undefined;
- this.#initialized = undefined;
- this.#connected = undefined;
- this.#terminated = undefined;
- this.#url = undefined;
- }
-}
-
-function stoppedReason(reason: JSC.Debugger.PausedEvent["reason"]): DAP.StoppedEvent["reason"] {
- switch (reason) {
- case "Breakpoint":
- return "breakpoint";
- case "FunctionCall":
- return "function breakpoint";
- case "PauseOnNextStatement":
- case "DebuggerStatement":
- return "pause";
- case "exception":
- case "assert":
- return "exception";
- default:
- return "breakpoint";
- }
-}
-
-function titleize(name: string): string {
- return name.charAt(0).toUpperCase() + name.slice(1);
-}
-
-function sourcePresentationHint(url?: string): DAP.Source["presentationHint"] {
- if (!url || !url.startsWith("/")) {
- return "deemphasize";
- }
- if (url.includes("/node_modules/")) {
- return "normal";
- }
- return "emphasize";
-}
-
-function sourceName(url?: string): string {
- if (!url) {
- return "unknown.js";
- }
- if (isJavaScript(url)) {
- return url.split("/").pop() || url;
- }
- return `${url}.js`;
-}
-
-function stackFramePresentationHint(path?: string): DAP.StackFrame["presentationHint"] {
- if (!path || path.includes("/node_modules/")) {
- return "subtle";
- }
- return "normal";
-}
-
-function scopePresentationHint(type: JSC.Debugger.Scope["type"]): DAP.Scope["presentationHint"] {
- switch (type) {
- case "closure":
- case "functionName":
- case "with":
- case "catch":
- case "nestedLexical":
- return "locals";
- case "global":
- case "globalLexicalEnvironment":
- return "globals";
- default:
- return undefined;
- }
-}
-
-function isIndexed(subtype: JSC.Runtime.RemoteObject["subtype"]): boolean {
- return subtype === "array" || subtype === "set" || subtype === "weakset";
-}
-
-function isNamedIndexed(subtype: JSC.Runtime.RemoteObject["subtype"]): boolean {
- return subtype === "map" || subtype === "weakmap";
-}
-
-function exceptionFiltersToPauseOnExceptionsState(
- filters?: string[],
-): JSC.Debugger.SetPauseOnExceptionsRequest["state"] {
- if (filters?.includes("all")) {
- return "all";
- }
- if (filters?.includes("uncaught")) {
- return "uncaught";
- }
- return "none";
-}
-
-function breakpointOptions(breakpoint?: Partial<DAP.SourceBreakpoint>): JSC.Debugger.BreakpointOptions {
- const { condition } = breakpoint ?? {};
- // TODO: hitCondition, logMessage
- return {
- condition,
- };
-}
-
-function consoleMessageGroup(type: JSC.Console.ConsoleMessage["type"]): DAP.OutputEvent["group"] {
- switch (type) {
- case "startGroup":
- return "start";
- case "startGroupCollapsed":
- return "startCollapsed";
- case "endGroup":
- return "end";
- }
- return undefined;
-}
-
-function sourceToPath(source?: DAP.Source): string {
- const { path } = source ?? {};
- if (!path) {
- throw new Error("No source found.");
- }
- return path;
-}
-
-function sourceToId(source?: DAP.Source): string | number {
- const { path, sourceReference } = source ?? {};
- if (path) {
- return path;
- }
- if (sourceReference) {
- return sourceReference;
- }
- throw new Error("No source found.");
-}
-
-function callFrameToId(callFrame: JSC.Console.CallFrame): string {
- const { url, lineNumber, columnNumber } = callFrame;
- return `${url}:${lineNumber}:${columnNumber}`;
-}
-
-function sanitizeExpression(expression: string): string {
- expression = expression.trim();
- if (expression.startsWith("{")) {
- expression = `(${expression})`;
- }
- if (expression.startsWith("return ")) {
- expression = expression.slice(7);
- }
- if (expression.startsWith("await ")) {
- expression = expression.slice(6);
- }
- return expression;
-}
-
-function remoteObjectToVariablePresentationHint(
- remoteObject: JSC.Runtime.RemoteObject,
- propertyDescriptor?: JSC.Runtime.PropertyDescriptor,
-): DAP.VariablePresentationHint {
- const { type, subtype } = remoteObject;
- const { name, configurable, writable, isPrivate, symbol, get, set, wasThrown } = propertyDescriptor ?? {};
- const hasGetter = get?.type === "function";
- const hasSetter = set?.type === "function";
- const hasSymbol = symbol?.type === "symbol";
- let kind: string | undefined;
- let visibility: string | undefined;
- let lazy: boolean | undefined;
- let attributes: string[] = [];
- if (type === "function") {
- kind = "method";
- }
- if (subtype === "class") {
- kind = "class";
- }
- if (isPrivate || configurable === false || hasSymbol || name === "__proto__") {
- visibility = "internal";
- }
- if (type === "string") {
- attributes.push("rawString");
- }
- if (writable === false || (hasGetter && !hasSetter)) {
- attributes.push("readOnly");
- }
- if (wasThrown || hasGetter) {
- lazy = true;
- attributes.push("hasSideEffects");
- }
- return {
- kind,
- visibility,
- lazy,
- attributes,
- };
-}
-
-function propertyDescriptorToName(propertyDescriptor?: JSC.Runtime.PropertyDescriptor): string {
- if (!propertyDescriptor) {
- return "";
- }
- const { name } = propertyDescriptor;
- if (name === "__proto__") {
- return "[[Prototype]]";
- }
- return name;
-}
-
-function unknownToError(input: unknown): Error {
- if (input instanceof Error) {
- return input;
- }
- return new Error(String(input));
-}
-
-function isJavaScript(path: string): boolean {
- return /\.(c|m)?(j|t)sx?$/.test(path);
-}
-
-function parseUrl(hostname?: string, port?: number): URL {
- hostname ||= "localhost";
- port ||= 6499;
- let url: URL;
- try {
- if (hostname.includes("://")) {
- url = new URL(hostname);
- } else if (hostname.includes(":") && !hostname.startsWith("[")) {
- url = new URL(`ws://[${hostname}]:${port}/`);
- } else {
- url = new URL(`ws://${hostname}:${port}/`);
- }
- } catch {
- throw new Error(`Invalid URL or hostname/port: ${hostname}`);
- }
- // HACK: Bun sometimes has issues connecting through "127.0.0.1"
- if (url.hostname === "localhost" || url.hostname === "127.0.0.1") {
- url.hostname = "[::1]";
- }
- return url;
-}
-
-function parseUrlMaybe(string: string): URL | undefined {
- const match = /(wss?:\/\/.*)/im.exec(string);
- if (!match) {
- return undefined;
- }
- const [_, href] = match;
- try {
- return parseUrl(href);
- } catch {
- return undefined;
- }
-}
-
-function variablesSortBy(a: DAP.Variable, b: DAP.Variable): number {
- const visibility = (variable: DAP.Variable): number => {
- const { presentationHint } = variable;
- switch (presentationHint?.visibility) {
- case "protected":
- return 1;
- case "private":
- return 2;
- case "internal":
- return 3;
- }
- return 0;
- };
- const index = (variable: DAP.Variable): number => {
- const { name } = variable;
- switch (name) {
- case "[[Prototype]]":
- case "prototype":
- case "__proto__":
- return Number.MAX_VALUE;
- }
- const index = parseInt(name);
- if (isFinite(index)) {
- return index;
- }
- return 0;
- };
- const av = visibility(a);
- const bv = visibility(b);
- if (av > bv) return 1;
- if (av < bv) return -1;
- const ai = index(a);
- const bi = index(b);
- if (ai > bi) return 1;
- if (ai < bi) return -1;
- return 0;
-}
-
-function isSameLocation(a: { line?: number; column?: number }, b: { line?: number; column?: number }): boolean {
- return (a.line === b.line || (!a.line && !b.line)) && (a.column === b.column || (!a.column && !b.column));
-}
-
-function consoleLevelToAnsiColor(level: JSC.Console.ConsoleMessage["level"]): string | undefined {
- switch (level) {
- case "warning":
- return "\u001b[33m";
- case "error":
- return "\u001b[31m";
- case "debug":
- return "\u001b[36m";
- }
- return undefined;
-}
-
-function numberIsValid(number?: number): number is number {
- return typeof number === "number" && isFinite(number) && number >= 0;
-}
diff --git a/packages/bun-debug-adapter-protocol/debugger/capabilities.ts b/packages/bun-debug-adapter-protocol/debugger/capabilities.ts
deleted file mode 100644
index 3ba968e86..000000000
--- a/packages/bun-debug-adapter-protocol/debugger/capabilities.ts
+++ /dev/null
@@ -1,271 +0,0 @@
-import type { DAP } from "..";
-
-const capabilities: DAP.Capabilities = {
- /**
- * The debug adapter supports the `configurationDone` request.
- * @see configurationDone
- */
- supportsConfigurationDoneRequest: true,
-
- /**
- * The debug adapter supports function breakpoints using the `setFunctionBreakpoints` request.
- * @see setFunctionBreakpoints
- */
- supportsFunctionBreakpoints: true,
-
- /**
- * The debug adapter supports conditional breakpoints.
- * @see setBreakpoints
- * @see setInstructionBreakpoints
- * @see setFunctionBreakpoints
- * @see setExceptionBreakpoints
- * @see setDataBreakpoints
- */
- supportsConditionalBreakpoints: true,
-
- /**
- * The debug adapter supports breakpoints that break execution after a specified number of hits.
- * @see setBreakpoints
- * @see setInstructionBreakpoints
- * @see setFunctionBreakpoints
- * @see setExceptionBreakpoints
- * @see setDataBreakpoints
- */
- supportsHitConditionalBreakpoints: true,
-
- /**
- * The debug adapter supports a (side effect free) `evaluate` request for data hovers.
- * @see evaluate
- */
- supportsEvaluateForHovers: true,
-
- /**
- * Available exception filter options for the `setExceptionBreakpoints` request.
- * @see setExceptionBreakpoints
- */
- exceptionBreakpointFilters: [
- {
- filter: "all",
- label: "Caught Exceptions",
- default: false,
- supportsCondition: true,
- description: "Breaks on all throw errors, even if they're caught later.",
- conditionDescription: `error.name == "CustomError"`,
- },
- {
- filter: "uncaught",
- label: "Uncaught Exceptions",
- default: false,
- supportsCondition: true,
- description: "Breaks only on errors or promise rejections that are not handled.",
- conditionDescription: `error.name == "CustomError"`,
- },
- ],
-
- /**
- * The debug adapter supports stepping back via the `stepBack` and `reverseContinue` requests.
- * @see stepBack
- * @see reverseContinue
- */
- supportsStepBack: false,
-
- /**
- * The debug adapter supports setting a variable to a value.
- * @see setVariable
- */
- supportsSetVariable: false,
-
- /**
- * The debug adapter supports restarting a frame.
- * @see restartFrame
- */
- supportsRestartFrame: false,
-
- /**
- * The debug adapter supports the `gotoTargets` request.
- * @see gotoTargets
- */
- supportsGotoTargetsRequest: false,
-
- /**
- * The debug adapter supports the `stepInTargets` request.
- * @see stepInTargets
- */
- supportsStepInTargetsRequest: false,
-
- /**
- * The debug adapter supports the `completions` request.
- * @see completions
- */
- supportsCompletionsRequest: false,
-
- /**
- * The set of characters that should trigger completion in a REPL.
- * If not specified, the UI should assume the `.` character.
- * @see completions
- */
- completionTriggerCharacters: [".", "[", '"', "'"],
-
- /**
- * The debug adapter supports the `modules` request.
- * @see modules
- */
- supportsModulesRequest: false,
-
- /**
- * The set of additional module information exposed by the debug adapter.
- * @see modules
- */
- additionalModuleColumns: [],
-
- /**
- * Checksum algorithms supported by the debug adapter.
- */
- supportedChecksumAlgorithms: [],
-
- /**
- * The debug adapter supports the `restart` request.
- * In this case a client should not implement `restart` by terminating
- * and relaunching the adapter but by calling the `restart` request.
- * @see restart
- */
- supportsRestartRequest: false,
-
- /**
- * The debug adapter supports `exceptionOptions` on the `setExceptionBreakpoints` request.
- * @see setExceptionBreakpoints
- */
- supportsExceptionOptions: false,
-
- /**
- * The debug adapter supports a `format` attribute on the `stackTrace`, `variables`, and `evaluate` requests.
- * @see stackTrace
- * @see variables
- * @see evaluate
- */
- supportsValueFormattingOptions: false,
-
- /**
- * The debug adapter supports the `exceptionInfo` request.
- * @see exceptionInfo
- */
- supportsExceptionInfoRequest: true,
-
- /**
- * The debug adapter supports the `terminateDebuggee` attribute on the `disconnect` request.
- * @see disconnect
- */
- supportTerminateDebuggee: true,
-
- /**
- * The debug adapter supports the `suspendDebuggee` attribute on the `disconnect` request.
- * @see disconnect
- */
- supportSuspendDebuggee: false,
-
- /**
- * The debug adapter supports the delayed loading of parts of the stack,
- * which requires that both the `startFrame` and `levels` arguments and
- * the `totalFrames` result of the `stackTrace` request are supported.
- * @see stackTrace
- */
- supportsDelayedStackTraceLoading: true,
-
- /**
- * The debug adapter supports the `loadedSources` request.
- * @see loadedSources
- */
- supportsLoadedSourcesRequest: true,
-
- /**
- * The debug adapter supports log points by interpreting the `logMessage` attribute of the `SourceBreakpoint`.
- * @see setBreakpoints
- */
- supportsLogPoints: true,
-
- /**
- * The debug adapter supports the `terminateThreads` request.
- * @see terminateThreads
- */
- supportsTerminateThreadsRequest: false,
-
- /**
- * The debug adapter supports the `setExpression` request.
- * @see setExpression
- */
- supportsSetExpression: false,
-
- /**
- * The debug adapter supports the `terminate` request.
- * @see terminate
- */
- supportsTerminateRequest: true,
-
- /**
- * The debug adapter supports data breakpoints.
- * @see setDataBreakpoints
- */
- supportsDataBreakpoints: false,
-
- /**
- * The debug adapter supports the `readMemory` request.
- * @see readMemory
- */
- supportsReadMemoryRequest: false,
-
- /**
- * The debug adapter supports the `writeMemory` request.
- * @see writeMemory
- */
- supportsWriteMemoryRequest: false,
-
- /**
- * The debug adapter supports the `disassemble` request.
- * @see disassemble
- */
- supportsDisassembleRequest: false,
-
- /**
- * The debug adapter supports the `cancel` request.
- * @see cancel
- */
- supportsCancelRequest: false,
-
- /**
- * The debug adapter supports the `breakpointLocations` request.
- * @see breakpointLocations
- */
- supportsBreakpointLocationsRequest: true,
-
- /**
- * The debug adapter supports the `clipboard` context value in the `evaluate` request.
- * @see evaluate
- */
- supportsClipboardContext: false,
-
- /**
- * The debug adapter supports stepping granularities (argument `granularity`) for the stepping requests.
- * @see stepIn
- */
- supportsSteppingGranularity: false,
-
- /**
- * The debug adapter supports adding breakpoints based on instruction references.
- * @see setInstructionBreakpoints
- */
- supportsInstructionBreakpoints: false,
-
- /**
- * The debug adapter supports `filterOptions` as an argument on the `setExceptionBreakpoints` request.
- * @see setExceptionBreakpoints
- */
- supportsExceptionFilterOptions: true,
-
- /**
- * The debug adapter supports the `singleThread` property on the execution requests
- * (`continue`, `next`, `stepIn`, `stepOut`, `reverseContinue`, `stepBack`).
- */
- supportsSingleThreadExecutionRequests: false,
-};
-
-export default capabilities;
diff --git a/packages/bun-debug-adapter-protocol/debugger/fixtures/preview.js b/packages/bun-debug-adapter-protocol/debugger/fixtures/preview.js
deleted file mode 100644
index 15062240b..000000000
--- a/packages/bun-debug-adapter-protocol/debugger/fixtures/preview.js
+++ /dev/null
@@ -1,99 +0,0 @@
-console.log(
- undefined,
- null,
- true,
- false,
- 0,
- 1,
- Math.PI,
- -Math.E,
- NaN,
- Infinity,
- -Infinity,
- BigInt(0),
- BigInt(1),
- BigInt("10000000000000"),
- BigInt("-10000000000000"),
- "",
- " ",
- "Hello",
- "Hello World",
- [],
- [1, 2, 3],
- ["a", 1, null, undefined],
- [1, [2, [3, [4, [5, [6, [7, [8, [9, [10]]]]]]]]]],
- [[[[[]]]]],
- {},
- { a: 1 },
- { a: 1, b: 2, c: 3 },
- { a: { b: { c: { d: { e: { f: { g: { h: { i: { j: 10 } } } } } } } } } },
- function () {},
- function namedFunction() {},
- class {},
- class namedClass {},
- class namedClass {
- a() {}
- b = 1;
- c = [
- null,
- undefined,
- "a",
- {
- a: 1,
- b: 2,
- c: 3,
- },
- ];
- },
- new Date(0),
- new Date(NaN),
- new RegExp(),
- new RegExp("abc"),
- new RegExp("abc", "g"),
- /abc/,
- new Set(),
- new Set([1, 2, 3]),
- new WeakSet(),
- new WeakSet([{ a: 1 }, { b: 2 }, { c: 3 }]),
- new Map(),
- new Map([
- ["a", 1],
- ["b", 2],
- ["c", 3],
- ]),
- new WeakMap(),
- new WeakMap([
- [{ a: 1 }, 1],
- [{ b: 2 }, 2],
- [{ c: 3 }, 3],
- ]),
- Symbol(),
- Symbol("namedSymbol"),
- new Error(),
- new TypeError("This is a TypeError"),
- //"a".repeat(10000),
- //["a"].fill("a", 0, 10000),
- new Headers(),
- new Headers({
- a: "1",
- b: "2",
- }),
- new Request("https://example.com/"),
- new Request("https://example.com/", {
- method: "POST",
- headers: {
- a: "1",
- b: "2",
- },
- body: '{"example":true}',
- }),
- new Response(),
- new Response('{"example":true}', {
- status: 200,
- statusText: "OK",
- headers: {
- a: "1",
- b: "2",
- },
- }),
-);
diff --git a/packages/bun-debug-adapter-protocol/debugger/fixtures/with-sourcemap.js b/packages/bun-debug-adapter-protocol/debugger/fixtures/with-sourcemap.js
deleted file mode 100644
index 6c16a1202..000000000
--- a/packages/bun-debug-adapter-protocol/debugger/fixtures/with-sourcemap.js
+++ /dev/null
@@ -1,36 +0,0 @@
-"use strict";
-export default {
- fetch(request) {
- const animal = getAnimal(request.url);
- const voice = animal.talk();
- return new Response(voice);
- },
-};
-function getAnimal(query) {
- switch (query.split("/").pop()) {
- case "dog":
- return new Dog();
- case "cat":
- return new Cat();
- }
- return new Bird();
-}
-class Dog {
- name = "dog";
- talk() {
- return "woof";
- }
-}
-class Cat {
- name = "cat";
- talk() {
- return "meow";
- }
-}
-class Bird {
- name = "bird";
- talk() {
- return "chirp";
- }
-}
-//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsicGFja2FnZXMvYnVuLWRlYnVnLWFkYXB0ZXItcHJvdG9jb2wvZGVidWdnZXIvZml4dHVyZXMvd2l0aC1zb3VyY2VtYXAudHMiXSwKICAic291cmNlc0NvbnRlbnQiOiBbImV4cG9ydCBkZWZhdWx0IHtcbiAgZmV0Y2gocmVxdWVzdDogUmVxdWVzdCk6IFJlc3BvbnNlIHtcbiAgICBjb25zdCBhbmltYWwgPSBnZXRBbmltYWwocmVxdWVzdC51cmwpO1xuICAgIGNvbnN0IHZvaWNlID0gYW5pbWFsLnRhbGsoKTtcbiAgICByZXR1cm4gbmV3IFJlc3BvbnNlKHZvaWNlKTtcbiAgfSxcbn07XG5cbmZ1bmN0aW9uIGdldEFuaW1hbChxdWVyeTogc3RyaW5nKTogQW5pbWFsIHtcbiAgc3dpdGNoIChxdWVyeS5zcGxpdChcIi9cIikucG9wKCkpIHtcbiAgICBjYXNlIFwiZG9nXCI6XG4gICAgICByZXR1cm4gbmV3IERvZygpO1xuICAgIGNhc2UgXCJjYXRcIjpcbiAgICAgIHJldHVybiBuZXcgQ2F0KCk7XG4gIH1cbiAgcmV0dXJuIG5ldyBCaXJkKCk7XG59XG5cbmludGVyZmFjZSBBbmltYWwge1xuICByZWFkb25seSBuYW1lOiBzdHJpbmc7XG4gIHRhbGsoKTogc3RyaW5nO1xufVxuXG5jbGFzcyBEb2cgaW1wbGVtZW50cyBBbmltYWwge1xuICBuYW1lID0gXCJkb2dcIjtcblxuICB0YWxrKCk6IHN0cmluZyB7XG4gICAgcmV0dXJuIFwid29vZlwiO1xuICB9XG59XG5cbmNsYXNzIENhdCBpbXBsZW1lbnRzIEFuaW1hbCB7XG4gIG5hbWUgPSBcImNhdFwiO1xuXG4gIHRhbGsoKTogc3RyaW5nIHtcbiAgICByZXR1cm4gXCJtZW93XCI7XG4gIH1cbn1cblxuY2xhc3MgQmlyZCBpbXBsZW1lbnRzIEFuaW1hbCB7XG4gIG5hbWUgPSBcImJpcmRcIjtcblxuICB0YWxrKCk6IHN0cmluZyB7XG4gICAgcmV0dXJuIFwiY2hpcnBcIjtcbiAgfVxufVxuIl0sCiAgIm1hcHBpbmdzIjogIjtBQUFBLGVBQWU7QUFBQSxFQUNiLE1BQU0sU0FBNEI7QUFDaEMsVUFBTSxTQUFTLFVBQVUsUUFBUSxHQUFHO0FBQ3BDLFVBQU0sUUFBUSxPQUFPLEtBQUs7QUFDMUIsV0FBTyxJQUFJLFNBQVMsS0FBSztBQUFBLEVBQzNCO0FBQ0Y7QUFFQSxTQUFTLFVBQVUsT0FBdUI7QUFDeEMsVUFBUSxNQUFNLE1BQU0sR0FBRyxFQUFFLElBQUksR0FBRztBQUFBLElBQzlCLEtBQUs7QUFDSCxhQUFPLElBQUksSUFBSTtBQUFBLElBQ2pCLEtBQUs7QUFDSCxhQUFPLElBQUksSUFBSTtBQUFBLEVBQ25CO0FBQ0EsU0FBTyxJQUFJLEtBQUs7QUFDbEI7QUFPQSxNQUFNLElBQXNCO0FBQUEsRUFDMUIsT0FBTztBQUFBLEVBRVAsT0FBZTtBQUNiLFdBQU87QUFBQSxFQUNUO0FBQ0Y7QUFFQSxNQUFNLElBQXNCO0FBQUEsRUFDMUIsT0FBTztBQUFBLEVBRVAsT0FBZTtBQUNiLFdBQU87QUFBQSxFQUNUO0FBQ0Y7QUFFQSxNQUFNLEtBQXVCO0FBQUEsRUFDM0IsT0FBTztBQUFBLEVBRVAsT0FBZTtBQUNiLFdBQU87QUFBQSxFQUNUO0FBQ0Y7IiwKICAibmFtZXMiOiBbXQp9Cg==
diff --git a/packages/bun-debug-adapter-protocol/debugger/fixtures/with-sourcemap.ts b/packages/bun-debug-adapter-protocol/debugger/fixtures/with-sourcemap.ts
deleted file mode 100644
index f245ebf76..000000000
--- a/packages/bun-debug-adapter-protocol/debugger/fixtures/with-sourcemap.ts
+++ /dev/null
@@ -1,46 +0,0 @@
-export default {
- fetch(request: Request): Response {
- const animal = getAnimal(request.url);
- const voice = animal.talk();
- return new Response(voice);
- },
-};
-
-function getAnimal(query: string): Animal {
- switch (query.split("/").pop()) {
- case "dog":
- return new Dog();
- case "cat":
- return new Cat();
- }
- return new Bird();
-}
-
-interface Animal {
- readonly name: string;
- talk(): string;
-}
-
-class Dog implements Animal {
- name = "dog";
-
- talk(): string {
- return "woof";
- }
-}
-
-class Cat implements Animal {
- name = "cat";
-
- talk(): string {
- return "meow";
- }
-}
-
-class Bird implements Animal {
- name = "bird";
-
- talk(): string {
- return "chirp";
- }
-}
diff --git a/packages/bun-debug-adapter-protocol/debugger/fixtures/without-sourcemap.js b/packages/bun-debug-adapter-protocol/debugger/fixtures/without-sourcemap.js
deleted file mode 100644
index 6a5d9a948..000000000
--- a/packages/bun-debug-adapter-protocol/debugger/fixtures/without-sourcemap.js
+++ /dev/null
@@ -1,20 +0,0 @@
-export default {
- fetch(request) {
- return new Response(a());
- },
-};
-
-function a() {
- return b();
-}
-
-function b() {
- return c();
-}
-
-function c() {
- function d() {
- return "hello";
- }
- return d();
-}
diff --git a/packages/bun-debug-adapter-protocol/debugger/preview.test.ts b/packages/bun-debug-adapter-protocol/debugger/preview.test.ts
deleted file mode 100644
index 666913719..000000000
--- a/packages/bun-debug-adapter-protocol/debugger/preview.test.ts
+++ /dev/null
@@ -1,62 +0,0 @@
-import { beforeAll, afterAll, test, expect } from "bun:test";
-import type { JSC } from "../../bun-inspector-protocol";
-import { WebSocketInspector } from "../../bun-inspector-protocol";
-import type { PipedSubprocess } from "bun";
-import { spawn } from "bun";
-import { remoteObjectToString } from "./preview";
-
-let subprocess: PipedSubprocess | undefined;
-let objects: JSC.Runtime.RemoteObject[] = [];
-
-beforeAll(async () => {
- subprocess = spawn({
- cwd: import.meta.dir,
- cmd: [process.argv0, "--inspect-wait=0", "fixtures/preview.js"],
- stdout: "pipe",
- stderr: "pipe",
- stdin: "pipe",
- });
- const decoder = new TextDecoder();
- let url: URL;
- for await (const chunk of subprocess!.stdout) {
- const text = decoder.decode(chunk);
- if (text.includes("ws://")) {
- url = new URL(/(ws:\/\/.*)/.exec(text)![0]);
- break;
- }
- }
- objects = await new Promise((resolve, reject) => {
- const inspector = new WebSocketInspector({
- url,
- listener: {
- ["Inspector.connected"]: () => {
- inspector.send("Inspector.enable");
- inspector.send("Runtime.enable");
- inspector.send("Console.enable");
- inspector.send("Debugger.enable");
- inspector.send("Debugger.resume");
- inspector.send("Inspector.initialized");
- },
- ["Inspector.disconnected"]: error => {
- reject(error);
- },
- ["Console.messageAdded"]: ({ message }) => {
- const { parameters } = message;
- resolve(parameters!);
- inspector.close();
- },
- },
- });
- inspector.start();
- });
-});
-
-afterAll(() => {
- subprocess?.kill();
-});
-
-test("remoteObjectToString", () => {
- for (const object of objects) {
- expect(remoteObjectToString(object)).toMatchSnapshot();
- }
-});
diff --git a/packages/bun-debug-adapter-protocol/debugger/preview.ts b/packages/bun-debug-adapter-protocol/debugger/preview.ts
deleted file mode 100644
index 6012623d2..000000000
--- a/packages/bun-debug-adapter-protocol/debugger/preview.ts
+++ /dev/null
@@ -1,110 +0,0 @@
-import type { JSC } from "../../bun-inspector-protocol";
-
-export function remoteObjectToString(remoteObject: JSC.Runtime.RemoteObject): string {
- const { type, subtype, value, description, className, preview } = remoteObject;
- switch (type) {
- case "undefined":
- return "undefined";
- case "boolean":
- case "number":
- return description ?? JSON.stringify(value);
- case "string":
- return JSON.stringify(value ?? description);
- case "symbol":
- case "bigint":
- return description!;
- case "function":
- return description!.replace("function", "ƒ") || "ƒ";
- }
- switch (subtype) {
- case "null":
- return "null";
- case "regexp":
- case "date":
- case "error":
- return description!;
- }
- if (preview) {
- return objectPreviewToString(preview);
- }
- if (className) {
- return className;
- }
- return description || "Object";
-}
-
-export function objectPreviewToString(objectPreview: JSC.Runtime.ObjectPreview): string {
- const { type, subtype, entries, properties, overflow, description, size } = objectPreview;
- if (type !== "object") {
- return remoteObjectToString(objectPreview);
- }
- let items: string[];
- if (entries) {
- items = entries.map(entryPreviewToString).sort();
- } else if (properties) {
- if (isIndexed(subtype)) {
- items = properties.map(indexedPropertyPreviewToString).sort();
- } else {
- items = properties.map(namedPropertyPreviewToString).sort();
- }
- } else {
- items = ["…"];
- }
- if (overflow) {
- items.push("…");
- }
- let label: string;
- if (description === "Object") {
- label = "";
- } else if (size === undefined) {
- label = description!;
- } else {
- label = `${description}(${size})`;
- }
- if (!items.length) {
- return label || "{}";
- }
- if (label) {
- label += " ";
- }
- if (isIndexed(subtype)) {
- return `${label}[${items.join(", ")}]`;
- }
- return `${label}{${items.join(", ")}}`;
-}
-
-function propertyPreviewToString(propertyPreview: JSC.Runtime.PropertyPreview): string {
- const { type, value, ...preview } = propertyPreview;
- if (type === "accessor") {
- return "ƒ";
- }
- return remoteObjectToString({ ...preview, type, description: value });
-}
-
-function entryPreviewToString(entryPreview: JSC.Runtime.EntryPreview): string {
- const { key, value } = entryPreview;
- if (key) {
- return `${objectPreviewToString(key)} => ${objectPreviewToString(value)}`;
- }
- return objectPreviewToString(value);
-}
-
-function namedPropertyPreviewToString(propertyPreview: JSC.Runtime.PropertyPreview): string {
- const { name, valuePreview } = propertyPreview;
- if (valuePreview) {
- return `${name}: ${objectPreviewToString(valuePreview)}`;
- }
- return `${name}: ${propertyPreviewToString(propertyPreview)}`;
-}
-
-function indexedPropertyPreviewToString(propertyPreview: JSC.Runtime.PropertyPreview): string {
- const { valuePreview } = propertyPreview;
- if (valuePreview) {
- return objectPreviewToString(valuePreview);
- }
- return propertyPreviewToString(propertyPreview);
-}
-
-function isIndexed(type?: JSC.Runtime.RemoteObject["subtype"]): boolean {
- return type === "array" || type === "set" || type === "weakset";
-}
diff --git a/packages/bun-debug-adapter-protocol/debugger/sourcemap.test.ts b/packages/bun-debug-adapter-protocol/debugger/sourcemap.test.ts
deleted file mode 100644
index 44d9ca362..000000000
--- a/packages/bun-debug-adapter-protocol/debugger/sourcemap.test.ts
+++ /dev/null
@@ -1,31 +0,0 @@
-import { test, expect } from "bun:test";
-import { readFileSync } from "node:fs";
-import { SourceMap } from "./sourcemap";
-
-test("works without source map", () => {
- const sourceMap = getSourceMap("without-sourcemap.js");
- expect(sourceMap.generatedLocation({ line: 7 })).toEqual({ line: 7, column: 0, verified: true });
- expect(sourceMap.generatedLocation({ line: 7, column: 2 })).toEqual({ line: 7, column: 2, verified: true });
- expect(sourceMap.originalLocation({ line: 11 })).toEqual({ line: 11, column: 0, verified: true });
- expect(sourceMap.originalLocation({ line: 11, column: 2 })).toEqual({ line: 11, column: 2, verified: true });
-});
-
-test("works with source map", () => {
- const sourceMap = getSourceMap("with-sourcemap.js");
- // FIXME: Columns don't appear to be accurate for `generatedLocation`
- expect(sourceMap.generatedLocation({ line: 3 })).toMatchObject({ line: 4, verified: true });
- expect(sourceMap.generatedLocation({ line: 27 })).toMatchObject({ line: 20, verified: true });
- expect(sourceMap.originalLocation({ line: 32 })).toEqual({ line: 43, column: 4, verified: true });
- expect(sourceMap.originalLocation({ line: 13 })).toEqual({ line: 13, column: 6, verified: true });
-});
-
-function getSourceMap(filename: string): SourceMap {
- const { pathname } = new URL(`./fixtures/${filename}`, import.meta.url);
- const source = readFileSync(pathname, "utf-8");
- const match = source.match(/\/\/# sourceMappingURL=(.*)$/m);
- if (match) {
- const [, url] = match;
- return SourceMap(url);
- }
- return SourceMap();
-}
diff --git a/packages/bun-debug-adapter-protocol/debugger/sourcemap.ts b/packages/bun-debug-adapter-protocol/debugger/sourcemap.ts
deleted file mode 100644
index adb6dc57d..000000000
--- a/packages/bun-debug-adapter-protocol/debugger/sourcemap.ts
+++ /dev/null
@@ -1,187 +0,0 @@
-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);
-}