diff --git a/src/calltree.js b/src/calltree.js index 57ebae0..6649973 100644 --- a/src/calltree.js +++ b/src/calltree.js @@ -750,7 +750,7 @@ const select_return_value = state => { } else { result_node = find_node(frame, n => - n.type == 'function_call' + (n.type == 'function_call' || n.type == 'new') && n.result != null && n.result.call.id == state.current_calltree_node.id ) @@ -790,7 +790,7 @@ const select_arguments = (state, with_focus = true) => { } else { const call = find_node(frame, n => - n.type == 'function_call' + (n.type == 'function_call' || n.type == 'new') && n.result != null && n.result.call.id == state.current_calltree_node.id ) diff --git a/src/editor/calltree.js b/src/editor/calltree.js index d90b17e..abc4ad2 100644 --- a/src/editor/calltree.js +++ b/src/editor/calltree.js @@ -112,8 +112,8 @@ export class CallTree { + (n.fn.__location == null ? ' native' : '') , // TODO show `this` argument - n.fn.name - , + (n.is_new ? 'new ' : ''), + n.fn.name, '(' , ...join( n.args.map( diff --git a/src/eval.js b/src/eval.js index c058ab9..1028ac9 100644 --- a/src/eval.js +++ b/src/eval.js @@ -222,7 +222,8 @@ const codegen = (node, cxt, parent) => { } else if(node.type == 'spread'){ return '...(' + do_codegen(node.expr) + ')' } else if(node.type == 'new') { - return '(new (' + codegen(node.constructor) + ')(' + node.args.map(do_codegen).join(',') + '))' + const args = `[${node.args.children.map(do_codegen).join(',')}]` + return `trace_call(${do_codegen(node.constructor)}, null, ${args}, true)` } else if(node.type == 'grouping'){ return '(' + do_codegen(node.expr) + ')' } else if(node.type == 'array_destructuring') { @@ -441,13 +442,20 @@ export const eval_modules = ( return result } - const trace_call = (fn, context, args) => { - if(fn != null && fn.__location != null) { + const trace_call = (fn, context, args, is_new = false) => { + if(fn != null && fn.__location != null && !is_new) { + // Call will be traced, because tracing code is already embedded inside + // fn return fn(...args) } if(typeof(fn) != 'function') { - return fn.apply(context, args) + // Raise error + if(is_new) { + return new fn(...args) + } else { + return fn.apply(context, args) + } } const children_copy = children @@ -465,7 +473,11 @@ export const eval_modules = ( try { if(!is_log) { - value = fn.apply(context, args) + if(is_new) { + value = new fn(...args) + } else { + value = fn.apply(context, args) + } } else { value = undefined } @@ -489,6 +501,7 @@ export const eval_modules = ( args, context, is_log, + is_new, } const should_record_call = stack.pop() @@ -799,7 +812,7 @@ const do_eval_frame_expr = (node, scope, callsleft) => { {} ) return {ok, children, value, calls} - } else if(node.type == 'function_call'){ + } else if(node.type == 'function_call' || node.type == 'new'){ const {ok, children, calls} = eval_children(node, scope, callsleft) if(!ok) { return {ok: false, children, calls} @@ -940,15 +953,6 @@ const do_eval_frame_expr = (node, scope, callsleft) => { return eval_binary_expr(node, scope, callsleft) } - } else if(node.type == 'new') { - const {ok, children, calls} = eval_children(node, scope, callsleft) - if(!ok) { - return {ok, children, calls} - } else { - const [constructor, ...args] = children - const value = new (constructor.result.value)(...args.map(a => a.result.value)) - return {ok, children, value, calls} - } } else if(node.type == 'grouping'){ const {ok, children, calls} = eval_children(node, scope, callsleft) if(!ok) { diff --git a/src/parse_js.js b/src/parse_js.js index b5ebe1f..4bc78c3 100644 --- a/src/parse_js.js +++ b/src/parse_js.js @@ -984,11 +984,20 @@ const new_expr = if_ok( array_element, ) ]), - ({value, ...node}) => ({ - ...node, - type: 'new', - children: [value[1], ...value[2].value], - }) + ({value, ...node}) => { + const {value: args, ..._call_args} = value[2] + const call_args = { + ..._call_args, + children: args, + not_evaluatable: args.length == 0, + type: 'call_args', + } + return { + ...node, + type: 'new', + children: [value[1], call_args], + } + } ) const primary = if_fail( @@ -1384,7 +1393,7 @@ const update_children_not_rec = (node, children = node.children) => { expr: children[0] } } else if(node.type == 'new') { - return {...node, constructor: children[0], args: children.slice(1)} + return {...node, constructor: children[0], args: children[1]} } else if(node.type == 'grouping') { return {...node, expr: children[0]} } else if(node.type == 'return') { diff --git a/test/test.js b/test/test.js index 84d0d3f..d873518 100644 --- a/test/test.js +++ b/test/test.js @@ -423,6 +423,35 @@ export const tests = [ `, 'test') }), + test('new calls are recorded in calltree', () => { + const code = ` + const make_class = new Function("return class { constructor(x) { x() } }") + const clazz = make_class() + const x = () => 1 + new clazz(x) + ` + const i = test_initial_state(code) + const find_call = COMMANDS.move_cursor(i, code.indexOf('1')).state + assert_equal(root_calltree_node(find_call).children.length, 3) + const x_call = root_calltree_node(find_call).children[2].children[0] + assert_equal(x_call.fn.name, 'x') + }), + + test('new calls step into', () => { + const code = `new Set()` + const i = test_initial_state(code) + const into = COMMANDS.calltree.arrow_down(i) + assert_equal(into.state.current_calltree_node.fn.name, 'Set') + assert_equal(into.state.current_calltree_node.is_new, true) + }), + + test('new call non-constructor', () => { + assert_code_error( + `const x = () => 1; new x()`, + 'TypeError: fn is not a constructor' + ) + }), + test('method chaining', () => { assert_code_evals_to( ` @@ -1676,7 +1705,7 @@ const y = x()` const y = () => 1 const deep_error = x => { if(x == 10) { - throw new Error('deep_error') + throw 'deep_error' } else { y() deep_error(x + 1) @@ -1701,7 +1730,7 @@ const y = x()` assert_equal(depth(first), 10) assert_equal(first.ok, false) - assert_equal(first.error.message, 'deep_error') + assert_equal(first.error, 'deep_error') }), /* Test when node where error occured has subcalls */ @@ -1958,6 +1987,14 @@ const y = x()` const {state: s3, effects} = COMMANDS.calltree.select_return_value(s2) assert_equal(s3.selection_state.result.value, [1, 1, 1]) }), + + test('select_return_value new call', () => { + const code = `new String('1')` + const s1 = test_initial_state(code) + const s2 = COMMANDS.calltree.arrow_right(s1).state + const {state: s3, effects} = COMMANDS.calltree.select_return_value(s2) + assert_equal(s3.selection_state.result.value, '1') + }), test('select_arguments not_expanded', () => { const code = ` @@ -1989,6 +2026,14 @@ const y = x()` assert_equal(s3.effects, {type: 'set_focus'}) }), + test('select_arguments new call', () => { + const code = `new String("1")` + const s1 = test_initial_state(code) + const s2 = COMMANDS.calltree.arrow_right(s1).state + const s3 = COMMANDS.calltree.select_arguments(s2).state + assert_equal(s3.selection_state.result, {ok: true, value: ["1"]}) + }), + test('move_cursor arguments', () => { const code = ` const x = (a, b) => { }