Skip to content

Commit 3286617

Browse files
paoloricciutioscard0mRich-Harris
authored
feat: function called as tagged template literal is reactively called (#12692)
* feat: function called as tagged template literal is reactively called Co-authored-by: Oscar Dominguez <dominguez.celada@gmail.com> * chore: re-organize import of visitors * simplify --------- Co-authored-by: Oscar Dominguez <dominguez.celada@gmail.com> Co-authored-by: Rich Harris <rich.harris@vercel.com>
1 parent e4e66e2 commit 3286617

File tree

7 files changed

+81
-28
lines changed

7 files changed

+81
-28
lines changed

.changeset/serious-owls-think.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+
feat: function called as tagged template literal is reactively called

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ import { SvelteElement } from './visitors/SvelteElement.js';
5757
import { SvelteFragment } from './visitors/SvelteFragment.js';
5858
import { SvelteHead } from './visitors/SvelteHead.js';
5959
import { SvelteSelf } from './visitors/SvelteSelf.js';
60+
import { TaggedTemplateExpression } from './visitors/TaggedTemplateExpression.js';
6061
import { Text } from './visitors/Text.js';
6162
import { TitleElement } from './visitors/TitleElement.js';
6263
import { UpdateExpression } from './visitors/UpdateExpression.js';
@@ -160,6 +161,7 @@ const visitors = {
160161
SvelteFragment,
161162
SvelteComponent,
162163
SvelteSelf,
164+
TaggedTemplateExpression,
163165
Text,
164166
TitleElement,
165167
UpdateExpression,

packages/svelte/src/compiler/phases/2-analyze/visitors/CallExpression.js

Lines changed: 2 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import { get_rune } from '../../scope.js';
55
import * as e from '../../../errors.js';
66
import { get_parent, unwrap_optional } from '../../../utils/ast.js';
7-
import { is_safe_identifier } from './shared/utils.js';
7+
import { is_known_safe_call, is_safe_identifier } from './shared/utils.js';
88

99
/**
1010
* @param {CallExpression} node
@@ -150,7 +150,7 @@ export function CallExpression(node, context) {
150150
break;
151151
}
152152

153-
if (context.state.expression && !is_known_safe_call(node, context)) {
153+
if (context.state.expression && !is_known_safe_call(node.callee, context)) {
154154
context.state.expression.has_call = true;
155155
context.state.expression.has_state = true;
156156
}
@@ -182,28 +182,3 @@ export function CallExpression(node, context) {
182182
context.next();
183183
}
184184
}
185-
186-
/**
187-
* @param {CallExpression} node
188-
* @param {Context} context
189-
* @returns {boolean}
190-
*/
191-
function is_known_safe_call(node, context) {
192-
const callee = node.callee;
193-
194-
// String / Number / BigInt / Boolean casting calls
195-
if (callee.type === 'Identifier') {
196-
const name = callee.name;
197-
const binding = context.state.scope.get(name);
198-
if (
199-
binding === null &&
200-
(name === 'BigInt' || name === 'String' || name === 'Number' || name === 'Boolean')
201-
) {
202-
return true;
203-
}
204-
}
205-
206-
// TODO add more cases
207-
208-
return false;
209-
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/** @import { TaggedTemplateExpression, VariableDeclarator } from 'estree' */
2+
/** @import { Context } from '../types' */
3+
import { is_known_safe_call } from './shared/utils.js';
4+
5+
/**
6+
* @param {TaggedTemplateExpression} node
7+
* @param {Context} context
8+
*/
9+
export function TaggedTemplateExpression(node, context) {
10+
if (context.state.expression && !is_known_safe_call(node.tag, context)) {
11+
context.state.expression.has_call = true;
12+
context.state.expression.has_state = true;
13+
}
14+
15+
if (node.tag.type === 'Identifier') {
16+
const binding = context.state.scope.get(node.tag.name);
17+
18+
if (binding !== null) {
19+
binding.is_called = true;
20+
}
21+
}
22+
context.next();
23+
}

packages/svelte/src/compiler/phases/2-analyze/visitors/shared/utils.js

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/** @import { AssignmentExpression, Expression, Pattern, PrivateIdentifier, Super, UpdateExpression, VariableDeclarator } from 'estree' */
1+
/** @import { AssignmentExpression, CallExpression, Expression, Pattern, PrivateIdentifier, Super, TaggedTemplateExpression, UpdateExpression, VariableDeclarator } from 'estree' */
22
/** @import { Fragment } from '#compiler' */
33
/** @import { AnalysisState, Context } from '../../types' */
44
/** @import { Scope } from '../../../scope' */
@@ -165,3 +165,26 @@ export function is_safe_identifier(expression, scope) {
165165
binding.kind !== 'rest_prop'
166166
);
167167
}
168+
169+
/**
170+
* @param {Expression | Super} callee
171+
* @param {Context} context
172+
* @returns {boolean}
173+
*/
174+
export function is_known_safe_call(callee, context) {
175+
// String / Number / BigInt / Boolean casting calls
176+
if (callee.type === 'Identifier') {
177+
const name = callee.name;
178+
const binding = context.state.scope.get(name);
179+
if (
180+
binding === null &&
181+
(name === 'BigInt' || name === 'String' || name === 'Number' || name === 'Boolean')
182+
) {
183+
return true;
184+
}
185+
}
186+
187+
// TODO add more cases
188+
189+
return false;
190+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { flushSync } from 'svelte';
2+
import { test } from '../../test';
3+
4+
export default test({
5+
mode: ['client'],
6+
async test({ assert, target, ok }) {
7+
const button = target.querySelector('button');
8+
9+
assert.htmlEqual(target.innerHTML, `0 <button></button>`);
10+
11+
flushSync(() => {
12+
button?.click();
13+
});
14+
15+
assert.htmlEqual(target.innerHTML, `1 <button></button>`);
16+
}
17+
});
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<script>
2+
let count = $state(0);
3+
function showCount() {
4+
return count;
5+
}
6+
</script>
7+
8+
{showCount``} <button onclick={() => count++}></button>

0 commit comments

Comments
 (0)