diff --git a/src/js/internal/inspector/cdp.ts b/src/js/internal/inspector/cdp.ts index 9ac01a8f88e0..259dcca85a8a 100644 --- a/src/js/internal/inspector/cdp.ts +++ b/src/js/internal/inspector/cdp.ts @@ -571,6 +571,7 @@ class InspectorCDPAdapter { endLine: params.endLine ?? 0, endColumn: params.endColumn ?? 0, }); + const { scriptType } = params; this.#emitToClient("Debugger.scriptParsed", { scriptId: params.scriptId, url: cdpUrl, @@ -580,10 +581,10 @@ class InspectorCDPAdapter { endColumn: params.endColumn ?? 0, executionContextId: EXECUTION_CONTEXT_ID, hash: "", - isModule: !!params.module, + isModule: scriptType === "module", sourceMapURL: params.sourceMapURL, embedderName: cdpUrl, - scriptLanguage: "JavaScript", + scriptLanguage: scriptType === "webassembly" ? "WebAssembly" : "JavaScript", }); return; } diff --git a/test/js/node/inspector/inspector.test.ts b/test/js/node/inspector/inspector.test.ts index 05e3d6274df1..2b4db78be71a 100644 --- a/test/js/node/inspector/inspector.test.ts +++ b/test/js/node/inspector/inspector.test.ts @@ -998,6 +998,79 @@ export { after }; expect(await proc.exited).toBe(0); }); +// JSC's Debugger.scriptParsed classifies a script with scriptType ("program", +// "module" or "webassembly"); V8 clients read isModule and scriptLanguage. The +// fixture is its own CDP client: it enables the Debugger domain, then loads one +// script of each kind and prints what the adapter reported for them. +const scriptParsedFixture = ` +import inspector from "node:inspector"; + +inspector.open(0, "127.0.0.1", false); +const ws = new WebSocket(inspector.url()); +const pending = new Map(); +const scripts = []; +let nextId = 1; +ws.onmessage = event => { + const message = JSON.parse(event.data); + if (message.id) { + pending.get(message.id)(message); + pending.delete(message.id); + } else if (message.method === "Debugger.scriptParsed") { + scripts.push(message.params); + } +}; +const send = (method, params) => + new Promise(resolve => { + const id = nextId++; + pending.set(id, resolve); + ws.send(JSON.stringify({ id, method, params })); + }); +await new Promise(resolve => (ws.onopen = resolve)); + +await send("Debugger.enable", {}); +await import("./esm.mjs"); +await import("./lib.cjs"); +// The empty module: just the wasm magic and version. +new WebAssembly.Module(new Uint8Array([0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00])); +// The backend answers commands through the same ordered queue it emits events +// on, so this reply arriving proves the scriptParsed events for the three +// scripts above have arrived too. +await send("Debugger.setBreakpointsActive", { active: true }); +inspector.close(); + +console.log( + JSON.stringify( + scripts + .filter(({ url }) => /\\/esm\\.mjs$|\\/lib\\.cjs$|\\.wasm$/.test(url)) + .map(({ url, isModule, scriptLanguage }) => ({ url, isModule, scriptLanguage })), + ), +); +`; + +test("Debugger.scriptParsed reports isModule and scriptLanguage from JSC's scriptType", async () => { + using dir = tempDir("inspector-script-parsed", { + "fixture.mjs": scriptParsedFixture, + "esm.mjs": `export const esm = true;\n`, + "lib.cjs": `module.exports = { cjs: true };\n`, + }); + + await using proc = Bun.spawn({ + cmd: [bunExe(), "fixture.mjs"], + env: bunEnv, + cwd: String(dir), + stderr: "pipe", + }); + const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]); + expect({ stderrIfFailed: exitCode === 0 ? "" : stderr, exitCode }).toEqual({ stderrIfFailed: "", exitCode: 0 }); + + expect(JSON.parse(stdout.trim().split("\n").at(-1)!)).toEqual([ + { url: expect.stringMatching(/^file:\/\/.*\/esm\.mjs$/), isModule: true, scriptLanguage: "JavaScript" }, + { url: expect.stringMatching(/^file:\/\/.*\/lib\.cjs$/), isModule: false, scriptLanguage: "JavaScript" }, + // JSC names a WebAssembly.Module compiled from bytes .wasm itself. + { url: expect.stringMatching(/\.wasm$/), isModule: false, scriptLanguage: "WebAssembly" }, + ]); +}); + test("disconnect does not clobber a console method reassigned by user code", () => { const session = new inspector.Session(); session.connect();