diff --git a/packages/javascript-kernel/src/executor.ts b/packages/javascript-kernel/src/executor.ts index 6cd1efb..0919aa7 100644 --- a/packages/javascript-kernel/src/executor.ts +++ b/packages/javascript-kernel/src/executor.ts @@ -10,6 +10,9 @@ import type { IMimeBundle } from '@jupyterlab/nbformat'; export { IDisplayData, IDisplayCallbacks, DisplayHelper } from './display'; +/** Matches the word "eval" in a stack frame (user eval code). */ +const RE_EVAL = /\beval\b/; + /** * Configuration for magic imports. */ @@ -768,23 +771,42 @@ export class JavaScriptExecutor { * @returns The cleaned stack trace string. */ cleanStackTrace(error: Error): string { - const errStackStr = error.stack || ''; - const errStackLines = errStackStr.split('\n'); - const usedLines: string[] = []; + const header = `${error.name}: ${error.message}`; + const stack = error.stack || ''; + const lines = stack.split('\n'); + const userFrames: string[] = []; + + for (const line of lines) { + const trimmed = line.trim(); + if (!trimmed) { + continue; + } + + // Some browsers repeat `Name: message` as the first stack line. + if (trimmed.startsWith(`${error.name}:`)) { + continue; + } - for (const line of errStackLines) { - // Stop at internal implementation details + // Stop once we reach internal executor frames. if ( - line.includes('makeAsyncFromCode') || - line.includes('new Function') || - line.includes('asyncFunction') + trimmed.includes('makeAsyncFromCode') || + trimmed.includes('new Function') || + trimmed.includes('asyncFunction') ) { break; } - usedLines.push(line); + + // Only keep lines that reference user eval code. + if (RE_EVAL.test(trimmed) || trimmed.includes('')) { + userFrames.push(line); + } + } + + if (userFrames.length > 0) { + return `${header}\n${userFrames.join('\n')}`; } - return usedLines.join('\n'); + return header; } /** diff --git a/packages/javascript-kernel/src/runtime_evaluator.ts b/packages/javascript-kernel/src/runtime_evaluator.ts index 7492625..d77c917 100644 --- a/packages/javascript-kernel/src/runtime_evaluator.ts +++ b/packages/javascript-kernel/src/runtime_evaluator.ts @@ -53,10 +53,20 @@ export class JavaScriptRuntimeEvaluator { code: string, executionCount: number ): Promise { + // Parse-time errors are syntax errors, so show only `Name: message`. + let asyncFunction: () => Promise; + let withReturn: boolean; try { - const { asyncFunction, withReturn } = - this._executor.makeAsyncFromCode(code); + const parsed = this._executor.makeAsyncFromCode(code); + asyncFunction = parsed.asyncFunction; + withReturn = parsed.withReturn; + } catch (error) { + const normalized = normalizeError(error); + return this._emitError(executionCount, normalized, false); + } + // Runtime errors may include useful eval frames from user code. + try { const resultPromise = this._evalFunc(asyncFunction); if (withReturn) { @@ -84,24 +94,7 @@ export class JavaScriptRuntimeEvaluator { }; } catch (error) { const normalized = normalizeError(error); - const cleanedStack = this._executor.cleanStackTrace(normalized); - - const content: KernelMessage.IReplyErrorContent = { - status: 'error', - ename: normalized.name || 'Error', - evalue: normalized.message || '', - traceback: [cleanedStack] - }; - - this._onOutput({ - type: 'execute_error', - bundle: content - }); - - return { - ...content, - execution_count: executionCount - }; + return this._emitError(executionCount, normalized, true); } } @@ -148,6 +141,36 @@ export class JavaScriptRuntimeEvaluator { return asyncFunc.call(this._globalScope); } + /** + * Build and emit an execute error reply. + */ + private _emitError( + executionCount: number, + error: Error, + includeStack: boolean + ): KernelMessage.IExecuteReplyMsg['content'] { + const traceback = includeStack + ? this._executor.cleanStackTrace(error) + : `${error.name}: ${error.message}`; + + const content: KernelMessage.IReplyErrorContent = { + status: 'error', + ename: error.name || 'Error', + evalue: error.message || '', + traceback: [traceback] + }; + + this._onOutput({ + type: 'execute_error', + bundle: content + }); + + return { + ...content, + execution_count: executionCount + }; + } + /** * Patch console methods in runtime scope to emit Jupyter stream messages. */