|
1 | 1 | import is_reference from 'is-reference';
|
2 | 2 | import { walk } from 'zimmerframe';
|
3 | 3 | import { error } from '../../errors.js';
|
4 |
| -import * as assert from '../../utils/assert.js'; |
5 | 4 | import {
|
6 | 5 | extract_identifiers,
|
| 6 | + extract_all_identifiers_from_expression, |
7 | 7 | extract_paths,
|
8 | 8 | is_event_attribute,
|
9 | 9 | is_text_attribute,
|
@@ -1008,39 +1008,52 @@ const common_visitors = {
|
1008 | 1008 |
|
1009 | 1009 | if (node.name !== 'group') return;
|
1010 | 1010 |
|
| 1011 | + // Traverse the path upwards and find all EachBlocks who are (indirectly) contributing to bind:group, |
| 1012 | + // i.e. one of their declarations is referenced in the binding. This allows group bindings to work |
| 1013 | + // correctly when referencing a variable declared in an EachBlock by using the index of the each block |
| 1014 | + // entries as keys. |
1011 | 1015 | i = context.path.length;
|
| 1016 | + const each_blocks = []; |
| 1017 | + const expression_ids = extract_all_identifiers_from_expression(node.expression); |
| 1018 | + let ids = expression_ids; |
1012 | 1019 | while (i--) {
|
1013 | 1020 | const parent = context.path[i];
|
1014 | 1021 | if (parent.type === 'EachBlock') {
|
1015 |
| - parent.metadata.contains_group_binding = true; |
1016 |
| - for (const binding of parent.metadata.references) { |
1017 |
| - binding.mutated = true; |
| 1022 | + const references = ids.filter((id) => parent.metadata.declarations.has(id.name)); |
| 1023 | + if (references.length > 0) { |
| 1024 | + parent.metadata.contains_group_binding = true; |
| 1025 | + for (const binding of parent.metadata.references) { |
| 1026 | + binding.mutated = true; |
| 1027 | + } |
| 1028 | + each_blocks.push(parent); |
| 1029 | + ids = ids.filter((id) => !references.includes(id)); |
| 1030 | + ids.push(...extract_all_identifiers_from_expression(parent.expression)); |
1018 | 1031 | }
|
1019 | 1032 | }
|
1020 | 1033 | }
|
1021 | 1034 |
|
1022 |
| - const id = object(node.expression); |
1023 |
| - |
1024 |
| - const binding = id === null ? null : context.state.scope.get(id.name); |
1025 |
| - assert.ok(binding); |
1026 |
| - |
1027 |
| - let group = context.state.analysis.binding_groups.get(binding); |
1028 |
| - if (!group) { |
1029 |
| - group = { |
1030 |
| - name: context.state.scope.root.unique('binding_group'), |
1031 |
| - directives: [] |
1032 |
| - }; |
1033 |
| - |
1034 |
| - context.state.analysis.binding_groups.set(binding, group); |
| 1035 | + // The identifiers that make up the binding expression form they key for the binding group. |
| 1036 | + // If the same identifiers in the same order are used in another bind:group, they will be in the same group. |
| 1037 | + // (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, |
| 1038 | + // but this is a limitation of the current static analysis we do; it also never worked in Svelte 4) |
| 1039 | + const bindings = expression_ids.map((id) => context.state.scope.get(id.name)); |
| 1040 | + let group_name; |
| 1041 | + outer: for (const [b, group] of context.state.analysis.binding_groups) { |
| 1042 | + if (b.length !== bindings.length) continue; |
| 1043 | + for (let i = 0; i < bindings.length; i++) { |
| 1044 | + if (bindings[i] !== b[i]) continue outer; |
| 1045 | + } |
| 1046 | + group_name = group; |
1035 | 1047 | }
|
1036 | 1048 |
|
1037 |
| - group.directives.push(node); |
| 1049 | + if (!group_name) { |
| 1050 | + group_name = context.state.scope.root.unique('binding_group'); |
| 1051 | + context.state.analysis.binding_groups.set(bindings, group_name); |
| 1052 | + } |
1038 | 1053 |
|
1039 | 1054 | node.metadata = {
|
1040 |
| - binding_group_name: group.name, |
1041 |
| - parent_each_blocks: /** @type {import('#compiler').EachBlock[]} */ ( |
1042 |
| - context.path.filter((p) => p.type === 'EachBlock') |
1043 |
| - ) |
| 1055 | + binding_group_name: group_name, |
| 1056 | + parent_each_blocks: each_blocks |
1044 | 1057 | };
|
1045 | 1058 | },
|
1046 | 1059 | ArrowFunctionExpression: function_visitor,
|
|
0 commit comments