diff options
author | 2023-08-24 22:53:34 -0700 | |
---|---|---|
committer | 2023-08-24 22:53:34 -0700 | |
commit | 1480889205d49cf7221a36608a8896b452967cea (patch) | |
tree | e1427e4041cf19ef1e8e8e0f58cfbbceb4cbbf74 /packages/bun-debug-adapter-protocol/scripts/generate-protocol.ts | |
parent | f269432d90826ad3e5b66c7685a6e826e0fb05e2 (diff) | |
download | bun-1480889205d49cf7221a36608a8896b452967cea.tar.gz bun-1480889205d49cf7221a36608a8896b452967cea.tar.zst bun-1480889205d49cf7221a36608a8896b452967cea.zip |
Improved support for `debug-adapter-protocol` (#4186)
* Improve support for \`debug-adapter-protocol\`
* More improvements, fix formatting in debug console
* Fix attaching
* Prepare for source maps
* Start of source map support, breakpoints work
* Source map support
* add some package.jsons
* wip
* Update package.json
* More fixes
* Make source maps safer if exception occurs
* Check bun version if it fails
* Fix console.log formatting
* Fix source maps partly
* More source map fixes
* Prepare for extension
* watch mode with dap
* Improve preview code
* Prepare for extension 2
---------
Co-authored-by: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com>
Diffstat (limited to 'packages/bun-debug-adapter-protocol/scripts/generate-protocol.ts')
-rw-r--r-- | packages/bun-debug-adapter-protocol/scripts/generate-protocol.ts | 176 |
1 files changed, 176 insertions, 0 deletions
diff --git a/packages/bun-debug-adapter-protocol/scripts/generate-protocol.ts b/packages/bun-debug-adapter-protocol/scripts/generate-protocol.ts new file mode 100644 index 000000000..724a48490 --- /dev/null +++ b/packages/bun-debug-adapter-protocol/scripts/generate-protocol.ts @@ -0,0 +1,176 @@ +import type { Protocol, Type } from "../protocol/schema.d.ts"; +import { writeFileSync } from "node:fs"; +import { spawnSync } from "node:child_process"; + +run().catch(console.error); + +async function run() { + const cwd = new URL("../protocol/", import.meta.url); + const runner = "Bun" in globalThis ? "bunx" : "npx"; + const write = (name: string, data: string) => { + const path = new URL(name, cwd); + writeFileSync(path, data); + spawnSync(runner, ["prettier", "--write", path.pathname], { cwd, stdio: "ignore" }); + }; + const schema: Protocol = await download( + "https://microsoft.github.io/debug-adapter-protocol/debugAdapterProtocol.json", + ); + write("protocol.json", JSON.stringify(schema)); + const types = formatProtocol(schema); + write("index.d.ts", `// GENERATED - DO NOT EDIT\n${types}`); +} + +function formatProtocol(protocol: Protocol, extraTs?: string): string { + const { definitions } = protocol; + const requestMap = new Map(); + const responseMap = new Map(); + const eventMap = new Map(); + let body = `export namespace DAP {`; + loop: for (const [key, definition] of Object.entries(definitions)) { + if (/[a-z]+Request$/i.test(key)) { + continue; + } + if (/[a-z]+Arguments$/i.test(key)) { + const name = key.replace(/(Request)?Arguments$/, ""); + const requestName = `${name}Request`; + requestMap.set(toMethod(name), requestName); + body += formatType(definition, requestName); + continue; + } + if ("allOf" in definition) { + const { allOf } = definition; + for (const type of allOf) { + if (type.type !== "object") { + continue; + } + const { description, properties = {} } = type; + if (/[a-z]+Event$/i.test(key)) { + const { event, body: type = {} } = properties; + if (!event || !("enum" in event)) { + continue; + } + const [eventKey] = event.enum ?? []; + eventMap.set(eventKey, key); + const eventType: Type = { + type: "object", + description, + ...type, + }; + body += formatType(eventType, key); + continue loop; + } + if (/[a-z]+Response$/i.test(key)) { + const { body: type = {} } = properties; + const bodyType: Type = { + type: "object", + description, + ...type, + }; + const name = key.replace(/Response$/, ""); + responseMap.set(toMethod(name), key); + body += formatType(bodyType, key); + continue loop; + } + } + } + body += formatType(definition, key); + } + for (const [key, name] of responseMap) { + if (requestMap.has(key)) { + continue; + } + const requestName = `${name.replace(/Response$/, "")}Request`; + requestMap.set(key, requestName); + body += formatType({ type: "object", properties: {} }, requestName); + } + body += formatMapType("RequestMap", requestMap); + body += formatMapType("ResponseMap", responseMap); + body += formatMapType("EventMap", eventMap); + if (extraTs) { + body += extraTs; + } + return body + "};"; +} + +function formatMapType(key: string, typeMap: Map<string, string>): string { + const type: Type = { + type: "object", + required: [...typeMap.keys()], + properties: Object.fromEntries([...typeMap.entries()].map(([key, value]) => [key, { $ref: value }])), + }; + return formatType(type, key); +} + +function formatType(type: Type, key?: string): string { + const { description, type: kind } = type; + let body = ""; + if (key) { + if (description) { + body += `\n${toComment(description)}\n`; + } + body += `export type ${key}=`; + } + if (kind === "boolean") { + body += "boolean"; + } else if (kind === "number" || kind === "integer") { + body += "number"; + } else if (kind === "string") { + const { enum: choices } = type; + if (choices) { + body += choices.map(value => `"${value}"`).join("|"); + } else { + body += "string"; + } + } else if (kind === "array") { + const { items } = type; + const itemType = items ? formatType(items) : "unknown"; + body += `${itemType}[]`; + } else if (kind === "object") { + const { properties, required } = type; + if (!properties || Object.keys(properties).length === 0) { + body += "{}"; + } else { + body += "{"; + for (const [key, { description, ...type }] of Object.entries(properties)) { + if (description) { + body += `\n${toComment(description)}`; + } + const delimit = required?.includes(key) ? ":" : "?:"; + body += `\n${key}${delimit}${formatType(type)};`; + } + body += "}"; + } + } else if ("$ref" in type) { + const { $ref: ref } = type; + body += ref.split("/").pop() || "unknown"; + } else if ("allOf" in type) { + const { allOf } = type; + body += allOf.map(type => formatType(type)).join("&"); + } else { + body += "unknown"; + } + if (key) { + body += ";"; + } + return body; +} + +function toMethod(name: string): string { + return `${name.substring(0, 1).toLowerCase()}${name.substring(1)}`; +} + +function toComment(description?: string): string { + if (!description) { + return ""; + } + const lines = ["/**", ...description.split("\n").map(line => ` * ${line.trim()}`), "*/"]; + return lines.join("\n"); +} + +async function download<T>(url: string | URL): Promise<T> { + const response = await fetch(url); + if (!response.ok) { + throw new Error(`Failed to download ${url}: ${response.statusText}`); + } + return response.json(); +} |