Repository: sveltejs/svelte (JavaScript)
Date: 2018-05-01 00:26:07+00:00
Task type: Bug Fix

Issue:
`this` overwritten as component name in arguments of hoisted event handler
See [2.1.1](https://svelte.technology/repl?version=2.1.1&gist=3edebbc9ba03ffeed57bea1c20312113) vs [2.2.0](https://svelte.technology/repl?version=2.2.0&gist=3edebbc9ba03ffeed57bea1c20312113).

This _might_ be as simple as wrapping [this](https://github.com/sveltejs/svelte/blob/d010aff9fa468c0feaba390b5c95781e831daf55/src/compile/nodes/EventHandler.ts#L80-L82) in a check for `!this.shouldHoist`, but not sure whether that's the best solution. Maybe we should instead make sure `this.shouldHoist` is false if `this` is one of the arguments to the handler?

cc @stalkerg 


Issue discussion:
> I realised the other day that the 'is this a descendant of an each block or not?' test doesn't really make sense, since the component _itself_ could be in an each block

 @Rich-Harris Sooo ... what does this mean for what we want to do here? Are you suggesting that we _always_ want to hoist event handlers (unless it's a custom element)?
I think we should prefer hoisting, yeah. I think we probably only want to overwrite `this` if it's a custom event
@Conduitry thanks for this issue, looks like my gitter doesn't work. 
@stalkerg mine too, I think it's broken 😱 

Solution patch:
diff --git a/src/compile/nodes/EventHandler.ts b/src/compile/nodes/EventHandler.ts
--- a/src/compile/nodes/EventHandler.ts
+++ b/src/compile/nodes/EventHandler.ts
@@ -77,16 +77,18 @@ export default class EventHandler extends Node {
 			}
 		}
 
-		this.args.forEach(arg => {
-			arg.overwriteThis(this.parent.var);
-		});
-
-		if (this.isCustomEvent && this.callee && this.callee.name === 'this') {
-			const node = this.callee.nodes[0];
-			compiler.code.overwrite(node.start, node.end, this.parent.var, {
-				storeName: true,
-				contentOnly: true
+		if (this.isCustomEvent) {
+			this.args.forEach(arg => {
+				arg.overwriteThis(this.parent.var);
 			});
+
+			if (this.callee && this.callee.name === 'this') {
+				const node = this.callee.nodes[0];
+				compiler.code.overwrite(node.start, node.end, this.parent.var, {
+					storeName: true,
+					contentOnly: true
+				});
+			}
 		}
 	}
 }
\ No newline at end of file


Test patch:
diff --git a/test/runtime/samples/event-handler-each-this/_config.js b/test/runtime/samples/event-handler-each-this/_config.js
new file mode 100644
--- /dev/null
+++ b/test/runtime/samples/event-handler-each-this/_config.js
@@ -0,0 +1,28 @@
+export default {
+	data: {
+		items: ['foo', 'bar', 'baz'],
+	},
+
+	html: `
+		<button>foo</button>
+		<button>bar</button>
+		<button>baz</button>
+	`,
+
+	test(assert, component, target, window) {
+		const buttons = target.querySelectorAll('button');
+		const event = new window.MouseEvent('click');
+
+		const clicked = [];
+
+		component.on('clicked', event => {
+			clicked.push(event.node);
+		});
+
+		buttons[1].dispatchEvent(event);
+
+		assert.equal(clicked.length, 1);
+		assert.equal(clicked[0].nodeName, 'BUTTON');
+		assert.equal(clicked[0].textContent, 'bar');
+	}
+};
diff --git a/test/runtime/samples/event-handler-each-this/main.html b/test/runtime/samples/event-handler-each-this/main.html
new file mode 100644
--- /dev/null
+++ b/test/runtime/samples/event-handler-each-this/main.html
@@ -0,0 +1,3 @@
+{#each items as item}
+	<button on:click='fire("clicked", { node: this })'>{item}</button>
+{/each}
\ No newline at end of file