Repository: sveltejs/svelte (JavaScript)
Date: 2018-12-31 15:53:18+00:00
Task type: Bug Fix

Issue:
Using store autosubscription in template without instance script generates invalid code
```html
{$foo}
```

This generates code that attempts to declare `$foo` twice. If we want to allow this syntax, I would _think_ it should just have an implicit prop `foo`, and not `$foo`. In the compiler's current state, I don't think we can rely on `this.template_references` to get the implicit props anymore.

(This is also somewhat adjacent to the issue of maintaining a list of all implicit/hidden references included in the scripts and template for the purposes of linting.)


Issue discussion:
_Maybe_ a single set `template_references` could continue to be sufficient for all of these different things - but it does seem better to split up its duties a bit.

- one set containing the actual instance/module data items that are being referred to. This includes stuff in the template (without `$`s) as well as stores that are autosubscribed to in the script block. This can be used to generate the list of automatic props when compiling script-less components - and can also be used by linting tools so they know which unused var messages to ignore.
- another set containing the stores that have been autosubscribed to (whether in the template or in a script block). (Or maybe we need template and script separately? Not familiar enough with the implementation.) This _could_ also double as a list of things that linting plugins should consider as being implicitly declared (and non-re-assignable), but I think it would be nicer if the compiler expose separate sets for 'implicit `let` declarations' and 'implicit `const` declarations'.

Solution patch:
diff --git a/src/compile/render-dom/index.ts b/src/compile/render-dom/index.ts
--- a/src/compile/render-dom/index.ts
+++ b/src/compile/render-dom/index.ts
@@ -257,6 +257,7 @@ export default function dom(
 	const filtered_props = component.props.filter(prop => {
 		if (component.hoistable_names.has(prop.name)) return false;
 		if (component.imported_declarations.has(prop.name)) return false;
+		if (prop.name[0] === '$') return false;
 		return true;
 	});
 
diff --git a/src/compile/render-ssr/index.ts b/src/compile/render-ssr/index.ts
--- a/src/compile/render-ssr/index.ts
+++ b/src/compile/render-ssr/index.ts
@@ -28,11 +28,7 @@ export default function ssr(
 		component.rewrite_props();
 		user_code = component.javascript;
 	} else if (component.ast.js.length === 0 && component.props.length > 0) {
-		const props = component.props.map(prop => {
-			return prop.as === prop.name
-				? prop.as
-				: `${prop.as}: ${prop.name}`
-		});
+		const props = component.props.map(prop => prop.as).filter(name => name[0] !== '$');
 
 		user_code = `let { ${props.join(', ')} } = $$props;`
 	}


Test patch:
diff --git a/test/runtime/samples/store-auto-subscribe-implicit/_config.js b/test/runtime/samples/store-auto-subscribe-implicit/_config.js
new file mode 100644
--- /dev/null
+++ b/test/runtime/samples/store-auto-subscribe-implicit/_config.js
@@ -0,0 +1,28 @@
+import { writable } from '../../../../store.js';
+
+export default {
+	props: {
+		count: writable(0)
+	},
+
+	html: `
+		<button>count 0</button>
+	`,
+
+	async test({ assert, component, target, window }) {
+		const button = target.querySelector('button');
+		const click = new window.MouseEvent('click');
+
+		await button.dispatchEvent(click);
+
+		assert.htmlEqual(target.innerHTML, `
+			<button>count 1</button>
+		`);
+
+		await component.count.set(42);
+
+		assert.htmlEqual(target.innerHTML, `
+			<button>count 42</button>
+		`);
+	}
+};
\ No newline at end of file
diff --git a/test/runtime/samples/store-auto-subscribe-implicit/main.html b/test/runtime/samples/store-auto-subscribe-implicit/main.html
new file mode 100644
--- /dev/null
+++ b/test/runtime/samples/store-auto-subscribe-implicit/main.html
@@ -0,0 +1 @@
+<button on:click="{() => count.update(n => n + 1)}">count {$count}</button>
\ No newline at end of file