Repository: sveltejs/svelte (JavaScript) — Bug Fix

Issue:
Component props as default values in #each destructuring
### Describe the problem

I expected this to work without issues ([REPL](https://svelte.dev/repl/79c5f5baa2bb4d2781d42b6142365cf4?version=3))

```svelte
<script>
  export let defaultA = 1
</script>

{#each [{a: 2},{b: 3}] as {a = defaultA, b}}
  {a} {b}
{/each}
```

but I'm getting a compiler warning

> App has unused export property 'defaultA'. If it is for external reference only, please consider using `export const defaultA` (2:12)



### Describe the proposed solution

Silence the warning.

### Alternatives considered

None

### Importance

would make my life easier


Discussion:

Resolving patch:
diff --git a/src/compiler/compile/nodes/AwaitBlock.ts b/src/compiler/compile/nodes/AwaitBlock.ts
--- a/src/compiler/compile/nodes/AwaitBlock.ts
+++ b/src/compiler/compile/nodes/AwaitBlock.ts
@@ -33,12 +33,12 @@ export default class AwaitBlock extends Node {
 
 		if (this.then_node) {
 			this.then_contexts = [];
-			unpack_destructuring(this.then_contexts, info.value);
+			unpack_destructuring({ contexts: this.then_contexts, node: info.value, scope, component });
 		}
 
 		if (this.catch_node) {
 			this.catch_contexts = [];
-			unpack_destructuring(this.catch_contexts, info.error);
+			unpack_destructuring({ contexts: this.catch_contexts, node: info.error, scope, component });
 		}
 
 		this.pending = new PendingBlock(component, this, scope, info.pending);
diff --git a/src/compiler/compile/nodes/EachBlock.ts b/src/compiler/compile/nodes/EachBlock.ts
--- a/src/compiler/compile/nodes/EachBlock.ts
+++ b/src/compiler/compile/nodes/EachBlock.ts
@@ -39,7 +39,7 @@ export default class EachBlock extends AbstractBlock {
 		this.scope = scope.child();
 
 		this.contexts = [];
-		unpack_destructuring(this.contexts, info.context);
+		unpack_destructuring({ contexts: this.contexts, node: info.context, scope, component });
 
 		this.contexts.forEach(context => {
 			this.scope.add(context.key.name, this.expression.dependencies, this);
diff --git a/src/compiler/compile/nodes/shared/Context.ts b/src/compiler/compile/nodes/shared/Context.ts
--- a/src/compiler/compile/nodes/shared/Context.ts
+++ b/src/compiler/compile/nodes/shared/Context.ts
@@ -3,6 +3,9 @@ import { Node, Identifier, Expression } from 'estree';
 import { walk } from 'estree-walker';
 import is_reference, { NodeWithPropertyDefinition } from 'is-reference';
 import { clone } from '../../../utils/clone';
+import Component from '../../Component';
+import flatten_reference from '../../utils/flatten_reference';
+import TemplateScope from './TemplateScope';
 
 export interface Context {
 	key: Identifier;
@@ -11,7 +14,21 @@ export interface Context {
 	default_modifier: (node: Node, to_ctx: (name: string) => Node) => Node;
 }
 
-export function unpack_destructuring(contexts: Context[], node: Node, modifier: Context['modifier'] = node => node, default_modifier: Context['default_modifier'] = node => node) {
+export function unpack_destructuring({
+	contexts,
+	node,
+	modifier = (node) => node,
+	default_modifier = (node) => node,
+	scope,
+	component
+}: {
+	contexts: Context[];
+	node: Node;
+	modifier?: Context['modifier'];
+	default_modifier?: Context['default_modifier'];
+	scope: TemplateScope;
+	component: Component;
+}) {
 	if (!node) return;
 
 	if (node.type === 'Identifier') {
@@ -29,13 +46,41 @@ export function unpack_destructuring(contexts: Context[], node: Node, modifier:
 	} else if (node.type === 'ArrayPattern') {
 		node.elements.forEach((element, i) => {
 			if (element && element.type === 'RestElement') {
-				unpack_destructuring(contexts, element, node => x`${modifier(node)}.slice(${i})` as Node, default_modifier);
+				unpack_destructuring({
+					contexts,
+					node: element,
+					modifier: (node) => x`${modifier(node)}.slice(${i})` as Node,
+					default_modifier,
+					scope,
+					component
+				});
 			} else if (element && element.type === 'AssignmentPattern') {
 				const n = contexts.length;
+				mark_referenced(element.right, scope, component);
 
-				unpack_destructuring(contexts, element.left, node => x`${modifier(node)}[${i}]`, (node, to_ctx) => x`${node} !== undefined ? ${node} : ${update_reference(contexts, n, element.right, to_ctx)}` as Node);
+				unpack_destructuring({
+					contexts,
+					node: element.left,
+					modifier: (node) => x`${modifier(node)}[${i}]`,
+					default_modifier: (node, to_ctx) =>
+						x`${node} !== undefined ? ${node} : ${update_reference(
+							contexts,
+							n,
+							element.right,
+							to_ctx
+						)}` as Node,
+					scope,
+					component
+				});
 			} else {
-				unpack_destructuring(contexts, element, node => x`${modifier(node)}[${i}]` as Node, default_modifier);
+				unpack_destructuring({
+					contexts,
+					node: element,
+					modifier: (node) => x`${modifier(node)}[${i}]` as Node,
+					default_modifier,
+					scope,
+					component
+				});
 			}
 		});
 	} else if (node.type === 'ObjectPattern') {
@@ -43,12 +88,17 @@ export function unpack_destructuring(contexts: Context[], node: Node, modifier:
 
 		node.properties.forEach((property) => {
 			if (property.type === 'RestElement') {
-				unpack_destructuring(
+				unpack_destructuring({
 					contexts,
-					property.argument,
-					node => x`@object_without_properties(${modifier(node)}, [${used_properties}])` as Node,
-					default_modifier
-				);
+					node: property.argument,
+					modifier: (node) =>
+						x`@object_without_properties(${modifier(
+							node
+						)}, [${used_properties}])` as Node,
+					default_modifier,
+					scope,
+					component
+				});
 			} else {
 				const key = property.key as Identifier;
 				const value = property.value;
@@ -57,16 +107,43 @@ export function unpack_destructuring(contexts: Context[], node: Node, modifier:
 				if (value.type === 'AssignmentPattern') {
 					const n = contexts.length;
 
-					unpack_destructuring(contexts, value.left, node => x`${modifier(node)}.${key.name}`, (node, to_ctx) => x`${node} !== undefined ? ${node} : ${update_reference(contexts, n, value.right, to_ctx)}` as Node);
+					mark_referenced(value.right, scope, component);
+
+					unpack_destructuring({
+						contexts,
+						node: value.left,
+						modifier: (node) => x`${modifier(node)}.${key.name}`,
+						default_modifier: (node, to_ctx) =>
+							x`${node} !== undefined ? ${node} : ${update_reference(
+								contexts,
+								n,
+								value.right,
+								to_ctx
+							)}` as Node,
+						scope,
+						component
+					});
 				} else {
-					unpack_destructuring(contexts, value, node => x`${modifier(node)}.${key.name}` as Node, default_modifier);
+					unpack_destructuring({
+						contexts,
+						node: value,
+						modifier: (node) => x`${modifier(node)}.${key.name}` as Node,
+						default_modifier,
+						scope,
+						component
+					});
 				}
 			}
 		});
 	}
 }
 
-function update_reference(contexts: Context[], n: number, expression: Expression, to_ctx: (name: string) => Node): Node {
+function update_reference(
+	contexts: Context[],
+	n: number,
+	expression: Expression,
+	to_ctx: (name: string) => Node
+): Node {
 	const find_from_context = (node: Identifier) => {
 		for (let i = n; i < contexts.length; i++) {
 			const { key } = contexts[i];
@@ -85,7 +162,12 @@ function update_reference(contexts: Context[], n: number, expression: Expression
 	expression = clone(expression) as Expression;
 	walk(expression, {
 		enter(node, parent: Node) {
-			if (is_reference(node as NodeWithPropertyDefinition, parent as NodeWithPropertyDefinition)) {
+			if (
+				is_reference(
+					node as NodeWithPropertyDefinition,
+					parent as NodeWithPropertyDefinition
+				)
+			) {
 				this.replace(find_from_context(node as Identifier));
 				this.skip();
 			}
@@ -94,3 +176,20 @@ function update_reference(contexts: Context[], n: number, expression: Expression
 
 	return expression;
 }
+
+function mark_referenced(
+	node: Node,
+	scope: TemplateScope,
+	component: Component
+) {
+	walk(node, {
+		enter(node: any, parent: any) {
+			if (is_reference(node, parent)) {
+				const { name } = flatten_reference(node);
+				if (!scope.is_let(name) && !scope.names.has(name)) {
+					component.add_reference(name);
+				}
+			}
+		}
+	});
+}


Test patch:
diff --git a/test/validator/samples/unreferenced-variables-each/input.svelte b/test/validator/samples/unreferenced-variables-each/input.svelte
new file mode 100644
--- /dev/null
+++ b/test/validator/samples/unreferenced-variables-each/input.svelte
@@ -0,0 +1,20 @@
+<script>
+	let Component;
+	export let array;
+	export let default_value_1;
+	export let default_value_2;
+	export let default_value_3;
+	export let default_value_4;
+	export let default_value_5;
+	export let default_value_6;
+</script>
+
+<Component>
+	<svelte:fragment let:a={default_value_6}>
+		{#each array as default_value_5}
+			{#each array as { a = default_value_1, b: { b = [default_value_2, default_value_3], c: [c = default_value_4] }, d = default_value_5, e = default_value_6 }}
+				{a}{b}{c}{d}{e}
+			{/each}
+		{/each}
+	</svelte:fragment>
+</Component>
diff --git a/test/validator/samples/unreferenced-variables-each/warnings.json b/test/validator/samples/unreferenced-variables-each/warnings.json
new file mode 100644
--- /dev/null
+++ b/test/validator/samples/unreferenced-variables-each/warnings.json
@@ -0,0 +1,32 @@
+[
+	{
+		"code": "unused-export-let",
+		"message": "Component_1 has unused export property 'default_value_5'. If it is for external reference only, please consider using `export const default_value_5`",
+		"pos": 172,
+		"start": {
+			"character": 172,
+			"column": 12,
+			"line": 8
+		},
+		"end": {
+			"character": 187,
+			"column": 27,
+			"line": 8
+		}
+	},
+	{
+		"code": "unused-export-let",
+		"message": "Component_1 has unused export property 'default_value_6'. If it is for external reference only, please consider using `export const default_value_6`",
+		"pos": 201,
+		"start": {
+			"character": 201,
+			"column": 12,
+			"line": 9
+		},
+		"end": {
+			"character": 216,
+			"column": 27,
+			"line": 9
+		}
+	}
+]