svelte
Version:
Cybernetically enhanced web apps
82 lines (72 loc) • 2.25 kB
JavaScript
/** @typedef {{ file: string, line: number, column: number }} Location */
import { get_descriptor } from '../../shared/utils.js';
import { LEGACY_PROPS, STATE_SYMBOL } from '../constants.js';
import { FILENAME } from '../../../constants.js';
import { component_context } from '../context.js';
import * as w from '../warnings.js';
import { sanitize_location } from '../../../utils.js';
/**
* Sets up a validator that
* - traverses the path of a prop to find out if it is allowed to be mutated
* - checks that the binding chain is not interrupted
* @param {Record<string, any>} props
*/
export function create_ownership_validator(props) {
const component = component_context?.function;
const parent = component_context?.p?.function;
return {
/**
* @param {string} prop
* @param {any[]} path
* @param {any} result
* @param {number} line
* @param {number} column
*/
mutation: (prop, path, result, line, column) => {
const name = path[0];
if (is_bound_or_unset(props, name) || !parent) {
return result;
}
/** @type {any} */
let value = props;
for (let i = 0; i < path.length - 1; i++) {
value = value[path[i]];
if (!value?.[STATE_SYMBOL]) {
return result;
}
}
const location = sanitize_location(`${component[FILENAME]}:${line}:${column}`);
w.ownership_invalid_mutation(name, location, prop, parent[FILENAME]);
return result;
},
/**
* @param {any} key
* @param {any} child_component
* @param {() => any} value
*/
binding: (key, child_component, value) => {
if (!is_bound_or_unset(props, key) && parent && value()?.[STATE_SYMBOL]) {
w.ownership_invalid_binding(
component[FILENAME],
key,
child_component[FILENAME],
parent[FILENAME]
);
}
}
};
}
/**
* @param {Record<string, any>} props
* @param {string} prop_name
*/
function is_bound_or_unset(props, prop_name) {
// Can be the case when someone does `mount(Component, props)` with `let props = $state({...})`
// or `createClassComponent(Component, props)`
const is_entry_props = STATE_SYMBOL in props || LEGACY_PROPS in props;
return (
!!get_descriptor(props, prop_name)?.set ||
(is_entry_props && prop_name in props) ||
!(prop_name in props)
);
}