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``}