prevent component using bind: with object it does not own

pull/11184/head
Rich Harris 2 years ago
parent fab34ab529
commit 8e515b12c3

@ -1,7 +1,7 @@
/** @typedef {{ file: string, line: number, column: number }} Location */
import { STATE_SYMBOL } from '../constants.js';
import { untrack } from '../runtime.js';
import { current_component_context, untrack } from '../runtime.js';
/** @type {Record<string, Array<{ start: Location, end: Location, component: Function }>>} */
const boundaries = {};
@ -98,11 +98,27 @@ export function mark_module_end(component) {
}
/**
*
* @param {any} object
* @param {any} owner
* @param {boolean} [global]
*/
export function add_owner(object, owner) {
export function add_owner(object, owner, global = false) {
if (object && !global) {
// @ts-expect-error
const component = current_component_context.function;
const metadata = object[STATE_SYMBOL];
if (metadata && !has_owner(metadata, component)) {
let original = get_owner(metadata);
if (owner.filename !== component.filename) {
let message = `${component.filename} passed a value to ${owner.filename} with \`bind:\`, but the value is owned by ${original.filename}. Consider creating a binding between ${original.filename} and ${component.filename}`;
// eslint-disable-next-line no-console
console.warn(message);
}
}
}
untrack(() => {
add_owner_to_object(object, owner);
});
@ -141,7 +157,7 @@ function has_owner(metadata, component) {
/**
* @param {import('#client').ProxyMetadata} metadata
* @returns {Function}
* @returns {any}
*/
function get_owner(metadata) {
return (

@ -894,7 +894,7 @@ export function getContext(key) {
// @ts-expect-error
const fn = current_component_context?.function;
if (fn) {
add_owner(result, fn);
add_owner(result, fn, true);
}
}
@ -950,7 +950,7 @@ export function getAllContexts() {
const fn = current_component_context?.function;
if (fn) {
for (const value of context_map.values()) {
add_owner(value, fn);
add_owner(value, fn, true);
}
}
}

@ -0,0 +1,8 @@
<script>
/** @type {{ object: { count: number }}} */
let { object = $bindable() } = $props();
</script>
<button onclick={() => object.count += 1}>
clicks: {object.count}
</button>

@ -0,0 +1,8 @@
<script>
import Counter from './Counter.svelte';
/** @type {{ object: { count: number }}} */
let { object } = $props();
</script>
<Counter bind:object={object} />

@ -0,0 +1,13 @@
import { test } from '../../test';
export default test({
html: `<button>clicks: 0</button>`,
compileOptions: {
dev: true
},
warnings: [
'.../samples/non-local-mutation-with-binding-2/Intermediate.svelte passed a value to .../samples/non-local-mutation-with-binding-2/Counter.svelte with `bind:`, but the value is owned by .../samples/non-local-mutation-with-binding-2/main.svelte. Consider creating a binding between .../samples/non-local-mutation-with-binding-2/main.svelte and .../samples/non-local-mutation-with-binding-2/Intermediate.svelte'
]
});

@ -0,0 +1,7 @@
<script>
import Intermediate from './Intermediate.svelte';
let object = $state({ count: 0 });
</script>
<Intermediate object={object} />
Loading…
Cancel
Save