From 6da2b2d87e45a157f52e3dd1abd14f21e56079d7 Mon Sep 17 00:00:00 2001 From: Linus Groh Date: Thu, 7 May 2026 22:23:41 +0100 Subject: [PATCH] Implement error stack parsing for kiesel --- jest.config.js | 2 +- lib/agents/kiesel.js | 38 +++++++++++++++++--------- test/agents/kiesel.js | 62 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 88 insertions(+), 14 deletions(-) create mode 100644 test/agents/kiesel.js diff --git a/jest.config.js b/jest.config.js index 1a8223b..8e28322 100644 --- a/jest.config.js +++ b/jest.config.js @@ -11,7 +11,7 @@ const jestConfig = { // }, // }, rootDir: "./", - testMatch: ["/test/*.js"], + testMatch: ["/test/*.js", "/test/agents/*.js"], testPathIgnorePatterns: [], transform: {}, verbose: true, diff --git a/lib/agents/kiesel.js b/lib/agents/kiesel.js index d375be8..407503a 100644 --- a/lib/agents/kiesel.js +++ b/lib/agents/kiesel.js @@ -3,7 +3,8 @@ import fs from "node:fs"; import * as runtimePath from "../runtime-path.js"; import { ConsoleAgent } from "../ConsoleAgent.js"; -const errorRe = /^Uncaught exception: (.+?)(?:: (.+))?$/m; +const ERROR_REGEXP = /^Uncaught exception: (?.+?)(?:: (?.+))?$/m; +const STACK_FRAME_REGEXP = /^ {2}at fn (?.+)$/; class KieselAgent extends ConsoleAgent { static RUNTIME = fs.readFileSync(runtimePath.for("kiesel"), "utf8"); @@ -27,30 +28,41 @@ class KieselAgent extends ConsoleAgent { } parseError(str) { - const match = str.match(errorRe); - - if (!match) { + const errorMatch = str.match(ERROR_REGEXP); + if (!errorMatch) { return null; } - const name = match[1] ? match[1].trim() : ""; - const message = match[2] ? match[2].trim() : ""; + const { name, message = "" } = errorMatch.groups; + const stack = []; + + const lines = str.slice(errorMatch[0].length).split(/\r?\n/g); + for (const line of lines) { + const stackFrameMatch = line.match(STACK_FRAME_REGEXP); + if (stackFrameMatch) { + const { functionName } = stackFrameMatch.groups; + stack.push({ + source: line, + functionName, + }); + } + } return { name, message, - stack: [], + stack, }; } normalizeResult(result) { - const match = result.stdout.match(errorRe); - - if (match) { - result.stdout = result.stdout.replace(errorRe, ""); - result.stderr = match[0]; + const errorMatch = ERROR_REGEXP.exec(result.stdout); + if (errorMatch) { + const { index } = errorMatch; + const stdout = result.stdout; + result.stdout = stdout.slice(0, index); + result.stderr = stdout.slice(index); } - return result; } } diff --git a/test/agents/kiesel.js b/test/agents/kiesel.js new file mode 100644 index 0000000..ae05dfd --- /dev/null +++ b/test/agents/kiesel.js @@ -0,0 +1,62 @@ +import KieselAgent from "../../lib/agents/kiesel.js"; + +describe("KieselAgent", () => { + describe("normalizeResult", () => { + it("works", () => { + const agent = new KieselAgent(); + const result = { + stdout: `ok +Uncaught exception: Error: boom + at fn foo + at fn bar + at fn baz +`, + stderr: "", + error: null, + }; + + const normalized = agent.normalizeResult(result); + + expect(normalized).toEqual({ + stdout: `ok +`, + stderr: `Uncaught exception: Error: boom + at fn foo + at fn bar + at fn baz +`, + error: null, + }); + }); + }); + + describe("parseError", () => { + it("works", () => { + const agent = new KieselAgent(); + const parsed = agent.parseError(`Uncaught exception: Error: boom + at fn foo + at fn bar + at fn baz +`); + + expect(parsed).toEqual({ + name: "Error", + message: "boom", + stack: [ + { + source: " at fn foo", + functionName: "foo", + }, + { + source: " at fn bar", + functionName: "bar", + }, + { + source: " at fn baz", + functionName: "baz", + }, + ], + }); + }); + }); +});