Repository: sveltejs/svelte (JavaScript)
Date: 2017-04-18 12:33:12+00:00
Task type: Bug Fix

Issue:
Warnings in dev mode showing up for helper functions in version 1.16.0
This seems like a regression in version 1.16.0. I can’t reproduce it on the REPL (since it doesn’t run in dev mode). 

As an example I have a component that looks like this

```html
<div id="time" maxwidth="8">{{ timeToDisplayTime(time) }}</div>

<script>
    import { timeToDisplayTime } from '../util';

    export default {
        helpers: {
            timeToDisplayTime
        }
    };
</script>
```

The generated code looks like this:

```javascript
function TimeCode ( options ) {
	options = options || {};
	this._state = options.data || {};
	if ( !( 'timeToDisplayTime' in this._state ) ) { console.warn( "Component was created without expected data property 'timeToDisplayTime'" ); }
	if ( !( 'time' in this._state ) ) { console.warn( "Component was created without expected data property 'time'" ); }
…
```

`timeToDisplayTime` should not be checked as part of the state (I don’t think).

This could be an issue with my code. I may not be using the helper functions correctly
Warnings in dev mode showing up for helper functions in version 1.16.0
This seems like a regression in version 1.16.0. I can’t reproduce it on the REPL (since it doesn’t run in dev mode). 

As an example I have a component that looks like this

```html
<div id="time" maxwidth="8">{{ timeToDisplayTime(time) }}</div>

<script>
    import { timeToDisplayTime } from '../util';

    export default {
        helpers: {
            timeToDisplayTime
        }
    };
</script>
```

The generated code looks like this:

```javascript
function TimeCode ( options ) {
	options = options || {};
	this._state = options.data || {};
	if ( !( 'timeToDisplayTime' in this._state ) ) { console.warn( "Component was created without expected data property 'timeToDisplayTime'" ); }
	if ( !( 'time' in this._state ) ) { console.warn( "Component was created without expected data property 'time'" ); }
…
```

`timeToDisplayTime` should not be checked as part of the state (I don’t think).

This could be an issue with my code. I may not be using the helper functions correctly


Issue discussion:
The obvious way to address this is to add a `!this.helpers.has( name )` condition before adding `name` to `this.expectedProperties`. But I'm not sure whether that would be missing some subtler thing. Should the helper be in the dependencies at all? Do we need special handling in the case where someone has an `{{#each}}` block iterating with a variable that shadows a helper function?
The obvious way to address this is to add a `!this.helpers.has( name )` condition before adding `name` to `this.expectedProperties`. But I'm not sure whether that would be missing some subtler thing. Should the helper be in the dependencies at all? Do we need special handling in the case where someone has an `{{#each}}` block iterating with a variable that shadows a helper function?

Solution patch:
diff --git a/src/generators/Generator.js b/src/generators/Generator.js
--- a/src/generators/Generator.js
+++ b/src/generators/Generator.js
@@ -156,6 +156,8 @@ export default class Generator {
 		let scope = annotateWithScopes( expression );
 		const dependencies = [];
 
+		const generator = this; // can't use arrow functions, because of this.skip()
+
 		walk( expression, {
 			enter ( node, parent ) {
 				if ( node._scope ) {
@@ -165,7 +167,7 @@ export default class Generator {
 
 				if ( isReference( node, parent ) ) {
 					const { name } = flattenReference( node );
-					if ( scope.has( name ) ) return;
+					if ( scope.has( name ) || generator.helpers.has( name ) ) return;
 
 					if ( contextDependencies.has( name ) ) {
 						dependencies.push( ...contextDependencies.get( name ) );
diff --git a/src/shared/dom.js b/src/shared/dom.js
--- a/src/shared/dom.js
+++ b/src/shared/dom.js
@@ -39,15 +39,15 @@ export function createComment () {
 }
 
 export function addEventListener ( node, event, handler ) {
-	node.addEventListener ( event, handler, false );
+	node.addEventListener( event, handler, false );
 }
 
 export function removeEventListener ( node, event, handler ) {
-	node.removeEventListener ( event, handler, false );
+	node.removeEventListener( event, handler, false );
 }
 
 export function setAttribute ( node, attribute, value ) {
-	node.setAttribute ( attribute, value );
+	node.setAttribute( attribute, value );
 }
 
 export function setXlinkAttribute ( node, attribute, value ) {


Test patch:
diff --git a/test/runtime/samples/dev-warning-helper/_config.js b/test/runtime/samples/dev-warning-helper/_config.js
new file mode 100644
--- /dev/null
+++ b/test/runtime/samples/dev-warning-helper/_config.js
@@ -0,0 +1,11 @@
+export default {
+	dev: true,
+
+	data: {
+		bar: 1
+	},
+
+	html: '2',
+
+	warnings: []
+};
diff --git a/test/runtime/samples/dev-warning-helper/main.html b/test/runtime/samples/dev-warning-helper/main.html
new file mode 100644
--- /dev/null
+++ b/test/runtime/samples/dev-warning-helper/main.html
@@ -0,0 +1,11 @@
+{{foo(bar)}}
+
+<script>
+	export default {
+		helpers: {
+			foo ( bar ) {
+				return bar * 2;
+			}
+		}
+	};
+</script>
\ No newline at end of file