Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 32 additions & 10 deletions packages/javascript-kernel/src/executor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand Down Expand Up @@ -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('<anonymous>')) {
userFrames.push(line);
}
}

if (userFrames.length > 0) {
return `${header}\n${userFrames.join('\n')}`;
}

return usedLines.join('\n');
return header;
}

/**
Expand Down
63 changes: 43 additions & 20 deletions packages/javascript-kernel/src/runtime_evaluator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,20 @@ export class JavaScriptRuntimeEvaluator {
code: string,
executionCount: number
): Promise<KernelMessage.IExecuteReplyMsg['content']> {
// Parse-time errors are syntax errors, so show only `Name: message`.
let asyncFunction: () => Promise<any>;
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) {
Expand Down Expand Up @@ -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);
}
}

Expand Down Expand Up @@ -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.
*/
Expand Down