diff --git a/.changeset/serious-owls-think.md b/.changeset/serious-owls-think.md new file mode 100644 index 0000000000..7093f5af4c --- /dev/null +++ b/.changeset/serious-owls-think.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +feat: function called as tagged template literal is reactively called diff --git a/packages/svelte/src/compiler/phases/2-analyze/index.js b/packages/svelte/src/compiler/phases/2-analyze/index.js index 6d90ca1617..40e8719a40 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/index.js +++ b/packages/svelte/src/compiler/phases/2-analyze/index.js @@ -57,6 +57,7 @@ import { SvelteElement } from './visitors/SvelteElement.js'; import { SvelteFragment } from './visitors/SvelteFragment.js'; import { SvelteHead } from './visitors/SvelteHead.js'; import { SvelteSelf } from './visitors/SvelteSelf.js'; +import { TaggedTemplateExpression } from './visitors/TaggedTemplateExpression.js'; import { Text } from './visitors/Text.js'; import { TitleElement } from './visitors/TitleElement.js'; import { UpdateExpression } from './visitors/UpdateExpression.js'; @@ -160,6 +161,7 @@ const visitors = { SvelteFragment, SvelteComponent, SvelteSelf, + TaggedTemplateExpression, Text, TitleElement, UpdateExpression, 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 9d5a3ef5c1..98b9a3d7ec 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/CallExpression.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/CallExpression.js @@ -4,7 +4,7 @@ import { get_rune } from '../../scope.js'; import * as e from '../../../errors.js'; import { get_parent, unwrap_optional } from '../../../utils/ast.js'; -import { is_safe_identifier } from './shared/utils.js'; +import { is_known_safe_call, is_safe_identifier } from './shared/utils.js'; /** * @param {CallExpression} node @@ -150,7 +150,7 @@ export function CallExpression(node, context) { break; } - if (context.state.expression && !is_known_safe_call(node, context)) { + if (context.state.expression && !is_known_safe_call(node.callee, context)) { context.state.expression.has_call = true; context.state.expression.has_state = true; } @@ -182,28 +182,3 @@ export function CallExpression(node, context) { context.next(); } } - -/** - * @param {CallExpression} node - * @param {Context} context - * @returns {boolean} - */ -function is_known_safe_call(node, context) { - const callee = node.callee; - - // String / Number / BigInt / Boolean casting calls - if (callee.type === 'Identifier') { - const name = callee.name; - const binding = context.state.scope.get(name); - if ( - binding === null && - (name === 'BigInt' || name === 'String' || name === 'Number' || name === 'Boolean') - ) { - return true; - } - } - - // TODO add more cases - - return false; -} diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/TaggedTemplateExpression.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/TaggedTemplateExpression.js new file mode 100644 index 0000000000..b079b67512 --- /dev/null +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/TaggedTemplateExpression.js @@ -0,0 +1,23 @@ +/** @import { TaggedTemplateExpression, VariableDeclarator } from 'estree' */ +/** @import { Context } from '../types' */ +import { is_known_safe_call } from './shared/utils.js'; + +/** + * @param {TaggedTemplateExpression} node + * @param {Context} context + */ +export function TaggedTemplateExpression(node, context) { + if (context.state.expression && !is_known_safe_call(node.tag, context)) { + context.state.expression.has_call = true; + context.state.expression.has_state = true; + } + + if (node.tag.type === 'Identifier') { + const binding = context.state.scope.get(node.tag.name); + + if (binding !== null) { + binding.is_called = true; + } + } + context.next(); +} 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 dd790665ce..2980ac3cbe 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 @@ -1,4 +1,4 @@ -/** @import { AssignmentExpression, Expression, Pattern, PrivateIdentifier, Super, UpdateExpression, VariableDeclarator } from 'estree' */ +/** @import { AssignmentExpression, CallExpression, Expression, Pattern, PrivateIdentifier, Super, TaggedTemplateExpression, UpdateExpression, VariableDeclarator } from 'estree' */ /** @import { Fragment } from '#compiler' */ /** @import { AnalysisState, Context } from '../../types' */ /** @import { Scope } from '../../../scope' */ @@ -165,3 +165,26 @@ export function is_safe_identifier(expression, scope) { binding.kind !== 'rest_prop' ); } + +/** + * @param {Expression | Super} callee + * @param {Context} context + * @returns {boolean} + */ +export function is_known_safe_call(callee, context) { + // String / Number / BigInt / Boolean casting calls + if (callee.type === 'Identifier') { + const name = callee.name; + const binding = context.state.scope.get(name); + if ( + binding === null && + (name === 'BigInt' || name === 'String' || name === 'Number' || name === 'Boolean') + ) { + return true; + } + } + + // TODO add more cases + + return false; +} diff --git a/packages/svelte/tests/runtime-runes/samples/tagged-template-literal-reactive/_config.js b/packages/svelte/tests/runtime-runes/samples/tagged-template-literal-reactive/_config.js new file mode 100644 index 0000000000..beef0bbff2 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/tagged-template-literal-reactive/_config.js @@ -0,0 +1,17 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + mode: ['client'], + async test({ assert, target, ok }) { + const button = target.querySelector('button'); + + assert.htmlEqual(target.innerHTML, `0 `); + + flushSync(() => { + button?.click(); + }); + + assert.htmlEqual(target.innerHTML, `1 `); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/tagged-template-literal-reactive/main.svelte b/packages/svelte/tests/runtime-runes/samples/tagged-template-literal-reactive/main.svelte new file mode 100644 index 0000000000..7b9da329c2 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/tagged-template-literal-reactive/main.svelte @@ -0,0 +1,8 @@ + + +{showCount``}