Repository: sveltejs/svelte (JavaScript)
Date: 2017-04-10 22:44:05+00:00
Task type: Bug Fix

Issue:
<:Self> breaks for components whose name starts with a lowercase letter
Reproduction at https://github.com/TehShrike/svelte-self-repro

Component looks like:

```
<h1>{{heading}}</h1>
{{#if subheading}}
<:Self heading="{{subheading}}" />
{{/if}}
```

When the first letter of the component base name passed to `compile` starts with a lowercase letter, the output code includes:

```js
var component = new component({
	target: null,
	_root: component._root || component,
	data: { heading: root.subheading }
});
```

The resulting runtime error looks like:
```
Uncaught TypeError: component is not a constructor
    at render_if_block_0 (component-bundle.js:63)
    at render_main_fragment (component-bundle.js:22)
    at new component (component-bundle.js:107)
    at index.html:10
```


Issue discussion:
Interesting. When using a component normally that begins with a lowercase letter, the component instance is correctly deconflicted from the class name. Maybe we need to add the component name passed through the `name` option to the compiler to the global list of names to avoid? And probably also that "Component names should be capitalised" warning should check the `name` as well as just the `components`. I'm not sure where that should live, though. That's not actually part of the component validation, as the `name` is an entirely separate option being passed into the compiler.

Solution patch:
diff --git a/src/generators/Generator.js b/src/generators/Generator.js
--- a/src/generators/Generator.js
+++ b/src/generators/Generator.js
@@ -39,7 +39,7 @@ export default class Generator {
 		// Svelte's builtin `import { get, ... } from 'svelte/shared.js'`;
 		this.importedNames = new Set();
 		this._aliases = new Map();
-		this._usedNames = new Set();
+		this._usedNames = new Set( [ name ] );
 	}
 
 	addSourcemapLocations ( node ) {
diff --git a/src/validate/index.js b/src/validate/index.js
--- a/src/validate/index.js
+++ b/src/validate/index.js
@@ -44,6 +44,15 @@ export default function validate ( parsed, source, { onerror, onwarn, name, file
 		onerror( error );
 	}
 
+	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 );
 	}
diff --git a/src/validate/js/propValidators/components.js b/src/validate/js/propValidators/components.js
--- a/src/validate/js/propValidators/components.js
+++ b/src/validate/js/propValidators/components.js
@@ -11,8 +11,7 @@ export default function components ( validator, prop ) {
 	checkForComputedKeys( validator, prop.value.properties );
 
 	prop.value.properties.forEach( component => {
-		const char = component.key.name[0];
-		if ( char === char.toLowerCase() ) {
+		if ( !/^[A-Z]/.test( component.key.name ) ) {
 			validator.warn( `Component names should be capitalised`, component.start );
 		}
 	});


Test patch:
diff --git a/test/validator/index.js b/test/validator/index.js
--- a/test/validator/index.js
+++ b/test/validator/index.js
@@ -69,4 +69,19 @@ describe( 'validate', () => {
 			});
 		}, /options\.name must be a valid identifier/ );
 	});
+
+	it( 'warns if options.name is not capitalised', () => {
+		const warnings = [];
+		svelte.compile( '<div></div>', {
+			name: 'lowercase',
+			onwarn ( warning ) {
+				warnings.push({
+					message: warning.message,
+					pos: warning.pos,
+					loc: warning.loc
+				});
+			}
+		});
+		assert.deepEqual( warnings, [ { message: 'options.name should be capitalised', pos: undefined, loc: undefined } ] );
+	});
 });