|
|
|
@ -17,106 +17,6 @@ import { is_content_editable_binding, is_svg } from '../../../../utils.js';
|
|
|
|
|
* @param {Context} context
|
|
|
|
|
*/
|
|
|
|
|
export function BindDirective(node, context) {
|
|
|
|
|
if (Array.isArray(node.expression)) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
validate_no_const_assignment(node, node.expression, context.state.scope, true);
|
|
|
|
|
|
|
|
|
|
const assignee = node.expression;
|
|
|
|
|
const left = object(assignee);
|
|
|
|
|
|
|
|
|
|
if (left === null) {
|
|
|
|
|
e.bind_invalid_expression(node);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const binding = context.state.scope.get(left.name);
|
|
|
|
|
|
|
|
|
|
if (assignee.type === 'Identifier') {
|
|
|
|
|
// reassignment
|
|
|
|
|
if (
|
|
|
|
|
node.name !== 'this' && // bind:this also works for regular variables
|
|
|
|
|
(!binding ||
|
|
|
|
|
(binding.kind !== 'state' &&
|
|
|
|
|
binding.kind !== 'raw_state' &&
|
|
|
|
|
binding.kind !== 'prop' &&
|
|
|
|
|
binding.kind !== 'bindable_prop' &&
|
|
|
|
|
binding.kind !== 'each' &&
|
|
|
|
|
binding.kind !== 'store_sub' &&
|
|
|
|
|
!binding.updated)) // TODO wut?
|
|
|
|
|
) {
|
|
|
|
|
e.bind_invalid_value(node.expression);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (context.state.analysis.runes && binding?.kind === 'each') {
|
|
|
|
|
e.each_item_invalid_assignment(node);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (binding?.kind === 'snippet') {
|
|
|
|
|
e.snippet_parameter_assignment(node);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (node.name === 'group') {
|
|
|
|
|
if (!binding) {
|
|
|
|
|
throw new Error('Cannot find declaration for bind:group');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Traverse the path upwards and find all EachBlocks who are (indirectly) contributing to bind:group,
|
|
|
|
|
// i.e. one of their declarations is referenced in the binding. This allows group bindings to work
|
|
|
|
|
// correctly when referencing a variable declared in an EachBlock by using the index of the each block
|
|
|
|
|
// entries as keys.
|
|
|
|
|
const each_blocks = [];
|
|
|
|
|
const [keypath, expression_ids] = extract_all_identifiers_from_expression(node.expression);
|
|
|
|
|
let ids = expression_ids;
|
|
|
|
|
|
|
|
|
|
let i = context.path.length;
|
|
|
|
|
while (i--) {
|
|
|
|
|
const parent = context.path[i];
|
|
|
|
|
|
|
|
|
|
if (parent.type === 'EachBlock') {
|
|
|
|
|
const references = ids.filter((id) => parent.metadata.declarations.has(id.name));
|
|
|
|
|
|
|
|
|
|
if (references.length > 0) {
|
|
|
|
|
parent.metadata.contains_group_binding = true;
|
|
|
|
|
|
|
|
|
|
each_blocks.push(parent);
|
|
|
|
|
ids = ids.filter((id) => !references.includes(id));
|
|
|
|
|
ids.push(...extract_all_identifiers_from_expression(parent.expression)[1]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// The identifiers that make up the binding expression form they key for the binding group.
|
|
|
|
|
// If the same identifiers in the same order are used in another bind:group, they will be in the same group.
|
|
|
|
|
// (there's an edge case where `bind:group={a[i]}` will be in a different group than `bind:group={a[j]}` even when i == j,
|
|
|
|
|
// but this is a limitation of the current static analysis we do; it also never worked in Svelte 4)
|
|
|
|
|
const bindings = expression_ids.map((id) => context.state.scope.get(id.name));
|
|
|
|
|
let group_name;
|
|
|
|
|
|
|
|
|
|
outer: for (const [[key, b], group] of context.state.analysis.binding_groups) {
|
|
|
|
|
if (b.length !== bindings.length || key !== keypath) continue;
|
|
|
|
|
for (let i = 0; i < bindings.length; i++) {
|
|
|
|
|
if (bindings[i] !== b[i]) continue outer;
|
|
|
|
|
}
|
|
|
|
|
group_name = group;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!group_name) {
|
|
|
|
|
group_name = context.state.scope.root.unique('binding_group');
|
|
|
|
|
context.state.analysis.binding_groups.set([keypath, bindings], group_name);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
node.metadata = {
|
|
|
|
|
binding_group_name: group_name,
|
|
|
|
|
parent_each_blocks: each_blocks
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (binding?.kind === 'each' && binding.metadata?.inside_rest) {
|
|
|
|
|
w.bind_invalid_each_rest(binding.node, binding.node.name);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const parent = context.path.at(-1);
|
|
|
|
|
|
|
|
|
|
if (
|
|
|
|
@ -222,5 +122,105 @@ export function BindDirective(node, context) {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (Array.isArray(node.expression)) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
validate_no_const_assignment(node, node.expression, context.state.scope, true);
|
|
|
|
|
|
|
|
|
|
const assignee = node.expression;
|
|
|
|
|
const left = object(assignee);
|
|
|
|
|
|
|
|
|
|
if (left === null) {
|
|
|
|
|
e.bind_invalid_expression(node);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const binding = context.state.scope.get(left.name);
|
|
|
|
|
|
|
|
|
|
if (assignee.type === 'Identifier') {
|
|
|
|
|
// reassignment
|
|
|
|
|
if (
|
|
|
|
|
node.name !== 'this' && // bind:this also works for regular variables
|
|
|
|
|
(!binding ||
|
|
|
|
|
(binding.kind !== 'state' &&
|
|
|
|
|
binding.kind !== 'raw_state' &&
|
|
|
|
|
binding.kind !== 'prop' &&
|
|
|
|
|
binding.kind !== 'bindable_prop' &&
|
|
|
|
|
binding.kind !== 'each' &&
|
|
|
|
|
binding.kind !== 'store_sub' &&
|
|
|
|
|
!binding.updated)) // TODO wut?
|
|
|
|
|
) {
|
|
|
|
|
e.bind_invalid_value(node.expression);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (context.state.analysis.runes && binding?.kind === 'each') {
|
|
|
|
|
e.each_item_invalid_assignment(node);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (binding?.kind === 'snippet') {
|
|
|
|
|
e.snippet_parameter_assignment(node);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (node.name === 'group') {
|
|
|
|
|
if (!binding) {
|
|
|
|
|
throw new Error('Cannot find declaration for bind:group');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Traverse the path upwards and find all EachBlocks who are (indirectly) contributing to bind:group,
|
|
|
|
|
// i.e. one of their declarations is referenced in the binding. This allows group bindings to work
|
|
|
|
|
// correctly when referencing a variable declared in an EachBlock by using the index of the each block
|
|
|
|
|
// entries as keys.
|
|
|
|
|
const each_blocks = [];
|
|
|
|
|
const [keypath, expression_ids] = extract_all_identifiers_from_expression(node.expression);
|
|
|
|
|
let ids = expression_ids;
|
|
|
|
|
|
|
|
|
|
let i = context.path.length;
|
|
|
|
|
while (i--) {
|
|
|
|
|
const parent = context.path[i];
|
|
|
|
|
|
|
|
|
|
if (parent.type === 'EachBlock') {
|
|
|
|
|
const references = ids.filter((id) => parent.metadata.declarations.has(id.name));
|
|
|
|
|
|
|
|
|
|
if (references.length > 0) {
|
|
|
|
|
parent.metadata.contains_group_binding = true;
|
|
|
|
|
|
|
|
|
|
each_blocks.push(parent);
|
|
|
|
|
ids = ids.filter((id) => !references.includes(id));
|
|
|
|
|
ids.push(...extract_all_identifiers_from_expression(parent.expression)[1]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// The identifiers that make up the binding expression form they key for the binding group.
|
|
|
|
|
// If the same identifiers in the same order are used in another bind:group, they will be in the same group.
|
|
|
|
|
// (there's an edge case where `bind:group={a[i]}` will be in a different group than `bind:group={a[j]}` even when i == j,
|
|
|
|
|
// but this is a limitation of the current static analysis we do; it also never worked in Svelte 4)
|
|
|
|
|
const bindings = expression_ids.map((id) => context.state.scope.get(id.name));
|
|
|
|
|
let group_name;
|
|
|
|
|
|
|
|
|
|
outer: for (const [[key, b], group] of context.state.analysis.binding_groups) {
|
|
|
|
|
if (b.length !== bindings.length || key !== keypath) continue;
|
|
|
|
|
for (let i = 0; i < bindings.length; i++) {
|
|
|
|
|
if (bindings[i] !== b[i]) continue outer;
|
|
|
|
|
}
|
|
|
|
|
group_name = group;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!group_name) {
|
|
|
|
|
group_name = context.state.scope.root.unique('binding_group');
|
|
|
|
|
context.state.analysis.binding_groups.set([keypath, bindings], group_name);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
node.metadata = {
|
|
|
|
|
binding_group_name: group_name,
|
|
|
|
|
parent_each_blocks: each_blocks
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (binding?.kind === 'each' && binding.metadata?.inside_rest) {
|
|
|
|
|
w.bind_invalid_each_rest(binding.node, binding.node.name);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
context.next();
|
|
|
|
|
}
|
|
|
|
|