Skip to content

Commit 1bb8fd8

Browse files
committed
feat: allow for overriding of file and or line
Rather than extracting these from the trace. Useful where the file might be "<exec>" in Pyodide.
1 parent c5c7552 commit 1bb8fd8

4 files changed

Lines changed: 46 additions & 1 deletion

File tree

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,14 @@ if (result) {
5050
// no friendly explanation — show the original traceback as-is
5151
}
5252

53+
// if the trace reports an unhelpful source location (eg. Pyodide runs code as "<exec>"), pass file explicitly to override what's parsed from the trace:
54+
const result = friendlyExplain({
55+
error: rawTracebackString,
56+
code: editorCode,
57+
runtime: "pyodide",
58+
file: "main.py", // overrides the file from the trace
59+
});
60+
5361
// optionally limit which sections appear in result.html:
5462
const result = friendlyExplain({
5563
error: rawTracebackString,

src/engine.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,9 +148,19 @@ export const friendlyExplain = (opts: ExplainOptions): ExplainResult | null => {
148148
// The error could not be parsed — no friendly explanation; caller uses the raw error.
149149
if (!trace) return null;
150150

151-
if (code && trace.line && !trace.codeLine) {
151+
// Caller-provided file/line take precedence over whatever was parsed from the trace
152+
// Useful when the traceback's innermost frame references an internal file (eg.
153+
// Pyodide's "<exec>") instead of the user's filename, or when the line needs correcting.
154+
if (opts.file !== undefined) trace.file = opts.file;
155+
if (opts.line !== undefined) trace.line = opts.line;
156+
157+
// (Re)derive the code context from the effective line: when the caller overrides the
158+
// line, or when a pre-parsed trace arrived without a codeLine.
159+
if (code && trace.line && (opts.line !== undefined || !trace.codeLine)) {
152160
const lines = code.split(/\r?\n/);
153161
trace.codeLine = lines[trace.line - 1]?.trim();
162+
trace.codeBefore = lines.slice(Math.max(0, trace.line - 3), trace.line - 1);
163+
trace.codeAfter = lines.slice(trace.line, trace.line + 2);
154164
}
155165

156166
const chosen = pickVariant(trace, code, opts.sections);

src/types.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,14 @@ export type Section = "title" | "summary" | "why" | "steps" | "patch" | "details
2222
export type ExplainOptions = {
2323
error: string | Error | Trace;
2424
code?: string;
25+
/**
26+
* Override the source file, instead of using the one parsed from the trace.
27+
* Useful when the traceback's innermost frame references an internal file
28+
* (eg. Pyodide's "<exec>") rather than the user's filename.
29+
*/
30+
file?: string;
31+
/** Override the source line, instead of using the one parsed from the trace. */
32+
line?: number;
2533
locale?: string;
2634
runtime?: string;
2735
sections?: Section[];

tests/engine.test.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,25 @@ ValueError: invalid literal for int() with base 10: 'abc'`;
6767
expect(res).toBeNull();
6868
});
6969

70+
it("lets caller-provided file/line override the trace (e.g. Pyodide's <exec>)", () => {
71+
const code = `for i in range(3)\n print(i)`;
72+
const raw = `Traceback (most recent call last):
73+
File "/lib/python312.zip/_pyodide/_base.py", line 148, in _parse_and_compile_gen
74+
File "<exec>", line 1
75+
for i in range(3)
76+
^
77+
SyntaxError: expected ':'`;
78+
79+
const without = friendlyExplain({ error: raw, code, runtime: "skulpt" });
80+
expect(without!.trace.file).toBe("<exec>");
81+
82+
const withOverride = friendlyExplain({ error: raw, code, runtime: "skulpt", file: "main.py", line: 1 });
83+
expect(withOverride!.trace.file).toBe("main.py");
84+
expect(withOverride!.trace.line).toBe(1);
85+
expect(withOverride!.trace.codeLine).toBe("for i in range(3)");
86+
expect(withOverride!.summary).toContain("main.py");
87+
});
88+
7089
it("explains NameError with name and patch", () => {
7190
const code = `print("Hello")\nprint(kittens)\n`;
7291
const raw = `Traceback (most recent call last):

0 commit comments

Comments
 (0)