mirror of https://github.com/sveltejs/svelte
parent
c6fd759ddf
commit
b3aa0fe9af
@ -1,239 +1,227 @@
|
|||||||
import { x } from 'code-red';
|
import { x } from 'code-red';
|
||||||
import { Node, Identifier, Expression, PrivateIdentifier, Pattern } from 'estree';
|
|
||||||
import { walk } from 'estree-walker';
|
import { walk } from 'estree-walker';
|
||||||
import is_reference, { NodeWithPropertyDefinition } from 'is-reference';
|
import is_reference from 'is-reference';
|
||||||
import { clone } from '../../../utils/clone';
|
import { clone } from '../../../utils/clone.js';
|
||||||
import Component from '../../Component';
|
import flatten_reference from '../../utils/flatten_reference.js';
|
||||||
import flatten_reference from '../../utils/flatten_reference';
|
/**
|
||||||
import TemplateScope from './TemplateScope';
|
* @param {{
|
||||||
|
* contexts: Context[];
|
||||||
export type Context = DestructuredVariable | ComputedProperty;
|
* node: import('estree').Pattern;
|
||||||
|
* modifier?: DestructuredVariable['modifier'];
|
||||||
interface ComputedProperty {
|
* default_modifier?: DestructuredVariable['default_modifier'];
|
||||||
type: 'ComputedProperty';
|
* scope: import('./TemplateScope.js').default;
|
||||||
property_name: Identifier;
|
* component: import('../../Component.js').default;
|
||||||
key: Expression | PrivateIdentifier;
|
* context_rest_properties: Map<string, import('estree').Node>;
|
||||||
|
* in_rest_element?: boolean;
|
||||||
|
* }}
|
||||||
|
*/
|
||||||
|
export function unpack_destructuring({ contexts, node, modifier = (node) => node, default_modifier = (node) => node, scope, component, context_rest_properties, in_rest_element = false }) {
|
||||||
|
if (!node)
|
||||||
|
return;
|
||||||
|
if (node.type === 'Identifier') {
|
||||||
|
contexts.push({
|
||||||
|
type: 'DestructuredVariable',
|
||||||
|
key: /** @type {import('estree').Identifier} */ (node),
|
||||||
|
modifier,
|
||||||
|
default_modifier
|
||||||
|
});
|
||||||
|
if (in_rest_element) {
|
||||||
|
context_rest_properties.set(node.name, node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (node.type === 'ArrayPattern') {
|
||||||
|
node.elements.forEach((element, i) => {
|
||||||
|
if (!element) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if (element.type === 'RestElement') {
|
||||||
|
unpack_destructuring({
|
||||||
|
contexts,
|
||||||
|
node: element.argument,
|
||||||
|
modifier: (node) => /** @type {import('estree').Node} */ (x `${modifier(node)}.slice(${i})`),
|
||||||
|
default_modifier,
|
||||||
|
scope,
|
||||||
|
component,
|
||||||
|
context_rest_properties,
|
||||||
|
in_rest_element: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else if (element.type === 'AssignmentPattern') {
|
||||||
|
const n = contexts.length;
|
||||||
|
mark_referenced(element.right, scope, component);
|
||||||
|
unpack_destructuring({
|
||||||
|
contexts,
|
||||||
|
node: element.left,
|
||||||
|
modifier: (node) => x `${modifier(node)}[${i}]`,
|
||||||
|
default_modifier: (node, to_ctx) =>
|
||||||
|
/** @type {import('estree').Node} */ (x `${node} !== undefined ? ${node} : ${update_reference(contexts, n, element.right, to_ctx)}`),
|
||||||
|
scope,
|
||||||
|
component,
|
||||||
|
context_rest_properties,
|
||||||
|
in_rest_element
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
unpack_destructuring({
|
||||||
|
contexts,
|
||||||
|
node: element,
|
||||||
|
modifier: (node) => /** @type {import('estree').Node} */ (x `${modifier(node)}[${i}]`),
|
||||||
|
default_modifier,
|
||||||
|
scope,
|
||||||
|
component,
|
||||||
|
context_rest_properties,
|
||||||
|
in_rest_element
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else if (node.type === 'ObjectPattern') {
|
||||||
|
const used_properties = [];
|
||||||
|
node.properties.forEach((property) => {
|
||||||
|
if (property.type === 'RestElement') {
|
||||||
|
unpack_destructuring({
|
||||||
|
contexts,
|
||||||
|
node: property.argument,
|
||||||
|
modifier: (node) =>
|
||||||
|
/** @type {import('estree').Node} */ (x `@object_without_properties(${modifier(node)}, [${used_properties}])`),
|
||||||
|
default_modifier,
|
||||||
|
scope,
|
||||||
|
component,
|
||||||
|
context_rest_properties,
|
||||||
|
in_rest_element: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else if (property.type === 'Property') {
|
||||||
|
const key = property.key;
|
||||||
|
const value = property.value;
|
||||||
|
|
||||||
|
/** @type {(node: import('estree').Node) => import('estree').Node} */
|
||||||
|
let new_modifier;
|
||||||
|
if (property.computed) {
|
||||||
|
// e.g { [computedProperty]: ... }
|
||||||
|
const property_name = component.get_unique_name('computed_property');
|
||||||
|
contexts.push({
|
||||||
|
type: 'ComputedProperty',
|
||||||
|
property_name,
|
||||||
|
key
|
||||||
|
});
|
||||||
|
new_modifier = (node) => x `${modifier(node)}[${property_name}]`;
|
||||||
|
used_properties.push(x `${property_name}`);
|
||||||
|
}
|
||||||
|
else if (key.type === 'Identifier') {
|
||||||
|
// e.g. { someProperty: ... }
|
||||||
|
const property_name = key.name;
|
||||||
|
new_modifier = (node) => x `${modifier(node)}.${property_name}`;
|
||||||
|
used_properties.push(x `"${property_name}"`);
|
||||||
|
}
|
||||||
|
else if (key.type === 'Literal') {
|
||||||
|
// e.g. { "property-in-quotes": ... } or { 14: ... }
|
||||||
|
const property_name = key.value;
|
||||||
|
new_modifier = (node) => x `${modifier(node)}["${property_name}"]`;
|
||||||
|
used_properties.push(x `"${property_name}"`);
|
||||||
|
}
|
||||||
|
if (value.type === 'AssignmentPattern') {
|
||||||
|
// e.g. { property = default } or { property: newName = default }
|
||||||
|
const n = contexts.length;
|
||||||
|
mark_referenced(value.right, scope, component);
|
||||||
|
unpack_destructuring({
|
||||||
|
contexts,
|
||||||
|
node: value.left,
|
||||||
|
modifier: new_modifier,
|
||||||
|
default_modifier: (node, to_ctx) =>
|
||||||
|
/** @type {import('estree').Node} */ (x `${node} !== undefined ? ${node} : ${update_reference(contexts, n, value.right, to_ctx)}`),
|
||||||
|
scope,
|
||||||
|
component,
|
||||||
|
context_rest_properties,
|
||||||
|
in_rest_element
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// e.g. { property } or { property: newName }
|
||||||
|
unpack_destructuring({
|
||||||
|
contexts,
|
||||||
|
node: value,
|
||||||
|
modifier: new_modifier,
|
||||||
|
default_modifier,
|
||||||
|
scope,
|
||||||
|
component,
|
||||||
|
context_rest_properties,
|
||||||
|
in_rest_element
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface DestructuredVariable {
|
/**
|
||||||
type: 'DestructuredVariable';
|
* @param {Context[]} contexts
|
||||||
key: Identifier;
|
* @param {number} n
|
||||||
name?: string;
|
* @param {import('estree').Expression} expression
|
||||||
modifier: (node: Node) => Node;
|
* @param {(name: string) => import('estree').Node} to_ctx
|
||||||
default_modifier: (node: Node, to_ctx: (name: string) => Node) => Node;
|
* @returns {import("C:/repos/svelte/svelte/node_modules/.pnpm/@types+estree@1.0.0/node_modules/@types/estree/index").import('estree').Node}
|
||||||
|
*/
|
||||||
|
function update_reference(contexts, n, expression, to_ctx) {
|
||||||
|
|
||||||
|
/** @param {import('estree').Identifier} node */
|
||||||
|
const find_from_context = (node) => {
|
||||||
|
for (let i = n; i < contexts.length; i++) {
|
||||||
|
const cur_context = contexts[i];
|
||||||
|
if (cur_context.type !== 'DestructuredVariable')
|
||||||
|
continue;
|
||||||
|
const { key } = cur_context;
|
||||||
|
if (node.name === key.name) {
|
||||||
|
throw new Error(`Cannot access '${node.name}' before initialization`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return to_ctx(node.name);
|
||||||
|
};
|
||||||
|
if (expression.type === 'Identifier') {
|
||||||
|
return find_from_context(expression);
|
||||||
|
}
|
||||||
|
// NOTE: avoid unnecessary deep clone?
|
||||||
|
expression = /** @type {import('estree').Expression} */ (clone(expression));
|
||||||
|
walk(expression, {
|
||||||
|
enter(node, parent) {
|
||||||
|
if (is_reference(/** @type {import('is-reference').NodeWithPropertyDefinition} */ (node), /** @type {import('is-reference').NodeWithPropertyDefinition} */ (parent))) {
|
||||||
|
this.replace(find_from_context(/** @type {import('estree').Identifier} */ (node)));
|
||||||
|
this.skip();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return expression;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function unpack_destructuring({
|
/**
|
||||||
contexts,
|
* @param {import('estree').Node} node
|
||||||
node,
|
* @param {import('./TemplateScope.js').default} scope
|
||||||
modifier = (node) => node,
|
* @param {import('../../Component.js').default} component
|
||||||
default_modifier = (node) => node,
|
*/
|
||||||
scope,
|
function mark_referenced(node, scope, component) {
|
||||||
component,
|
walk(node, {
|
||||||
context_rest_properties,
|
enter(node, parent) {
|
||||||
in_rest_element = false
|
if (is_reference(node, parent)) {
|
||||||
}: {
|
const { name } = flatten_reference(node);
|
||||||
contexts: Context[];
|
if (!scope.is_let(name) && !scope.names.has(name)) {
|
||||||
node: Pattern;
|
component.add_reference(node, name);
|
||||||
modifier?: DestructuredVariable['modifier'];
|
}
|
||||||
default_modifier?: DestructuredVariable['default_modifier'];
|
}
|
||||||
scope: TemplateScope;
|
}
|
||||||
component: Component;
|
});
|
||||||
context_rest_properties: Map<string, Node>;
|
|
||||||
in_rest_element?: boolean;
|
|
||||||
}) {
|
|
||||||
if (!node) return;
|
|
||||||
|
|
||||||
if (node.type === 'Identifier') {
|
|
||||||
contexts.push({
|
|
||||||
type: 'DestructuredVariable',
|
|
||||||
key: node as Identifier,
|
|
||||||
modifier,
|
|
||||||
default_modifier
|
|
||||||
});
|
|
||||||
|
|
||||||
if (in_rest_element) {
|
|
||||||
context_rest_properties.set(node.name, node);
|
|
||||||
}
|
|
||||||
} else if (node.type === 'ArrayPattern') {
|
|
||||||
node.elements.forEach((element: Pattern | null, i: number) => {
|
|
||||||
if (!element) {
|
|
||||||
return;
|
|
||||||
} else if (element.type === 'RestElement') {
|
|
||||||
unpack_destructuring({
|
|
||||||
contexts,
|
|
||||||
node: element.argument,
|
|
||||||
modifier: (node) => x`${modifier(node)}.slice(${i})` as Node,
|
|
||||||
default_modifier,
|
|
||||||
scope,
|
|
||||||
component,
|
|
||||||
context_rest_properties,
|
|
||||||
in_rest_element: true
|
|
||||||
});
|
|
||||||
} else if (element.type === 'AssignmentPattern') {
|
|
||||||
const n = contexts.length;
|
|
||||||
mark_referenced(element.right, scope, component);
|
|
||||||
|
|
||||||
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,
|
|
||||||
context_rest_properties,
|
|
||||||
in_rest_element
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
unpack_destructuring({
|
|
||||||
contexts,
|
|
||||||
node: element,
|
|
||||||
modifier: (node) => x`${modifier(node)}[${i}]` as Node,
|
|
||||||
default_modifier,
|
|
||||||
scope,
|
|
||||||
component,
|
|
||||||
context_rest_properties,
|
|
||||||
in_rest_element
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else if (node.type === 'ObjectPattern') {
|
|
||||||
const used_properties = [];
|
|
||||||
|
|
||||||
node.properties.forEach((property) => {
|
|
||||||
if (property.type === 'RestElement') {
|
|
||||||
unpack_destructuring({
|
|
||||||
contexts,
|
|
||||||
node: property.argument,
|
|
||||||
modifier: (node) =>
|
|
||||||
x`@object_without_properties(${modifier(node)}, [${used_properties}])` as Node,
|
|
||||||
default_modifier,
|
|
||||||
scope,
|
|
||||||
component,
|
|
||||||
context_rest_properties,
|
|
||||||
in_rest_element: true
|
|
||||||
});
|
|
||||||
} else if (property.type === 'Property') {
|
|
||||||
const key = property.key;
|
|
||||||
const value = property.value;
|
|
||||||
|
|
||||||
let new_modifier: (node: Node) => Node;
|
|
||||||
|
|
||||||
if (property.computed) {
|
|
||||||
// e.g { [computedProperty]: ... }
|
|
||||||
const property_name = component.get_unique_name('computed_property');
|
|
||||||
|
|
||||||
contexts.push({
|
|
||||||
type: 'ComputedProperty',
|
|
||||||
property_name,
|
|
||||||
key
|
|
||||||
});
|
|
||||||
|
|
||||||
new_modifier = (node) => x`${modifier(node)}[${property_name}]`;
|
|
||||||
used_properties.push(x`${property_name}`);
|
|
||||||
} else if (key.type === 'Identifier') {
|
|
||||||
// e.g. { someProperty: ... }
|
|
||||||
const property_name = key.name;
|
|
||||||
new_modifier = (node) => x`${modifier(node)}.${property_name}`;
|
|
||||||
used_properties.push(x`"${property_name}"`);
|
|
||||||
} else if (key.type === 'Literal') {
|
|
||||||
// e.g. { "property-in-quotes": ... } or { 14: ... }
|
|
||||||
const property_name = key.value;
|
|
||||||
new_modifier = (node) => x`${modifier(node)}["${property_name}"]`;
|
|
||||||
used_properties.push(x`"${property_name}"`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (value.type === 'AssignmentPattern') {
|
|
||||||
// e.g. { property = default } or { property: newName = default }
|
|
||||||
const n = contexts.length;
|
|
||||||
|
|
||||||
mark_referenced(value.right, scope, component);
|
|
||||||
|
|
||||||
unpack_destructuring({
|
|
||||||
contexts,
|
|
||||||
node: value.left,
|
|
||||||
modifier: new_modifier,
|
|
||||||
default_modifier: (node, to_ctx) =>
|
|
||||||
x`${node} !== undefined ? ${node} : ${update_reference(
|
|
||||||
contexts,
|
|
||||||
n,
|
|
||||||
value.right,
|
|
||||||
to_ctx
|
|
||||||
)}` as Node,
|
|
||||||
scope,
|
|
||||||
component,
|
|
||||||
context_rest_properties,
|
|
||||||
in_rest_element
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// e.g. { property } or { property: newName }
|
|
||||||
unpack_destructuring({
|
|
||||||
contexts,
|
|
||||||
node: value,
|
|
||||||
modifier: new_modifier,
|
|
||||||
default_modifier,
|
|
||||||
scope,
|
|
||||||
component,
|
|
||||||
context_rest_properties,
|
|
||||||
in_rest_element
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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 cur_context = contexts[i];
|
|
||||||
if (cur_context.type !== 'DestructuredVariable') continue;
|
|
||||||
const { key } = cur_context;
|
|
||||||
if (node.name === key.name) {
|
|
||||||
throw new Error(`Cannot access '${node.name}' before initialization`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return to_ctx(node.name);
|
|
||||||
};
|
|
||||||
|
|
||||||
if (expression.type === 'Identifier') {
|
|
||||||
return find_from_context(expression);
|
|
||||||
}
|
|
||||||
|
|
||||||
// NOTE: avoid unnecessary deep clone?
|
/** @typedef {DestructuredVariable | ComputedProperty} Context */
|
||||||
expression = clone(expression) as Expression;
|
|
||||||
walk(expression, {
|
/** @typedef {Object} ComputedProperty
|
||||||
enter(node, parent: Node) {
|
* @property {'ComputedProperty'} type
|
||||||
if (is_reference(node as NodeWithPropertyDefinition, parent as NodeWithPropertyDefinition)) {
|
* @property {Identifier} property_name
|
||||||
this.replace(find_from_context(node as Identifier));
|
* @property {Expression|PrivateIdentifier} key
|
||||||
this.skip();
|
*/
|
||||||
}
|
/** @typedef {Object} DestructuredVariable
|
||||||
}
|
* @property {'DestructuredVariable'} type
|
||||||
});
|
* @property {Identifier} key
|
||||||
|
* @property {string} [name]
|
||||||
return expression;
|
* @property {(node:Node)=>Node} modifier
|
||||||
}
|
* @property {(node:Node,to_ctx:(name:string)=>Node)=>Node} default_modifier
|
||||||
|
*/
|
||||||
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(node, name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
Loading…
Reference in new issue