Skip to content

Commit d17a90c

Browse files
tanhauhauhabibrosyadConduitry
authored
allow destructured defaults to refer to variables (#5986)
Co-authored-by: M. Habib Rosyad <habib@volantis.io> Co-authored-by: Conduitry <git@chor.date>
1 parent b764374 commit d17a90c

File tree

14 files changed

+115
-29
lines changed

14 files changed

+115
-29
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
## Unreleased
44

55
* In custom elements, call `onMount` functions when connecting and clean up when disconnecting ([#1152](https://github.com/sveltejs/svelte/issues/1152), [#2227](https://github.com/sveltejs/svelte/issues/2227), [#4522](https://github.com/sveltejs/svelte/pull/4522))
6+
* Allow destructured defaults to refer to other variables ([#5066](https://github.com/sveltejs/svelte/issues/5066))
67
* Do not emit `contextual-store` warnings for function parameters or declared variables ([#6008](https://github.com/sveltejs/svelte/pull/6008))
78

89
## 3.32.3

src/compiler/compile/nodes/AwaitBlock.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,12 @@ export default class AwaitBlock extends Node {
3333

3434
if (this.then_node) {
3535
this.then_contexts = [];
36-
unpack_destructuring(this.then_contexts, info.value, node => node);
36+
unpack_destructuring(this.then_contexts, info.value);
3737
}
3838

3939
if (this.catch_node) {
4040
this.catch_contexts = [];
41-
unpack_destructuring(this.catch_contexts, info.error, node => node);
41+
unpack_destructuring(this.catch_contexts, info.error);
4242
}
4343

4444
this.pending = new PendingBlock(component, this, scope, info.pending);

src/compiler/compile/nodes/EachBlock.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ export default class EachBlock extends AbstractBlock {
3838
this.scope = scope.child();
3939

4040
this.contexts = [];
41-
unpack_destructuring(this.contexts, info.context, node => node);
41+
unpack_destructuring(this.contexts, info.context);
4242

4343
this.contexts.forEach(context => {
4444
this.scope.add(context.key.name, this.expression.dependencies, this);
Lines changed: 50 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,40 @@
11
import { x } from 'code-red';
2-
import { Node, Identifier } from 'estree';
2+
import { Node, Identifier, Expression } from 'estree';
3+
import { walk } from 'estree-walker';
4+
import is_reference from 'is-reference';
35

46
export interface Context {
57
key: Identifier;
68
name?: string;
79
modifier: (node: Node) => Node;
10+
default_modifier: (node: Node, to_ctx: (name: string) => Node) => Node;
811
}
912

10-
export function unpack_destructuring(contexts: Context[], node: Node, modifier: (node: Node) => Node) {
13+
export function unpack_destructuring(contexts: Context[], node: Node, modifier: Context['modifier'] = node => node, default_modifier: Context['default_modifier'] = node => node) {
1114
if (!node) return;
1215

1316
if (node.type === 'Identifier') {
1417
contexts.push({
1518
key: node as Identifier,
16-
modifier
19+
modifier,
20+
default_modifier
1721
});
1822
} else if (node.type === 'RestElement') {
1923
contexts.push({
2024
key: node.argument as Identifier,
21-
modifier
25+
modifier,
26+
default_modifier
2227
});
2328
} else if (node.type === 'ArrayPattern') {
2429
node.elements.forEach((element, i) => {
2530
if (element && element.type === 'RestElement') {
26-
unpack_destructuring(contexts, element, node => x`${modifier(node)}.slice(${i})` as Node);
31+
unpack_destructuring(contexts, element, node => x`${modifier(node)}.slice(${i})` as Node, default_modifier);
2732
} else if (element && element.type === 'AssignmentPattern') {
28-
unpack_destructuring(contexts, element.left, node => x`${modifier(node)}[${i}] !== undefined ? ${modifier(node)}[${i}] : ${element.right}` as Node);
33+
const n = contexts.length;
34+
35+
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);
2936
} else {
30-
unpack_destructuring(contexts, element, node => x`${modifier(node)}[${i}]` as Node);
37+
unpack_destructuring(contexts, element, node => x`${modifier(node)}[${i}]` as Node, default_modifier);
3138
}
3239
});
3340
} else if (node.type === 'ObjectPattern') {
@@ -38,19 +45,51 @@ export function unpack_destructuring(contexts: Context[], node: Node, modifier:
3845
unpack_destructuring(
3946
contexts,
4047
property.argument,
41-
node => x`@object_without_properties(${modifier(node)}, [${used_properties}])` as Node
48+
node => x`@object_without_properties(${modifier(node)}, [${used_properties}])` as Node,
49+
default_modifier
4250
);
4351
} else {
4452
const key = property.key as Identifier;
4553
const value = property.value;
4654

47-
used_properties.push(x`"${(key as Identifier).name}"`);
55+
used_properties.push(x`"${key.name}"`);
4856
if (value.type === 'AssignmentPattern') {
49-
unpack_destructuring(contexts, value.left, node => x`${modifier(node)}.${key.name} !== undefined ? ${modifier(node)}.${key.name} : ${value.right}` as Node);
57+
const n = contexts.length;
58+
59+
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);
5060
} else {
51-
unpack_destructuring(contexts, value, node => x`${modifier(node)}.${key.name}` as Node);
61+
unpack_destructuring(contexts, value, node => x`${modifier(node)}.${key.name}` as Node, default_modifier);
5262
}
5363
}
5464
});
5565
}
5666
}
67+
68+
function update_reference(contexts: Context[], n: number, expression: Expression, to_ctx: (name: string) => Node): Node {
69+
const find_from_context = (node: Identifier) => {
70+
for (let i = n; i < contexts.length; i++) {
71+
const { key } = contexts[i];
72+
if (node.name === key.name) {
73+
throw new Error(`Cannot access '${node.name}' before initialization`);
74+
}
75+
}
76+
return to_ctx(node.name);
77+
};
78+
79+
if (expression.type === 'Identifier') {
80+
return find_from_context(expression);
81+
}
82+
83+
// NOTE: avoid unnecessary deep clone?
84+
expression = JSON.parse(JSON.stringify(expression)) as Expression;
85+
walk(expression, {
86+
enter(node, parent: Node) {
87+
if (is_reference(node, parent)) {
88+
this.replace(find_from_context(node as Identifier));
89+
this.skip();
90+
}
91+
}
92+
});
93+
94+
return expression;
95+
}

src/compiler/compile/nodes/shared/Expression.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -325,7 +325,8 @@ export default class Expression {
325325
// child_ctx[x] = function () { ... }
326326
(template_scope.get_owner(deps[0]) as EachBlock).contexts.push({
327327
key: func_id,
328-
modifier: () => func_expression
328+
modifier: () => func_expression,
329+
default_modifier: node => node
329330
});
330331
this.replace(block.renderer.reference(func_id));
331332
}

src/compiler/compile/render_dom/Block.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ export interface Bindings {
99
property: Identifier;
1010
snippet: Node;
1111
store: string;
12-
tail: Node;
1312
modifier: (node: Node) => Node;
1413
}
1514

src/compiler/compile/render_dom/wrappers/AwaitBlock.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ class AwaitBlockBranch extends Wrapper {
8787
}
8888

8989
render_destructure() {
90-
const props = this.value_contexts.map(prop => b`#ctx[${this.block.renderer.context_lookup.get(prop.key.name).index}] = ${prop.modifier(x`#ctx[${this.value_index}]`)};`);
90+
const props = this.value_contexts.map(prop => b`#ctx[${this.block.renderer.context_lookup.get(prop.key.name).index}] = ${prop.default_modifier(prop.modifier(x`#ctx[${this.value_index}]`), name => this.renderer.reference(name))};`);
9191
const get_context = this.block.renderer.component.get_unique_name(`get_${this.status}_context`);
9292
this.block.renderer.blocks.push(b`
9393
function ${get_context}(#ctx) {

src/compiler/compile/render_dom/wrappers/EachBlock.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -149,8 +149,7 @@ export default class EachBlockWrapper extends Wrapper {
149149
property: this.index_name,
150150
modifier: prop.modifier,
151151
snippet: prop.modifier(x`${this.vars.each_block_value}[${this.index_name}]` as Node),
152-
store,
153-
tail: prop.modifier(x`[${this.index_name}]` as Node)
152+
store
154153
});
155154
});
156155

@@ -347,7 +346,7 @@ export default class EachBlockWrapper extends Wrapper {
347346
this.else.fragment.render(this.else.block, null, x`#nodes` as Identifier);
348347
}
349348

350-
this.context_props = this.node.contexts.map(prop => b`child_ctx[${renderer.context_lookup.get(prop.key.name).index}] = ${prop.modifier(x`list[i]`)};`);
349+
this.context_props = this.node.contexts.map(prop => b`child_ctx[${renderer.context_lookup.get(prop.key.name).index}] = ${prop.default_modifier(prop.modifier(x`list[i]`), name => renderer.context_lookup.has(name) ? x`child_ctx[${renderer.context_lookup.get(name).index}]`: { type: 'Identifier', name })};`);
351350

352351
if (this.node.has_binding) this.context_props.push(b`child_ctx[${renderer.context_lookup.get(this.vars.each_block_value.name).index}] = list;`);
353352
if (this.node.has_binding || this.node.has_index_binding || this.node.index) this.context_props.push(b`child_ctx[${renderer.context_lookup.get(this.index_name.name).index}] = i;`);
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export default {
2+
error(assert, err) {
3+
assert.ok(err.message === "Cannot access 'c' before initialization" || err.message === 'c is not defined');
4+
}
5+
};
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<script>
2+
let array = [{a: 1, c: 2}];
3+
</script>
4+
5+
{#each array as { a, b = c, c }}
6+
{a}{b}{c}
7+
{/each}

0 commit comments

Comments
 (0)