Skip to content

Commit 4fcd1cb

Browse files
trueadmRich-Harris
andauthored
fix: add compiler error for each block mutations in runes mode (#10428)
* fix: improve code generation for mutation to each block reference * fix: add compiler error for each block mutations in runes mode * lint * lint * lint * use existing validate_assignment logic * assignment, not mutation --------- Co-authored-by: Rich Harris <[email protected]>
1 parent 976e807 commit 4fcd1cb

File tree

5 files changed

+40
-17
lines changed

5 files changed

+40
-17
lines changed

.changeset/violet-pigs-jam.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"svelte": patch
3+
---
4+
5+
fix: add compiler error for each block mutations in runes mode

packages/svelte/src/compiler/errors.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,8 @@ const runes = {
206206
}`,
207207
/** @param {string} name */
208208
'invalid-runes-mode-import': (name) => `${name} cannot be used in runes mode`,
209-
'duplicate-props-rune': () => `Cannot use $props() more than once`
209+
'duplicate-props-rune': () => `Cannot use $props() more than once`,
210+
'invalid-each-assignment': () => `Cannot reassign each block argument in runes mode`
210211
};
211212

212213
/** @satisfies {Errors} */

packages/svelte/src/compiler/phases/2-analyze/validation.js

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,9 @@ function is_tag_valid_with_parent(tag, parent_tag) {
334334
* @type {import('zimmerframe').Visitors<import('#compiler').SvelteNode, import('./types.js').AnalysisState>}
335335
*/
336336
const validation = {
337+
AssignmentExpression(node, context) {
338+
validate_assignment(node, node.left, context.state);
339+
},
337340
BindDirective(node, context) {
338341
validate_no_const_assignment(node, node.expression, context.state.scope, true);
339342

@@ -655,6 +658,9 @@ const validation = {
655658
error(child, 'invalid-title-content');
656659
}
657660
},
661+
UpdateExpression(node, context) {
662+
validate_assignment(node, node.argument, context.state);
663+
},
658664
ExpressionTag(node, context) {
659665
if (!node.parent) return;
660666
if (context.state.parent_element) {
@@ -908,39 +914,35 @@ function validate_no_const_assignment(node, argument, scope, is_binding) {
908914
function validate_assignment(node, argument, state) {
909915
validate_no_const_assignment(node, argument, state.scope, false);
910916

911-
let left = /** @type {import('estree').Expression | import('estree').Super} */ (argument);
912-
913-
if (left.type === 'Identifier') {
914-
const binding = state.scope.get(left.name);
917+
if (state.analysis.runes && argument.type === 'Identifier') {
918+
const binding = state.scope.get(argument.name);
915919
if (binding?.kind === 'derived') {
916920
error(node, 'invalid-derived-assignment');
917921
}
922+
923+
if (binding?.kind === 'each') {
924+
error(node, 'invalid-each-assignment');
925+
}
918926
}
919927

928+
let object = /** @type {import('estree').Expression | import('estree').Super} */ (argument);
929+
920930
/** @type {import('estree').Expression | import('estree').PrivateIdentifier | null} */
921931
let property = null;
922932

923-
while (left.type === 'MemberExpression') {
924-
property = left.property;
925-
left = left.object;
933+
while (object.type === 'MemberExpression') {
934+
property = object.property;
935+
object = object.object;
926936
}
927937

928-
if (left.type === 'ThisExpression' && property?.type === 'PrivateIdentifier') {
938+
if (object.type === 'ThisExpression' && property?.type === 'PrivateIdentifier') {
929939
if (state.private_derived_state.includes(property.name)) {
930940
error(node, 'invalid-derived-assignment');
931941
}
932942
}
933943
}
934944

935945
export const validation_runes = merge(validation, a11y_validators, {
936-
AssignmentExpression(node, { state, path }) {
937-
const parent = path.at(-1);
938-
if (parent && parent.type === 'ConstTag') return;
939-
validate_assignment(node, node.left, state);
940-
},
941-
UpdateExpression(node, { state }) {
942-
validate_assignment(node, node.argument, state);
943-
},
944946
LabeledStatement(node, { path }) {
945947
if (node.label.name !== '$' || path.at(-1)?.type !== 'Program') return;
946948
error(node, 'invalid-legacy-reactive-statement');
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { test } from '../../test';
2+
3+
export default test({
4+
error: {
5+
code: 'invalid-each-assignment',
6+
message: 'Cannot reassign each block argument in runes mode'
7+
}
8+
});
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<script>
2+
let arr = $state([1,2,3]);
3+
</script>
4+
5+
{#each arr as value}
6+
<button onclick={() => value += 1}>click</button>
7+
{/each}

0 commit comments

Comments
 (0)