From 5d0f430fd48f8da57cc36c1cc42efbf4472ca4d1 Mon Sep 17 00:00:00 2001 From: Tan Li Hau Date: Sun, 21 Jun 2020 10:49:41 +0800 Subject: [PATCH] mark reactive declarations dependencies as mutated if the declaration is mutated --- src/compiler/compile/Component.ts | 32 ++++- .../_config.js | 112 ++++++++++++++++++ .../main.svelte | 28 +++++ 3 files changed, 171 insertions(+), 1 deletion(-) create mode 100644 test/runtime/samples/binding-input-text-contextual-reactive-2/_config.js create mode 100644 test/runtime/samples/binding-input-text-contextual-reactive-2/main.svelte diff --git a/src/compiler/compile/Component.ts b/src/compiler/compile/Component.ts index a5a31c8070..cb15a323e1 100644 --- a/src/compiler/compile/Component.ts +++ b/src/compiler/compile/Component.ts @@ -1166,13 +1166,28 @@ export default class Component { if (node.type === 'LabeledStatement' && node.label.name === '$') { this.reactive_declaration_nodes.add(node); - const assignees = new Set(); + const assignees = new Set(); const assignee_nodes = new Set(); const dependencies = new Set(); let scope = this.instance_scope; const map = this.instance_scope_map; + const assignee_stack: Array<{ + has_mutated_assignee: boolean, + }> = []; + + const new_assignee_stack = () => assignee_stack.push({ + has_mutated_assignee: false, + }); + + const update_assignee_stack = (name) => { + const variable = component.var_lookup.get(name); + if (variable) { + if (variable.mutated) assignee_stack[assignee_stack.length - 1].has_mutated_assignee = true; + } + }; + walk(node.body, { enter(node: Node, parent) { if (map.has(node)) { @@ -1182,9 +1197,13 @@ export default class Component { if (node.type === 'AssignmentExpression') { const left = get_object(node.left); + new_assignee_stack(); + extract_identifiers(left).forEach(node => { assignee_nodes.add(node); assignees.add(node.name); + + update_assignee_stack(node.name); }); if (node.operator !== '=') { @@ -1193,6 +1212,9 @@ export default class Component { } else if (node.type === 'UpdateExpression') { const identifier = get_object(node.argument); assignees.add(identifier.name); + + new_assignee_stack(); + update_assignee_stack(identifier.name); } else if (is_reference(node as Node, parent as Node)) { const identifier = get_object(node); if (!assignee_nodes.has(identifier)) { @@ -1200,6 +1222,11 @@ export default class Component { const owner = scope.find_owner(name); const variable = component.var_lookup.get(name); if (variable) variable.is_reactive_dependency = true; + + if (variable && owner === component.instance_scope && assignee_stack.length) { + if (assignee_stack[assignee_stack.length - 1].has_mutated_assignee) variable.mutated = true; + } + const is_writable_or_mutated = variable && (variable.writable || variable.mutated); if ( @@ -1218,6 +1245,9 @@ export default class Component { if (map.has(node)) { scope = scope.parent; } + if (node.type === 'AssignmentExpression' || node.type === 'UpdateExpression') { + assignee_stack.pop(); + } }, }); diff --git a/test/runtime/samples/binding-input-text-contextual-reactive-2/_config.js b/test/runtime/samples/binding-input-text-contextual-reactive-2/_config.js new file mode 100644 index 0000000000..caa7ecea7e --- /dev/null +++ b/test/runtime/samples/binding-input-text-contextual-reactive-2/_config.js @@ -0,0 +1,112 @@ +export default { + html: ` +
+ + +

one

+
+
+ + +

two

+
+
+ + +

three

+
+

completed 1, remaining 2, total 3

+ `, + + ssrHtml: ` +
+ + +

one

+
+
+ + +

two

+
+
+ + +

three

+
+

completed 1, remaining 2, total 3

+ `, + + async test({ assert, component, target, window }) { + function set_text(i, text) { + const input = target.querySelectorAll('input[type="text"]')[i]; + input.value = text; + input.dispatchEvent(new window.Event('input')); + } + + function set_done(i, done) { + const input = target.querySelectorAll('input[type="checkbox"]')[i]; + input.checked = done; + input.dispatchEvent(new window.Event('change')); + } + + component.filter = 'remaining'; + + assert.htmlEqual(target.innerHTML, ` +
+ + +

one

+
+
+ + +

three

+
+

completed 1, remaining 2, total 3

+ `); + + await set_text(1, 'four'); + + assert.htmlEqual(target.innerHTML, ` +
+ + +

one

+
+
+ + +

four

+
+

completed 1, remaining 2, total 3

+ `); + + await set_done(0, true); + + assert.htmlEqual(target.innerHTML, ` +
+ + +

four

+
+

completed 2, remaining 1, total 3

+ `); + + component.filter = 'done'; + + assert.htmlEqual(target.innerHTML, ` +
+ + +

one

+
+
+ + +

two

+
+

completed 2, remaining 1, total 3

+ `); + }, +}; diff --git a/test/runtime/samples/binding-input-text-contextual-reactive-2/main.svelte b/test/runtime/samples/binding-input-text-contextual-reactive-2/main.svelte new file mode 100644 index 0000000000..1f06b7068e --- /dev/null +++ b/test/runtime/samples/binding-input-text-contextual-reactive-2/main.svelte @@ -0,0 +1,28 @@ + + +{#each filtered as item} +
+ + +

{item.text}

+
+{/each} + +

completed {done.length}, remaining {remaining.length}, total {items.length}

\ No newline at end of file