find call with respect of cursor position inside branch

This commit is contained in:
Dmitry Vasilev
2023-08-02 06:00:08 +03:00
parent adbca074c2
commit 54d502396d
9 changed files with 528 additions and 86 deletions

View File

@@ -2,7 +2,7 @@ import {map_accum, map_find, map_object, stringify, findLast} from './utils.js'
import {is_eq, find_error_origin_node} from './ast_utils.js' import {is_eq, find_error_origin_node} from './ast_utils.js'
import {find_node, find_leaf, ancestry_inc} from './ast_utils.js' import {find_node, find_leaf, ancestry_inc} from './ast_utils.js'
import {color} from './color.js' import {color} from './color.js'
import {eval_frame, eval_expand_calltree_node} from './eval.js' import {eval_frame, eval_expand_calltree_node, get_after_if_path} from './eval.js'
export const pp_calltree = tree => ({ export const pp_calltree = tree => ({
id: tree.id, id: tree.id,
@@ -91,27 +91,38 @@ export const get_calltree_node_by_loc = (state, index) => {
} }
} }
const get_selected_calltree_node_by_loc = (state, node) => const get_selected_calltree_node_by_loc = (
state,
node,
module = state.current_module
) =>
state.selected_calltree_node_by_loc state.selected_calltree_node_by_loc
?.[state.current_module] ?.[module]
?.[ ?.[
state.parse_result.modules[state.current_module] == node state.parse_result.modules[module] == node
// identify toplevel by index `-1`, because function and toplevel can // identify toplevel by index `-1`, because function and toplevel can
// have the same index (in case when module starts with function_expr) // have the same index (in case when module starts with function_expr)
? -1 ? -1
: node.index : node.index
] ]
const add_selected_calltree_node_by_loc = (state, loc, node_id) => { const set_node_by_loc = (node_by_loc, loc, node_id) => {
return { return {...node_by_loc,
...state,
selected_calltree_node_by_loc:
{...state.selected_calltree_node_by_loc,
[loc.module]: { [loc.module]: {
...state.selected_calltree_node_by_loc?.[loc.module], ...node_by_loc?.[loc.module],
[loc.index ?? -1]: node_id [loc.index ?? -1]: node_id
} }
} }
}
const set_selected_calltree_node_by_loc = (state, loc, node_id) => {
return {
...state,
selected_calltree_node_by_loc: set_node_by_loc(
state.selected_calltree_node_by_loc,
loc,
node_id,
)
} }
} }
@@ -133,24 +144,38 @@ export const add_frame = (
current_calltree_node = active_calltree_node, current_calltree_node = active_calltree_node,
) => { ) => {
let with_frame let with_frame
if(state.frames?.[active_calltree_node.id] == null) { let frame
const frame = eval_frame(active_calltree_node, state.modules) frame = state.frames?.[active_calltree_node.id]
if(frame == null) {
frame = eval_frame(active_calltree_node, state.modules)
const execution_paths = active_calltree_node.toplevel
? null
: get_execution_paths(frame)
const coloring = color(frame) const coloring = color(frame)
with_frame = {...state, with_frame = {...state,
frames: {...state.frames, frames: {...state.frames,
[active_calltree_node.id]: {...frame, coloring} [active_calltree_node.id]: {...frame, coloring, execution_paths}
} }
} }
} else { } else {
with_frame = state with_frame = state
} }
// TODO only add if it is not the same
const result = add_selected_calltree_node_by_loc( const loc = calltree_node_loc(active_calltree_node)
with_frame,
calltree_node_loc(active_calltree_node), const with_colored_frames = {...with_frame,
colored_frames: set_node_by_loc(
with_frame.colored_frames,
loc,
active_calltree_node.id, active_calltree_node.id,
) )
return set_active_calltree_node(result, active_calltree_node, current_calltree_node) }
return set_active_calltree_node(
with_colored_frames,
active_calltree_node,
current_calltree_node
)
} }
const replace_calltree_node = (root, node, replacement) => { const replace_calltree_node = (root, node, replacement) => {
@@ -269,7 +294,13 @@ const jump_calltree_node = (_state, _current_calltree_node) => {
// of body? // of body?
: set_location(next, loc) : set_location(next, loc)
return {...with_location, const with_selected_calltree_node = set_selected_calltree_node_by_loc(
with_location,
calltree_node_loc(active_calltree_node),
with_location.active_calltree_node.id,
)
return {...with_selected_calltree_node,
value_explorer: next.current_calltree_node.toplevel value_explorer: next.current_calltree_node.toplevel
? null ? null
: { : {
@@ -537,20 +568,88 @@ export const initial_calltree_node = state => {
export const default_expand_path = state => initial_calltree_node(state).state export const default_expand_path = state => initial_calltree_node(state).state
export const get_execution_paths = frame => {
/*
- depth-first search tree. if 'result == null', then stop. Do not descend
into function_expr
- look for 'if' and ternary
- Get executed branch (branch.result.ok != null). There may be no executed
branch if cond executed with error
- for 'if' statement we also add 'get_after_if_path(if_node.index)'
*/
const do_get = (node, next_node) => {
if(node.type == 'function_expr' || node.result == null) {
return []
}
let after_if, branch
if(node.type == 'if' || node.type == 'ternary') {
const [cond, ...branches] = node.children
branch = branches.find(b => b.result != null)
}
if(node.type == 'if' && next_node != null && next_node.result != null) {
after_if = get_after_if_path(node)
}
const paths = [branch?.index, after_if].filter(i => i != null)
const children = node.children ?? []
const child_paths = children
.map((c, i) => do_get(c, children[i + 1]))
.flat()
return [...paths, ...child_paths]
}
const [args, body] = frame.children
return new Set(do_get(body))
}
const find_execution_path = (node, index) => {
if(node.children == null) {
return []
}
const child = node.children.find(c =>
c.index <= index && c.index + c.length > index
)
if(child == null) {
return []
}
const prev_ifs = node
.children
.filter(c =>
c.index < child.index && c.type == 'if'
)
.map(c => get_after_if_path(c))
const child_path = find_execution_path(child, index)
// TODO other conditionals, like &&, ||, ??, ?., ?.[]
if(node.type == 'if' || node.type == 'ternary') {
const [cond, left, right] = node.children
if(child == left || child == right) {
return [...prev_ifs, child.index, ...child_path]
}
}
return [...prev_ifs, ...child_path]
}
export const find_call_node = (state, index) => { export const find_call_node = (state, index) => {
const module = state.parse_result.modules[state.current_module] const module = state.parse_result.modules[state.current_module]
if(module == null) { if(module == null) {
// Module is not executed // Module is not executed
return null return {node: null, path: null}
} }
let node let node, path
if(index < module.index || index >= module.index + module.length) { if(index < module.index || index >= module.index + module.length) {
// index is outside of module, it can happen because of whitespace and // index is outside of module, it can happen because of whitespace and
// comments in the beginning and the end // comments in the beginning and the end
node = module node = module
path = null
} else { } else {
const leaf = find_leaf(module, index) const leaf = find_leaf(module, index)
const anc = ancestry_inc(leaf, module) const anc = ancestry_inc(leaf, module)
@@ -558,44 +657,19 @@ export const find_call_node = (state, index) => {
node = fn == null node = fn == null
? module ? module
: fn : fn
path = find_execution_path(node, index)
} }
return node return {node, path}
} }
export const find_call = (state, index) => { export const find_call = (state, index) => {
const node = find_call_node(state, index) const {node, path} = find_call_node(state, index)
if(node == null) { if(node == null) {
return state return state
} }
if(state.active_calltree_node != null && is_eq(node, state.active_calltree_node.code)) {
return state
}
const selected_ct_node_id = get_selected_calltree_node_by_loc(state, node)
/*
TODO remove because it interferes with find_call deferred calls
if(selected_ct_node_id === null) {
// strict compare (===) with null, to check if we put null earlier to
// designate that fn is not reachable
return set_active_calltree_node(state, null)
}
*/
if(selected_ct_node_id != null) {
const ct_node = find_node(
state.calltree,
n => n.id == selected_ct_node_id
)
if(ct_node == null) {
throw new Error('illegal state')
}
return set_active_calltree_node(state, ct_node, ct_node)
}
if(node == state.parse_result.modules[root_calltree_module(state)]) { if(node == state.parse_result.modules[root_calltree_module(state)]) {
const toplevel = root_calltree_node(state) const toplevel = root_calltree_node(state)
return add_frame( return add_frame(
@@ -610,19 +684,42 @@ export const find_call = (state, index) => {
return state return state
} }
const selected_ct_node_id = get_selected_calltree_node_by_loc(state, node)
const execution_paths = selected_ct_node_id == null
? null
: state.frames[selected_ct_node_id].execution_paths
const ct_node_id = get_calltree_node_by_loc(state, node.index) const ct_node_id = get_calltree_node_by_loc(state, node.index)
if(ct_node_id == null) { if(ct_node_id == null) {
return set_active_calltree_node(state, null) return set_active_calltree_node(state, null)
} }
const path_ct_node_id = [node.index, ...path]
.map(path_elem => {
const is_selected_node_hits_path =
selected_ct_node_id != null
&&
(execution_paths.has(path_elem) || node.index == path_elem)
if(is_selected_node_hits_path) {
return selected_ct_node_id
}
// If there is no node selected for this fn, or it did not hit the path
// when executed, try to find node calltree_node_by_loc
return get_calltree_node_by_loc(state, path_elem)
})
.findLast(node_id => node_id != null)
const ct_node = find_node( const ct_node = find_node(
state.calltree, state.calltree,
n => n.id == ct_node_id n => n.id == path_ct_node_id
) )
if(ct_node == null) { if(ct_node == null) {
throw new Error('illegal state') throw new Error('illegal state')
} }
return add_frame( return add_frame(
expand_path(state, ct_node), expand_path(state, ct_node),
ct_node, ct_node,

View File

@@ -11,9 +11,9 @@ import {
root_calltree_node, root_calltree_module, make_calltree, root_calltree_node, root_calltree_module, make_calltree,
get_deferred_calls, get_deferred_calls,
calltree_commands, calltree_commands,
add_frame, calltree_node_loc, get_calltree_node_by_loc, expand_path, add_frame, calltree_node_loc, expand_path,
initial_calltree_node, default_expand_path, toggle_expanded, active_frame, initial_calltree_node, default_expand_path, toggle_expanded, active_frame,
find_call, find_call_node, set_active_calltree_node, find_call, set_active_calltree_node,
set_cursor_position, current_cursor_position, set_location, set_cursor_position, current_cursor_position, set_location,
} from './calltree.js' } from './calltree.js'
@@ -96,6 +96,7 @@ const run_code = (s, dirty_files) => {
active_calltree_node: null, active_calltree_node: null,
calltree_node_is_expanded: null, calltree_node_is_expanded: null,
frames: null, frames: null,
colored_frames: null,
calltree_node_by_loc: null, calltree_node_by_loc: null,
selected_calltree_node_by_loc: null, selected_calltree_node_by_loc: null,
selection_state: null, selection_state: null,
@@ -223,14 +224,11 @@ const eval_modules_finished = (state, prev_state, result) => {
const {node, state: next2} = initial_calltree_node(next) const {node, state: next2} = initial_calltree_node(next)
result_state = set_active_calltree_node(next2, null, node) result_state = set_active_calltree_node(next2, null, node)
} else { } else {
result_state = add_frame( result_state = default_expand_path(
default_expand_path(
expand_path( expand_path(
next, next,
next.active_calltree_node next.active_calltree_node
) )
),
next.active_calltree_node,
) )
} }

View File

@@ -209,8 +209,7 @@ export const color = frame => {
export const color_file = (state, file) => export const color_file = (state, file) =>
Object Object
.values(state.selected_calltree_node_by_loc?.[file] ?? {}) .values(state.colored_frames?.[file] ?? {})
// node_id == null means it is unreachable, so do not color
.filter(node_id => node_id != null) .filter(node_id => node_id != null)
.map(node_id => state.frames[node_id].coloring) .map(node_id => state.frames[node_id].coloring)
.flat() .flat()

View File

@@ -52,6 +52,7 @@ const normalize_events = (ace_editor, {
}) })
ace_editor.on('changeSelection', (...args) => { ace_editor.on('changeSelection', (...args) => {
// TODO debounce changeSelection?
if(is_change_selection_supressed()) { if(is_change_selection_supressed()) {
return return
} }

2
src/effects.js vendored
View File

@@ -249,7 +249,7 @@ export const apply_side_effects = (prev, next, command, ui) => {
ui.calltree.render_select_node(prev, next) ui.calltree.render_select_node(prev, next)
} }
if(prev.selected_calltree_node_by_loc != next.selected_calltree_node_by_loc) { if(prev.colored_frames != next.colored_frames) {
render_coloring(ui, next) render_coloring(ui, next)
} }

View File

@@ -72,11 +72,13 @@ const codegen_function_expr = (node, node_cxt) => {
? `(${args}) => ` ? `(${args}) => `
: `function(${args})` : `function(${args})`
// TODO gensym __obj, __fn, __call_id
const prolog = '{const __call_id = __cxt.call_counter;'
const call = (node.is_async ? 'async ' : '') + decl + ( const call = (node.is_async ? 'async ' : '') + decl + (
// TODO gensym __obj, __fn
(node.body.type == 'do') (node.body.type == 'do')
? '{ let __obj, __fn; ' + do_codegen(node.body) + '}' ? prolog + do_codegen(node.body) + '}'
: '{ let __obj, __fn; return ' + do_codegen(node.body) + '}' : prolog + 'return ' + do_codegen(node.body) + '}'
) )
const argscount = node const argscount = node
@@ -135,6 +137,11 @@ ${JSON.stringify(errormessage)})`
} }
// Note that we use 'node.index + 1' as index, which
// does not correspond to any ast node, we just use it as a convenient
// marker
export const get_after_if_path = node => node.index + 1
const codegen = (node, node_cxt, parent) => { const codegen = (node, node_cxt, parent) => {
const do_codegen = (n, parent) => codegen(n, node_cxt, parent) const do_codegen = (n, parent) => codegen(n, node_cxt, parent)
@@ -161,11 +168,20 @@ const codegen = (node, node_cxt, parent) => {
} else if(node.type == 'throw') { } else if(node.type == 'throw') {
return 'throw ' + do_codegen(node.expr) + ';' return 'throw ' + do_codegen(node.expr) + ';'
} else if(node.type == 'if') { } else if(node.type == 'if') {
const left = 'if(' + do_codegen(node.cond) + '){' + const codegen_branch = branch =>
do_codegen(node.branches[0]) + ' } ' `{ __save_ct_node_for_path(__cxt, __calltree_node_by_loc, ${branch.index}, __call_id);`
return node.branches[1] == null + do_codegen(branch)
+ '}'
const left = 'if(' + do_codegen(node.cond) + ')'
+ codegen_branch(node.branches[0])
const result = node.branches[1] == null
? left ? left
: left + ' else { ' + do_codegen(node.branches[1]) + ' }' : left + ' else ' + codegen_branch(node.branches[1])
// add path also for point after if statement, in case there was a return
// inside if statement.
return result +
`__save_ct_node_for_path(__cxt, __calltree_node_by_loc, `
+ `${get_after_if_path(node)}, __call_id);`
} else if(node.type == 'array_literal'){ } else if(node.type == 'array_literal'){
return '[' + node.elements.map(c => do_codegen(c)).join(', ') + ']' return '[' + node.elements.map(c => do_codegen(c)).join(', ') + ']'
} else if(node.type == 'object_literal'){ } else if(node.type == 'object_literal'){
@@ -189,13 +205,18 @@ const codegen = (node, node_cxt, parent) => {
} else if(node.type == 'function_expr'){ } else if(node.type == 'function_expr'){
return codegen_function_expr(node, node_cxt) return codegen_function_expr(node, node_cxt)
} else if(node.type == 'ternary'){ } else if(node.type == 'ternary'){
const branches = node.branches.map(branch =>
`(__save_ct_node_for_path(__cxt, __calltree_node_by_loc, ${branch.index}, __call_id), `
+ do_codegen(branch)
+ ')'
)
return '' return ''
+ '(' + '('
+ do_codegen(node.cond) + do_codegen(node.cond)
+ ')\n? ' + ')\n? '
+ do_codegen(node.branches[0]) + branches[0]
+'\n: ' +'\n: '
+ do_codegen(node.branches[1]) + branches[1]
} else if(node.type == 'const'){ } else if(node.type == 'const'){
const res = 'const ' + do_codegen(node.name_node) + ' = ' + do_codegen(node.expr, node) + ';' const res = 'const ' + do_codegen(node.name_node) + ' = ' + do_codegen(node.expr, node) + ';'
if(node.name_node.type == 'identifier' && node.expr.type == 'function_call') { if(node.name_node.type == 'identifier' && node.expr.type == 'function_call') {
@@ -297,7 +318,7 @@ export const eval_modules = (
io_trace, io_trace,
location location
) => { ) => {
// TODO gensym __cxt, __trace, __trace_call // TODO gensym __cxt, __trace, __trace_call, __calltree_node_by_loc, __do_await
// TODO bug if module imported twice, once as external and as regular // TODO bug if module imported twice, once as external and as regular
@@ -310,11 +331,21 @@ export const eval_modules = (
const module_fns = parse_result.sorted.map(module => ( const module_fns = parse_result.sorted.map(module => (
{ {
module, module,
// TODO refactor, instead of multiple args prefixed with '__', pass
// single arg called `runtime`
fn: new Function( fn: new Function(
'__cxt', '__cxt',
'__calltree_node_by_loc',
'__trace', '__trace',
'__trace_call', '__trace_call',
'__do_await', '__do_await',
'__save_ct_node_for_path',
/* Add dummy __call_id for toplevel. It does not make any sence
* (toplevel is executed only once unlike function), we only add it
* because we dont want to codegen differently for if statements in
* toplevel and if statements within functions*/
'const __call_id = "SOMETHING_WRONG_HAPPENED";' +
codegen(parse_result.modules[module], {module}) codegen(parse_result.modules[module], {module})
) )
} }
@@ -354,7 +385,7 @@ export const eval_modules = (
logs: result.logs, logs: result.logs,
eval_cxt: result.eval_cxt, eval_cxt: result.eval_cxt,
calltree, calltree,
calltree_node_by_loc: result.eval_cxt.calltree_node_by_loc, calltree_node_by_loc: result.calltree_node_by_loc,
io_trace: result.eval_cxt.io_trace, io_trace: result.eval_cxt.io_trace,
} }
} }

View File

@@ -246,6 +246,7 @@ export const with_code_execution = (action, state = get_state()) => {
*/ */
if(state.eval_cxt != null) { if(state.eval_cxt != null) {
state.eval_cxt.is_recording_deferred_calls = false state.eval_cxt.is_recording_deferred_calls = false
state.eval_cxt.skip_save_ct_node_for_path = true
} }
try { try {
@@ -253,6 +254,7 @@ export const with_code_execution = (action, state = get_state()) => {
} finally { } finally {
if(state.eval_cxt != null) { if(state.eval_cxt != null) {
state.eval_cxt.is_recording_deferred_calls = true state.eval_cxt.is_recording_deferred_calls = true
state.eval_cxt.skip_save_ct_node_for_path = false
} }
} }
} }

View File

@@ -38,20 +38,24 @@ const make_promise_with_rejector = cxt => {
const do_run = function*(module_fns, cxt, io_trace){ const do_run = function*(module_fns, cxt, io_trace){
let calltree let calltree
const calltree_node_by_loc = new Map(
module_fns.map(({module}) => [module, new Map()])
)
const [replay_aborted_promise, io_trace_abort_replay] = const [replay_aborted_promise, io_trace_abort_replay] =
make_promise_with_rejector(cxt) make_promise_with_rejector(cxt)
cxt = (io_trace == null || io_trace.length == 0) cxt = (io_trace == null || io_trace.length == 0)
// TODO group all io_trace_ properties to single object? // TODO group all io_trace_ properties to single object?
? {...cxt, ? {...cxt,
calltree_node_by_loc,
logs: [], logs: [],
calltree_node_by_loc: new Map(),
io_trace_is_recording: true, io_trace_is_recording: true,
io_trace: [], io_trace: [],
} }
: {...cxt, : {...cxt,
calltree_node_by_loc,
logs: [], logs: [],
calltree_node_by_loc: new Map(),
io_trace_is_recording: false, io_trace_is_recording: false,
io_trace, io_trace,
io_trace_is_replay_aborted: false, io_trace_is_replay_aborted: false,
@@ -79,7 +83,14 @@ const do_run = function*(module_fns, cxt, io_trace){
try { try {
cxt.modules[module] = {} cxt.modules[module] = {}
const result = fn(cxt, __trace, __trace_call, __do_await) const result = fn(
cxt,
calltree_node_by_loc.get(module),
__trace,
__trace_call,
__do_await,
__save_ct_node_for_path,
)
if(result instanceof cxt.window.Promise) { if(result instanceof cxt.window.Promise) {
yield cxt.window.Promise.race([replay_aborted_promise, result]) yield cxt.window.Promise.race([replay_aborted_promise, result])
} else { } else {
@@ -108,6 +119,7 @@ const do_run = function*(module_fns, cxt, io_trace){
calltree, calltree,
logs: _logs, logs: _logs,
eval_cxt: cxt, eval_cxt: cxt,
calltree_node_by_loc,
} }
} }
@@ -244,7 +256,7 @@ const __trace = (cxt, fn, name, argscount, __location, get_closure) => {
const call_id = ++cxt.call_counter const call_id = ++cxt.call_counter
// populate calltree_node_by_loc only for entrypoint module // populate calltree_node_by_loc only for entrypoint module
if(cxt.is_entrypoint) { if(cxt.is_entrypoint && !cxt.skip_save_ct_node_for_path) {
let nodes_of_module = cxt.calltree_node_by_loc.get(__location.module) let nodes_of_module = cxt.calltree_node_by_loc.get(__location.module)
if(nodes_of_module == null) { if(nodes_of_module == null) {
nodes_of_module = new Map() nodes_of_module = new Map()
@@ -392,7 +404,6 @@ const __trace_call = (cxt, fn, context, args, errormessage, is_new = false) => {
} }
if(is_log) { if(is_log) {
// TODO do not collect logs on find_call?
cxt.logs.push(call) cxt.logs.push(call)
} }
@@ -412,3 +423,17 @@ const __trace_call = (cxt, fn, context, args, errormessage, is_new = false) => {
} }
} }
const __save_ct_node_for_path = (cxt, __calltree_node_by_loc, index, __call_id) => {
if(!cxt.is_entrypoint) {
return
}
if(cxt.skip_save_ct_node_for_path) {
return
}
if(__calltree_node_by_loc.get(index) == null) {
__calltree_node_by_loc.set(index, __call_id)
set_record_call(cxt)
}
}

View File

@@ -9,6 +9,7 @@ import {
pp_calltree, pp_calltree,
get_deferred_calls, get_deferred_calls,
current_cursor_position, current_cursor_position,
get_execution_paths,
} from '../src/calltree.js' } from '../src/calltree.js'
import {color_file} from '../src/color.js' import {color_file} from '../src/color.js'
import { import {
@@ -1802,14 +1803,10 @@ const y = x()`
countdown(10) countdown(10)
`) `)
const first = root_calltree_node(s).children[0] const first = root_calltree_node(s).children[0]
assert_equal(first.children[0].children, undefined)
assert_equal(first.children[0].has_more_children, true)
assert_equal(first.value, 10) assert_equal(first.value, 10)
const s2 = COMMANDS.calltree.click(s, first.id) const s2 = COMMANDS.calltree.click(s, first.id)
const first2 = root_calltree_node(s2).children[0] const first2 = root_calltree_node(s2).children[0]
assert_equal(first2.children[0].value, 9) assert_equal(first2.children[0].value, 9)
assert_equal(first2.children[0].children, undefined)
assert_equal(first2.children[0].has_more_children, true)
assert_equal(first2.code, first2.children[0].code) assert_equal(first2.code, first2.children[0].code)
}), }),
@@ -2544,6 +2541,279 @@ const y = x()`
assert_equal(root_calltree_node(s2).module, '') assert_equal(root_calltree_node(s2).module, '')
}), }),
test('find branch initial', () => {
const code = `
function x(cond) {
if(cond) {
return true
} else {
return false
}
}
x(true)
x(false)
`
const i = test_initial_state(code, code.indexOf('return false'))
assert_equal(i.value_explorer.result.value, false)
}),
test('find branch empty branch', () => {
const code = `
function x(cond) {
if(cond) {
/* label */
}
}
x(false)
x(true)
`
const i = test_initial_state(code, code.indexOf('label'))
assert_equal(i.active_calltree_node.args[0], true)
}),
test('find branch move_cursor', () => {
const code = `
function x(cond) {
if(cond) {
return true
} else {
return false
}
}
x(true)
x(false)
`
const i = test_initial_state(code)
const moved = COMMANDS.move_cursor(i, code.indexOf('return false'))
assert_equal(moved.value_explorer.result.value, false)
assert_equal(
i.colored_frames != moved.colored_frames,
true
)
}),
test('find branch ternary', () => {
const code = `
function x(cond) {
return cond ? true : false
}
x(true)
x(false)
`
const i = test_initial_state(code, code.indexOf('false'))
assert_equal(i.value_explorer.result.value, false)
}),
test('find branch move cursor within fn', () => {
const code = `
function x(cond) {
if(cond) {
return true
} else {
return false
}
}
x(true)
x(false)
`
const i = test_initial_state(code)
const s1 = COMMANDS.move_cursor(i, code.indexOf('return false'))
const s2 = COMMANDS.move_cursor(s1, code.indexOf('return true'))
assert_equal(s2.value_explorer.result.value, true)
assert_equal(
s1.colored_frames != s2.colored_frames,
true
)
}),
test('find branch fibonacci', () => {
const code = `
function fib(n) {
if(n == 0 || n == 1) {
return n
} else {
return fib(n - 1) + fib(n - 2)
}
}
fib(6)
`
const i = test_initial_state(code)
const moved = COMMANDS.move_cursor(i, code.indexOf('return n'))
assert_equal(moved.value_explorer.result.value, 1)
}),
test('find branch after if with return', () => {
const code = `
function x(cond) {
if(cond) {
return true
}
1
}
x(true)
x(false)
`
const i = test_initial_state(code, code.indexOf('1'))
assert_equal(i.value_explorer.result.value, 1)
}),
test('find branch after if with return complex', () => {
const code = `
function x(a, b) {
if(a) {
return true
}
if(a) {
return true
}
if(b) {
return true
} else {
if(false) {
return null
}
1
}
}
x(true)
x(false, true)
x(false, false)
`
const i = test_initial_state(code, code.indexOf('1'))
assert_equal(i.value_explorer.result.value, 1)
assert_equal(i.active_calltree_node.args, [false, false])
}),
test('find branch get_execution_paths', () => {
const code = `
function x() {
if(true) {/*1*/
}
if(false) {
} else {/*2*/
if(true) {/*3*/
true ? 4 : 5
}
return null
}
// not executed
if(true) {
}
// not executed
true ? 6 : 7
}
x()
`
const i = test_initial_state(code, code.indexOf('if'))
assert_equal(
[...get_execution_paths(active_frame(i))].toSorted((a,b) => a - b),
[
code.indexOf('if(true)') + 1,
code.indexOf('/*1*/') - 1,
code.indexOf('/*2*/') - 1,
code.indexOf('if(true) {/*3*/') + 1,
code.indexOf('/*3*/') - 1,
code.indexOf('4'),
]
)
}),
test('find branch get_execution_paths consice body', () => {
const code = `
const x = () => true ? 1 : 2
x()
`
const i = test_initial_state(code, code.indexOf('true'))
assert_equal(
get_execution_paths(active_frame(i)),
[code.indexOf('1')],
)
}),
test('find branch get_execution_paths nested fn', () => {
const code = `
function x() {
function y() {
true ? 1 : 2
}
}
x()
`
const i = test_initial_state(code, code.indexOf('{'))
assert_equal(
get_execution_paths(active_frame(i)),
[],
)
}),
test('find branch jump_calltree_node', () => {
const code = `
function test(x) {
if(x > 0) {
'label'
}
}
test(1)
test(2)
`
const i = test_initial_state(code, code.indexOf('label'))
assert_equal(i.active_calltree_node.args[0], 1)
// select second call
const second = COMMANDS.calltree.click(i, root_calltree_node(i).children[1].id)
assert_equal(second.active_calltree_node.args[0], 2)
}),
test('find branch preserve selected calltree node when moving inside fn', () => {
const code = `
function x(cond) {
if(cond) {
true
} else {
false
}
'finish'
}
x(true)
x(false)
`
const i = test_initial_state(code)
const first_call_id = root_calltree_node(i).children[0].id
// explicitly select first call
const selected = COMMANDS.calltree.click(i, first_call_id)
// implicitly select second call by moving cursor
const moved = COMMANDS.move_cursor(selected, code.indexOf('false'))
const finish = COMMANDS.move_cursor(moved, code.indexOf('finish'))
assert_equal(finish.active_calltree_node.id, first_call_id)
}),
test('find branch select calltree node from logs', () => {
const code = `
function f(x) {
if(x > 1) {
console.log(x)
} else {
console.log(x)
}
}
f(5)
f(10)
`
const i = test_initial_state(code)
const log_selected = COMMANDS.calltree.navigate_logs_position(i, 1)
const moved = COMMANDS.move_cursor(
log_selected,
code.indexOf('console.log')
)
assert_equal(moved.active_calltree_node.args, [10])
}),
test('stale id in frame function_call.result.calls bug', () => { test('stale id in frame function_call.result.calls bug', () => {
const code = ` const code = `
const x = () => {/*x*/ const x = () => {/*x*/
@@ -2846,6 +3116,25 @@ const y = x()`
expanded.modules[''].fn(10) expanded.modules[''].fn(10)
}), }),
test('deferred_calls find call bug', () => {
const code = `
export const fn = () => 1
`
const {state: i, on_deferred_call} = test_deferred_calls_state(code)
const moved = COMMANDS.move_cursor(i, code.indexOf('1'))
assert_equal(moved.active_calltree_node, null)
// Make deferred call
moved.modules[''].fn(10)
const after_call = on_deferred_call(moved)
const moved2 = COMMANDS.move_cursor(after_call, code.indexOf('1'))
assert_equal(moved2.active_calltree_node.value, 1)
}),
test('async/await await non promise', async () => { test('async/await await non promise', async () => {
await assert_code_evals_to_async( await assert_code_evals_to_async(
` `