Skip to content

Commit bed2f0c

Browse files
committed
WIP
1 parent 4df7320 commit bed2f0c

File tree

7 files changed

+195
-36
lines changed

7 files changed

+195
-36
lines changed

packages/svelte/src/compiler/migrate/index.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -603,15 +603,15 @@ const instance_script = {
603603
);
604604
// Turn export let into props. It's really really weird because export let { x: foo, z: [bar]} = ..
605605
// means that foo and bar are the props (i.e. the leafs are the prop names), not x and z.
606-
// const tmp = state.scope.generate('tmp');
607-
// const paths = extract_paths(declarator.id);
606+
// const tmp = b.id(state.scope.generate('tmp'));
607+
// const paths = destructure(declarator.id, tmp);
608608
// state.props_pre.push(
609-
// b.declaration('const', b.id(tmp), visit(declarator.init!) as Expression)
609+
// b.declaration('const', tmp, visit(declarator.init!) as Expression)
610610
// );
611611
// for (const path of paths) {
612612
// const name = (path.node as Identifier).name;
613613
// const binding = state.scope.get(name)!;
614-
// const value = path.expression!(b.id(tmp));
614+
// const value = path.expression;
615615
// if (binding.kind === 'bindable_prop' || binding.kind === 'rest_prop') {
616616
// state.props.push({
617617
// local: name,

