Skip to content

Commit ca3b6ea

Browse files
committed
Improve error handling
1 parent 7d34a53 commit ca3b6ea

2 files changed

Lines changed: 81 additions & 30 deletions

File tree

packages/javascript-kernel/src/executor.ts

Lines changed: 38 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -768,23 +768,51 @@ export class JavaScriptExecutor {
768768
* @returns The cleaned stack trace string.
769769
*/
770770
cleanStackTrace(error: Error): string {
771-
const errStackStr = error.stack || '';
772-
const errStackLines = errStackStr.split('\n');
773-
const usedLines: string[] = [];
771+
const header = `${error.name}: ${error.message}`;
772+
const stack = error.stack || '';
773+
const lines = stack.split('\n');
774+
const userFrames: string[] = [];
775+
776+
for (const line of lines) {
777+
const trimmed = line.trim();
778+
if (!trimmed) {
779+
continue;
780+
}
781+
782+
// Some browsers repeat `Name: message` as the first stack line.
783+
if (trimmed.startsWith(`${error.name}:`)) {
784+
continue;
785+
}
774786

775-
for (const line of errStackLines) {
776-
// Stop at internal implementation details
787+
// Stop once we reach internal executor frames.
777788
if (
778-
line.includes('makeAsyncFromCode') ||
779-
line.includes('new Function') ||
780-
line.includes('asyncFunction')
789+
trimmed.includes('makeAsyncFromCode') ||
790+
trimmed.includes('new Function') ||
791+
trimmed.includes('asyncFunction')
781792
) {
782793
break;
783794
}
784-
usedLines.push(line);
795+
796+
// Keep eval frames from user code.
797+
if (/\beval\b/.test(trimmed) || trimmed.includes('<anonymous>')) {
798+
userFrames.push(line);
799+
continue;
800+
}
801+
802+
// Drop stack frames that point to bundled code.
803+
if (/^\s*at\s+/.test(trimmed) || /^[^@]*@\S+:\d+:\d+$/.test(trimmed)) {
804+
continue;
805+
}
806+
807+
// Keep any other lines (may be useful context).
808+
userFrames.push(line);
809+
}
810+
811+
if (userFrames.length > 0) {
812+
return `${header}\n${userFrames.join('\n')}`;
785813
}
786814

787-
return usedLines.join('\n');
815+
return header;
788816
}
789817

790818
/**

packages/javascript-kernel/src/runtime_evaluator.ts

Lines changed: 43 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -53,10 +53,20 @@ export class JavaScriptRuntimeEvaluator {
5353
code: string,
5454
executionCount: number
5555
): Promise<KernelMessage.IExecuteReplyMsg['content']> {
56+
// Parse-time errors are syntax errors, so show only `Name: message`.
57+
let asyncFunction: () => Promise<any>;
58+
let withReturn: boolean;
5659
try {
57-
const { asyncFunction, withReturn } =
58-
this._executor.makeAsyncFromCode(code);
60+
const parsed = this._executor.makeAsyncFromCode(code);
61+
asyncFunction = parsed.asyncFunction;
62+
withReturn = parsed.withReturn;
63+
} catch (error) {
64+
const normalized = normalizeError(error);
65+
return this._emitError(executionCount, normalized, false);
66+
}
5967

68+
// Runtime errors may include useful eval frames from user code.
69+
try {
6070
const resultPromise = this._evalFunc(asyncFunction);
6171

6272
if (withReturn) {
@@ -84,24 +94,7 @@ export class JavaScriptRuntimeEvaluator {
8494
};
8595
} catch (error) {
8696
const normalized = normalizeError(error);
87-
const cleanedStack = this._executor.cleanStackTrace(normalized);
88-
89-
const content: KernelMessage.IReplyErrorContent = {
90-
status: 'error',
91-
ename: normalized.name || 'Error',
92-
evalue: normalized.message || '',
93-
traceback: [cleanedStack]
94-
};
95-
96-
this._onOutput({
97-
type: 'execute_error',
98-
bundle: content
99-
});
100-
101-
return {
102-
...content,
103-
execution_count: executionCount
104-
};
97+
return this._emitError(executionCount, normalized, true);
10598
}
10699
}
107100

@@ -148,6 +141,36 @@ export class JavaScriptRuntimeEvaluator {
148141
return asyncFunc.call(this._globalScope);
149142
}
150143

144+
/**
145+
* Build and emit an execute error reply.
146+
*/
147+
private _emitError(
148+
executionCount: number,
149+
error: Error,
150+
includeStack: boolean
151+
): KernelMessage.IExecuteReplyMsg['content'] {
152+
const traceback = includeStack
153+
? this._executor.cleanStackTrace(error)
154+
: `${error.name}: ${error.message}`;
155+
156+
const content: KernelMessage.IReplyErrorContent = {
157+
status: 'error',
158+
ename: error.name || 'Error',
159+
evalue: error.message || '',
160+
traceback: [traceback]
161+
};
162+
163+
this._onOutput({
164+
type: 'execute_error',
165+
bundle: content
166+
});
167+
168+
return {
169+
...content,
170+
execution_count: executionCount
171+
};
172+
}
173+
151174
/**
152175
* Patch console methods in runtime scope to emit Jupyter stream messages.
153176
*/

0 commit comments

Comments
 (0)