1test("a trace without frames is only its message", () => {
2 const error = new RangeError("out of it");
3 error.stack = String(error);
4 assert.deepEqual(stack.parse(error), []);
5 assert.equal(stack.format(error), "RangeError: out of it\n");
6});
7
8test("an AggregateError of values that are not errors still formats", () => {
9 assert.doesNotThrow(() => stack.format(new AggregateError(["not an error"], "all failed")));
10});
11
12test("a trace without colors has no escape codes", () => {
13 const error = new Error("x");
14 error.stack = "Error: x\n at foo (native)";
15 assert.doesNotMatch(stack.format(error, false), /\x1b/);
16});
17
18test("an error is labeled with its severity, unless its name says more", () => {
19 const label = (error: Error, severity?: stack.Severity) => stack.describe(error, { severity }).label;
20 assert.equal(label(new Error("x")), "error");
21 assert.equal(label(new TypeError("x"), "warning"), "warning");
22 assert.equal(label(new RangeError("x")), "RangeError");
23 assert.equal(label(Object.assign(new Error("x"), { name: "Spelling" }), "warning"), "Spelling");
24});
25
26test("the place an error says it is about is shown once, with the lines around it, before where it was thrown", () => {
27 const error = Object.assign(new Error("first test"), { file: "lib/log/stack.test.ts", line: 1, col: 1, code: "E1" });
28 const diagnostic = stack.describe(error, { root: repo });
29 assert.equal(diagnostic.location?.file, "lib/log/stack.test.ts");
30 assert.deepEqual(diagnostic.location?.code, [
31 `test("a trace without frames is only its message", () => {`,
32 ` const error = new RangeError("out of it");`,
33 ]);
34 assert.deepEqual(diagnostic.location?.span, [0, 4]);
35 assert.deepEqual(diagnostic.fields, [["code", `"E1"`]]);
36 assert.equal(diagnostic.notes[0]?.text, "thrown here");
37 assert.equal(diagnostic.notes[0]?.frames[0]?.file, "lib/log/stack.test.ts");
38
39 const [header, location, code, field, note] = stack.format(error, false, { root: repo }).split("\n");
40 assert.equal(header, "error: first test");
41 assert.equal(location, "lib/log/stack.test.ts:1:1");
42 assert.equal(code, `test("a trace without frames is only its message", () => {`);
43 assert.equal(field, ` code: "E1"`);
44 assert.equal(note, "note: thrown here");
45});
46
47test("a thrown error is about its first frame outside of a runtime or package", () => {
48 const error = new Error("x");
49 error.stack = [
50 "Error: x",
51 " at open (node:fs:10:5)",
52 " at load (/app/node_modules/pkg/index.js:3:4)",
53 " at config (/app/src/config.ts:3:4)",
54 " at main (/app/src/main.ts:1:1)",
55 ].join("\n");
56 const { location, notes } = stack.describe(error, { root: "/app" });
57 assert.equal(location?.file, "src/config.ts");
58 assert.equal(location?.fn, "config");
59 assert.deepEqual(notes.map((note) => [note.text, note.frames.map((frame) => frame.fn)]), [
60 ["thrown inside", ["open", "load"]],
61 ["called from", ["main"]],
62 ]);
63});
64
65test("notes point at their own places", () => {
66 const error = Object.assign(new Error("duplicate"), {
67 file: "lib/log/stack.test.ts",
68 line: 2,
69 col: 3,
70 notes: [{ text: "first defined here", file: "lib/log/stack.test.ts", line: 1, col: 1 }],
71 });
72 const [note] = stack.describe(error, { root: repo }).notes;
73 assert.equal(note?.text, "first defined here");
74 assert.equal(note?.location?.line, 1);
75 assert.deepEqual(note?.location?.code, [`test("a trace without frames is only its message", () => {`]);
76});
77
78test("a width marks that much of the line, where a column alone marks a word", () => {
79 const place = { file: "lib/log/stack.test.ts", line: 1, col: 6, width: 30 };
80 const { location, notes } = stack.describe(
81 Object.assign(new Error("x"), { ...place, notes: [{ text: "and here", ...place, width: 2 }] }),
82 { root: repo },
83 );
84 assert.deepEqual(location?.span, [5, 35]);
85 assert.deepEqual(notes[0]?.location?.span, [5, 7]);
86});
87
88const repo = path.resolve(import.meta.dirname, "../..");
89
90import assert from "node:assert/strict";
91import * as path from "node:path";
92import { test } from "vitest";
93import * as stack from "./stack.ts";