Repository: sveltejs/svelte (JavaScript) — Bug Fix

Issue:
Infinite loop while binding an array to a child component
**Describe the bug**

I have two components :

***Cpn.svelte***

```
<script>
  export let items = [];
</script>
```

and a consumer :

```
<script>
  import Cpn from './Cpn';
  let items = []
</script>

<Cpn {items} bind:items/>
```

this consumer has a "bug" : it injects {items} and creates a binding with items. However svelte doesn't output any warning, and the generated code has an infinite loop when the component is rendered.

It "worked" in svelte 3.12

The bug is not really in svelte but in my code, however it's a "hard to debug" case, because chrome/firefox just crash every time the component is shown (and it worked in 3.12).

**To Reproduce**

I can't provide a REPL because this bug crashes the app. 

**Expected behavior**

The generated code doesn't have an infinite loop/svelte emits an error.

**Information about your Svelte project:**
- Chrome 79
- Windows 7
- Svelte 3.18.0
- Webpack




Discussion:
Svelte detects when you add the same attribute twice:
```html
<Component value="1" value="2" />
```
Will correctly give you the error: **Attributes need to be unique**

However
```html
<Component value="1" bind:value />
```
Does not give the same error, although I think it should considering it's somehow also a duplicate attribute.
Yeah, I agree that `foo={...}`, `{foo}`, and `bind:foo` should probably all be mutually exclusive, and using any two of them should be a compile-time error.

Resolving patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,9 @@
 # Svelte changelog
 
+## Unreleased
+
+* Disallow attribute/prop names from matching two-way-bound names or `{shorthand}` attribute/prop names ([#4325](https://github.com/sveltejs/svelte/issues/4325))
+
 ## 3.18.1
 
 * Fix code generation error with adjacent inline and block comments ([#4312](https://github.com/sveltejs/svelte/issues/4312))
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
@@ -288,6 +288,16 @@ function read_tag_name(parser: Parser) {
 function read_attribute(parser: Parser, unique_names: Set<string>) {
 	const start = parser.index;
 
+	function check_unique(name: string) {
+		if (unique_names.has(name)) {
+			parser.error({
+				code: `duplicate-attribute`,
+				message: 'Attributes need to be unique'
+			}, start);
+		}
+		unique_names.add(name);
+	}
+
 	if (parser.eat('{')) {
 		parser.allow_whitespace();
 
@@ -310,6 +320,8 @@ function read_attribute(parser: Parser, unique_names: Set<string>) {
 			parser.allow_whitespace();
 			parser.eat('}', true);
 
+			check_unique(name);
+
 			return {
 				start,
 				end: parser.index,
@@ -341,17 +353,6 @@ function read_attribute(parser: Parser, unique_names: Set<string>) {
 	const colon_index = name.indexOf(':');
 	const type = colon_index !== -1 && get_directive_type(name.slice(0, colon_index));
 
-	if (unique_names.has(name)) {
-		parser.error({
-			code: `duplicate-attribute`,
-			message: 'Attributes need to be unique'
-		}, start);
-	}
-
-	if (type !== "EventHandler") {
-		unique_names.add(name);
-	}
-
 	let value: any[] | true = true;
 	if (parser.eat('=')) {
 		parser.allow_whitespace();
@@ -367,6 +368,12 @@ function read_attribute(parser: Parser, unique_names: Set<string>) {
 	if (type) {
 		const [directive_name, ...modifiers] = name.slice(colon_index + 1).split('|');
 
+		if (type === 'Binding' && directive_name !== 'this') {
+			check_unique(directive_name);
+		} else if (type !== 'EventHandler') {
+			check_unique(name);
+		}
+
 		if (type === 'Ref') {
 			parser.error({
 				code: `invalid-ref-directive`,
@@ -410,6 +417,8 @@ function read_attribute(parser: Parser, unique_names: Set<string>) {
 		return directive;
 	}
 
+	check_unique(name);
+
 	return {
 		start,
 		end,


Test patch:
diff --git a/test/parser/samples/attribute-unique-binding-error/error.json b/test/parser/samples/attribute-unique-binding-error/error.json
new file mode 100644
--- /dev/null
+++ b/test/parser/samples/attribute-unique-binding-error/error.json
@@ -0,0 +1,10 @@
+{
+	"code": "duplicate-attribute",
+	"message": "Attributes need to be unique",
+	"start": {
+		"line": 1,
+		"column": 17,
+		"character": 17
+	},
+	"pos": 17
+}
diff --git a/test/parser/samples/attribute-unique-binding-error/input.svelte b/test/parser/samples/attribute-unique-binding-error/input.svelte
new file mode 100644
--- /dev/null
+++ b/test/parser/samples/attribute-unique-binding-error/input.svelte
@@ -0,0 +1 @@
+<Widget foo={42} bind:foo/>
\ No newline at end of file
diff --git a/test/parser/samples/attribute-unique-shorthand-error/error.json b/test/parser/samples/attribute-unique-shorthand-error/error.json
new file mode 100644
--- /dev/null
+++ b/test/parser/samples/attribute-unique-shorthand-error/error.json
@@ -0,0 +1,10 @@
+{
+	"code": "duplicate-attribute",
+	"message": "Attributes need to be unique",
+	"start": {
+		"line": 1,
+		"column": 17,
+		"character": 17
+	},
+	"pos": 17
+}
diff --git a/test/parser/samples/attribute-unique-shorthand-error/input.svelte b/test/parser/samples/attribute-unique-shorthand-error/input.svelte
new file mode 100644
--- /dev/null
+++ b/test/parser/samples/attribute-unique-shorthand-error/input.svelte
@@ -0,0 +1 @@
+<div title='foo' {title}></div>
\ No newline at end of file