Skip to content

Commit ef50e45

Browse files
authored
fix: migration fixes (#12176)
- add $state rune at correct location, fixes #12171 - correctly indent event handlers, fixes #12170
1 parent 4540ff3 commit ef50e45

File tree

9 files changed

+115
-24
lines changed

9 files changed

+115
-24
lines changed

.changeset/empty-files-prove.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+
fix: robustify migration script around indentation and comments

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

Lines changed: 53 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -322,7 +322,10 @@ const instance_script = {
322322
state.str.prependLeft(/** @type {number} */ (declarator.init.start), '$state(');
323323
state.str.appendRight(/** @type {number} */ (declarator.init.end), ')');
324324
} else {
325-
state.str.prependLeft(/** @type {number} */ (declarator.id.end), ' = $state()');
325+
state.str.prependLeft(
326+
/** @type {number} */ (declarator.id.typeAnnotation?.end ?? declarator.id.end),
327+
' = $state()'
328+
);
326329
}
327330
}
328331

@@ -587,13 +590,13 @@ function extract_type_and_comment(declarator, str, path) {
587590
}
588591

589592
/**
590-
* @param {import('#compiler').RegularElement | import('#compiler').SvelteElement | import('#compiler').SvelteWindow | import('#compiler').SvelteDocument | import('#compiler').SvelteBody} node
593+
* @param {import('#compiler').RegularElement | import('#compiler').SvelteElement | import('#compiler').SvelteWindow | import('#compiler').SvelteDocument | import('#compiler').SvelteBody} element
591594
* @param {State} state
592595
*/
593-
function handle_events(node, state) {
596+
function handle_events(element, state) {
594597
/** @type {Map<string, import('#compiler').OnDirective[]>} */
595598
const handlers = new Map();
596-
for (const attribute of node.attributes) {
599+
for (const attribute of element.attributes) {
597600
if (attribute.type !== 'OnDirective') continue;
598601

599602
let name = `on${attribute.name}`;
@@ -625,6 +628,7 @@ function handle_events(node, state) {
625628

626629
for (let i = 0; i < nodes.length - 1; i += 1) {
627630
const node = nodes[i];
631+
const indent = get_indent(state, node, element);
628632
if (node.expression) {
629633
let body = '';
630634
if (node.expression.type === 'ArrowFunctionExpression') {
@@ -638,19 +642,20 @@ function handle_events(node, state) {
638642
/** @type {number} */ (node.expression.end)
639643
)}();`;
640644
}
641-
// TODO check how many indents needed
645+
642646
for (const modifier of node.modifiers) {
643647
if (modifier === 'stopPropagation') {
644-
body = `\n${state.indent}${payload_name}.stopPropagation();\n${body}`;
648+
body = `\n${indent}${payload_name}.stopPropagation();\n${body}`;
645649
} else if (modifier === 'preventDefault') {
646-
body = `\n${state.indent}${payload_name}.preventDefault();\n${body}`;
650+
body = `\n${indent}${payload_name}.preventDefault();\n${body}`;
647651
} else if (modifier === 'stopImmediatePropagation') {
648-
body = `\n${state.indent}${payload_name}.stopImmediatePropagation();\n${body}`;
652+
body = `\n${indent}${payload_name}.stopImmediatePropagation();\n${body}`;
649653
} else {
650-
body = `\n${state.indent}// @migration-task: incorporate ${modifier} modifier\n${body}`;
654+
body = `\n${indent}// @migration-task: incorporate ${modifier} modifier\n${body}`;
651655
}
652656
}
653-
prepend += `\n${state.indent}${body}\n`;
657+
658+
prepend += `\n${indent}${body}\n`;
654659
} else {
655660
if (!local) {
656661
local = state.scope.generate(`on${node.name}`);
@@ -663,7 +668,7 @@ function handle_events(node, state) {
663668
type: '(event: any) => void'
664669
});
665670
}
666-
prepend += `\n${state.indent}${local}?.(${payload_name});\n`;
671+
prepend += `\n${indent}${local}?.(${payload_name});\n`;
667672
}
668673

669674
state.str.remove(node.start, node.end);
@@ -683,15 +688,17 @@ function handle_events(node, state) {
683688
state.str.appendRight(last.start + last.name.length + 3, 'capture');
684689
}
685690

691+
const indent = get_indent(state, last, element);
692+
686693
for (const modifier of last.modifiers) {
687694
if (modifier === 'stopPropagation') {
688-
prepend += `\n${state.indent}${payload_name}.stopPropagation();\n`;
695+
prepend += `\n${indent}${payload_name}.stopPropagation();\n`;
689696
} else if (modifier === 'preventDefault') {
690-
prepend += `\n${state.indent}${payload_name}.preventDefault();\n`;
697+
prepend += `\n${indent}${payload_name}.preventDefault();\n`;
691698
} else if (modifier === 'stopImmediatePropagation') {
692-
prepend += `\n${state.indent}${payload_name}.stopImmediatePropagation();\n`;
699+
prepend += `\n${indent}${payload_name}.stopImmediatePropagation();\n`;
693700
} else if (modifier !== 'capture') {
694-
prepend += `\n${state.indent}// @migration-task: incorporate ${modifier} modifier\n`;
701+
prepend += `\n${indent}// @migration-task: incorporate ${modifier} modifier\n`;
695702
}
696703
}
697704

@@ -723,17 +730,20 @@ function handle_events(node, state) {
723730
pos = /** @type {number} */ (pos) + (needs_curlies ? 0 : 1);
724731
if (needs_curlies && state.str.original[pos - 1] === '(') {
725732
// Prettier does something like on:click={() => (foo = true)}, we need to remove the braces in this case
726-
state.str.update(pos - 1, pos, `{${prepend}${state.indent}`);
727-
state.str.update(end, end + 1, '\n}');
733+
state.str.update(pos - 1, pos, `{${prepend}${indent}`);
734+
state.str.update(end, end + 1, `\n${indent.slice(state.indent.length)}}`);
728735
} else {
729-
state.str.prependRight(pos, `${needs_curlies ? '{' : ''}${prepend}${state.indent}`);
730-
state.str.appendRight(end, `\n${needs_curlies ? '}' : ''}`);
736+
state.str.prependRight(pos, `${needs_curlies ? '{' : ''}${prepend}${indent}`);
737+
state.str.appendRight(
738+
end,
739+
`\n${indent.slice(state.indent.length)}${needs_curlies ? '}' : ''}`
740+
);
731741
}
732742
} else {
733743
state.str.update(
734744
/** @type {number} */ (last.expression.start),
735745
/** @type {number} */ (last.expression.end),
736-
`(${payload_name}) => {${prepend}\n${state.indent}${state.str.original.substring(
746+
`(${payload_name}) => {${prepend}\n${indent}${state.str.original.substring(
737747
/** @type {number} */ (last.expression.start),
738748
/** @type {number} */ (last.expression.end)
739749
)}?.(${payload_name});\n}`
@@ -771,6 +781,29 @@ function handle_events(node, state) {
771781
}
772782
}
773783

784+
/**
785+
* Returns the next indentation level of the first node that has all-whitespace before it
786+
* @param {State} state
787+
* @param {Array<{start: number; end: number}>} nodes
788+
*/
789+
function get_indent(state, ...nodes) {
790+
let indent = state.indent;
791+
792+
for (const node of nodes) {
793+
const line_start = state.str.original.lastIndexOf('\n', node.start);
794+
indent = state.str.original.substring(line_start + 1, node.start);
795+
796+
if (indent.trim() === '') {
797+
indent = state.indent + indent;
798+
return indent;
799+
} else {
800+
indent = state.indent;
801+
}
802+
}
803+
804+
return indent;
805+
}
806+
774807
/**
775808
* @param {import('#compiler').OnDirective} last
776809
* @param {State} state

packages/svelte/src/compiler/phases/1-parse/acorn.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,10 @@ function amend(source, node) {
138138
}
139139
}
140140

141-
if (/** @type {any} */ (node).typeAnnotation && node.end === undefined) {
141+
if (
142+
/** @type {any} */ (node).typeAnnotation &&
143+
(node.end === undefined || node.end < node.start)
144+
) {
142145
// i think there might be a bug in acorn-typescript that prevents
143146
// `end` from being assigned when there's a type annotation
144147
let end = /** @type {any} */ (node).typeAnnotation.start;

packages/svelte/tests/migrate/samples/event-handlers/input.svelte

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,17 @@
1818
<button on:click|self={() => ''}>click me</button>
1919

2020
<Button on:click={() => 'leave untouched'} on:click>click me</Button>
21+
22+
<div>
23+
<button
24+
on:click={() => {
25+
console.log('hi');
26+
}}>click me</button
27+
>
28+
<button
29+
on:click|preventDefault={() => {
30+
console.log('hi');
31+
}}>click me</button
32+
>
33+
<button on:click|preventDefault={() => (count += 1)}>click me</button>
34+
</div>

packages/svelte/tests/migrate/samples/event-handlers/output.svelte

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,4 +50,24 @@
5050
''
5151
}}>click me</button>
5252

53-
<Button on:click={() => 'leave untouched'} on:click>click me</Button>
53+
<Button on:click={() => 'leave untouched'} on:click>click me</Button>
54+
55+
<div>
56+
<button
57+
onclick={() => {
58+
console.log('hi');
59+
}}>click me</button
60+
>
61+
<button
62+
onclick={(event) => {
63+
event.preventDefault();
64+
65+
console.log('hi');
66+
67+
}}>click me</button
68+
>
69+
<button onclick={(event) => {
70+
event.preventDefault();
71+
count += 1
72+
}}>click me</button>
73+
</div>
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<script lang="ts">
2+
// here is a comment
3+
let div: HTMLIFrameElement;
4+
let count = 0;
5+
</script>
6+
7+
<div bind:this={div}></div>
8+
<button on:click={() => count++}>{count}</button>
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<script lang="ts">
2+
// here is a comment
3+
let div: HTMLIFrameElement = $state();
4+
let count = $state(0);
5+
</script>
6+
7+
<div bind:this={div}></div>
8+
<button onclick={() => count++}>{count}</button>

packages/svelte/tests/parser-modern/samples/comment-before-script/output.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@
7676
"id": {
7777
"type": "Identifier",
7878
"start": 52,
79-
"end": 18,
79+
"end": 57,
8080
"loc": {
8181
"start": {
8282
"line": 3,

packages/svelte/tests/parser-modern/samples/typescript-in-event-handler/output.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@
155155
"id": {
156156
"type": "Identifier",
157157
"start": 102,
158-
"end": 20,
158+
"end": 106,
159159
"loc": {
160160
"start": {
161161
"line": 7,

0 commit comments

Comments
 (0)