diff --git a/.changeset/rare-carpets-wave.md b/.changeset/rare-carpets-wave.md new file mode 100644 index 0000000000..cfd90cad6a --- /dev/null +++ b/.changeset/rare-carpets-wave.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: allow mutation of private derived state diff --git a/packages/svelte/src/compiler/phases/2-analyze/index.js b/packages/svelte/src/compiler/phases/2-analyze/index.js index bce6672505..ad9db24e1e 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/index.js +++ b/packages/svelte/src/compiler/phases/2-analyze/index.js @@ -605,7 +605,7 @@ export function analyze_component(root, source, options) { has_props_rune: false, component_slots: new Set(), expression: null, - private_derived_state: [], + derived_state: [], function_depth: scope.function_depth, instance_scope: instance.scope, reactive_statement: null, @@ -676,7 +676,7 @@ export function analyze_component(root, source, options) { reactive_statements: analysis.reactive_statements, component_slots: new Set(), expression: null, - private_derived_state: [], + derived_state: [], function_depth: scope.function_depth }; diff --git a/packages/svelte/src/compiler/phases/2-analyze/types.d.ts b/packages/svelte/src/compiler/phases/2-analyze/types.d.ts index 1e71accb9f..14b14f9c84 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/types.d.ts +++ b/packages/svelte/src/compiler/phases/2-analyze/types.d.ts @@ -19,7 +19,7 @@ export interface AnalysisState { component_slots: Set; /** Information about the current expression/directive/block value */ expression: ExpressionMetadata | null; - private_derived_state: string[]; + derived_state: string[]; function_depth: number; // legacy stuff diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/ClassBody.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/ClassBody.js index d445af0ebf..ed397258f8 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/ClassBody.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/ClassBody.js @@ -8,20 +8,20 @@ import { get_rune } from '../../scope.js'; */ export function ClassBody(node, context) { /** @type {string[]} */ - const private_derived_state = []; + const derived_state = []; for (const definition of node.body) { if ( definition.type === 'PropertyDefinition' && - definition.key.type === 'PrivateIdentifier' && + (definition.key.type === 'PrivateIdentifier' || definition.key.type === 'Identifier') && definition.value?.type === 'CallExpression' ) { const rune = get_rune(definition.value, context.state.scope); if (rune === '$derived' || rune === '$derived.by') { - private_derived_state.push(definition.key.name); + derived_state.push(definition.key.name); } } } - context.next({ ...context.state, private_derived_state }); + context.next({ ...context.state, derived_state }); } diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/shared/utils.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/shared/utils.js index e265637c40..5fe2a8f24e 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/shared/utils.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/shared/utils.js @@ -35,20 +35,17 @@ export function validate_assignment(node, argument, state) { } } - let object = /** @type {Expression | Super} */ (argument); - - /** @type {Expression | PrivateIdentifier | null} */ - let property = null; - - while (object.type === 'MemberExpression') { - property = object.property; - object = object.object; - } - - if (object.type === 'ThisExpression' && property?.type === 'PrivateIdentifier') { - if (state.private_derived_state.includes(property.name)) { - e.constant_assignment(node, 'derived state'); - } + if ( + argument.type === 'MemberExpression' && + argument.object.type === 'ThisExpression' && + (((argument.property.type === 'PrivateIdentifier' || argument.property.type === 'Identifier') && + state.derived_state.includes(argument.property.name)) || + (argument.property.type === 'Literal' && + argument.property.value && + typeof argument.property.value === 'string' && + state.derived_state.includes(argument.property.value))) + ) { + e.constant_assignment(node, 'derived state'); } } diff --git a/packages/svelte/tests/validator/samples/mutate-derived-private-field/errors.json b/packages/svelte/tests/validator/samples/mutate-derived-private-field/errors.json new file mode 100644 index 0000000000..fe51488c70 --- /dev/null +++ b/packages/svelte/tests/validator/samples/mutate-derived-private-field/errors.json @@ -0,0 +1 @@ +[] diff --git a/packages/svelte/tests/validator/samples/mutate-derived-private-field/input.svelte b/packages/svelte/tests/validator/samples/mutate-derived-private-field/input.svelte new file mode 100644 index 0000000000..0a3df4bc1b --- /dev/null +++ b/packages/svelte/tests/validator/samples/mutate-derived-private-field/input.svelte @@ -0,0 +1,9 @@ + \ No newline at end of file diff --git a/packages/svelte/tests/validator/samples/reassign-derived-literal/errors.json b/packages/svelte/tests/validator/samples/reassign-derived-literal/errors.json new file mode 100644 index 0000000000..8681d84ab2 --- /dev/null +++ b/packages/svelte/tests/validator/samples/reassign-derived-literal/errors.json @@ -0,0 +1,14 @@ +[ + { + "code": "constant_assignment", + "message": "Cannot assign to derived state", + "start": { + "column": 3, + "line": 6 + }, + "end": { + "column": 29, + "line": 6 + } + } +] diff --git a/packages/svelte/tests/validator/samples/reassign-derived-literal/input.svelte b/packages/svelte/tests/validator/samples/reassign-derived-literal/input.svelte new file mode 100644 index 0000000000..8f109c9e1f --- /dev/null +++ b/packages/svelte/tests/validator/samples/reassign-derived-literal/input.svelte @@ -0,0 +1,9 @@ + \ No newline at end of file diff --git a/packages/svelte/tests/validator/samples/reassign-derived-private-field/errors.json b/packages/svelte/tests/validator/samples/reassign-derived-private-field/errors.json new file mode 100644 index 0000000000..c211aa4608 --- /dev/null +++ b/packages/svelte/tests/validator/samples/reassign-derived-private-field/errors.json @@ -0,0 +1,14 @@ +[ + { + "code": "constant_assignment", + "message": "Cannot assign to derived state", + "start": { + "column": 3, + "line": 6 + }, + "end": { + "column": 27, + "line": 6 + } + } +] diff --git a/packages/svelte/tests/validator/samples/reassign-derived-private-field/input.svelte b/packages/svelte/tests/validator/samples/reassign-derived-private-field/input.svelte new file mode 100644 index 0000000000..62e2317e03 --- /dev/null +++ b/packages/svelte/tests/validator/samples/reassign-derived-private-field/input.svelte @@ -0,0 +1,9 @@ + \ No newline at end of file diff --git a/packages/svelte/tests/validator/samples/reassign-derived-public-field/errors.json b/packages/svelte/tests/validator/samples/reassign-derived-public-field/errors.json new file mode 100644 index 0000000000..98837589ac --- /dev/null +++ b/packages/svelte/tests/validator/samples/reassign-derived-public-field/errors.json @@ -0,0 +1,14 @@ +[ + { + "code": "constant_assignment", + "message": "Cannot assign to derived state", + "start": { + "column": 3, + "line": 6 + }, + "end": { + "column": 26, + "line": 6 + } + } +] diff --git a/packages/svelte/tests/validator/samples/reassign-derived-public-field/input.svelte b/packages/svelte/tests/validator/samples/reassign-derived-public-field/input.svelte new file mode 100644 index 0000000000..e2c4693e86 --- /dev/null +++ b/packages/svelte/tests/validator/samples/reassign-derived-public-field/input.svelte @@ -0,0 +1,9 @@ + \ No newline at end of file