Repository: sveltejs/svelte (JavaScript)
Date: 2017-04-12 15:42:35+00:00
Task type: Feature

Issue:
Help people distinguish between `methods` and `helpers`
It's not totally obvious what the distinction is between methods and helpers:

```html
<button on:click='myMethod()'>{{myHelper(foo)}}</button>

<script>
  export default {
    methods: {
      myMethod () {
        alert( 'called a method' );
      }
    },

    helpers: {
      myHelper ( x ) {
        return x.toUpperCase();
      }
    }
  };
</script>
```

While we could do a better job of explaining in the docs (a *helper* is a pure function that transforms its inputs i.e. it *returns something*, whereas a *method* has side-effects i.e. it *does something*), we could do one better: we could identify cases where someone was trying to call a helper function in an event handler or trying to use a method in a `{{tag}}` and print the appropriate warning at compile time.


Issue discussion:
The guide says "Helpers are simple functions that are used in your template." If that's the only distinction between methods and helpers, why not merge the concepts? What is gained by treating them separately?
They're conceptually very different things. Methods become part of the public API...

```js
import MyComponent from './MyComponent.html';

var component = new MyComponent({...});
component.doCoolThing();
```

...whereas helpers are only available in templates, and behave differently (`this` has no value). The only thing they really have in common is that they're functions – the relationship between helpers and methods is basically the same as the relationship between helpers and lifecycle hooks (`onrender` and `onteardown`). Someone reading a component's source code will be able to understand what everything does more quickly if we retain the distinction.
Component methods that are part of the public api also are/could be functions (in both the sense that they are `Function`s and that they may possibly be implemented as a "pure" transformation, such as if they ignored their own `this` binding and only referenced their parameters). Therefore, I don't see why it is useful to partition them from helpers, and I'm not convinced that the distinction isn't arbitrary. I suspect you will counter that they need to be partitioned for implementation reasons, ie, you wouldn't want to make ALL component methods available in your templates for aesthetic, philosophical, or performance reasons.

I assume that you would think it's completely acceptable to _implement_ helpers as component api methods, and then reference them as helpers, such as in your guide example for helpers;

```js
 export default {
    helpers: {
      // from the guide
      leftPad,
      // hypothetically, a helper which is also a method
      doCoolThing,
    },
    methods: {
      doCoolThing: function() {
        return "cool thing";
      },
    }
    // etc
  }
```
Additionally, the guide example for event handlers kind of reinforces my point; doing ` <button on:click='select( category )'>` where `select` is a component api method feels fine, but then I could apparently not do `<button ...>{{ select("default") }}</button>` without also setting up my `select` method as a helper? You're making me do bookkeeping that I shouldn't have to do. If someone has to set up `select` as a helper, at least make the compiler do it. Ie, don't warn about it, just set it up that way.

I'm not sure what lifecycle hooks have to do with this. Those are special because their names are reserved and they are typically not manually invoked. I'm certainly not asking to be able to manually invoke lifecycle hook methods from a template (I would also say, it'd be weird to invoke lifecycle hook methods manually from other normal component methods). The difference is, I implemented methods on my component to allow my component to do something, but in order to do that thing from a template, I have to either declare the method as an event handler function, or cross reference it from my `helpers` object.... and now I'm doing bookkeeping I feel is unnecessary.

I'm also trying to think about it from the other angle, where a function is a helper only and not a method. That's your guide example case for helpers, and I do see how it is useful to expose a utility pure function to a template without absorbing it into your component public api. I think that is your strongest argument to continue explicitly partitioning helpers. However, that doesn't feel like a dominant scenario. I could be wrong, especially if people are constantly pulling in helper utilities as your guide example suggests.
> Additionally, the guide example for event handlers kind of reinforces my point; doing `<button on:click='select( category )'>` where `select` is a component api method feels fine, but then I could apparently not do `<button ...>{{ select("default") }}</button>` without also setting up my select method as a helper?

This is as clear a demonstration of why we need the distinction as you could invent 😀 

The click handler is *doing something* – when you click the button, you select the category. Or you could programmatically do...

```js
component.select( 'someCategory' );
```

...in your app. `{{ select("default") }}` is *absolutely not the same thing*. It's an expression that returns some text – the text that is displayed on the button – and the point of a template-based framework like Svelte is that the framework calls that function whenever it suspects that it might need to update the DOM, rather than at specific predictable moments (i.e. when you call the method, or the `click` event takes place).

Because of that, it's important that the helper not have side-effects, whereas the *whole point* of methods is to have side-effects (or to return a value that's determined by the state of the object to which they belong). If helpers have side-effects, your app will behave unpredictably, which is why this warning appears in the docs:

