diff options
Diffstat (limited to 'packages/bun-error/markdown.ts')
-rw-r--r-- | packages/bun-error/markdown.ts | 389 |
1 files changed, 389 insertions, 0 deletions
diff --git a/packages/bun-error/markdown.ts b/packages/bun-error/markdown.ts new file mode 100644 index 000000000..72f36674c --- /dev/null +++ b/packages/bun-error/markdown.ts @@ -0,0 +1,389 @@ +import { + normalizedFilename, + StackFrameIdentifier, + thisCwd, + StackFrameScope, +} from "./index"; +import type { + FallbackMessageContainer, + JSException, + JSException as JSExceptionType, + Location, + Message, + Problems, + SourceLine, + StackFrame, + WebsocketMessageBuildFailure, +} from "../../src/api/schema"; + +export function problemsToMarkdown(problems: Problems) { + var markdown = ""; + if (problems?.build?.msgs?.length) { + markdown += messagesToMarkdown(problems.build.msgs); + } + + if (problems?.exceptions?.length) { + markdown += exceptionsToMarkdown(problems.exceptions); + } + + return markdown; +} + +export function messagesToMarkdown(messages: Message[]): string { + return messages + .map(messageToMarkdown) + .map((a) => a.trim()) + .join("\n"); +} + +export function exceptionsToMarkdown(exceptions: JSExceptionType[]): string { + return exceptions + .map(exceptionToMarkdown) + .map((a) => a.trim()) + .join("\n"); +} + +function exceptionToMarkdown(exception: JSException): string { + const { name: name_, message: message_, stack } = exception; + var name = String(name_).trim(); + var message = String(message_).trim(); + + let markdown = ""; + + if ( + name === "Error" || + name === "RangeError" || + name === "TypeError" || + name === "ReferenceError" || + name === "DOMException" + ) { + markdown += `**${message}**\n`; + } else { + markdown += `**${name}**\n${message}\n`; + } + + if (stack.frames.length > 0) { + var frames = stack.frames; + if (stack.source_lines.length > 0) { + const { + file: _file = "", + function_name = "", + position: { + line = -1, + column_start: column = -1, + column_stop: columnEnd = column, + } = { + line: -1, + column_start: -1, + column_stop: -1, + }, + scope = 0, + } = stack.frames[0]; + const file = normalizedFilename(_file, thisCwd); + + if (file) { + if (function_name.length > 0) { + markdown += `In \`${function_name}\` – ${file}`; + } else if (scope > 0 && scope < StackFrameScope.Constructor + 1) { + markdown += `${StackFrameIdentifier({ + functionName: function_name, + scope, + markdown: true, + })} ${file}`; + } else { + markdown += `In ${file}`; + } + + if (line > -1) { + markdown += `:${line}`; + if (column > -1) { + markdown += `:${column}`; + } + } + + if (stack.source_lines.length > 0) { + // TODO: include loader + const extnameI = file.lastIndexOf("."); + const extname = extnameI > -1 ? file.slice(extnameI + 1) : ""; + + markdown += "\n```"; + markdown += extname; + markdown += "\n"; + stack.source_lines.forEach((sourceLine) => { + const lineText = sourceLine.text.trimEnd(); + markdown += lineText + "\n"; + if (sourceLine.line === line && stack.source_lines.length > 1) { + // the comment should start at the first non-whitespace character + // ideally it should be length the original line + // but it may not be + var prefix = "".padStart( + lineText.length - lineText.trimStart().length, + " " + ); + + prefix += + "/* ".padEnd(column - 1 - prefix.length, " ") + + "^ happend here "; + markdown += + prefix.padEnd(Math.max(lineText.length, 1) - 1, " ") + "*/\n"; + } + }); + markdown = markdown.trimEnd() + "\n```"; + } + } + } + + if (frames.length > 0) { + markdown += "\nStack trace:\n"; + var padding = 0; + // Limit to 8 frames because it may be a huge stack trace + // and we want to not hit the message limit + const framesToDisplay = frames.slice(0, Math.min(frames.length, 8)); + for (let frame of framesToDisplay) { + const { + function_name = "", + position: { line = -1, column_start: column = -1 } = { + line: -1, + column_start: -1, + }, + scope = 0, + } = frame; + padding = Math.max( + padding, + StackFrameIdentifier({ + scope, + functionName: function_name, + markdown: true, + }).length + ); + } + + markdown += "```js\n"; + + for (let frame of framesToDisplay) { + const { + file = "", + function_name = "", + position: { line = -1, column_start: column = -1 } = { + line: -1, + column_start: -1, + }, + scope = 0, + } = frame; + + markdown += ` + ${StackFrameIdentifier({ + scope, + functionName: function_name, + markdown: true, + }).padEnd(padding, " ")}`; + + if (file) { + markdown += ` ${normalizedFilename(file, thisCwd)}`; + if (line > -1) { + markdown += `:${line}`; + if (column > -1) { + markdown += `:${column}`; + } + } + } + } + + markdown += "\n```\n"; + } + } + + return markdown; +} + +function messageToMarkdown(message: Message): string { + var tag = "Error"; + if (message.on.build) { + tag = "BuildError"; + } + var lines = (message.data.text ?? "").split("\n"); + + var markdown = ""; + if (message?.on?.resolve) { + markdown += `**ResolveError**: "${message.on.resolve}" failed to resolve\n`; + } else { + var firstLine = lines[0]; + lines = lines.slice(1); + if (firstLine.length > 120) { + const words = firstLine.split(" "); + var end = 0; + for (let i = 0; i < words.length; i++) { + if (end + words[i].length >= 120) { + firstLine = words.slice(0, i).join(" "); + lines.unshift(words.slice(i).join(" ")); + break; + } + } + } + + markdown += `**${tag}**${firstLine.length > 0 ? ": " + firstLine : ""}\n`; + } + + if (message.data?.location?.file) { + markdown += `In ${normalizedFilename(message.data.location.file, thisCwd)}`; + if (message.data.location.line > -1) { + markdown += `:${message.data.location.line}`; + if (message.data.location.column > -1) { + markdown += `:${message.data.location.column}`; + } + } + + if (message.data.location.line_text.length) { + const extnameI = message.data.location.file.lastIndexOf("."); + const extname = + extnameI > -1 ? message.data.location.file.slice(extnameI + 1) : ""; + + markdown += + "\n```" + extname + "\n" + message.data.location.line_text + "\n```\n"; + } else { + markdown += "\n"; + } + + if (lines.length > 0) { + markdown += lines.join("\n"); + } + } + + return markdown; +} + +export const withBunInfo = (text) => { + const bunInfo = getBunInfo(); + + const trimmed = text.trim(); + + if (bunInfo && "then" in bunInfo) { + return bunInfo.then( + (info) => { + const markdown = bunInfoToMarkdown(info).trim(); + return trimmed + "\n" + markdown + "\n"; + }, + () => trimmed + "\n" + ); + } + + if (bunInfo) { + const markdown = bunInfoToMarkdown(bunInfo).trim(); + + return trimmed + "\n" + markdown + "\n"; + } + + return trimmed + "\n"; +}; + +function bunInfoToMarkdown(_info) { + if (!_info) return; + const info = { ..._info, platform: { ..._info.platform } }; + + var operatingSystemVersion = info.platform.version; + + if (info.platform.os.toLowerCase() === "macos") { + const [major, minor, patch] = operatingSystemVersion.split("."); + switch (major) { + case "22": { + operatingSystemVersion = `13.${minor}.${patch}`; + break; + } + case "21": { + operatingSystemVersion = `12.${minor}.${patch}`; + break; + } + case "20": { + operatingSystemVersion = `11.${minor}.${patch}`; + break; + } + + case "19": { + operatingSystemVersion = `10.15.${patch}`; + break; + } + + case "18": { + operatingSystemVersion = `10.14.${patch}`; + break; + } + + case "17": { + operatingSystemVersion = `10.13.${patch}`; + break; + } + + case "16": { + operatingSystemVersion = `10.12.${patch}`; + break; + } + + case "15": { + operatingSystemVersion = `10.11.${patch}`; + break; + } + } + info.platform.os = "macOS"; + } + + if (info.platform.arch === "arm" && info.platform.os === "macOS") { + info.platform.arch = "Apple Silicon"; + } else if (info.platform.arch === "arm") { + info.platform.arch = "aarch64"; + } + + var base = `Info: + > bun v${info.bun_version} + `; + + if (info.framework && info.framework_version) { + base += `> framework: ${info.framework}@${info.framework_version}`; + } else if (info.framework) { + base += `> framework: ${info.framework}`; + } + + base = + base.trim() + + ` + > ${info.platform.os} ${operatingSystemVersion} (${info.platform.arch}) + > User-Agent: ${globalThis.navigator.userAgent} + > Pathname: ${globalThis.location.pathname} + `; + + return base; +} + +var bunInfoMemoized; +function getBunInfo() { + if (bunInfoMemoized) return bunInfoMemoized; + if ("sessionStorage" in globalThis) { + try { + const bunInfoMemoizedString = sessionStorage.getItem("__bunInfo"); + if (bunInfoMemoizedString) { + bunInfoMemoized = JSON.parse(bunInfoMemoized); + return bunInfoMemoized; + } + } catch (exception) {} + } + const controller = new AbortController(); + const timeout = 1000; + const id = setTimeout(() => controller.abort(), timeout); + return fetch("/bun:info", { + signal: controller.signal, + headers: { + Accept: "application/json", + }, + }) + .then((resp) => resp.json()) + .then((bunInfo) => { + clearTimeout(id); + bunInfoMemoized = bunInfo; + if ("sessionStorage" in globalThis) { + try { + sessionStorage.setItem("__bunInfo", JSON.stringify(bunInfo)); + } catch (exception) {} + } + + return bunInfo; + }); +} |