Skip to content

Commit 6695a5e

Browse files
committed
feat: support migration of single assignment labeled statements
1 parent b665425 commit 6695a5e

File tree

4 files changed

+190
-7
lines changed

4 files changed

+190
-7
lines changed

.changeset/great-dots-wonder.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: support migration of single assignment labeled statements

packages/svelte/src/compiler/migrate/index.js

Lines changed: 87 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/** @import { VariableDeclarator, Node, Identifier } from 'estree' */
1+
/** @import { VariableDeclarator, Node, Identifier, AssignmentExpression, LabeledStatement } from 'estree' */
22
/** @import { Visitors } from 'zimmerframe' */
33
/** @import { ComponentAnalysis } from '../phases/types.js' */
44
/** @import { Scope, ScopeRoot } from '../phases/scope.js' */
@@ -88,7 +88,8 @@ export function migrate(source) {
8888
},
8989
legacy_imports: new Set(),
9090
script_insertions: new Set(),
91-
derived_components: new Map()
91+
derived_components: new Map(),
92+
derived_labeled_statements: new Set()
9293
};
9394

9495
if (parsed.module) {
@@ -102,6 +103,13 @@ export function migrate(source) {
102103
walk(parsed.instance.content, state, instance_script);
103104
}
104105

106+
for (let labeled_to_remove of state.derived_labeled_statements) {
107+
state.str.remove(
108+
/** @type {number} */ (labeled_to_remove.start),
109+
/** @type {number} */ (labeled_to_remove.end)
110+
);
111+
}
112+
105113
state = { ...state, scope: analysis.template.scope };
106114
walk(parsed.fragment, state, template);
107115

@@ -289,7 +297,8 @@ export function migrate(source) {
289297
* names: Record<string, string>;
290298
* legacy_imports: Set<string>;
291299
* script_insertions: Set<string>;
292-
* derived_components: Map<string, string>
300+
* derived_components: Map<string, string>,
301+
* derived_labeled_statements: Set<LabeledStatement>
293302
* }} State
294303
*/
295304

