Skip to content

Commit e89137b

Browse files
committed
mark reactive declarations dependencies as reassigned/mutated if the declaration is reassigned/mutated
1 parent 1644f20 commit e89137b

File tree

3 files changed

+175
-1
lines changed

3 files changed

+175
-1
lines changed

src/compiler/compile/Component.ts

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1166,13 +1166,31 @@ export default class Component {
11661166
if (node.type === 'LabeledStatement' && node.label.name === '$') {
11671167
this.reactive_declaration_nodes.add(node);
11681168

1169-
const assignees = new Set();
1169+
const assignees = new Set<string>();
11701170
const assignee_nodes = new Set();
11711171
const dependencies = new Set();
11721172

11731173
let scope = this.instance_scope;
11741174
const map = this.instance_scope_map;
11751175

1176+
const assignee_stack: Array<{
1177+
has_reassigned_assignee: boolean,
1178+
has_mutated_assignee: boolean,
1179+
}> = [];
1180+
1181+
const new_assignee_stack = () => assignee_stack.push({
1182+
has_mutated_assignee: false,
1183+
has_reassigned_assignee: false
1184+
});
1185+
1186+
const update_assignee_stack = (name) => {
1187+
const variable = component.var_lookup.get(name);
1188+
if (variable) {
1189+
if (variable.reassigned) assignee_stack[assignee_stack.length - 1].has_reassigned_assignee = true;
1190+
if (variable.mutated) assignee_stack[assignee_stack.length - 1].has_mutated_assignee = true;
1191+
}
1192+
};
1193+
11761194
walk(node.body, {
11771195
enter(node: Node, parent) {
11781196
if (map.has(node)) {
@@ -1182,9 +1200,13 @@ export default class Component {
11821200
if (node.type === 'AssignmentExpression') {
11831201
const left = get_object(node.left);
11841202

1203+
new_assignee_stack();
1204+
11851205
extract_identifiers(left).forEach(node => {
11861206
assignee_nodes.add(node);
11871207
assignees.add(node.name);
1208+
1209+
update_assignee_stack(node.name);
11881210
});
11891211

11901212
if (node.operator !== '=') {
@@ -1193,13 +1215,22 @@ export default class Component {
11931215
} else if (node.type === 'UpdateExpression') {
11941216
const identifier = get_object(node.argument);
11951217
assignees.add(identifier.name);
1218+
1219+
new_assignee_stack();
1220+
update_assignee_stack(identifier.name);
11961221
} else if (is_reference(node as Node, parent as Node)) {
11971222
const identifier = get_object(node);
11981223
if (!assignee_nodes.has(identifier)) {
11991224
const { name } = identifier;
12001225
const owner = scope.find_owner(name);
12011226
const variable = component.var_lookup.get(name);
12021227
if (variable) variable.is_reactive_dependency = true;
1228+
1229+
if (variable && owner === component.instance_scope) {
1230+
if (assignee_stack[assignee_stack.length - 1].has_reassigned_assignee) variable.reassigned = true;
1231+
if (assignee_stack[assignee_stack.length - 1].has_mutated_assignee) variable.mutated = true;
1232+
}
1233+
12031234
const is_writable_or_mutated =
12041235
variable && (variable.writable || variable.mutated);
12051236
if (
@@ -1218,6 +1249,9 @@ export default class Component {
12181249
if (map.has(node)) {
12191250
scope = scope.parent;
12201251
}
1252+
if (node.type === 'AssignmentExpression' || node.type === 'UpdateExpression') {
1253+
assignee_stack.pop();
1254+
}
12211255
},
12221256
});
12231257

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
export default {
2+
html: `
3+
<div>
4+
<input type="checkbox">
5+
<input type="text">
6+
<p>one</p>
7+
</div>
8+
<div>
9+
<input type="checkbox">
10+
<input type="text">
11+
<p>two</p>
12+
</div>
13+
<div>
14+
<input type="checkbox">
15+
<input type="text">
16+
<p>three</p>
17+
</div>
18+
<p>completed 1, remaining 2, total 3</p>
19+
`,
20+
21+
ssrHtml: `
22+
<div>
23+
<input type="checkbox">
24+
<input type="text" value="one">
25+
<p>one</p>
26+
</div>
27+
<div>
28+
<input checked="" type="checkbox">
29+
<input type="text" value="two">
30+
<p>two</p>
31+
</div>
32+
<div>
33+
<input type="checkbox">
34+
<input type="text" value="three">
35+
<p>three</p>
36+
</div>
37+
<p>completed 1, remaining 2, total 3</p>
38+
`,
39+
40+
async test({ assert, component, target, window }) {
41+
function set_text(i, text) {
42+
const input = target.querySelectorAll('input[type="text"]')[i];
43+
input.value = text;
44+
input.dispatchEvent(new window.Event('input'));
45+
}
46+
47+
function set_done(i, done) {
48+
const input = target.querySelectorAll('input[type="checkbox"]')[i];
49+
input.checked = done;
50+
input.dispatchEvent(new window.Event('change'));
51+
}
52+
53+
component.filter = 'remaining';
54+
55+
assert.htmlEqual(target.innerHTML, `
56+
<div>
57+
<input type="checkbox">
58+
<input type="text">
59+
<p>one</p>
60+
</div>
61+
<div>
62+
<input type="checkbox">
63+
<input type="text">
64+
<p>three</p>
65+
</div>
66+
<p>completed 1, remaining 2, total 3</p>
67+
`);
68+
69+
await set_text(1, 'four');
70+
71+
assert.htmlEqual(target.innerHTML, `
72+
<div>
73+
<input type="checkbox">
74+
<input type="text">
75+
<p>one</p>
76+
</div>
77+
<div>
78+
<input type="checkbox">
79+
<input type="text">
80+
<p>four</p>
81+
</div>
82+
<p>completed 1, remaining 2, total 3</p>
83+
`);
84+
85+
await set_done(0, true);
86+
87+
assert.htmlEqual(target.innerHTML, `
88+
<div>
89+
<input type="checkbox">
90+
<input type="text">
91+
<p>four</p>
92+
</div>
93+
<p>completed 2, remaining 1, total 3</p>
94+
`);
95+
96+
component.filter = 'done';
97+
98+
assert.htmlEqual(target.innerHTML, `
99+
<div>
100+
<input type="checkbox">
101+
<input type="text">
102+
<p>one</p>
103+
</div>
104+
<div>
105+
<input type="checkbox">
106+
<input type="text">
107+
<p>two</p>
108+
</div>
109+
<p>completed 2, remaining 1, total 3</p>
110+
`);
111+
},
112+
};
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<script>
2+
let items = [
3+
{ done: false, text: 'one' },
4+
{ done: true, text: 'two' },
5+
{ done: false, text: 'three' }
6+
];
7+
export let filter = 'all';
8+
9+
$: done = items.filter(item => item.done);
10+
$: remaining = items.filter(item => !item.done);
11+
12+
$: filtered = (
13+
filter === 'all' ? items :
14+
filter === 'done' ? items.filter(item => item.done) :
15+
items.filter(item => !item.done)
16+
);
17+
18+
</script>
19+
20+
{#each filtered as item}
21+
<div>
22+
<input type="checkbox" bind:checked={item.done}>
23+
<input type="text" bind:value={item.text}>
24+
<p>{item.text}</p>
25+
</div>
26+
{/each}
27+
28+
<p>completed {done.length}, remaining {remaining.length}, total {items.length}</p>

0 commit comments

Comments
 (0)