pull/16015/head
Rich Harris 4 months ago
parent bed2f0c910
commit bda21bf2cc

@ -604,7 +604,7 @@ const instance_script = {
// Turn export let into props. It's really really weird because export let { x: foo, z: [bar]} = .. // Turn export let into props. It's really really weird because export let { x: foo, z: [bar]} = ..
// means that foo and bar are the props (i.e. the leafs are the prop names), not x and z. // means that foo and bar are the props (i.e. the leafs are the prop names), not x and z.
// const tmp = b.id(state.scope.generate('tmp')); // const tmp = b.id(state.scope.generate('tmp'));
// const paths = destructure(declarator.id, tmp); // const paths = extract_paths(declarator.id, tmp);
// state.props_pre.push( // state.props_pre.push(
// b.declaration('const', tmp, visit(declarator.init!) as Expression) // b.declaration('const', tmp, visit(declarator.init!) as Expression)
// ); // );

@ -7,6 +7,7 @@ import * as e from '../../../errors.js';
import * as w from '../../../warnings.js'; import * as w from '../../../warnings.js';
import { extract_paths } from '../../../utils/ast.js'; import { extract_paths } from '../../../utils/ast.js';
import { equal } from '../../../utils/assert.js'; import { equal } from '../../../utils/assert.js';
import * as b from '#compiler/builders';
/** /**
* @param {VariableDeclarator} node * @param {VariableDeclarator} node
@ -18,7 +19,7 @@ export function VariableDeclarator(node, context) {
if (context.state.analysis.runes) { if (context.state.analysis.runes) {
const init = node.init; const init = node.init;
const rune = get_rune(init, context.state.scope); const rune = get_rune(init, context.state.scope);
const paths = extract_paths(node.id); const paths = extract_paths(node.id, b.id('dummy'));
for (const path of paths) { for (const path of paths) {
validate_identifier_name(context.state.scope.get(/** @type {Identifier} */ (path.node).name)); validate_identifier_name(context.state.scope.get(/** @type {Identifier} */ (path.node).name));