<img width="787" alt="screen shot 2016-12-09 at 6 27 26 pm" src="https://cloud.githubusercontent.com/assets/1162160/21067992/f4c6b3bc-be3c-11e6-996b-9db882304a87.png">
> a helper is a pure function that transforms its inputs i.e. it returns something

Just breezed through the thread. Isn't this a "computed property" in Ractive speak? I also view computed props as "a way to represent data in another way without creating another entity to represent it", should that be of help in clarifying.

In Angular, I believe they're called filters:

> Filters format the value of an expression for display to the user.
>
> https://docs.angularjs.org/guide/filter

Syntactically different, but sort of works the same way (value in, value out). Not sure if "filter" is a better name than helpers though. "filter" to me is like `array.filter`. But the pipe-style syntax does make it look familiar.

---

Helpers: Format data for display.

Methods: Mutate data, call events, call APIs, do rendering, operate the DOM, etc.

---

How about "transformers"?

My 2c 😄 
> Helpers: Format data for display.
> 
> Methods: Mutate data, call events, call APIs, do rendering, operate the DOM, etc.

Yeah, we should have something like that in the docs – that's a good way to clarify the distinction.

Svelte has `computed` properties which are very similar to Ractive's `computed` properties (except that the dependencies are declared as function parameters and resolved statically, rather than at runtime using the `this.get(...)` dependency tracking machinery – means we can do a simple toposort and update computations very efficiently. The parameter thing is a little bit magical but the ergonomics and simplicity make it oh-so-worth-it 😀 ).

`helpers` is an extension of `data`, in a sense – it's separate because I found a lot of people were a bit weirded out by having functions on `data` in Ractive. If you're not one of those people then you can basically do without helpers altogether (put helpers on `data`), but I've been living with `helpers` for the last couple of weeks and I think I prefer it. It feels cleaner.

(BTW: I *loathe* the Angular filter syntax – `{{ expression | filter:argument1:argument2:... }}` is hot garbage!)
I would find ```expression:``` or ```magic:``` for {{ ... }} it sound clearer on first read.
> the point of a template-based framework like Svelte is that the framework calls that function

I understand what you're going for now. In your jargon, "helpers" are code that, like lifecycle hooks, will be invoked by the framework internally, whereas component api methods will be invoked by external user events or other component code. I think coming at it from the invocation angle is more enlightening than coming at it from the "no no, they're _pure functions_" angle.

Even Ractive computed properties can have side effects. Probably shouldn't, but technically can. Similarly with Svelte, it sounds like helpers don't actually _need_ to be pure. That's merely an architectural recommendation rather than a declaration on why helpers need to exist separately from component methods. Especially since helpers could be coming from external libs, where who knows what's going on.

However, even given this, I still believe there's room for improvement. Lifecycle methods are declared on the component, same as Ractive and it all works because people memorize what the right method names are. Why is it that lifecycle hooks okay to simply define on the component, but helpers need to be called out separately, when to you they're more similar than different? Again this just gets back to my original point that it feels like unnecessary user bookkeeping.
> Why is it that lifecycle hooks okay to simply define on the component, but helpers need to be called out separately, when to you they're more similar than different?

What I said was that helpers have the same relation to lifecycle hooks as they do to methods – i.e, none whatsoever! They're three very different things that just happen to all be functions 😀 

