Repository: sveltejs/svelte (JavaScript) — Bug Fix

Issue:
svelte:component and svelte:self parsed too loosely
`<svelte:componentfoo` and `<svelte:selffoo` are both accepted and parsed as `<svelte:component foo` and `<svelte:self foo`, because of how they are parsed: `if (parser.eat(SELF))` and `if (parser.eat(COMPONENT))`. We should go up to the next whitespace, forward slash, or greater then - like we do with other tag names.

This is present in v2 and v3.
svelte:component and svelte:self parsed too loosely
`<svelte:componentfoo` and `<svelte:selffoo` are both accepted and parsed as `<svelte:component foo` and `<svelte:self foo`, because of how they are parsed: `if (parser.eat(SELF))` and `if (parser.eat(COMPONENT))`. We should go up to the next whitespace, forward slash, or greater then - like we do with other tag names.

This is present in v2 and v3.


Discussion:

Resolving patch:
diff --git a/src/parse/state/tag.ts b/src/parse/state/tag.ts
--- a/src/parse/state/tag.ts
+++ b/src/parse/state/tag.ts
@@ -5,16 +5,20 @@ import { decodeCharacterReferences } from '../utils/html';
 import isVoidElementName from '../../utils/isVoidElementName';
 import { Parser } from '../index';
 import { Node } from '../../interfaces';
+import fuzzymatch from '../../utils/fuzzymatch';
+import list from '../../utils/list';
 
 const validTagName = /^\!?[a-zA-Z]{1,}:?[a-zA-Z0-9\-]*/;
 
 const metaTags = new Map([
-	['svelte:document', 'Document'],
-	['svelte:window', 'Window'],
+	['svelte:head', 'Head'],
 	['svelte:meta', 'Meta'],
-	['svelte:head', 'Head']
+	['svelte:window', 'Window'],
+	['svelte:document', 'Document']
 ]);
 
+const valid_meta_tags = [...metaTags.keys(), 'svelte:self', 'svelte:component'];
+
 const specials = new Map([
 	[
 		'script',
@@ -32,8 +36,8 @@ const specials = new Map([
 	],
 ]);
 
-const SELF = 'svelte:self';
-const COMPONENT = 'svelte:component';
+const SELF = /^svelte:self(?=[\s\/>])/;
+const COMPONENT = /^svelte:component(?=[\s\/>])/;
 
 // based on http://developers.whatwg.org/syntax.html#syntax-tag-omission
 const disallowedContents = new Map([
@@ -278,7 +282,7 @@ export default function tag(parser: Parser) {
 function readTagName(parser: Parser) {
 	const start = parser.index;
 
-	if (parser.eat(SELF)) {
+	if (parser.read(SELF)) {
 		// check we're inside a block, otherwise this
 		// will cause infinite recursion
 		let i = parser.stack.length;
@@ -295,19 +299,31 @@ function readTagName(parser: Parser) {
 		if (!legal) {
 			parser.error({
 				code: `invalid-self-placement`,
-				message: `<${SELF}> components can only exist inside if-blocks or each-blocks`
+				message: `<svelte:self> components can only exist inside if-blocks or each-blocks`
 			}, start);
 		}
 
-		return SELF;
+		return 'svelte:self';
 	}
 
-	if (parser.eat(COMPONENT)) return COMPONENT;
+	if (parser.read(COMPONENT)) return 'svelte:component';
 
 	const name = parser.readUntil(/(\s|\/|>)/);
 
 	if (metaTags.has(name)) return name;
 
+	if (name.startsWith('svelte:')) {
+		const match = fuzzymatch(name.slice(7), valid_meta_tags);
+
+		let message = `Valid <svelte:...> tag names are ${list(valid_meta_tags)}`;
+		if (match) message += ` (did you mean '${match}'?)`;
+
+		parser.error({
+			code: 'invalid-tag-name',
+			message
+		}, start);
+	}
+
 	if (!validTagName.test(name)) {
 		parser.error({
 			code: `invalid-tag-name`,


Test patch:
diff --git a/test/parser/samples/error-svelte-selfdestructive/error.json b/test/parser/samples/error-svelte-selfdestructive/error.json
new file mode 100644
--- /dev/null
+++ b/test/parser/samples/error-svelte-selfdestructive/error.json
@@ -0,0 +1,10 @@
+{
+	"code": "invalid-tag-name",
+	"message": "Valid <svelte:...> tag names are svelte:head, svelte:meta, svelte:window, svelte:document, svelte:self or svelte:component",
+	"pos": 10,
+	"start": {
+		"character": 10,
+		"line": 2,
+		"column": 2
+	}
+}
\ No newline at end of file
diff --git a/test/parser/samples/error-svelte-selfdestructive/input.html b/test/parser/samples/error-svelte-selfdestructive/input.html
new file mode 100644
--- /dev/null
+++ b/test/parser/samples/error-svelte-selfdestructive/input.html
@@ -0,0 +1,3 @@
+{#if x}
+	<svelte:selfdestructive x="{x - 1}"/>
+{/if}
\ No newline at end of file