@ -10,7 +10,7 @@ import {
EACH_ITEM_REACTIVE EACH_ITEM_REACTIVE
} from '../../../../../constants.js'; } from '../../../../../constants.js';
import { dev } from '../../../../state.js'; import { dev } from '../../../../state.js';
import { destructure, object } from '../../../../utils/ast.js'; import { extract_paths, object } from '../../../../utils/ast.js';
import * as b from '#compiler/builders'; import * as b from '#compiler/builders';
import { build_getter } from '../utils.js'; import { build_getter } from '../utils.js';
import { get_value } from './shared/declarations.js'; import { get_value } from './shared/declarations.js';
@ -234,7 +234,7 @@ export function EachBlock(node, context) {
} else if (node.context) { } else if (node.context) {
const unwrapped = (flags & EACH_ITEM_REACTIVE) !== 0 ? b.call('$.get', item) : item; const unwrapped = (flags & EACH_ITEM_REACTIVE) !== 0 ? b.call('$.get', item) : item;
for (const path of destructure(node.context, unwrapped)) { for (const path of extract_paths(node.context, unwrapped)) {
const name = /** @type {Identifier} */ (path.node).name; const name = /** @type {Identifier} */ (path.node).name;
const needs_derived = path.has_default_value; // to ensure that default value is only called once const needs_derived = path.has_default_value; // to ensure that default value is only called once

@ -2,7 +2,7 @@
/** @import { AST } from '#compiler' */ /** @import { AST } from '#compiler' */
/** @import { ComponentContext } from '../types' */ /** @import { ComponentContext } from '../types' */
import { dev } from '../../../../state.js'; import { dev } from '../../../../state.js';
import { destructure } from '../../../../utils/ast.js'; import { extract_paths } from '../../../../utils/ast.js';
import * as b from '#compiler/builders'; import * as b from '#compiler/builders';
import { get_value } from './shared/declarations.js'; import { get_value } from './shared/declarations.js';
@ -43,7 +43,7 @@ export function SnippetBlock(node, context) {
let arg_alias = `$$arg${i}`; let arg_alias = `$$arg${i}`;
args.push(b.id(arg_alias)); args.push(b.id(arg_alias));
const paths = destructure(argument, b.maybe_call(b.id(arg_alias))); const paths = extract_paths(argument, b.maybe_call(b.id(arg_alias)));
for (const path of paths) { for (const path of paths) {
const name = /** @type {Identifier} */ (path.node).name; const name = /** @type {Identifier} */ (path.node).name;

@ -2,7 +2,7 @@
/** @import { Binding } from '#compiler' */ /** @import { Binding } from '#compiler' */
/** @import { ComponentClientTransformState, ComponentContext } from '../types' */ /** @import { ComponentClientTransformState, ComponentContext } from '../types' */
import { dev } from '../../../../state.js'; import { dev } from '../../../../state.js';
import { destructure, extract_paths } from '../../../../utils/ast.js'; import { extract_paths } from '../../../../utils/ast.js';
import * as b from '#compiler/builders'; import * as b from '#compiler/builders';
import * as assert from '../../../../utils/assert.js'; import * as assert from '../../../../utils/assert.js';
import { get_rune } from '../../../scope.js'; import { get_rune } from '../../../scope.js';
@ -142,7 +142,7 @@ export function VariableDeclaration(node, context) {
); );
} else { } else {
const tmp = b.id(context.state.scope.generate('tmp')); const tmp = b.id(context.state.scope.generate('tmp'));
const paths = destructure(declarator.id, tmp); const paths = extract_paths(declarator.id, tmp);
declarations.push( declarations.push(
b.declarator(tmp, value), b.declarator(tmp, value),
...paths.map((path) => { ...paths.map((path) => {
@ -170,18 +170,12 @@ export function VariableDeclaration(node, context) {
) )
); );
} else { } else {
const paths = extract_paths(declarator.id);
const init = /** @type {CallExpression} */ (declarator.init); const init = /** @type {CallExpression} */ (declarator.init);
/** @type {Identifier} */
let id;
let rhs = value; let rhs = value;
if (rune === '$derived' && init.arguments[0].type === 'Identifier') { if (rune !== '$derived' || init.arguments[0].type !== 'Identifier') {
id = init.arguments[0]; const id = b.id(context.state.scope.generate('$$d'));
} else {
id = b.id(context.state.scope.generate('$$d'));
rhs = b.call('$.get', id); rhs = b.call('$.get', id);
declarations.push( declarations.push(
@ -189,10 +183,9 @@ export function VariableDeclaration(node, context) {
); );
} }
for (let i = 0; i < paths.length; i++) { for (const path of extract_paths(declarator.id, rhs)) {
const path = paths[i];
declarations.push( declarations.push(
b.declarator(path.node, b.call('$.derived', b.thunk(path.expression(rhs)))) b.declarator(path.node, b.call('$.derived', b.thunk(path.expression)))
); );
} }
} }
@ -225,7 +218,7 @@ export function VariableDeclaration(node, context) {
// Turn export let into props. It's really really weird because export let { x: foo, z: [bar]} = .. // Turn export let into props. It's really really weird because export let { x: foo, z: [bar]} = ..
// means that foo and bar are the props (i.e. the leafs are the prop names), not x and z. // means that foo and bar are the props (i.e. the leafs are the prop names), not x and z.
const tmp = b.id(context.state.scope.generate('tmp')); const tmp = b.id(context.state.scope.generate('tmp'));
const paths = destructure(declarator.id, tmp); const paths = extract_paths(declarator.id, tmp);
declarations.push( declarations.push(
b.declarator( b.declarator(
@ -305,7 +298,7 @@ function create_state_declarators(declarator, { scope, analysis }, value) {
} }
const tmp = b.id(scope.generate('tmp')); const tmp = b.id(scope.generate('tmp'));
const paths = destructure(declarator.id, tmp); const paths = extract_paths(declarator.id, tmp);
return [ return [
b.declarator(tmp, value), b.declarator(tmp, value),
...paths.map((path) => { ...paths.map((path) => {

@ -3,7 +3,7 @@
/** @import { Context } from '../types.js' */ /** @import { Context } from '../types.js' */
/** @import { ComponentAnalysis } from '../../../types.js' */ /** @import { ComponentAnalysis } from '../../../types.js' */
/** @import { Scope } from '../../../scope.js' */ /** @import { Scope } from '../../../scope.js' */
import { build_fallback, destructure } from '../../../../utils/ast.js'; import { build_fallback, extract_paths } from '../../../../utils/ast.js';
import * as b from '#compiler/builders'; import * as b from '#compiler/builders';
import { get_rune } from '../../../scope.js'; import { get_rune } from '../../../scope.js';
import { walk } from 'zimmerframe'; import { walk } from 'zimmerframe';
@ -121,7 +121,7 @@ export function VariableDeclaration(node, context) {
// Turn export let into props. It's really really weird because export let { x: foo, z: [bar]} = .. // Turn export let into props. It's really really weird because export let { x: foo, z: [bar]} = ..
// means that foo and bar are the props (i.e. the leafs are the prop names), not x and z. // means that foo and bar are the props (i.e. the leafs are the prop names), not x and z.
const tmp = b.id(context.state.scope.generate('tmp')); const tmp = b.id(context.state.scope.generate('tmp'));
const paths = destructure(declarator.id, tmp); const paths = extract_paths(declarator.id, tmp);
declarations.push( declarations.push(
b.declarator( b.declarator(
tmp, tmp,
@ -189,7 +189,7 @@ function create_state_declarators(declarator, scope, value) {
} }
const tmp = b.id(scope.generate('tmp')); const tmp = b.id(scope.generate('tmp'));
const paths = destructure(declarator.id, tmp); const paths = extract_paths(declarator.id, tmp);
return [ return [
b.declarator(tmp, value), // TODO inject declarator for opts, so we can use it below b.declarator(tmp, value), // TODO inject declarator for opts, so we can use it below
...paths.map((path) => { ...paths.map((path) => {

@ -1,7 +1,7 @@
/** @import { AssignmentExpression, AssignmentOperator, Expression, Node, Pattern } from 'estree' */ /** @import { AssignmentExpression, AssignmentOperator, Expression, Node, Pattern } from 'estree' */
/** @import { Context as ClientContext } from '../client/types.js' */ /** @import { Context as ClientContext } from '../client/types.js' */
/** @import { Context as ServerContext } from '../server/types.js' */ /** @import { Context as ServerContext } from '../server/types.js' */
import { destructure, is_expression_async } from '../../../utils/ast.js'; import { extract_paths, is_expression_async } from '../../../utils/ast.js';
import * as b from '#compiler/builders'; import * as b from '#compiler/builders';
/** /**
@ -23,7 +23,7 @@ export function visit_assignment_expression(node, context, build_assignment) {
let changed = false; let changed = false;
const assignments = destructure(node.left, rhs).map((path) => { const assignments = extract_paths(node.left, rhs).map((path) => {
const value = path.expression; const value = path.expression;
let assignment = build_assignment('=', path.node, value, context); let assignment = build_assignment('=', path.node, value, context);

@ -227,176 +227,6 @@ export function extract_identifiers_from_destructuring(node, nodes = []) {
* @property {ESTree.Identifier | ESTree.MemberExpression} node The node the destructuring path end in. Can be a member expression only for assignment expressions * @property {ESTree.Identifier | ESTree.MemberExpression} node The node the destructuring path end in. Can be a member expression only for assignment expressions
* @property {boolean} is_rest `true` if this is a `...rest` destructuring * @property {boolean} is_rest `true` if this is a `...rest` destructuring
* @property {boolean} has_default_value `true` if this has a fallback value like `const { foo = 'bar } = ..` * @property {boolean} has_default_value `true` if this has a fallback value like `const { foo = 'bar } = ..`
* @property {(expression: ESTree.Expression) => ESTree.Identifier | ESTree.MemberExpression | ESTree.CallExpression | ESTree.AwaitExpression} expression Returns an expression which walks the path starting at the given expression.
* 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
* Will be an await expression in case of an async default value (`const { foo = await bar } = ...`)
* @property {(expression: ESTree.Expression) => ESTree.Identifier | ESTree.MemberExpression | ESTree.CallExpression | ESTree.AwaitExpression} update_expression Like `expression` but without default values.
*/
/**
* Extracts all destructured assignments from a pattern.
* @param {ESTree.Node} param
* @returns {DestructuredAssignment[]}
*/
export function extract_paths(param) {
return _extract_paths(
[],
param,
(node) => /** @type {ESTree.Identifier | ESTree.MemberExpression} */ (node),
(node) => /** @type {ESTree.Identifier | ESTree.MemberExpression} */ (node),
false
);
}
/**
* @param {DestructuredAssignment[]} assignments
* @param {ESTree.Node} param
* @param {DestructuredAssignment['expression']} expression
* @param {DestructuredAssignment['update_expression']} update_expression
* @param {boolean} has_default_value
* @returns {DestructuredAssignment[]}
*/
function _extract_paths(assignments = [], param, expression, update_expression, has_default_value) {
switch (param.type) {
case 'Identifier':
case 'MemberExpression':
assignments.push({
node: param,
is_rest: false,
has_default_value,
expression,
update_expression
});
break;
case 'ObjectPattern':
for (const prop of param.properties) {
if (prop.type === 'RestElement') {
/** @type {DestructuredAssignment['expression']} */
const rest_expression = (object) => {
/** @type {ESTree.Expression[]} */
const props = [];
for (const p of param.properties) {
if (p.type === 'Property' && p.key.type !== 'PrivateIdentifier') {
if (p.key.type === 'Identifier' && !p.computed) {
props.push(b.literal(p.key.name));
} else if (p.key.type === 'Literal') {
props.push(b.literal(String(p.key.value)));
} else {
props.push(b.call('String', p.key));
}
}
}
return b.call('$.exclude_from_object', expression(object), b.array(props));
};
if (prop.argument.type === 'Identifier') {
assignments.push({
node: prop.argument,
is_rest: true,
has_default_value,
expression: rest_expression,
update_expression: rest_expression
});
} else {
_extract_paths(
assignments,
prop.argument,
rest_expression,
rest_expression,
has_default_value
);
}
} else {
/** @type {DestructuredAssignment['expression']} */
const object_expression = (object) =>
b.member(expression(object), prop.key, prop.computed || prop.key.type !== 'Identifier');
_extract_paths(
assignments,
prop.value,
object_expression,
object_expression,
has_default_value
);
}
}
break;
case 'ArrayPattern':
for (let i = 0; i < param.elements.length; i += 1) {
const element = param.elements[i];
if (element) {
if (element.type === 'RestElement') {
/** @type {DestructuredAssignment['expression']} */
const rest_expression = (object) =>
b.call(b.member(expression(object), 'slice'), b.literal(i));
if (element.argument.type === 'Identifier') {
assignments.push({
node: element.argument,
is_rest: true,
has_default_value,
expression: rest_expression,
update_expression: rest_expression
});
} else {
_extract_paths(
assignments,
element.argument,
rest_expression,
rest_expression,
has_default_value
);
}
} else {
/** @type {DestructuredAssignment['expression']} */
const array_expression = (object) => b.member(expression(object), b.literal(i), true);
_extract_paths(
assignments,
element,
array_expression,
array_expression,
has_default_value
);
}
}
}
break;
case 'AssignmentPattern': {
/** @type {DestructuredAssignment['expression']} */
const fallback_expression = (object) => build_fallback(expression(object), param.right);
if (param.left.type === 'Identifier') {
assignments.push({
node: param.left,
is_rest: false,
has_default_value: true,
expression: fallback_expression,
update_expression
});
} else {
_extract_paths(assignments, param.left, fallback_expression, update_expression, true);
}
break;
}
}
return assignments;
}
/**
* Represents the path of a destructured assignment from either a declaration
* or assignment expression. For example, given `const { foo: { bar: baz } } = quux`,
* the path of `baz` is `foo.bar`
* @typedef {Object} DestructuredAssignment2
* @property {ESTree.Identifier | ESTree.MemberExpression} node The node the destructuring path end in. Can be a member expression only for assignment expressions
* @property {boolean} is_rest `true` if this is a `...rest` destructuring
* @property {boolean} has_default_value `true` if this has a fallback value like `const { foo = 'bar } = ..`
* @property {ESTree.Expression} expression Returns an expression which walks the path starting at the given expression. * @property {ESTree.Expression} expression Returns an expression which walks the path starting at the given expression.
* 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 * 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
* Will be an await expression in case of an async default value (`const { foo = await bar } = ...`) * Will be an await expression in case of an async default value (`const { foo = await bar } = ...`)
@ -407,21 +237,21 @@ function _extract_paths(assignments = [], param, expression, update_expression,
* Extracts all destructured assignments from a pattern. * Extracts all destructured assignments from a pattern.
* @param {ESTree.Node} param * @param {ESTree.Node} param
* @param {ESTree.Expression} initial * @param {ESTree.Expression} initial
* @returns {DestructuredAssignment2[]} * @returns {DestructuredAssignment[]}
*/ */
export function destructure(param, initial) { export function extract_paths(param, initial) {
return _destructure([], param, initial, initial, false); return _extract_paths([], param, initial, initial, false);
} }
/** /**
* @param {DestructuredAssignment2[]} assignments * @param {DestructuredAssignment[]} assignments
* @param {ESTree.Node} param * @param {ESTree.Node} param
* @param {ESTree.Expression} expression * @param {ESTree.Expression} expression
* @param {ESTree.Expression} update_expression * @param {ESTree.Expression} update_expression
* @param {boolean} has_default_value * @param {boolean} has_default_value
* @returns {DestructuredAssignment2[]} * @returns {DestructuredAssignment[]}
*/ */
function _destructure(assignments = [], param, expression, update_expression, has_default_value) { function _extract_paths(assignments = [], param, expression, update_expression, has_default_value) {
switch (param.type) { switch (param.type) {
case 'Identifier': case 'Identifier':
case 'MemberExpression': case 'MemberExpression':
@ -463,7 +293,7 @@ function _destructure(assignments = [], param, expression, update_expression, ha
update_expression: rest_expression update_expression: rest_expression
}); });
} else { } else {
_destructure( _extract_paths(
assignments, assignments,
prop.argument, prop.argument,
rest_expression, rest_expression,
@ -478,7 +308,7 @@ function _destructure(assignments = [], param, expression, update_expression, ha
prop.computed || prop.key.type !== 'Identifier' prop.computed || prop.key.type !== 'Identifier'
); );
_destructure( _extract_paths(
assignments, assignments,
prop.value, prop.value,
object_expression, object_expression,
@ -506,7 +336,7 @@ function _destructure(assignments = [], param, expression, update_expression, ha
update_expression: rest_expression update_expression: rest_expression
}); });
} else { } else {
_destructure( _extract_paths(
assignments, assignments,
element.argument, element.argument,
rest_expression, rest_expression,
@ -517,7 +347,7 @@ function _destructure(assignments = [], param, expression, update_expression, ha
} else { } else {
const array_expression = b.member(expression, b.literal(i), true); const array_expression = b.member(expression, b.literal(i), true);
_destructure( _extract_paths(
assignments, assignments,
element, element,
array_expression, array_expression,
@ -542,7 +372,7 @@ function _destructure(assignments = [], param, expression, update_expression, ha
update_expression update_expression
}); });
} else { } else {
_destructure(assignments, param.left, fallback_expression, update_expression, true); _extract_paths(assignments, param.left, fallback_expression, update_expression, true);
} }
break; break;

Loading…
Cancel
Save