diff --git a/.changeset/fix-binding-ownership-ignore.md b/.changeset/fix-binding-ownership-ignore.md new file mode 100644 index 0000000000..13c9c797e3 --- /dev/null +++ b/.changeset/fix-binding-ownership-ignore.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: apply ownership mutation ignores to binding assignments diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/BindDirective.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/BindDirective.js index 6838e5006d..c7bf24b3f9 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/BindDirective.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/BindDirective.js @@ -1,7 +1,7 @@ /** @import { CallExpression, Expression, Pattern } from 'estree' */ /** @import { AST } from '#compiler' */ /** @import { ComponentContext } from '../types' */ -import { dev, is_ignored } from '../../../../state.js'; +import { dev, ignore_map, is_ignored } from '../../../../state.js'; import { is_text_attribute } from '../../../../utils/ast.js'; import * as b from '#compiler/builders'; import { binding_properties } from '../../../bindings.js'; @@ -40,9 +40,15 @@ export function BindDirective(node, context) { validate_binding(context.state, node, expression); } - const assignment = /** @type {Expression} */ ( - context.visit(b.assignment('=', /** @type {Pattern} */ (node.expression), b.id('$$value'))) + const raw_assignment = b.assignment( + '=', + /** @type {Pattern} */ (node.expression), + b.id('$$value') ); + // The assignment is generated, so inherit any ignores attached to the binding + ignore_map.set(raw_assignment, ignore_map.get(node) ?? []); + + const assignment = /** @type {Expression} */ (context.visit(raw_assignment)); if (dev) { // in dev, create named functions, so that `$inspect(...)` delivers @@ -58,16 +64,7 @@ export function BindDirective(node, context) { get = b.thunk(expression); /** @type {Expression | undefined} */ - set = b.unthunk( - b.arrow( - [b.id('$$value')], - /** @type {Expression} */ ( - context.visit( - b.assignment('=', /** @type {Pattern} */ (node.expression), b.id('$$value')) - ) - ) - ) - ); + set = b.unthunk(b.arrow([b.id('$$value')], assignment)); if (get === set) { set = undefined; diff --git a/packages/svelte/tests/runtime-runes/samples/ownership-invalid-mutation-binding-ignore/Item.svelte b/packages/svelte/tests/runtime-runes/samples/ownership-invalid-mutation-binding-ignore/Item.svelte new file mode 100644 index 0000000000..7beabf6124 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/ownership-invalid-mutation-binding-ignore/Item.svelte @@ -0,0 +1,6 @@ + + + + diff --git a/packages/svelte/tests/runtime-runes/samples/ownership-invalid-mutation-binding-ignore/_config.js b/packages/svelte/tests/runtime-runes/samples/ownership-invalid-mutation-binding-ignore/_config.js new file mode 100644 index 0000000000..11229fd13e --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/ownership-invalid-mutation-binding-ignore/_config.js @@ -0,0 +1,22 @@ +import { flushSync } from 'svelte'; +import { ok, test } from '../../test'; + +export default test({ + mode: ['client'], + compileOptions: { + dev: true + }, + test({ assert, target, warnings }) { + const input = target.querySelector('input'); + const output = target.querySelector('p'); + ok(input); + ok(output); + + input.value = 'renamed'; + input.dispatchEvent(new Event('input', { bubbles: true })); + flushSync(); + + assert.equal(output.textContent, 'renamed'); + assert.deepEqual(warnings, []); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/ownership-invalid-mutation-binding-ignore/main.svelte b/packages/svelte/tests/runtime-runes/samples/ownership-invalid-mutation-binding-ignore/main.svelte new file mode 100644 index 0000000000..e5b926cb9c --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/ownership-invalid-mutation-binding-ignore/main.svelte @@ -0,0 +1,8 @@ + + + +

{item.heading}