From 989ff2facfc3cd3fcc02c6887d16cb08de65dda5 Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Thu, 14 Nov 2024 20:41:15 +0000 Subject: [PATCH] move validation --- .../2-analyze/visitors/BindDirective.js | 200 +++++++++--------- 1 file changed, 100 insertions(+), 100 deletions(-) diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/BindDirective.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/BindDirective.js index 2c54b0e48b..e3c6629251 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/BindDirective.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/BindDirective.js @@ -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(); }