@@ -337,7 +346,7 @@ const instance_script = {
337346
state.str.remove(/** @type {number} */ (node.start), /** @type {number} */ (node.end));
338347
}
339348
},
340-
VariableDeclaration(node, { state, path }) {
349+
VariableDeclaration(node, { state, path, visit }) {
341350
if (state.scope !== state.analysis.instance.scope) {
342351
return;
343352
}
@@ -457,10 +466,80 @@ const instance_script = {
457466
state.str.prependLeft(start, '$state(');
458467
state.str.appendRight(end, ')');
459468
} else {
460-
state.str.prependLeft(
461-
/** @type {number} */ (declarator.id.typeAnnotation?.end ?? declarator.id.end),
462-
' = $state()'
469+
/**
470+
* @type {AssignmentExpression | undefined}
471+
*/
472+
let assignment_in_labeled;
473+
/**
474+
* @type {LabeledStatement | undefined}
475+
*/
476+
let labeled_statement;
477+
478+
const possible_derived = bindings.every((binding) =>
479+
binding.references.every((reference) => {
480+
const declaration_idx = reference.path.findIndex(
481+
(el) => el.type === 'VariableDeclaration'
482+
);
483+
const assignment_idx = reference.path.findIndex(
484+
(el) => el.type === 'AssignmentExpression'
485+
);
486+
const update_idx = reference.path.findIndex((el) => el.type === 'UpdateExpression');
487+
const labeled_idx = reference.path.findIndex(
488+
(el) => el.type === 'LabeledStatement' && el.label.name === '$'
489+
);
490+
491+
if (assignment_idx !== -1 && labeled_idx !== -1) {
492+
if (assignment_in_labeled) return false;
493+
assignment_in_labeled = /** @type {AssignmentExpression} */ (
494+
reference.path[assignment_idx]
495+
);
496+
labeled_statement = /** @type {LabeledStatement} */ (reference.path[labeled_idx]);
497+
}
498+
499+
return (
500+
update_idx === -1 &&
501+
(declaration_idx !== -1 ||
502+
(labeled_idx !== -1 && assignment_idx !== -1) ||
503+
(labeled_idx === -1 && assignment_idx === -1))
504+
);
505+
})
463506
);
507+
508+
const labeled_has_single_assignment =
509+
labeled_statement?.body.type === 'BlockStatement' &&
510+
labeled_statement.body.body.length === 1;
511+
512+
if (
513+
possible_derived &&
514+
assignment_in_labeled &&
515+
labeled_statement &&
516+
labeled_has_single_assignment
517+
) {
518+
state.str.appendRight(
519+
/** @type {number} */ (declarator.id.typeAnnotation?.end ?? declarator.id.end),
520+
' = $derived('
521+
);
522+
visit(assignment_in_labeled.right);
523+
state.str.appendRight(
524+
/** @type {number} */ (declarator.id.typeAnnotation?.end ?? declarator.id.end),
525+
state.str
526+
.snip(
527+
/** @type {number} */ (assignment_in_labeled.right.start),
528+
/** @type {number} */ (assignment_in_labeled.right.end)
529+
)
530+
.toString()
531+
);
532+
state.str.appendRight(
533+
/** @type {number} */ (declarator.id.typeAnnotation?.end ?? declarator.id.end),
534+
')'
535+
);
536+
state.derived_labeled_statements.add(labeled_statement);
537+
} else {
538+
state.str.prependLeft(
539+
/** @type {number} */ (declarator.id.typeAnnotation?.end ?? declarator.id.end),
540+
' = $state()'
541+
);
542+
}
464543
}
465544
}
466545

@@ -491,6 +570,7 @@ const instance_script = {
491570
if (state.analysis.runes) return;
492571
if (path.length > 1) return;
493572
if (node.label.name !== '$') return;
573+
if (state.derived_labeled_statements.has(node)) return;
494574

495575
next();
496576

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<script>
2+
let count = 0;
3+
let double;
4+
$:{
5+
double = count * 2;
6+
}
7+
8+
let quadruple;
9+
$:{
10+
quadruple = count * 4;
11+
console.log("i have a side effect")
12+
}
13+
14+
let eight_times;
15+
$:{
16+
// updated
17+
eight_times = count * 8;
18+
}
19+
20+
let sixteen_times;
21+
$:{
22+
// reassigned outside labeled statement
23+
sixteen_times = count * 16;
24+
}
25+
26+
let alot_times;
27+
$:{
28+
// reassigned in multiple labeled
29+
alot_times = count * 32;
30+
}
31+
$:{
32+
// reassigned in multiple labeled
33+
alot_times = count * 32;
34+
}
35+
36+
let evenmore;
37+
let evenmore_doubled;
38+
$:{
39+
// multiple stuff in label
40+
evenmore = count * 64;
41+
evenmore_doubled = evenmore * 2;
42+
}
43+
</script>
44+
45+
<button on:click={()=>{
46+
count++;
47+
eight_times++;
48+
sixteen_times += 1;
49+
}}>click</button>
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<script>
2+
import { run } from 'svelte/legacy';
3+
4+
let count = $state(0);
5+
let double = $derived(count * 2);
6+
7+
8+
let quadruple = $state();
9+
run(() => {
10+
quadruple = count * 4;
11+
console.log("i have a side effect")
12+
});
13+
14+
let eight_times = $state();
15+
run(() => {
16+
// updated
17+
eight_times = count * 8;
18+
});
19+
20+
let sixteen_times = $state();
21+
run(() => {
22+
// reassigned outside labeled statement
23+
sixteen_times = count * 16;
24+
});
25+
26+
let alot_times = $state();
27+
run(() => {
28+
// reassigned in multiple labeled
29+
alot_times = count * 32;
30+
});
31+
run(() => {
32+
// reassigned in multiple labeled
33+
alot_times = count * 32;
34+
});
35+
36+
let evenmore = $state();
37+
let evenmore_doubled = $state();
38+
run(() => {
39+
// multiple stuff in label
40+
evenmore = count * 64;
41+
evenmore_doubled = evenmore * 2;
42+
});
43+
</script>
44+
45+
<button onclick={()=>{
46+
count++;
47+
eight_times++;
48+
sixteen_times += 1;
49+
}}>click</button>

0 commit comments

Comments
 (0)