Bunging everything together at the top level might sound like it makes your life as a component author easier, but I really think those gains are illusory and temporary. It's a higher priority for components to be easy to *read* than to *write* (because that's true of all code) – it's not book-keeping, it's organisation, just like keeping (say) `components` and `events` separate. Happily, a better-organised and easier-to-read component is also easier to statically analyse. 
Hm, I wonder how `helpers` and `methods` compare in terms of optimisation. As far as I know, an engine interpreter will likely compile often called (or “hot”) functions to byte code/machine code. A pure function would be easier to highly optimise.

Since a template helper would be called often, it makes sense for it to be pure.

From a template authoring point of view I can tell, that it makes it easier if templates stay dumb and business logic is moved elsewhere (be it model/view/controllower or viewmodel etc).
> Bunging everything together at the top level might sound like it makes your life as a component author easier, but I really think those gains are illusory and temporary. It's a higher priority for components to be easy to read than to write (because that's true of all code) – it's not book-keeping, it's organisation, just like keeping (say) components and events separate. Happily, a better-organised and easier-to-read component is also easier to statically analyse.

In that perspective, should `lifecycle hooks` be a separate top level key, like `helpers`?

Solution patch:
diff --git a/src/utils/namespaces.js b/src/utils/namespaces.js
--- a/src/utils/namespaces.js
+++ b/src/utils/namespaces.js
@@ -5,4 +5,9 @@ export const xlink  = 'http://www.w3.org/1999/xlink';
 export const xml    = 'http://www.w3.org/XML/1998/namespace';
 export const xmlns  = 'http://www.w3.org/2000/xmlns';
 
+export const validNamespaces = [
+	'html', 'mathml', 'svg', 'xlink', 'xml', 'xmlns',
+	html, mathml, svg, xlink, xml, xmlns
+];
+
 export default { html, mathml, svg, xlink, xml, xmlns };
diff --git a/src/validate/html/index.js b/src/validate/html/index.js
--- a/src/validate/html/index.js
+++ b/src/validate/html/index.js
@@ -1,7 +1,13 @@
 import * as namespaces from '../../utils/namespaces.js';
+import flattenReference from '../../utils/flattenReference.js';
 
 const svg = /^(?:altGlyph|altGlyphDef|altGlyphItem|animate|animateColor|animateMotion|animateTransform|circle|clipPath|color-profile|cursor|defs|desc|discard|ellipse|feBlend|feColorMatrix|feComponentTransfer|feComposite|feConvolveMatrix|feDiffuseLighting|feDisplacementMap|feDistantLight|feDropShadow|feFlood|feFuncA|feFuncB|feFuncG|feFuncR|feGaussianBlur|feImage|feMerge|feMergeNode|feMorphology|feOffset|fePointLight|feSpecularLighting|feSpotLight|feTile|feTurbulence|filter|font|font-face|font-face-format|font-face-name|font-face-src|font-face-uri|foreignObject|g|glyph|glyphRef|hatch|hatchpath|hkern|image|line|linearGradient|marker|mask|mesh|meshgradient|meshpatch|meshrow|metadata|missing-glyph|mpath|path|pattern|polygon|polyline|radialGradient|rect|set|solidcolor|stop|switch|symbol|text|textPath|title|tref|tspan|unknown|use|view|vkern)$/;
 
