aboutsummaryrefslogtreecommitdiff
path: root/packages/bun-error/stack-trace-parser.ts
diff options
context:
space:
mode:
authorGravatar Jarred Sumner <jarred@jarredsumner.com> 2022-02-20 23:12:15 -0800
committerGravatar Jarred Sumner <jarred@jarredsumner.com> 2022-02-20 23:12:15 -0800
commitefb4baacdfb39d453203fe13f36fbbc884078abf (patch)
treea4c6dae9b820a8f8df23b3a71a8e303f61b59abb /packages/bun-error/stack-trace-parser.ts
parent59109a309fc5bfef3b44736211ffe518bfbd52fc (diff)
downloadbun-efb4baacdfb39d453203fe13f36fbbc884078abf.tar.gz
bun-efb4baacdfb39d453203fe13f36fbbc884078abf.tar.zst
bun-efb4baacdfb39d453203fe13f36fbbc884078abf.zip
[bun dev] Implement copy as markdown
Diffstat (limited to 'packages/bun-error/stack-trace-parser.ts')
-rw-r--r--packages/bun-error/stack-trace-parser.ts187
1 files changed, 187 insertions, 0 deletions
diff --git a/packages/bun-error/stack-trace-parser.ts b/packages/bun-error/stack-trace-parser.ts
new file mode 100644
index 000000000..c779a6185
--- /dev/null
+++ b/packages/bun-error/stack-trace-parser.ts
@@ -0,0 +1,187 @@
+const UNKNOWN_FUNCTION = "<unknown>";
+import type {
+ FallbackMessageContainer,
+ JSException,
+ Location,
+ Message,
+ SourceLine,
+ StackFrame,
+ WebsocketMessageBuildFailure,
+} from "../../src/api/schema";
+
+/**
+ * This parses the different stack traces and puts them into one format
+ * This borrows heavily from TraceKit (https://github.com/csnover/TraceKit)
+ */
+export function parse(stackString): StackFrame[] {
+ const lines = stackString.split("\n");
+
+ return lines.reduce((stack, line) => {
+ const parseResult =
+ parseChrome(line) ||
+ parseWinjs(line) ||
+ parseGecko(line) ||
+ parseNode(line) ||
+ parseJSC(line);
+
+ if (parseResult) {
+ stack.push(parseResult);
+ }
+
+ return stack;
+ }, []);
+}
+
+const formatFile = (file) => {
+ if (!file) {
+ return "";
+ }
+
+ if (file.startsWith("blob:")) {
+ if (globalThis["__BUN"]?.client) {
+ const replacement =
+ globalThis["__BUN"]?.client.dependencies.getFilePathFromBlob(file);
+ if (replacement) {
+ file = replacement;
+ }
+ }
+ }
+
+ var _file = String(file);
+ if (_file.startsWith(globalThis.location?.origin)) {
+ _file = _file.substring(globalThis.location?.origin.length);
+ }
+
+ while (_file.startsWith("/")) {
+ _file = _file.substring(1);
+ }
+
+ if (_file.endsWith(".bun")) {
+ _file = "node_modules.bun";
+ }
+
+ return _file;
+};
+
+const chromeRe =
+ /^\s*at (.*?) ?\(((?:file|https?|blob|chrome-extension|native|eval|webpack|<anonymous>|\/|[a-z]:\\|\\\\).*?)(?::(\d+))?(?::(\d+))?\)?\s*$/i;
+const chromeEvalRe = /\((\S*)(?::(\d+))(?::(\d+))\)/;
+
+function parseChrome(line) {
+ const parts = chromeRe.exec(line);
+
+ if (!parts) {
+ return null;
+ }
+
+ const isNative = parts[2] && parts[2].indexOf("native") === 0; // start of line
+ const isEval = parts[2] && parts[2].indexOf("eval") === 0; // start of line
+
+ const submatch = chromeEvalRe.exec(parts[2]);
+ if (isEval && submatch != null) {
+ // throw out eval line/column and use top-most line/column number
+ parts[2] = submatch[1]; // url
+ parts[3] = submatch[2]; // line
+ parts[4] = submatch[3]; // column
+ }
+
+ return {
+ file: formatFile(!isNative ? parts[2] : null),
+ function_name: parts[1] || "",
+ position: {
+ line: parts[3] ? +parts[3] : null,
+ column_start: parts[4] ? +parts[4] : null,
+ },
+ };
+}
+
+const winjsRe =
+ /^\s*at (?:((?:\[object object\])?.+) )?\(?((?:file|ms-appx|https?|webpack|blob):.*?):(\d+)(?::(\d+))?\)?\s*$/i;
+
+function parseWinjs(line) {
+ const parts = winjsRe.exec(line);
+
+ if (!parts) {
+ return null;
+ }
+
+ return {
+ file: formatFile(parts[2]),
+ function_name: parts[1],
+ position: {
+ line: +parts[3],
+ column_start: parts[4] ? +parts[4] : null,
+ },
+ };
+}
+
+const geckoRe =
+ /^\s*(.*?)(?:\((.*?)\))?(?:^|@)((?:file|https?|blob|chrome|webpack|resource|\[native).*?|[^@]*bundle)(?::(\d+))?(?::(\d+))?\s*$/i;
+const geckoEvalRe = /(\S+) line (\d+)(?: > eval line \d+)* > eval/i;
+
+function parseGecko(line) {
+ const parts = geckoRe.exec(line);
+
+ if (!parts) {
+ return null;
+ }
+
+ const isEval = parts[3] && parts[3].indexOf(" > eval") > -1;
+
+ const submatch = geckoEvalRe.exec(parts[3]);
+ if (isEval && submatch != null) {
+ // throw out eval line/column and use top-most line number
+ parts[3] = submatch[1];
+ parts[4] = submatch[2];
+ parts[5] = null; // no column when eval
+ }
+
+ return {
+ file: formatFile(parts[3]),
+ function_name: parts[1] || "",
+ position: {
+ line: parts[4] ? +parts[4] : null,
+ column_start: parts[5] ? +parts[5] : null,
+ },
+ };
+}
+
+const javaScriptCoreRe =
+ /^\s*(?:([^@]*)(?:\((.*?)\))?@)?(\S.*?):(\d+)(?::(\d+))?\s*$/i;
+
+function parseJSC(line) {
+ const parts = javaScriptCoreRe.exec(line);
+
+ if (!parts) {
+ return null;
+ }
+
+ return {
+ file: formatFile(parts[3]),
+ function_name: parts[1] || "",
+ position: {
+ line: +parts[4],
+ column_start: parts[5] ? +parts[5] : null,
+ },
+ };
+}
+
+const nodeRe =
+ /^\s*at (?:((?:\[object object\])?[^\\/]+(?: \[as \S+\])?) )?\(?(.*?):(\d+)(?::(\d+))?\)?\s*$/i;
+
+function parseNode(line) {
+ const parts = nodeRe.exec(line);
+
+ if (!parts) {
+ return null;
+ }
+
+ return {
+ file: formatFile(parts[2]),
+ function_name: parts[1] || "",
+ position: {
+ line: +parts[3],
+ column_start: parts[4] ? +parts[4] : null,
+ },
+ };
+}