Skip to content

Commit 88184cd

Browse files
authored
fix: don't emit assignment warnings for bindings (#14651)
Also fixes the possibility of an infinite loop due to the property access during the dev time check fixes #14643
1 parent 66e30d3 commit 88184cd

File tree

9 files changed

+73
-16
lines changed

9 files changed

+73
-16
lines changed

.changeset/thick-dryers-kiss.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'svelte': patch
3+
---
4+
5+
fix: don't emit assignment warnings for bindings

packages/svelte/src/compiler/phases/3-transform/client/visitors/AssignmentExpression.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,17 @@ function build_assignment(operator, left, right, context) {
169169
}
170170
}
171171

172+
// special case — ignore `bind:prop={getter, (v) => (...)}` / `bind:value={x.y}`
173+
if (
174+
path.at(-1) === 'Component' ||
175+
path.at(-1) === 'SvelteComponent' ||
176+
(path.at(-1) === 'ArrowFunctionExpression' &&
177+
path.at(-2) === 'SequenceExpression' &&
178+
(path.at(-3) === 'Component' || path.at(-3) === 'SvelteComponent'))
179+
) {
180+
should_transform = false;
181+
}
182+
172183
if (left.type === 'MemberExpression' && should_transform) {
173184
const callee = callees[operator];
174185

packages/svelte/src/internal/client/dev/assign.js

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { untrack } from '../runtime.js';
12
import * as w from '../warnings.js';
23
import { sanitize_location } from './location.js';
34

@@ -23,7 +24,12 @@ function compare(a, b, property, location) {
2324
* @param {string} location
2425
*/
2526
export function assign(object, property, value, location) {
26-
return compare((object[property] = value), object[property], property, location);
27+
return compare(
28+
(object[property] = value),
29+
untrack(() => object[property]),
30+
property,
31+
location
32+
);
2733
}
2834

2935
/**
@@ -33,7 +39,12 @@ export function assign(object, property, value, location) {
3339
* @param {string} location
3440
*/
3541
export function assign_and(object, property, value, location) {
36-
return compare((object[property] &&= value), object[property], property, location);
42+
return compare(
43+
(object[property] &&= value),
44+
untrack(() => object[property]),
45+
property,
46+
location
47+
);
3748
}
3849

3950
/**
@@ -43,7 +54,12 @@ export function assign_and(object, property, value, location) {
4354
* @param {string} location
4455
*/
4556
export function assign_or(object, property, value, location) {
46-
return compare((object[property] ||= value), object[property], property, location);
57+
return compare(
58+
(object[property] ||= value),
59+
untrack(() => object[property]),
60+
property,
61+
location
62+
);
4763
}
4864

4965
/**
@@ -53,5 +69,10 @@ export function assign_or(object, property, value, location) {
5369
* @param {string} location
5470
*/
5571
export function assign_nullish(object, property, value, location) {
56-
return compare((object[property] ??= value), object[property], property, location);
72+
return compare(
73+
(object[property] ??= value),
74+
untrack(() => object[property]),
75+
property,
76+
location
77+
);
5778
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<script>
2+
let { x = $bindable() } = $props();
3+
4+
$effect(() => {
5+
x = {};
6+
});
7+
8+
export function soThatTestReturnsAnObject() {
9+
return x;
10+
}
11+
</script>
Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,19 @@ export default test({
66
dev: true
77
},
88

9-
html: `<button>items: null</button>`,
9+
html: `<button>items: null</button> <div>x</div>`,
1010

1111
test({ assert, target, warnings }) {
1212
const btn = target.querySelector('button');
1313

1414
flushSync(() => btn?.click());
15-
assert.htmlEqual(target.innerHTML, `<button>items: []</button>`);
15+
assert.htmlEqual(target.innerHTML, `<button>items: []</button> <div>x</div>`);
1616

1717
flushSync(() => btn?.click());
18-
assert.htmlEqual(target.innerHTML, `<button>items: [0]</button>`);
18+
assert.htmlEqual(target.innerHTML, `<button>items: [0]</button> <div>x</div>`);
1919

2020
assert.deepEqual(warnings, [
21-
'Assignment to `items` property (main.svelte:5:24) will evaluate to the right-hand side, not the value of `items` following the assignment. This may result in unexpected behaviour.'
21+
'Assignment to `items` property (main.svelte:8:24) will evaluate to the right-hand side, not the value of `items` following the assignment. This may result in unexpected behaviour.'
2222
]);
2323
}
2424
});
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<script>
2+
import Test from './Test.svelte';
3+
4+
let entries = $state([]);
5+
let object = $state({ items: null });
6+
</script>
7+
8+
<button onclick={() => (object.items ??= []).push(object.items.length)}>
9+
items: {JSON.stringify(object.items)}
10+
</button>
11+
12+
<!-- these should not emit warnings -->
13+
<div bind:this={entries[0]}>x</div>
14+
<Test bind:this={entries[1]}></Test>
15+
<Test bind:this={() => entries[2], (v) => (entries[2] = v)}></Test>
16+
<Test bind:x={entries[3]}></Test>
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ export default test({
55
html: `<button>items: null</button>`,
66

77
test({ assert, target }) {
8-
const [btn1, btn2] = target.querySelectorAll('button');
8+
const [btn1] = target.querySelectorAll('button');
99

1010
flushSync(() => btn1.click());
1111
assert.htmlEqual(target.innerHTML, `<button>items: [0]</button>`);

packages/svelte/tests/runtime-runes/samples/proxy-nullish-coalescing-assignment-warning/main.svelte

Lines changed: 0 additions & 7 deletions
This file was deleted.

0 commit comments

Comments
 (0)