Repository: sveltejs/svelte (JavaScript)
Date: 2019-03-10 02:35:26+00:00
Task type: Bug Fix

Issue:
Unexpected token at Reactivity Statements tutorial

Open [Reactivity Statements tutorial](https://v3.svelte.technology/tutorial/reactive-statements)

Click button **SHOW ME**

Error `Unexpected token (Note that you need plugins to import files that are not JavaScript)`


**This happens too on local development:**

`rollup v1.5.0`
`bundles src/main.js → public/bundle.js...`
`(!) Error when using sourcemap for reporting an error: Can't resolve original location of error.
src/App.svelte: (106:6)`
`[!] Error: Unexpected token (Note that you need plugins to import files that are not JavaScript)
src/App.svelte (106:6)`
`Error: Unexpected token (Note that you need plugins to import files that are not JavaScript)`
`at error (/home/i/localhost/svelte/v3/tutorial/node_modules/rollup/dist/rollup.js:3601:30)`
`at Module.error (/home/i/localhost/svelte/v3/tutorial/node_modules/rollup/dist/rollup.js:14470:9)`
`at tryParse (/home/i/localhost/svelte/v3/tutorial/node_modules/rollup/dist/rollup.js:14390:16)`
`at Module.setSource (/home/i/localhost/svelte/v3/tutorial/node_modules/rollup/dist/rollup.js:14664:35)`
`at /home/i/localhost/svelte/v3/tutorial/node_modules/rollup/dist/rollup.js:17827:20`


Issue discussion:
Ah... this is a consequence of #2161. @Conduitry here's an example of a situation where maybe we should be tracking assignees as dependencies in reactive declarations. This feels like a legitimate use:

```js
$: if (count >= 10) {
	alert(`count is dangerously high!`);
	count = 9;
}
```

```js
$$self.$$.update = ($$dirty = { alert: 1 }) => {
	if () {
		if (count >= 10) {
			alert(`count is dangerously high!`);
			count = 9; $$invalidate('count', count);
		}
	}
};
```
I think this is a reason to reconsider my suggestion on #2129 of letting variables that are accessed in a reactive declaration before they are first assigned in the declaration would make the variable be both a dependency and an assignee. I still feel very strongly that variables that are only assigned should not be dependencies, and fairly strong-ish-ly that variable that are assigned before they are accessed should not be dependencies. (This last feature is so that you can gradually update a computed value in several stages throughout a reactive declaration, without doing the work on a temporary variable and assigning the computed value all at once at the end.)

edit: I guess if we don't want this complication then this last thing isn't absolutely necessary. Using a temporary variable for this isn't the end of the world - it might actually be good because more efficient code could be generated without a bunch of unnecessary `$$invalidate`s.

Solution patch:
diff --git a/src/compile/Component.ts b/src/compile/Component.ts
--- a/src/compile/Component.ts
+++ b/src/compile/Component.ts
@@ -1050,6 +1050,7 @@ export default class Component {
 				this.reactive_declaration_nodes.add(node);
 
 				const assignees = new Set();
+				const assignee_nodes = new Set();
 				const dependencies = new Set();
 
 				let scope = this.instance_scope;
@@ -1062,18 +1063,21 @@ export default class Component {
 						}
 
 						if (node.type === 'AssignmentExpression') {
-							const { name } = getObject(node.left)
-							assignees.add(name);
-							dependencies.delete(name);
+							const identifier = getObject(node.left)
+							assignee_nodes.add(identifier);
+							assignees.add(identifier.name);
 						} else if (node.type === 'UpdateExpression') {
-							const { name } = getObject(node.argument);
-							assignees.add(name);
-							dependencies.delete(name);
+							const identifier = getObject(node.argument);
+							assignee_nodes.add(identifier);
+							assignees.add(identifier.name);
 						} else if (isReference(node, parent)) {
-							const { name } = getObject(node);
-							const owner = scope.findOwner(name);
-							if ((!owner || owner === component.instance_scope) && (name[0] === '$' || component.var_lookup.has(name)) && !assignees.has(name)) {
-								dependencies.add(name);
+							const identifier = getObject(node);
+							if (!assignee_nodes.has(identifier)) {
+								const { name } = identifier;
+								const owner = scope.findOwner(name);
+								if ((!owner || owner === component.instance_scope) && (name[0] === '$' || component.var_lookup.has(name))) {
+									dependencies.add(name);
+								}
 							}
 
 							this.skip();


Test patch:
diff --git a/test/runtime/samples/reactive-values-self-dependency-b/_config.js b/test/runtime/samples/reactive-values-self-dependency-b/_config.js
new file mode 100644
--- /dev/null
+++ b/test/runtime/samples/reactive-values-self-dependency-b/_config.js
@@ -0,0 +1,19 @@
+export default {
+	html: `
+		<p>count: 0</p>
+	`,
+
+	test({ assert, component, target }) {
+		component.count = 5;
+
+		assert.htmlEqual(target.innerHTML, `
+			<p>count: 5</p>
+		`);
+
+		component.count = 50;
+
+		assert.htmlEqual(target.innerHTML, `
+			<p>count: 9</p>
+		`);
+	}
+};
diff --git a/test/runtime/samples/reactive-values-self-dependency-b/main.svelte b/test/runtime/samples/reactive-values-self-dependency-b/main.svelte
new file mode 100644
--- /dev/null
+++ b/test/runtime/samples/reactive-values-self-dependency-b/main.svelte
@@ -0,0 +1,9 @@
+<script>
+	export let count = 0;
+
+	$: if (count >= 10) {
+		count = 9;
+	}
+</script>
+
+<p>count: {count}</p>
\ No newline at end of file