diff --git a/src/ast_utils.js b/src/ast_utils.js index 017cf82..375603b 100644 --- a/src/ast_utils.js +++ b/src/ast_utils.js @@ -170,7 +170,18 @@ export const find_node = (node, pred) => { export const find_error_origin_node = node => find_node( node, - n => n.result != null && !n.result.ok && n.result.error != null + n => n.result != null && !n.result.ok && ( + n.result.error != null + || + // In case if throw null or throw undefined + n.type == 'throw' + || + // await can also throw null + n.type == 'unary' && n.operator == 'await' + // or function call throwing null or undefined + || + n.type == 'function_call' + ) ) /* Maps tree nodes, discarding mapped children, so maps only node contents, not diff --git a/src/calltree.js b/src/calltree.js index 048aba4..9483471 100644 --- a/src/calltree.js +++ b/src/calltree.js @@ -36,10 +36,10 @@ export const set_location = (state, location) => set_cursor_position( const is_stackoverflow = node => // Chrome - node.error.message == 'Maximum call stack size exceeded' + node.error?.message == 'Maximum call stack size exceeded' || // Firefox - node.error.message == "too much recursion" + node.error?.message == "too much recursion" export const calltree_node_loc = node => node.toplevel ? {module: node.module} diff --git a/src/editor/calltree.js b/src/editor/calltree.js index eba7f89..856859a 100644 --- a/src/editor/calltree.js +++ b/src/editor/calltree.js @@ -115,7 +115,7 @@ export class CallTree { el('i', '', 'toplevel: ' + (n.module == '' ? '*scratch*' : n.module), ), - n.ok ? '' : el('span', 'call_header error', '\xa0', n.error.toString()), + n.ok ? '' : el('span', 'call_header error', '\xa0', stringify_for_header(n.error)), ) : el('span', 'call_header ' @@ -135,7 +135,7 @@ export class CallTree { ), ')' , // TODO: show error message only where it was thrown, not every frame? - ': ', (n.ok ? stringify_for_header(n.value) : n.error.toString()) + ': ', (n.ok ? stringify_for_header(n.value) : stringify_for_header(n.error)) ), ), (n.children == null || !is_expanded) diff --git a/src/editor/editor.js b/src/editor/editor.js index b4c0257..9ede59e 100644 --- a/src/editor/editor.js +++ b/src/editor/editor.js @@ -1,5 +1,5 @@ import {exec, get_state} from '../index.js' -import {ValueExplorer} from './value_explorer.js' +import {ValueExplorer, stringify_for_header} from './value_explorer.js' import {el, stringify, fn_link} from './domutils.js' import {FLAGS} from '../feature_flags.js' @@ -244,7 +244,7 @@ export class Editor { exp.render(value) } else { - content.appendChild(el('span', 'eval_error', error.toString())) + content.appendChild(el('span', 'eval_error', stringify_for_header(error))) } this.widget = { diff --git a/test/test.js b/test/test.js index 4a5735a..01c28c4 100644 --- a/test/test.js +++ b/test/test.js @@ -456,6 +456,21 @@ export const tests = [ ) }), + test('throw null', () => { + assert_code_error(`throw null`, null) + }), + + test('throw null from function', () => { + const code = ` + const throws = () => { throw null } + throws() + ` + const s = test_initial_state(code) + const moved = COMMANDS.move_cursor(s, code.indexOf('throws()')) + assert_equal(moved.value_explorer.result.ok, false) + assert_equal(moved.value_explorer.result.error, null) + }), + test('new', () => { assert_code_evals_to('new Error("test").message', 'test') }), @@ -2774,6 +2789,13 @@ const y = x()` ) }), + test('async/await promise rejected with null', async () => { + await assert_code_error_async( + `await Promise.reject()`, + undefined + ) + }), + test('async/await await rejected Promise returned from async', async () => { await assert_code_error_async( ` diff --git a/test/utils.js b/test/utils.js index d79b5e3..abad6f7 100644 --- a/test/utils.js +++ b/test/utils.js @@ -1,5 +1,4 @@ -import {parse, print_debug_node, load_modules} from '../src/parse_js.js' -import {eval_tree, eval_frame} from '../src/eval.js' +import {print_debug_node, load_modules} from '../src/parse_js.js' import {active_frame, pp_calltree} from '../src/calltree.js' import {COMMANDS} from '../src/cmd.js' @@ -17,21 +16,18 @@ export const parse_modules = (entry, modules) => load_modules(entry, module_name => modules[module_name]) export const assert_code_evals_to = (codestring, expected) => { - const parse_result = parse(codestring) - assert_equal(parse_result.ok, true) - const tree = eval_tree(parse_result.node) - const frame = eval_frame(tree) + const s = test_initial_state(codestring) + const frame = active_frame(s) const result = frame.children.at(-1).result - assert_equal({ok: result.ok, value: result.value}, {ok: true, value: expected}) + assert_equal(result.ok, true) + assert_equal(result.value, expected) return frame } export const assert_code_error = (codestring, error) => { - const parse_result = parse(codestring) - assert_equal(parse_result.ok, true) - const tree = eval_tree(parse_result.node) - const frame = eval_frame(tree) - const result = frame.children[frame.children.length - 1].result + const state = test_initial_state(codestring) + const frame = active_frame(state) + const result = frame.children.at(-1).result assert_equal(result.ok, false) assert_equal(result.error, error) }