diff --git a/src/compiler/compile/nodes/AwaitBlock.ts b/src/compiler/compile/nodes/AwaitBlock.ts
index ef74d26848..735fdbfff3 100644
--- 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
index c03128c388..a8312b2462 100644
--- 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
index 6cc5a4358e..7507d1e37e 100644
--- 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);
+ }
+ }
+ }
+ });
+}
diff --git a/test/validator/samples/unreferenced-variables-each/input.svelte b/test/validator/samples/unreferenced-variables-each/input.svelte
new file mode 100644
index 0000000000..a96f9189a2
--- /dev/null
+++ b/test/validator/samples/unreferenced-variables-each/input.svelte
@@ -0,0 +1,20 @@
+
+
+
+
+ {#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}
+
+
diff --git a/test/validator/samples/unreferenced-variables-each/warnings.json b/test/validator/samples/unreferenced-variables-each/warnings.json
new file mode 100644
index 0000000000..fa89da0e68
--- /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
+ }
+ }
+]