diff --git a/.changeset/sharp-rings-march.md b/.changeset/sharp-rings-march.md new file mode 100644 index 000000000000..bd3a5e531210 --- /dev/null +++ b/.changeset/sharp-rings-march.md @@ -0,0 +1,5 @@ +--- +'svelte': minor +--- + +feat: allow `$state` in return statements diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/CallExpression.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/CallExpression.js index 2eac934b332c..995b1d79f831 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/CallExpression.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/CallExpression.js @@ -112,6 +112,20 @@ export function CallExpression(node, context) { } case '$state': + if ( + (!(parent.type === 'VariableDeclarator' || parent.type === 'ReturnStatement') || + get_parent(context.path, -3).type === 'ConstTag') && + !(parent.type === 'PropertyDefinition' && !parent.static && !parent.computed) && + !(parent.type === 'ArrowFunctionExpression' && parent.body === node) + ) { + e.state_invalid_placement(node, rune); + } + + if (node.arguments.length > 1) { + e.rune_invalid_arguments_length(node, rune, 'zero or one arguments'); + } + + break; case '$state.raw': case '$derived': case '$derived.by': diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/CallExpression.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/CallExpression.js index fda43ad7911a..aa718b9988a8 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/CallExpression.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/CallExpression.js @@ -4,12 +4,14 @@ import { dev, is_ignored } from '../../../../state.js'; import * as b from '../../../../utils/builders.js'; import { get_rune } from '../../../scope.js'; import { transform_inspect_rune } from '../../utils.js'; +import { should_proxy } from '../utils.js'; /** * @param {CallExpression} node * @param {Context} context */ export function CallExpression(node, context) { + const parent = context.path.at(-1); switch (get_rune(node, context.state.scope)) { case '$host': return b.id('$$props.$$host'); @@ -33,6 +35,23 @@ export function CallExpression(node, context) { case '$inspect': case '$inspect().with': return transform_inspect_rune(node, context); + case '$state': + if ( + parent?.type === 'ReturnStatement' || + (parent?.type === 'ArrowFunctionExpression' && parent.body === node) + ) { + if ( + node.arguments[0] && + should_proxy( + /** @type {Expression} */ (context.visit(node.arguments[0])), + context.state.scope + ) + ) { + return b.call('$.proxy', node.arguments[0]); + } else { + return node.arguments[0] ?? b.void0; + } + } } if ( diff --git a/packages/svelte/src/compiler/phases/3-transform/server/visitors/CallExpression.js b/packages/svelte/src/compiler/phases/3-transform/server/visitors/CallExpression.js index a425bc5ec430..ad573b03295b 100644 --- a/packages/svelte/src/compiler/phases/3-transform/server/visitors/CallExpression.js +++ b/packages/svelte/src/compiler/phases/3-transform/server/visitors/CallExpression.js @@ -1,4 +1,4 @@ -/** @import { CallExpression, Expression } from 'estree' */ +/** @import { ArrowFunctionExpression, CallExpression, Expression } from 'estree' */ /** @import { Context } from '../types.js' */ import { is_ignored } from '../../../../state.js'; import * as b from '../../../../utils/builders.js'; @@ -37,5 +37,17 @@ export function CallExpression(node, context) { return transform_inspect_rune(node, context); } + if ( + rune === '$state' && + (context.path.at(-1)?.type === 'ReturnStatement' || + (context.path.at(-1)?.type === 'ArrowFunctionExpression' && + /** @type {ArrowFunctionExpression} */ (context.path.at(-1)).body === node)) + ) { + if (node.arguments[0]) { + return context.visit(node.arguments[0]); + } + return b.void0; + } + context.next(); } diff --git a/packages/svelte/tests/snapshot/samples/state-in-return/_expected/client/index.svelte.js b/packages/svelte/tests/snapshot/samples/state-in-return/_expected/client/index.svelte.js new file mode 100644 index 000000000000..55f38f174e19 --- /dev/null +++ b/packages/svelte/tests/snapshot/samples/state-in-return/_expected/client/index.svelte.js @@ -0,0 +1,14 @@ +/* index.svelte.js generated by Svelte VERSION */ +import * as $ from 'svelte/internal/client'; + +export default function proxy(object) { + return $.proxy(object); +} + +export function createCounter() { + let count = $.state(0); + + $.update(count); +} + +export const proxy_in_arrow = (object) => $.proxy(object); \ No newline at end of file diff --git a/packages/svelte/tests/snapshot/samples/state-in-return/_expected/server/index.svelte.js b/packages/svelte/tests/snapshot/samples/state-in-return/_expected/server/index.svelte.js new file mode 100644 index 000000000000..de52cde883be --- /dev/null +++ b/packages/svelte/tests/snapshot/samples/state-in-return/_expected/server/index.svelte.js @@ -0,0 +1,14 @@ +/* index.svelte.js generated by Svelte VERSION */ +import * as $ from 'svelte/internal/server'; + +export default function proxy(object) { + return object; +} + +export function createCounter() { + let count = 0; + + count++; +} + +export const proxy_in_arrow = (object) => object; \ No newline at end of file diff --git a/packages/svelte/tests/snapshot/samples/state-in-return/index.svelte.js b/packages/svelte/tests/snapshot/samples/state-in-return/index.svelte.js new file mode 100644 index 000000000000..66d143f4096e --- /dev/null +++ b/packages/svelte/tests/snapshot/samples/state-in-return/index.svelte.js @@ -0,0 +1,8 @@ +export default function proxy(object) { + return $state(object); +} +export function createCounter() { + let count = $state(0); + count++; +} +export const proxy_in_arrow = (object) => $state(object);