Skip to content

Commit 3c97c0a

Browse files
trueadmRich-Harris
andauthored
fix: ensure function calls to identifiers are made reactive (#13264)
* fix: ensure function calls to identifiers are made reactive * update test * we have has_local at home * add note * make it a TODO * Update .changeset/red-ladybugs-turn.md --------- Co-authored-by: Rich Harris <[email protected]>
1 parent 9864138 commit 3c97c0a

File tree

10 files changed

+47
-30
lines changed

10 files changed

+47
-30
lines changed

.changeset/red-ladybugs-turn.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: treat pure call expressions as potentially reactive if they reference local bindings

packages/svelte/src/compiler/phases/2-analyze/visitors/CallExpression.js

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -143,11 +143,6 @@ export function CallExpression(node, context) {
143143
break;
144144
}
145145

146-
if (context.state.expression && !is_pure(node.callee, context)) {
147-
context.state.expression.has_call = true;
148-
context.state.expression.has_state = true;
149-
}
150-
151146
if (context.state.render_tag) {
152147
// Find out which of the render tag arguments contains this call expression
153148
const arg_idx = unwrap_optional(context.state.render_tag.expression).arguments.findIndex(
@@ -174,4 +169,15 @@ export function CallExpression(node, context) {
174169
} else {
175170
context.next();
176171
}
172+
173+
if (context.state.expression) {
174+
// TODO We assume that any dependencies are stateful, which isn't necessarily the case — see
175+
// https://github.com/sveltejs/svelte/issues/13266. This check also includes dependencies
176+
// outside the call expression itself (e.g. `{blah && pure()}`) resulting in additional
177+
// false positives, but for now we accept that trade-off
178+
if (!is_pure(node.callee, context) || context.state.expression.dependencies.size > 0) {
179+
context.state.expression.has_call = true;
180+
context.state.expression.has_state = true;
181+
}
182+
}
177183
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
<script module>
22
let foo = true;
3-
</script>
3+
</script>
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export let obj = $state({ a: 1, b: 2 });
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { flushSync } from 'svelte';
2+
import { test } from '../../test';
3+
4+
export default test({
5+
async test({ assert, target }) {
6+
const [b1] = target.querySelectorAll('button');
7+
b1.click();
8+
flushSync();
9+
assert.htmlEqual(target.innerHTML, `<button>Replace</button>\n9,10,11`);
10+
}
11+
});
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<script>
2+
import { obj } from "./Data.svelte.js";
3+
4+
function replaceProp() {
5+
Object.assign(obj, {a:9, b:10, c:11});
6+
}
7+
</script>
8+
9+
<button onclick={replaceProp}>Replace</button>
10+
11+
{Object.values(obj)}

packages/svelte/tests/snapshot/samples/inline-module-vars/_expected/server/index.svelte.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,4 @@ const __DECLARED_ASSET_3__ = "__VITE_ASSET__2AM7_y_g__";
77

88
export default function Inline_module_vars($$payload) {
99
$$payload.out += `<picture><source${$.attr("srcset", __DECLARED_ASSET_0__)} type="image/avif"> <source${$.attr("srcset", __DECLARED_ASSET_1__)} type="image/webp"> <source${$.attr("srcset", __DECLARED_ASSET_2__)} type="image/png"> <img${$.attr("src", __DECLARED_ASSET_3__)} alt="production test" width="1440" height="1440"></picture>`;
10-
}
10+
}

packages/svelte/tests/snapshot/samples/purity/_expected/client/index.svelte.js

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,17 @@ import * as $ from "svelte/internal/client";
44
var root = $.template(`<p></p> <p></p> <!>`, 1);
55

66
export default function Purity($$anchor) {
7-
let min = 0;
8-
let max = 100;
9-
let number = 50;
10-
let value = 'hello';
117
var fragment = root();
128
var p = $.first_child(fragment);
139

14-
p.textContent = Math.max(min, Math.min(max, number));
10+
p.textContent = Math.max(0, Math.min(0, 100));
1511

1612
var p_1 = $.sibling(p, 2);
1713

1814
p_1.textContent = location.href;
1915

2016
var node = $.sibling(p_1, 2);
2117

22-
Child(node, { prop: encodeURIComponent(value) });
18+
Child(node, { prop: encodeURIComponent('hello') });
2319
$.append($$anchor, fragment);
2420
}
Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,7 @@
11
import * as $ from "svelte/internal/server";
22

33
export default function Purity($$payload) {
4-
let min = 0;
5-
let max = 100;
6-
let number = 50;
7-
let value = 'hello';
8-
9-
$$payload.out += `<p>${$.escape(Math.max(min, Math.min(max, number)))}</p> <p>${$.escape(location.href)}</p> `;
10-
Child($$payload, { prop: encodeURIComponent(value) });
4+
$$payload.out += `<p>${$.escape(Math.max(0, Math.min(0, 100)))}</p> <p>${$.escape(location.href)}</p> `;
5+
Child($$payload, { prop: encodeURIComponent('hello') });
116
$$payload.out += `<!---->`;
127
}
Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,4 @@
1-
<script>
2-
let min = 0;
3-
let max = 100;
4-
let number = 50;
5-
6-
let value = 'hello';
7-
</script>
8-
9-
<p>{Math.max(min, Math.min(max, number))}</p>
1+
<p>{Math.max(0, Math.min(0, 100))}</p>
102
<p>{location.href}</p>
113

12-
<Child prop={encodeURIComponent(value)} />
4+
<Child prop={encodeURIComponent('hello')} />

0 commit comments

Comments
 (0)