From 8e515b12c3c74ace8ab12125ea8ced995e1cf8c0 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 15 Apr 2024 16:08:04 -0400 Subject: [PATCH] prevent component using bind: with object it does not own --- .../src/internal/client/dev/ownership.js | 24 +++++++++++++++---- .../svelte/src/internal/client/runtime.js | 4 ++-- .../Counter.svelte | 8 +++++++ .../Intermediate.svelte | 8 +++++++ .../_config.js | 13 ++++++++++ .../main.svelte | 7 ++++++ 6 files changed, 58 insertions(+), 6 deletions(-) create mode 100644 packages/svelte/tests/runtime-runes/samples/non-local-mutation-with-binding-2/Counter.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/non-local-mutation-with-binding-2/Intermediate.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/non-local-mutation-with-binding-2/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/non-local-mutation-with-binding-2/main.svelte diff --git a/packages/svelte/src/internal/client/dev/ownership.js b/packages/svelte/src/internal/client/dev/ownership.js index 6b2b56c654..7217dba566 100644 --- a/packages/svelte/src/internal/client/dev/ownership.js +++ b/packages/svelte/src/internal/client/dev/ownership.js @@ -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>} */ 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 ( diff --git a/packages/svelte/src/internal/client/runtime.js b/packages/svelte/src/internal/client/runtime.js index c1b8ee3e44..26ce7bca94 100644 --- a/packages/svelte/src/internal/client/runtime.js +++ b/packages/svelte/src/internal/client/runtime.js @@ -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); } } } diff --git a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-with-binding-2/Counter.svelte b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-with-binding-2/Counter.svelte new file mode 100644 index 0000000000..57cbebde12 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-with-binding-2/Counter.svelte @@ -0,0 +1,8 @@ + + + diff --git a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-with-binding-2/Intermediate.svelte b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-with-binding-2/Intermediate.svelte new file mode 100644 index 0000000000..76b713b851 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-with-binding-2/Intermediate.svelte @@ -0,0 +1,8 @@ + + + diff --git a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-with-binding-2/_config.js b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-with-binding-2/_config.js new file mode 100644 index 0000000000..1aef4cc3f1 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-with-binding-2/_config.js @@ -0,0 +1,13 @@ +import { test } from '../../test'; + +export default test({ + html: ``, + + 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' + ] +}); diff --git a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-with-binding-2/main.svelte b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-with-binding-2/main.svelte new file mode 100644 index 0000000000..cedc9a1d37 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-with-binding-2/main.svelte @@ -0,0 +1,7 @@ + + +