fix: various declaration tag bugs (#18348)

closes #18334
pull/18351/head
Rich Harris 1 month ago committed by GitHub
parent 378bb25097
commit b76b937e00
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: correctly transform references to earlier declarators in a declaration tag (e.g. `{let a = $state(0), b = $derived(a * 2)}`)

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: avoid spurious `state_referenced_locally` warnings for `$derived` declarations in declaration tags

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: tolerate whitespace before `let`/`const` in declaration tags

@ -2,14 +2,12 @@
/** @import { Context } from '../types' */
import * as b from '#compiler/builders';
import * as e from '../../../errors.js';
import { validate_opening_tag } from './shared/utils.js';
/**
* @param {AST.DeclarationTag} node
* @param {Context} context
*/
export function DeclarationTag(node, context) {
validate_opening_tag(node, context.state, node.declaration.kind[0]);
if (!context.state.analysis.runes && !context.state.analysis.maybe_runes) {
e.declaration_tag_no_legacy_mode(node);
}
@ -17,6 +15,10 @@ export function DeclarationTag(node, context) {
context.visit(node.declaration, {
...context.state,
in_declaration_tag: true,
// the declaration lives in the fragment scope, which is one level deeper than the
// `function_depth` we're tracking here (`set_scope` doesn't update `function_depth`).
// align them so that `state_referenced_locally` warnings are calculated correctly
function_depth: context.state.scope.function_depth,
expression: node.metadata.expression
});

@ -10,8 +10,10 @@ import { add_state_transformers } from './shared/declarations.js';
* @param {ComponentContext} context
*/
export function DeclarationTag(node, context) {
const declaration = /** @type {Statement | undefined} */ (context.visit(node.declaration));
// register the transformers _before_ visiting the declaration, so that
// later declarators can reference earlier ones (e.g. `{let a = $state(0), b = $derived(a * 2)}`)
add_state_transformers(context);
const declaration = /** @type {Statement | undefined} */ (context.visit(node.declaration));
if (
node.metadata.promises_id &&

@ -0,0 +1,17 @@
import { tick } from 'svelte';
import { test } from '../../test';
export default test({
html: `<button>increment</button><p>count: 0</p><p>doubled: 0</p><p>quadrupled: 0</p>`,
async test({ assert, target }) {
const [button] = target.querySelectorAll('button');
button.click();
await tick();
assert.htmlEqual(
target.innerHTML,
`<button>increment</button><p>count: 1</p><p>doubled: 2</p><p>quadrupled: 4</p>`
);
}
});

@ -0,0 +1,10 @@
<!-- a later declarator can reference an earlier one within the same tag... -->
{let count = $state(0), doubled = $derived(count * 2)}
<!-- ...and leading whitespace is tolerated -->
{ let quadrupled = $derived(doubled * 2) }
<button onclick={() => (count += 1)}>increment</button>
<p>count: {count}</p>
<p>doubled: {doubled}</p>
<p>quadrupled: {quadrupled}</p>

@ -0,0 +1,9 @@
<!-- `$derived` reads happen inside a closure, so these should _not_ warn -->
{let a = $state(0), b = $derived(a * 2)}
{let c = $state(0)}
{let d = $derived(c * 2)}
<!-- this is a non-closure read of state, so it _should_ warn -->
{let e = $state(0), f = e}
{a}{b}{c}{d}{e}{f}

@ -0,0 +1,14 @@
[
{
"code": "state_referenced_locally",
"message": "This reference only captures the initial value of `e`. Did you mean to reference it inside a closure instead?",
"start": {
"line": 7,
"column": 24
},
"end": {
"line": 7,
"column": 25
}
}
]
Loading…
Cancel
Save