+function list ( items, conjunction = 'or' ) {
+	if ( items.length === 1 ) return items[0];
+	return `${items.slice( 0, -1 ).join( ', ' )} ${conjunction} ${items[ items.length - 1 ]}`;
+}
+
 export default function validateHtml ( validator, html ) {
 	let elementDepth = 0;
 
@@ -12,13 +18,37 @@ export default function validateHtml ( validator, html ) {
 			}
 
 			elementDepth += 1;
+
+			node.attributes.forEach( attribute => {
+				if ( attribute.type === 'EventHandler' ) {
+					const { callee, start, type } = attribute.expression;
+
+					if ( type !== 'CallExpression' ) {
+						validator.error( `Expected a call expression`, start );
+					}
+
+					const { name } = flattenReference( callee );
+
+					if ( name === 'this' || name === 'event' ) return;
+					if ( callee.type === 'Identifier' && callee.name === 'set' || callee.name === 'fire' || callee.name in validator.methods ) return;
+
+					const validCallees = list( [ 'this.*', 'event.*', 'set', 'fire' ].concat( Object.keys( validator.methods ) ) );
+					let message = `'${validator.source.slice( callee.start, callee.end )}' is an invalid callee (should be one of ${validCallees})`;
+
+					if ( callee.type === 'Identifier' && callee.name in validator.helpers ) {
+						message += `. '${callee.name}' exists on 'helpers', did you put it in the wrong place?`;
+					}
+
+					validator.error( message, start );
+				}
+			});
 		}
 
 		if ( node.children ) {
 			node.children.forEach( visit );
 		}
 
-		if (node.else ) {
+		if ( node.else ) {
 			visit( node.else );
 		}
 
diff --git a/src/validate/index.js b/src/validate/index.js
--- a/src/validate/index.js
+++ b/src/validate/index.js
@@ -18,7 +18,7 @@ export default function validate ( parsed, source, { onerror, onwarn, name, file
 
 			error.toString = () => `${error.message} (${error.loc.line}:${error.loc.column})\n${error.frame}`;
 
-			onerror( error );
+			throw error;
 		},
 
 		warn: ( message, pos ) => {
@@ -36,28 +36,42 @@ export default function validate ( parsed, source, { onerror, onwarn, name, file
 			});
 		},
 
-		namespace: null
+		source,
+
+		namespace: null,
+		defaultExport: null,
+		properties: {},
+		methods: {},
+		helpers: {}
 	};
 
-	if ( name && !/^[a-zA-Z_$][a-zA-Z_$0-9]*$/.test( name ) ) {
-		const error = new Error( `options.name must be a valid identifier` );
-		onerror( error );
-	}
+	try {
+		if ( name && !/^[a-zA-Z_$][a-zA-Z_$0-9]*$/.test( name ) ) {
+			const error = new Error( `options.name must be a valid identifier` );
+			throw error;
+		}
 
-	if ( name && !/^[A-Z]/.test( name ) ) {
-		const message = `options.name should be capitalised`;
-		onwarn({
-			message,
-			filename,
-			toString: () => message
-		});
-	}
+		if ( name && !/^[A-Z]/.test( name ) ) {
+			const message = `options.name should be capitalised`;
+			onwarn({
+				message,
+				filename,
+				toString: () => message
+			});
+		}
 
-	if ( parsed.js ) {
-		validateJs( validator, parsed.js );
-	}
+		if ( parsed.js ) {
+			validateJs( validator, parsed.js );
+		}
 
-	if ( parsed.html ) {
-		validateHtml( validator, parsed.html );
+		if ( parsed.html ) {
+			validateHtml( validator, parsed.html );
+		}
+	} catch ( err ) {
+		if ( onerror ) {
+			onerror( err );
+		} else {
+			throw err;
+		}
 	}
 }
diff --git a/src/validate/js/index.js b/src/validate/js/index.js
--- a/src/validate/js/index.js
+++ b/src/validate/js/index.js
@@ -23,19 +23,19 @@ export default function validateJs ( validator, js ) {
 			checkForComputedKeys( validator, node.declaration.properties );
 			checkForDupes( validator, node.declaration.properties );
 
-			const templateProperties = {};
+			const props = validator.properties;
 
 			node.declaration.properties.forEach( prop => {
-				templateProperties[ prop.key.name ] = prop;
+				props[ prop.key.name ] = prop;
 			});
 
 			// Remove these checks in version 2
-			if ( templateProperties.oncreate && templateProperties.onrender ) {
-				validator.error( 'Cannot have both oncreate and onrender', templateProperties.onrender.start );
+			if ( props.oncreate && props.onrender ) {
+				validator.error( 'Cannot have both oncreate and onrender', props.onrender.start );
 			}
 
-			if ( templateProperties.ondestroy && templateProperties.onteardown ) {
-				validator.error( 'Cannot have both ondestroy and onteardown', templateProperties.onteardown.start );
+			if ( props.ondestroy && props.onteardown ) {
+				validator.error( 'Cannot have both ondestroy and onteardown', props.onteardown.start );
 			}
 
 			// ensure all exported props are valid
@@ -56,8 +56,8 @@ export default function validateJs ( validator, js ) {
 				}
 			});
 
-			if ( templateProperties.namespace ) {
-				const ns = templateProperties.namespace.value.value;
+			if ( props.namespace ) {
+				const ns = props.namespace.value.value;
 				validator.namespace = namespaces[ ns ] || ns;
 			}
 
@@ -72,4 +72,12 @@ export default function validateJs ( validator, js ) {
 			});
 		}
 	});
