diff options
author | 2023-08-24 19:30:55 -0700 | |
---|---|---|
committer | 2023-08-24 20:11:20 -0700 | |
commit | dacd00d7082fede526867f145dd36456143ee263 (patch) | |
tree | 02dce991c7b1eaef7db1b5b4a896a863c5463ecc /packages | |
parent | a068e9c8db5702a5e84e3ee9b3f0d99ab130e85d (diff) | |
download | bun-dacd00d7082fede526867f145dd36456143ee263.tar.gz bun-dacd00d7082fede526867f145dd36456143ee263.tar.zst bun-dacd00d7082fede526867f145dd36456143ee263.zip |
Improve preview code
Diffstat (limited to 'packages')
5 files changed, 415 insertions, 105 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 new file mode 100644 index 000000000..0acc17575 --- /dev/null +++ b/packages/bun-debug-adapter-protocol/debugger/__snapshots__/preview.test.ts.snap @@ -0,0 +1,143 @@ +// 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 index 01bbd6052..9dc55fe38 100644 --- a/packages/bun-debug-adapter-protocol/debugger/adapter.ts +++ b/packages/bun-debug-adapter-protocol/debugger/adapter.ts @@ -6,6 +6,7 @@ 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 & { @@ -1521,111 +1522,6 @@ function callFrameToId(callFrame: JSC.Console.CallFrame): string { return `${url}:${lineNumber}:${columnNumber}`; } -function remoteObjectToString(remoteObject: JSC.Runtime.RemoteObject): string { - const { type, subtype, value, description, className, preview } = remoteObject; - switch (type) { - case "undefined": - return "undefined"; - case "boolean": - case "string": - return JSON.stringify(value ?? description); - case "number": - return description ?? JSON.stringify(value); - 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"; -} - -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); - } else if (properties) { - if (isIndexed(subtype)) { - items = properties.map(indexedPropertyPreviewToString); - } else { - items = properties.map(namedPropertyPreviewToString); - } - } 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 sanitizeExpression(expression: string): string { expression = expression.trim(); if (expression.startsWith("{")) { diff --git a/packages/bun-debug-adapter-protocol/debugger/fixtures/preview.js b/packages/bun-debug-adapter-protocol/debugger/fixtures/preview.js new file mode 100644 index 000000000..15062240b --- /dev/null +++ b/packages/bun-debug-adapter-protocol/debugger/fixtures/preview.js @@ -0,0 +1,99 @@ +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/preview.test.ts b/packages/bun-debug-adapter-protocol/debugger/preview.test.ts new file mode 100644 index 000000000..666913719 --- /dev/null +++ b/packages/bun-debug-adapter-protocol/debugger/preview.test.ts @@ -0,0 +1,62 @@ +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 new file mode 100644 index 000000000..6012623d2 --- /dev/null +++ b/packages/bun-debug-adapter-protocol/debugger/preview.ts @@ -0,0 +1,110 @@ +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"; +} |