Skip to content

Commit 8c03818

Browse files
authored
Improve error handling (#23)
* Improve error handling * simplify
1 parent e6bc0b2 commit 8c03818

2 files changed

Lines changed: 75 additions & 30 deletions

File tree

packages/javascript-kernel/src/executor.ts

Lines changed: 32 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ import type { IMimeBundle } from '@jupyterlab/nbformat';
1010

1111
export { IDisplayData, IDisplayCallbacks, DisplayHelper } from './display';
1212

13+
/** Matches the word "eval" in a stack frame (user eval code). */
14+
const RE_EVAL = /\beval\b/;
15+
1316
/**
1417
* Configuration for magic imports.
1518
*/
@@ -768,23 +771,42 @@ export class JavaScriptExecutor {
768771
* @returns The cleaned stack trace string.
769772
*/
770773
cleanStackTrace(error: Error): string {
771-
const errStackStr = error.stack || '';
772-
const errStackLines = errStackStr.split('\n');
773-
const usedLines: string[] = [];
774+
const header = `${error.name}: ${error.message}`;
775+
const stack = error.stack || '';
776+
const lines = stack.split('\n');
777+
const userFrames: string[] = [];
778+
779+
for (const line of lines) {
780+
const trimmed = line.trim();
781+
if (!trimmed) {
782+
continue;
783+
}
784+
785+
// Some browsers repeat `Name: message` as the first stack line.
786+
if (trimmed.startsWith(`${error.name}:`)) {
787+
continue;
788+
}
774789

775-
for (const line of errStackLines) {
776-
// Stop at internal implementation details
790+
// Stop once we reach internal executor frames.
777791
if (
778-
line.includes('makeAsyncFromCode') ||
779-
line.includes('new Function') ||
780-
line.includes('asyncFunction')
792+
trimmed.includes('makeAsyncFromCode') ||
793+
trimmed.includes('new Function') ||
794+
trimmed.includes('asyncFunction')
781795
) {
782796
break;
783797
}
784-
usedLines.push(line);
798+
799+
// Only keep lines that reference user eval code.
800+
if (RE_EVAL.test(trimmed) || trimmed.includes('<anonymous>')) {
801+
userFrames.push(line);
802+
}
803+
}
804+
805+
if (userFrames.length > 0) {
806+
return `${header}\n${userFrames.join('\n')}`;
785807
}
786808

787-
return usedLines.join('\n');
809+
return header;
788810
}
789811

790812
/**

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)