+
+	[ 'methods', 'helpers' ].forEach( key => {
+		if ( validator.properties[ key ] ) {
+			validator.properties[ key ].value.properties.forEach( prop => {
+				validator[ key ][ prop.key.name ] = prop.value;
+			});
+		}
+	});
 }
diff --git a/src/validate/js/propValidators/helpers.js b/src/validate/js/propValidators/helpers.js
--- a/src/validate/js/propValidators/helpers.js
+++ b/src/validate/js/propValidators/helpers.js
@@ -1,5 +1,6 @@
 import checkForDupes from '../utils/checkForDupes.js';
 import checkForComputedKeys from '../utils/checkForComputedKeys.js';
+import { walk } from 'estree-walker';
 
 export default function helpers ( validator, prop ) {
 	if ( prop.value.type !== 'ObjectExpression' ) {
@@ -9,4 +10,45 @@ export default function helpers ( validator, prop ) {
 
 	checkForDupes( validator, prop.value.properties );
 	checkForComputedKeys( validator, prop.value.properties );
+
+	prop.value.properties.forEach( prop => {
+		if ( !/FunctionExpression/.test( prop.value.type ) ) return;
+
+		let lexicalDepth = 0;
+		let usesArguments = false;
+
+		walk( prop.value.body, {
+			enter ( node ) {
+				if ( /^Function/.test( node.type ) ) {
+					lexicalDepth += 1;
+				}
+
+				else if ( lexicalDepth === 0 ) {
+					// handle special case that's caused some people confusion — using `this.get(...)` instead of passing argument
+					// TODO do the same thing for computed values?
+					if ( node.type === 'CallExpression' && node.callee.type === 'MemberExpression' && node.callee.object.type === 'ThisExpression' && node.callee.property.name === 'get' && !node.callee.property.computed ) {
+						validator.error( `Cannot use this.get(...) — it must be passed into the helper function as an argument`, node.start );
+					}
+
+					if ( node.type === 'ThisExpression' ) {
+						validator.error( `Helpers should be pure functions — they do not have access to the component instance and cannot use 'this'. Did you mean to put this in 'methods'?`, node.start );
+					}
+
+					else if ( node.type === 'Identifier' && node.name === 'arguments' ) {
+						usesArguments = true;
+					}
+				}
+			},
+
+			leave ( node ) {
+				if ( /^Function/.test( node.type ) ) {
+					lexicalDepth -= 1;
+				}
+			}
+		});
+
+		if ( prop.value.params.length === 0 && !usesArguments ) {
+			validator.warn( `Helpers should be pure functions, with at least one argument`, prop.start );
+		}
+	});
 }
diff --git a/src/validate/js/propValidators/namespace.js b/src/validate/js/propValidators/namespace.js
--- a/src/validate/js/propValidators/namespace.js
+++ b/src/validate/js/propValidators/namespace.js
@@ -1,5 +1,22 @@
+import * as namespaces from '../../../utils/namespaces.js';
+import FuzzySet from '../utils/FuzzySet.js';
+
+const fuzzySet = new FuzzySet( namespaces.validNamespaces );
+const valid = new Set( namespaces.validNamespaces );
+
 export default function namespace ( validator, prop ) {
-	if ( prop.value.type !== 'Literal' || typeof prop.value.value !== 'string' ) {
+	const ns = prop.value.value;
+
+	if ( prop.value.type !== 'Literal' || typeof ns !== 'string' ) {
 		validator.error( `The 'namespace' property must be a string literal representing a valid namespace`, prop.start );
 	}
+
+	if ( !valid.has( ns ) ) {
+		const matches = fuzzySet.get( ns );
+		if ( matches && matches[0] && matches[0][0] > 0.7 ) {
+			validator.error( `Invalid namespace '${ns}' (did you mean '${matches[0][1]}'?)`, prop.start );
+		} else {
+			validator.error( `Invalid namespace '${ns}'`, prop.start );
+		}
+	}
 }


Test patch:
diff --git a/test/validator/samples/helper-purity-check-needs-arguments/input.html b/test/validator/samples/helper-purity-check-needs-arguments/input.html
new file mode 100644
--- /dev/null
+++ b/test/validator/samples/helper-purity-check-needs-arguments/input.html
@@ -0,0 +1,11 @@
+{{foo()}}
+
+<script>
+	export default {
+		helpers: {
+			foo () {
+				return Math.random();
+			}
+		}
+	}
+</script>
\ No newline at end of file
diff --git a/test/validator/samples/helper-purity-check-needs-arguments/warnings.json b/test/validator/samples/helper-purity-check-needs-arguments/warnings.json
new file mode 100644
--- /dev/null
+++ b/test/validator/samples/helper-purity-check-needs-arguments/warnings.json
@@ -0,0 +1,8 @@
+[{
+	"message": "Helpers should be pure functions, with at least one argument",
+	"pos": 54,
+	"loc": {
+		"line": 6,
+		"column": 3
+	}
+}]
\ No newline at end of file
diff --git a/test/validator/samples/helper-purity-check-no-this/errors.json b/test/validator/samples/helper-purity-check-no-this/errors.json
new file mode 100644
--- /dev/null
+++ b/test/validator/samples/helper-purity-check-no-this/errors.json
@@ -0,0 +1,8 @@
+[{
+	"message": "Helpers should be pure functions — they do not have access to the component instance and cannot use 'this'. Did you mean to put this in 'methods'?",
+	"pos": 95,
+	"loc": {
+		"line": 7,
+		"column": 4
+	}
+}]
\ No newline at end of file
diff --git a/test/validator/samples/helper-purity-check-no-this/input.html b/test/validator/samples/helper-purity-check-no-this/input.html
new file mode 100644
--- /dev/null
+++ b/test/validator/samples/helper-purity-check-no-this/input.html
@@ -0,0 +1,11 @@
+<button on:click='foo()'>foo</button>
+
+<script>
+	export default {
+		helpers: {
+			foo () {
+				this.set({ foo: true });
+			}
+		}
+	}
+</script>
\ No newline at end of file
diff --git a/test/validator/samples/helper-purity-check-this-get/errors.json b/test/validator/samples/helper-purity-check-this-get/errors.json
new file mode 100644
--- /dev/null
+++ b/test/validator/samples/helper-purity-check-this-get/errors.json
@@ -0,0 +1,8 @@
+[{
+	"message": "Cannot use this.get(...) — it must be passed into the helper function as an argument",
+	"pos": 74,
+	"loc": {
+		"line": 7,
+		"column": 11
+	}
+}]
\ No newline at end of file
diff --git a/test/validator/samples/helper-purity-check-this-get/input.html b/test/validator/samples/helper-purity-check-this-get/input.html
new file mode 100644
--- /dev/null
+++ b/test/validator/samples/helper-purity-check-this-get/input.html
@@ -0,0 +1,11 @@
+{{foo()}}
+
+<script>
+	export default {
+		helpers: {
+			foo () {
+				return this.get( 'bar' );
+			}
+		}
+	}
+</script>
\ No newline at end of file
diff --git a/test/validator/samples/helper-purity-check-uses-arguments/input.html b/test/validator/samples/helper-purity-check-uses-arguments/input.html
new file mode 100644
--- /dev/null
+++ b/test/validator/samples/helper-purity-check-uses-arguments/input.html
@@ -0,0 +1,13 @@
+{{sum(1, 2, 3)}}
+
+<script>
+	export default {
+		helpers: {
+			sum () {
+				var total = '';
+				for ( var i = 0; i < arguments.length; i += 1 ) total += arguments[i];
+				return total;
+			}
+		}
+	}
+</script>
\ No newline at end of file
diff --git a/test/validator/samples/helper-purity-check-uses-arguments/warnings.json b/test/validator/samples/helper-purity-check-uses-arguments/warnings.json
new file mode 100644
--- /dev/null
+++ b/test/validator/samples/helper-purity-check-uses-arguments/warnings.json
@@ -0,0 +1 @@
+[]
\ No newline at end of file
diff --git a/test/validator/samples/method-nonexistent-helper/errors.json b/test/validator/samples/method-nonexistent-helper/errors.json
new file mode 100644
--- /dev/null
+++ b/test/validator/samples/method-nonexistent-helper/errors.json
@@ -0,0 +1,8 @@
+[{
+	"message": "'foo' is an invalid callee (should be one of this.*, event.*, set, fire or bar). 'foo' exists on 'helpers', did you put it in the wrong place?",
+	"pos": 18,
+	"loc": {
+		"line": 1,
+		"column": 18
+	}
+}]
diff --git a/test/validator/samples/method-nonexistent-helper/input.html b/test/validator/samples/method-nonexistent-helper/input.html
new file mode 100644
--- /dev/null
+++ b/test/validator/samples/method-nonexistent-helper/input.html
@@ -0,0 +1,17 @@
+<button on:click='foo()'></button>
+
+<script>
+	export default {
+		methods: {
+			bar () {
+				// ...
+			}
+		},
+
+		helpers: {
+			foo ( x ) {
+				return x;
+			}
+		}
+	};
+</script>
diff --git a/test/validator/samples/method-nonexistent/errors.json b/test/validator/samples/method-nonexistent/errors.json
new file mode 100644
--- /dev/null
+++ b/test/validator/samples/method-nonexistent/errors.json
@@ -0,0 +1,8 @@
+[{
+	"message": "'foo' is an invalid callee (should be one of this.*, event.*, set, fire or bar)",
+	"pos": 18,
+	"loc": {
+		"line": 1,
+		"column": 18
+	}
+}]
diff --git a/test/validator/samples/method-nonexistent/input.html b/test/validator/samples/method-nonexistent/input.html
new file mode 100644
--- /dev/null
+++ b/test/validator/samples/method-nonexistent/input.html
@@ -0,0 +1,11 @@
+<button on:click='foo()'></button>
+
+<script>
+	export default {
+		methods: {
+			bar () {
+				// ...
+			}
+		}
+	};
+</script>
diff --git a/test/validator/samples/namespace-invalid/errors.json b/test/validator/samples/namespace-invalid/errors.json
new file mode 100644
--- /dev/null
+++ b/test/validator/samples/namespace-invalid/errors.json
@@ -0,0 +1,8 @@
+[{
+	"message": "Invalid namespace 'http://www.w3.org/1999/svg' (did you mean 'http://www.w3.org/2000/svg'?)",
+	"pos": 29,
+	"loc": {
+		"line": 3,
+		"column": 2
+	}
+}]
diff --git a/test/validator/samples/namespace-invalid/input.html b/test/validator/samples/namespace-invalid/input.html
new file mode 100644
--- /dev/null
+++ b/test/validator/samples/namespace-invalid/input.html
@@ -0,0 +1,5 @@
+<script>
+	export default {
+		namespace: 'http://www.w3.org/1999/svg'
+	};
+</script>
\ No newline at end of file