Repository: sveltejs/svelte (JavaScript)
Date: 2021-08-03 04:03:11+00:00
Task type: Bug Fix

Issue:
Parser unexpected behavior on value-less attribute
https://astexplorer.net/#/gist/2bbf556514bd54e799f8b9c836f6b7c9/3896519c24f22c47c49059def15ffed41c709c82
```svelte
<div foo= ></div>
```
```json
{
  "start": 5,
  "end": 10,
  "type": "Attribute",
  "name": "foo",
  "value": []
}
```
Unexpected `"value": []`, value is undefined.
Expected either Parser Error or `"value": true` (html behavior)


Issue discussion:
While browsers will repair this, it is invalid. I think this should be a parser error.
It's invalid html according to W3C Validator

Error: Attribute value missing.


Solution patch:
diff --git a/src/compiler/parse/errors.ts b/src/compiler/parse/errors.ts
--- a/src/compiler/parse/errors.ts
+++ b/src/compiler/parse/errors.ts
@@ -157,6 +157,10 @@ export default {
 		code: 'missing-component-definition',
 		message: '<svelte:component> must have a \'this\' attribute'
 	},
+	missing_attribute_value: {
+		code: 'missing-attribute-value',
+		message: 'Expected value for the attribute'
+	},
 	unclosed_script: {
 		code: 'unclosed-script',
 		message: '<script> must have a closing tag'
@@ -169,6 +173,10 @@ export default {
 		code: 'unclosed-comment',
 		message: 'comment was left open, expected -->'
 	},
+	unclosed_attribute_value: (token: string) => ({
+		code: 'unclosed-attribute-value',
+		message: `Expected to close the attribute value with ${token}`
+	}),
 	unexpected_block_close: {
 		code: 'unexpected-block-close',
 		message: 'Unexpected block closing tag'
diff --git a/src/compiler/parse/state/tag.ts b/src/compiler/parse/state/tag.ts
--- a/src/compiler/parse/state/tag.ts
+++ b/src/compiler/parse/state/tag.ts
@@ -438,7 +438,25 @@ function read_attribute_value(parser: Parser) {
 				/(\/>|[\s"'=<>`])/
 	);
 
-	const value = read_sequence(parser, () => !!parser.match_regex(regex));
+	let value;
+	try {
+		value = read_sequence(parser, () => !!parser.match_regex(regex));
+	} catch (error) {
+		if (error.code === 'parse-error') {
+			// if the attribute value didn't close + self-closing tag
+			// eg: `<Component test={{a:1} />`
+			// acorn may throw a `Unterminated regular expression` because of `/>`
+			if (parser.template.slice(error.pos - 1, error.pos + 1) === '/>') {
+				parser.index = error.pos;
+				parser.error(parser_errors.unclosed_attribute_value(quote_mark || '}'));
+			}
+		}
+		throw error;
+	}
+
+	if (value.length === 0 && !quote_mark) {
+		parser.error(parser_errors.missing_attribute_value);
+	}
 
 	if (quote_mark) parser.index += 1;
 	return value;


Test patch:
diff --git a/test/parser/samples/attribute-empty-error/error.json b/test/parser/samples/attribute-empty-error/error.json
new file mode 100644
--- /dev/null
+++ b/test/parser/samples/attribute-empty-error/error.json
@@ -0,0 +1,10 @@
+{
+  "code": "missing-attribute-value",
+  "message": "Expected value for the attribute",
+  "pos": 12,
+  "start": {
+    "character": 12,
+    "column": 12,
+    "line": 1
+  }
+}
\ No newline at end of file
diff --git a/test/parser/samples/attribute-empty-error/input.svelte b/test/parser/samples/attribute-empty-error/input.svelte
new file mode 100644
--- /dev/null
+++ b/test/parser/samples/attribute-empty-error/input.svelte
@@ -0,0 +1 @@
+<div class= ></div>
\ No newline at end of file
diff --git a/test/parser/samples/attribute-empty/input.svelte b/test/parser/samples/attribute-empty/input.svelte
new file mode 100644
--- /dev/null
+++ b/test/parser/samples/attribute-empty/input.svelte
@@ -0,0 +1 @@
+<div a="" b={''} c='' d="{''}" ></div>
\ No newline at end of file
diff --git a/test/parser/samples/attribute-empty/output.json b/test/parser/samples/attribute-empty/output.json
new file mode 100644
--- /dev/null
+++ b/test/parser/samples/attribute-empty/output.json
@@ -0,0 +1,108 @@
+{
+	"html": {
+		"start": 0,
+		"end": 38,
+		"type": "Fragment",
+		"children": [
+			{
+				"start": 0,
+				"end": 38,
+				"type": "Element",
+				"name": "div",
+				"attributes": [
+					{
+						"start": 5,
+						"end": 9,
+						"type": "Attribute",
+						"name": "a",
+						"value": [
+							{
+								"start": 8,
+								"end": 8,
+								"type": "Text",
+								"raw": "",
+								"data": ""
+							}
+						]
+					},
+					{
+						"start": 10,
+						"end": 16,
+						"type": "Attribute",
+						"name": "b",
+						"value": [
+							{
+								"start": 12,
+								"end": 16,
+								"type": "MustacheTag",
+								"expression": {
+									"type": "Literal",
+									"start": 13,
+									"end": 15,
+									"loc": {
+										"start": {
+											"line": 1,
+											"column": 13
+										},
+										"end": {
+											"line": 1,
+											"column": 15
+										}
+									},
+									"value": "",
+									"raw": "''"
+								}
+							}
+						]
+					},
+					{
+						"start": 17,
+						"end": 21,
+						"type": "Attribute",
+						"name": "c",
+						"value": [
+							{
+								"start": 20,
+								"end": 20,
+								"type": "Text",
+								"raw": "",
+								"data": ""
+							}
+						]
+					},
+					{
+						"start": 22,
+						"end": 30,
+						"type": "Attribute",
+						"name": "d",
+						"value": [
+							{
+								"start": 25,
+								"end": 29,
+								"type": "MustacheTag",
+								"expression": {
+									"type": "Literal",
+									"start": 26,
+									"end": 28,
+									"loc": {
+										"start": {
+											"line": 1,
+											"column": 26
+										},
+										"end": {
+											"line": 1,
+											"column": 28
+										}
+									},
+									"value": "",
+									"raw": "''"
+								}
+							}
+						]
+					}
+				],
+				"children": []
+			}
+		]
+	}
+}
\ No newline at end of file
diff --git a/test/parser/samples/error-unclosed-attribute-self-close-tag/error.json b/test/parser/samples/error-unclosed-attribute-self-close-tag/error.json
new file mode 100644
--- /dev/null
+++ b/test/parser/samples/error-unclosed-attribute-self-close-tag/error.json
@@ -0,0 +1,10 @@
+{
+	"code": "unclosed-attribute-value",
+	"message": "Expected to close the attribute value with }",
+	"pos": 25,
+	"start": {
+		"character": 25,
+		"column": 25,
+		"line": 1
+	}
+}
diff --git a/test/parser/samples/error-unclosed-attribute-self-close-tag/input.svelte b/test/parser/samples/error-unclosed-attribute-self-close-tag/input.svelte
new file mode 100644
--- /dev/null
+++ b/test/parser/samples/error-unclosed-attribute-self-close-tag/input.svelte
@@ -0,0 +1 @@
+<Component test={{a: 1} />
\ No newline at end of file