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}