import { nodes_match } from '../../utils/nodes_match.js'; import { x } from 'code-red'; /** * @param {import('./Renderer.js').default} renderer * @param {import('periscopic').Scope} scope * @param {import('estree').Node} node * @param {Set} names * @param {boolean} main_execution_context * @returns {any} */ export function invalidate(renderer, scope, node, names, main_execution_context = false) { const { component } = renderer; const [head, ...tail] = /** @type {import('../../interfaces.js').Var[]} */ ( Array.from(names) .filter((name) => { const owner = scope.find_owner(name); return !owner || owner === component.instance_scope; }) .map((name) => component.var_lookup.get(name)) .filter((variable) => { return ( variable && !variable.hoistable && !variable.global && !variable.module && (variable.referenced || variable.subscribable || variable.is_reactive_dependency || variable.export_name || variable.name[0] === '$') ); }) ); /** * @param {import('../../interfaces.js').Var} variable * @param {import('estree').Expression} [node] */ function get_invalidated(variable, node) { if (main_execution_context && !variable.subscribable && variable.name[0] !== '$') { return node; } return renderer_invalidate(renderer, variable.name, undefined, main_execution_context); } if (!head) { return node; } component.has_reactive_assignments = true; if ( node.type === 'AssignmentExpression' && node.operator === '=' && nodes_match(node.left, node.right, ['trailingComments', 'leadingComments']) && tail.length === 0 ) { return get_invalidated(head, node); } const is_store_value = head.name[0] === '$' && head.name[1] !== '$'; const extra_args = tail.map((variable) => get_invalidated(variable)).filter(Boolean); if (is_store_value) { return x`@set_store_value(${head.name.slice(1)}, ${node}, ${head.name}, ${extra_args})`; } let invalidate; if (!main_execution_context) { const pass_value = extra_args.length > 0 || (node.type === 'AssignmentExpression' && node.left.type !== 'Identifier') || (node.type === 'UpdateExpression' && (!node.prefix || node.argument.type !== 'Identifier')); if (pass_value) { extra_args.unshift({ type: 'Identifier', name: head.name }); } invalidate = x`$$invalidate(${ renderer.context_lookup.get(head.name).index }, ${node}, ${extra_args})`; } else { // skip `$$invalidate` if it is in the main execution context invalidate = extra_args.length ? [node, ...extra_args] : node; } if (head.subscribable && head.reassigned) { const subscribe = `$$subscribe_${head.name}`; invalidate = x`${subscribe}(${invalidate})`; } return invalidate; } /** * @param {import('./Renderer.js').default} renderer * @param {string} name * @param {any} [value] * @param {boolean} [main_execution_context] * @returns {import('estree').Node} */ export function renderer_invalidate(renderer, name, value, main_execution_context = false) { const variable = renderer.component.var_lookup.get(name); if (variable && variable.subscribable && (variable.reassigned || variable.export_name)) { if (main_execution_context) { return x`${`$$subscribe_${name}`}(${value || name})`; } else { const member = renderer.context_lookup.get(name); return x`${`$$subscribe_${name}`}($$invalidate(${member.index}, ${value || name}))`; } } if (name[0] === '$' && name[1] !== '$') { return x`${name.slice(1)}.set(${value || name})`; } if ( variable && (variable.module || (!variable.referenced && !variable.is_reactive_dependency && !variable.export_name && !name.startsWith('$$'))) ) { return value || name; } if (value) { if (main_execution_context) { return x`${value}`; } else { const member = renderer.context_lookup.get(name); return x`$$invalidate(${member.index}, ${value})`; } } if (main_execution_context) return; // if this is a reactive declaration, invalidate dependencies recursively const deps = new Set([name]); deps.forEach((name) => { const reactive_declarations = renderer.component.reactive_declarations.filter((x) => x.assignees.has(name) ); reactive_declarations.forEach((declaration) => { declaration.dependencies.forEach((name) => { deps.add(name); }); }); }); // TODO ideally globals etc wouldn't be here in the first place const filtered = Array.from(deps).filter((n) => renderer.context_lookup.has(n)); if (!filtered.length) return null; return filtered .map((n) => x`$$invalidate(${renderer.context_lookup.get(n).index}, ${n})`) .reduce((lhs, rhs) => x`${lhs}, ${rhs}`); }