Skip to content

Commit dc8387e

Browse files
committed
feat(core): improve stack trace accuracy in ThatError
Updated `ThatError` to capture the stack trace from the factory caller instead of the constructor. Added a test suite to ensure business code frames are prioritized over internal frames.
1 parent a4a76cc commit dc8387e

2 files changed

Lines changed: 29 additions & 4 deletions

File tree

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { describe, expect, test } from "bun:test";
2+
import { That } from "@thaterror/core";
3+
4+
describe("ThatError Caller Tracing", () => {
5+
6+
test("stack trace should start from business code, not factory", () => {
7+
const AppError = That({TEST: "msg"});
8+
const err = AppError.TEST();
9+
10+
const stack = err.stack ?? "";
11+
const lines = stack.split("\n");
12+
13+
const topFrame = lines[1] ?? "";
14+
15+
const pathSegments = new URL(import.meta.url).pathname.split('/');
16+
const currentFileName = pathSegments.at(-1) ?? "";
17+
18+
expect(topFrame).toContain(currentFileName);
19+
20+
expect(topFrame).not.toContain("define.ts");
21+
22+
expect(topFrame.length).toBeGreaterThan(0);
23+
});
24+
});

packages/core/src/define.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,19 +33,20 @@ export function That<const M extends ErrorMap>(
3333
readonly [CodeField]: Code;
3434

3535
constructor(
36+
caller: typeof cases[typeof key],
3637
readonly code: Code,
3738
args: Payload,
3839
readonly scope: symbol,
3940
message: string,
40-
options?: ErrorOptions
41+
options?: ErrorOptions,
4142
) {
4243
super(message, options);
4344
this.name = code;
4445
this[CodeField] = code;
4546
this[ScopeField] = scope;
4647
this[PayloadField] = args;
4748

48-
Error.captureStackTrace(this, this.constructor);
49+
Error.captureStackTrace(this, caller);
4950
}
5051

5152
is<K extends string, S extends ErrorSpec>(errorCase: ErrorCase<K, S>): this is DefinedError<K, ExtractPayload<S>> {
@@ -68,12 +69,12 @@ export function That<const M extends ErrorMap>(
6869
}
6970
}
7071
const message = (spec as (...a: unknown[]) => string)(...finalArgs);
71-
return new InternalBaseError(key, finalArgs as Payload, scope, message, options);
72+
return new InternalBaseError(factory, key, finalArgs as Payload, scope, message, options);
7273
}
7374

7475
if (typeof spec === "string") {
7576
options = args[0] as ErrorOptions | undefined;
76-
return new InternalBaseError(key, [] as Payload, scope, spec, options);
77+
return new InternalBaseError(factory, key, [] as Payload, scope, spec, options);
7778
}
7879

7980
throw new Error("Invalid ErrorSpec");

0 commit comments

Comments
 (0)