packages/svelte/src/compiler/phases/3-transform/client/visitors/EachBlock.js

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {
1010
EACH_ITEM_REACTIVE
1111
} from '../../../../../constants.js';
1212
import { dev } from '../../../../state.js';
13-
import { extract_paths, object } from '../../../../utils/ast.js';
13+
import { destructure, object } from '../../../../utils/ast.js';
1414
import * as b from '#compiler/builders';
1515
import { build_getter } from '../utils.js';
1616
import { get_value } from './shared/declarations.js';
@@ -234,13 +234,11 @@ export function EachBlock(node, context) {
234234
} else if (node.context) {
235235
const unwrapped = (flags & EACH_ITEM_REACTIVE) !== 0 ? b.call('$.get', item) : item;
236236

237-
for (const path of extract_paths(node.context)) {
237+
for (const path of destructure(node.context, unwrapped)) {
238238
const name = /** @type {Identifier} */ (path.node).name;
239239
const needs_derived = path.has_default_value; // to ensure that default value is only called once
240240

241-
const fn = b.thunk(
242-
/** @type {Expression} */ (context.visit(path.expression(unwrapped), child_state))
243-
);
241+
const fn = b.thunk(/** @type {Expression} */ (context.visit(path.expression, child_state)));
244242

245243
declarations.push(b.let(path.node, needs_derived ? b.call('$.derived_safe_equal', fn) : fn));
246244

@@ -249,7 +247,7 @@ export function EachBlock(node, context) {
249247
child_state.transform[name] = {
250248
read,
251249
assign: (_, value) => {
252-
const left = /** @type {Pattern} */ (path.update_expression(unwrapped));
250+
const left = /** @type {Pattern} */ (path.update_expression);
253251
return b.sequence([b.assignment('=', left, value), ...sequence]);
254252
},
255253
mutate: (_, mutation) => {

packages/svelte/src/compiler/phases/3-transform/client/visitors/SnippetBlock.js

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
/** @import { AST } from '#compiler' */
33
/** @import { ComponentContext } from '../types' */
44
import { dev } from '../../../../state.js';
5-
import { extract_paths } from '../../../../utils/ast.js';
5+
import { destructure } from '../../../../utils/ast.js';
66
import * as b from '#compiler/builders';
77
import { get_value } from './shared/declarations.js';
88

@@ -43,14 +43,12 @@ export function SnippetBlock(node, context) {
4343
let arg_alias = `$$arg${i}`;
4444
args.push(b.id(arg_alias));
4545

46-
const paths = extract_paths(argument);
46+
const paths = destructure(argument, b.maybe_call(b.id(arg_alias)));
4747

4848
for (const path of paths) {
4949
const name = /** @type {Identifier} */ (path.node).name;
5050
const needs_derived = path.has_default_value; // to ensure that default value is only called once
51-
const fn = b.thunk(
52-
/** @type {Expression} */ (context.visit(path.expression(b.maybe_call(b.id(arg_alias)))))
53-
);
51+
const fn = b.thunk(/** @type {Expression} */ (context.visit(path.expression)));
5452

5553
declarations.push(b.let(path.node, needs_derived ? b.call('$.derived_safe_equal', fn) : fn));
5654

packages/svelte/src/compiler/phases/3-transform/client/visitors/VariableDeclaration.js

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
/** @import { Binding } from '#compiler' */
33
/** @import { ComponentClientTransformState, ComponentContext } from '../types' */
44
import { dev } from '../../../../state.js';
5-
import { extract_paths } from '../../../../utils/ast.js';
5+
import { destructure, extract_paths } from '../../../../utils/ast.js';
66
import * as b from '#compiler/builders';
77
import * as assert from '../../../../utils/assert.js';
88
import { get_rune } from '../../../scope.js';
@@ -142,11 +142,11 @@ export function VariableDeclaration(node, context) {
142142
);
143143
} else {
144144
const tmp = b.id(context.state.scope.generate('tmp'));
145-
const paths = extract_paths(declarator.id);
145+
const paths = destructure(declarator.id, tmp);
146146
declarations.push(
147147
b.declarator(tmp, value),
148148
...paths.map((path) => {
149-
const value = path.expression(tmp);
149+
const value = path.expression;
150150
const binding = context.state.scope.get(/** @type {Identifier} */ (path.node).name);
151151
return b.declarator(
152152
path.node,
@@ -224,20 +224,20 @@ export function VariableDeclaration(node, context) {
224224
if (declarator.id.type !== 'Identifier') {
225225
// Turn export let into props. It's really really weird because export let { x: foo, z: [bar]} = ..
226226
// means that foo and bar are the props (i.e. the leafs are the prop names), not x and z.
227-
const tmp = context.state.scope.generate('tmp');
228-
const paths = extract_paths(declarator.id);
227+
const tmp = b.id(context.state.scope.generate('tmp'));
228+
const paths = destructure(declarator.id, tmp);
229229

230230
declarations.push(
231231
b.declarator(
232-
b.id(tmp),
232+
tmp,
233233
/** @type {Expression} */ (context.visit(/** @type {Expression} */ (declarator.init)))
234234
)
235235
);
236236

237237
for (const path of paths) {
238238
const name = /** @type {Identifier} */ (path.node).name;
239239
const binding = /** @type {Binding} */ (context.state.scope.get(name));
240-
const value = path.expression(b.id(tmp));
240+
const value = path.expression;
241241
declarations.push(
242242
b.declarator(
243243
path.node,
@@ -304,12 +304,12 @@ function create_state_declarators(declarator, { scope, analysis }, value) {
304304
];
305305
}
306306

307-
const tmp = scope.generate('tmp');
308-
const paths = extract_paths(declarator.id);
307+
const tmp = b.id(scope.generate('tmp'));
308+
const paths = destructure(declarator.id, tmp);
309309
return [
310-
b.declarator(b.id(tmp), value),
310+
b.declarator(tmp, value),
311311
...paths.map((path) => {
312-
const value = path.expression(b.id(tmp));
312+
const value = path.expression;
313313
const binding = scope.get(/** @type {Identifier} */ (path.node).name);
314314
return b.declarator(
315315
path.node,

packages/svelte/src/compiler/phases/3-transform/server/visitors/VariableDeclaration.js

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
/** @import { Context } from '../types.js' */
44
/** @import { ComponentAnalysis } from '../../../types.js' */
55
/** @import { Scope } from '../../../scope.js' */
6-
import { build_fallback, extract_paths } from '../../../../utils/ast.js';
6+
import { build_fallback, destructure } from '../../../../utils/ast.js';
77
import * as b from '#compiler/builders';
88
import { get_rune } from '../../../scope.js';
99
import { walk } from 'zimmerframe';
@@ -120,16 +120,16 @@ export function VariableDeclaration(node, context) {
120120
if (declarator.id.type !== 'Identifier') {
121121
// Turn export let into props. It's really really weird because export let { x: foo, z: [bar]} = ..
122122
// means that foo and bar are the props (i.e. the leafs are the prop names), not x and z.
123-
const tmp = context.state.scope.generate('tmp');
124-
const paths = extract_paths(declarator.id);
123+
const tmp = b.id(context.state.scope.generate('tmp'));
124+
const paths = destructure(declarator.id, tmp);
125125
declarations.push(
126126
b.declarator(
127-
b.id(tmp),
127+
tmp,
128128
/** @type {Expression} */ (context.visit(/** @type {Expression} */ (declarator.init)))
129129
)
130130
);
131131
for (const path of paths) {
132-
const value = path.expression(b.id(tmp));
132+
const value = path.expression;
133133
const name = /** @type {Identifier} */ (path.node).name;
134134
const binding = /** @type {Binding} */ (context.state.scope.get(name));
135135
const prop = b.member(b.id('$$props'), b.literal(binding.prop_alias ?? name), true);
@@ -189,11 +189,11 @@ function create_state_declarators(declarator, scope, value) {
189189
}
190190

191191
const tmp = b.id(scope.generate('tmp'));
192-
const paths = extract_paths(declarator.id);
192+
const paths = destructure(declarator.id, tmp);
193193
return [
194194
b.declarator(tmp, value), // TODO inject declarator for opts, so we can use it below
195195
...paths.map((path) => {
196-
const value = path.expression(tmp);
196+
const value = path.expression;
197197
return b.declarator(path.node, value);
198198
})
199199
];

packages/svelte/src/compiler/phases/3-transform/shared/assignments.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/** @import { AssignmentExpression, AssignmentOperator, Expression, Node, Pattern } from 'estree' */
22
/** @import { Context as ClientContext } from '../client/types.js' */
33
/** @import { Context as ServerContext } from '../server/types.js' */
4-
import { extract_paths, is_expression_async } from '../../../utils/ast.js';
4+
import { destructure, is_expression_async } from '../../../utils/ast.js';
55
import * as b from '#compiler/builders';
66

77
/**
@@ -23,8 +23,8 @@ export function visit_assignment_expression(node, context, build_assignment) {
2323

2424
let changed = false;
2525

26-
const assignments = extract_paths(node.left).map((path) => {
27-
const value = path.expression?.(rhs);
26+
const assignments = destructure(node.left, rhs).map((path) => {
27+
const value = path.expression;
2828

2929
let assignment = build_assignment('=', path.node, value, context);
3030
if (assignment !== null) changed = true;

packages/svelte/src/compiler/utils/ast.js

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -389,6 +389,169 @@ function _extract_paths(assignments = [], param, expression, update_expression,
389389
return assignments;
390390
}
391391

392+
/**
393+
* Represents the path of a destructured assignment from either a declaration
394+
* or assignment expression. For example, given `const { foo: { bar: baz } } = quux`,
395+
* the path of `baz` is `foo.bar`
396+
* @typedef {Object} DestructuredAssignment2
397+
* @property {ESTree.Identifier | ESTree.MemberExpression} node The node the destructuring path end in. Can be a member expression only for assignment expressions
398+
* @property {boolean} is_rest `true` if this is a `...rest` destructuring
399+
* @property {boolean} has_default_value `true` if this has a fallback value like `const { foo = 'bar } = ..`
400+
* @property {ESTree.Expression} expression Returns an expression which walks the path starting at the given expression.
401+
* This will be a call expression if a rest element or default is involved — e.g. `const { foo: { bar: baz = 42 }, ...rest } = quux` — since we can't represent `baz` or `rest` purely as a path
402+
* Will be an await expression in case of an async default value (`const { foo = await bar } = ...`)
403+
* @property {ESTree.Expression} update_expression Like `expression` but without default values.
404+
*/
405+
406+
/**
407+
* Extracts all destructured assignments from a pattern.
408+
* @param {ESTree.Node} param
409+
* @param {ESTree.Expression} initial
410+
* @returns {DestructuredAssignment2[]}
411+
*/
412+
export function destructure(param, initial) {
413+
return _destructure([], param, initial, initial, false);
414+
}
415+
416+
/**
417+
* @param {DestructuredAssignment2[]} assignments
418+
* @param {ESTree.Node} param
419+
* @param {ESTree.Expression} expression
420+
* @param {ESTree.Expression} update_expression
421+
* @param {boolean} has_default_value
422+
* @returns {DestructuredAssignment2[]}
423+
*/
424+
function _destructure(assignments = [], param, expression, update_expression, has_default_value) {
425+
switch (param.type) {
426+
case 'Identifier':
427+
case 'MemberExpression':
428+
assignments.push({
429+
node: param,
430+
is_rest: false,
431+
has_default_value,
432+
expression,
433+
update_expression
434+
});
435+
break;
436+
437+
case 'ObjectPattern':
438+
for (const prop of param.properties) {
439+
if (prop.type === 'RestElement') {
440+
/** @type {ESTree.Expression[]} */
441+
const props = [];
442+
443+
for (const p of param.properties) {
444+
if (p.type === 'Property' && p.key.type !== 'PrivateIdentifier') {
445+
if (p.key.type === 'Identifier' && !p.computed) {
446+
props.push(b.literal(p.key.name));
447+
} else if (p.key.type === 'Literal') {
448+
props.push(b.literal(String(p.key.value)));
449+
} else {
450+
props.push(b.call('String', p.key));
451+
}
452+
}
453+
}
454+
455+
const rest_expression = b.call('$.exclude_from_object', expression, b.array(props));
456+
457+
if (prop.argument.type === 'Identifier') {
458+
assignments.push({
459+
node: prop.argument,
460+
is_rest: true,
461+
has_default_value,
462+
expression: rest_expression,
463+
update_expression: rest_expression
464+
});
465+
} else {
466+
_destructure(
467+
assignments,
468+
prop.argument,
469+
rest_expression,
470+
rest_expression,
471+
has_default_value
472+
);
473+
}
474+
} else {
475+
const object_expression = b.member(
476+
expression,
477+
prop.key,
478+
prop.computed || prop.key.type !== 'Identifier'
479+
);
480+
481+
_destructure(
482+
assignments,
483+
prop.value,
484+
object_expression,
485+
object_expression,
486+
has_default_value
487+
);
488+
}
489+
}
490+
491+
break;
492+
493+
case 'ArrayPattern':
494+
for (let i = 0; i < param.elements.length; i += 1) {
495+
const element = param.elements[i];
496+
if (element) {
497+
if (element.type === 'RestElement') {
498+
const rest_expression = b.call(b.member(expression, 'slice'), b.literal(i));
499+
500+
if (element.argument.type === 'Identifier') {
501+
assignments.push({
502+
node: element.argument,
503+
is_rest: true,
504+
has_default_value,
505+
expression: rest_expression,
506+
update_expression: rest_expression
507+
});
508+
} else {
509+
_destructure(
510+
assignments,
511+
element.argument,
512+
rest_expression,
513+
rest_expression,
514+
has_default_value
515+
);
516+
}
517+
} else {
518+
const array_expression = b.member(expression, b.literal(i), true);
519+
520+
_destructure(
521+
assignments,
522+
element,
523+
array_expression,
524+
array_expression,
525+
has_default_value
526+
);
527+
}
528+
}
529+
}
530+
531+
break;
532+
533+
case 'AssignmentPattern': {
534+
const fallback_expression = build_fallback(expression, param.right);
535+
536+
if (param.left.type === 'Identifier') {
537+
assignments.push({
538+
node: param.left,
539+
is_rest: false,
540+
has_default_value: true,
541+
expression: fallback_expression,
542+
update_expression
543+
});
544+
} else {
545+
_destructure(assignments, param.left, fallback_expression, update_expression, true);
546+
}
547+
548+
break;
549+
}
550+
}
551+
552+
return assignments;
553+
}
554+
392555
/**
393556
* Like `path.at(x)`, but skips over `TSNonNullExpression` and `TSAsExpression` nodes and eases assertions a bit
394557
* by removing the `| undefined` from the resulting type.

0 commit comments

Comments
 (0)