Repository: sveltejs/language-tools
Pull request #1105 (2021-07-17 11:32:08)

(fix) handle missing semicolon in reactive assignment + store situation
#1097


Patch:
diff --git a/packages/svelte2tsx/src/svelte2tsx/nodes/ImplicitStoreValues.ts b/packages/svelte2tsx/src/svelte2tsx/nodes/ImplicitStoreValues.ts
index 8931e732..1474f5c4 100644
--- a/packages/svelte2tsx/src/svelte2tsx/nodes/ImplicitStoreValues.ts
+++ b/packages/svelte2tsx/src/svelte2tsx/nodes/ImplicitStoreValues.ts
@@ -1,6 +1,7 @@
 import MagicString from 'magic-string';
 import ts from 'typescript';
 import { surroundWithIgnoreComments } from '../../utils/ignore';
+import { preprendStr } from '../../utils/magic-string';
 import { extractIdentifiers, getNamesFromLabeledStatement } from '../utils/tsAst';
 
 /**
@@ -84,7 +85,14 @@ export class ImplicitStoreValues {
         );
         const endPos = node.getEnd() + astOffset;
 
-        str.appendRight(endPos, storeDeclarations);
+        // Hack for quick-fixing https://github.com/sveltejs/language-tools/issues/1097
+        // TODO think about a SourceMap-wrapper that does these things for us,
+        // or investigate altering the inner workings of SourceMap
+        if (str.original.charAt(endPos - 1) !== ';') {
+            preprendStr(str, endPos, storeDeclarations);
+        } else {
+            str.appendRight(endPos, storeDeclarations);
+        }
     }
 
     private attachStoreValueDeclarationOfImportsToRenderFn(str: MagicString) {
diff --git a/packages/svelte2tsx/src/svelte2tsx/nodes/ImplicitTopLevelNames.ts b/packages/svelte2tsx/src/svelte2tsx/nodes/ImplicitTopLevelNames.ts
index 1771100d..fb859f86 100644
--- a/packages/svelte2tsx/src/svelte2tsx/nodes/ImplicitTopLevelNames.ts
+++ b/packages/svelte2tsx/src/svelte2tsx/nodes/ImplicitTopLevelNames.ts
@@ -4,7 +4,7 @@ import {
     isParenthesizedObjectOrArrayLiteralExpression,
     getNamesFromLabeledStatement
 } from '../utils/tsAst';
-import { preprendStr } from '../../utils/magic-string';
+import { overwriteStr, preprendStr } from '../../utils/magic-string';
 
 export class ImplicitTopLevelNames {
     private map = new Set<ts.LabeledStatement>();
@@ -91,7 +91,7 @@ export class ImplicitTopLevelNames {
             const end = node.statement.expression.getEnd() + this.astOffset - 1;
             // We need to keep the `)` of the "wrap with invalidate" expression above.
             // We overwrite the same range so it's needed.
-            this.str.overwrite(end, end + 1, ')', { contentOnly: true });
+            overwriteStr(this.str, end, ')', true);
         }
     }
 }
diff --git a/packages/svelte2tsx/src/utils/magic-string.ts b/packages/svelte2tsx/src/utils/magic-string.ts
index 6f305311..e48d58f4 100644
--- a/packages/svelte2tsx/src/utils/magic-string.ts
+++ b/packages/svelte2tsx/src/utils/magic-string.ts
@@ -4,7 +4,43 @@ import MagicString from 'magic-string';
  * Prepends a string at the given index in a way that the source map maps the appended string
  * to the given character, not the previous character (as MagicString's other methods would).
  */
-export function preprendStr(str: MagicString, index: number, toAppend: string): MagicString {
+export function preprendStr(
+    str: MagicString,
+    index: number,
+    toAppend: string,
+    removeExisting?: boolean
+): MagicString {
+    const prepends = updatePrepends(str, index, toAppend, removeExisting);
+    toAppend = prepends.join('');
     str.overwrite(index, index + 1, toAppend + str.original.charAt(index), { contentOnly: true });
     return str;
 }
+
+/**
+ * Overwrites a string at the given index but also keeps the other preprends from `prependStr`
+ * if not explicitely told otherwise.
+ */
+export function overwriteStr(
+    str: MagicString,
+    index: number,
+    toOverwrite: string,
+    removeExisting?: boolean
+): MagicString {
+    const prepends = updatePrepends(str, index, toOverwrite, removeExisting);
+    toOverwrite = prepends.join('');
+    str.overwrite(index, index + 1, toOverwrite, { contentOnly: true });
+    return str;
+}
+
+function updatePrepends(
+    str: MagicString,
+    index: number,
+    toAppend: string,
+    removeExisting?: boolean
+) {
+    (str as any).__prepends__ = (str as any).__prepends__ || new Map<string, string[]>();
+    const prepends = removeExisting ? [] : (str as any).__prepends__.get(index) || [];
+    prepends.push(toAppend);
+    (str as any).__prepends__.set(index, prepends);
+    return prepends;
+}


Test patch:
diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/reactive-statements-store/expected.tsx b/packages/svelte2tsx/test/svelte2tsx/samples/reactive-statements-store/expected.tsx
new file mode 100644
index 00000000..07511ec2
--- /dev/null
+++ b/packages/svelte2tsx/test/svelte2tsx/samples/reactive-statements-store/expected.tsx
@@ -0,0 +1,21 @@
+///<reference types="svelte" />
+<></>;function render() {
+
+    const uid = readable('')/*Ωignore_startΩ*/;let $uid = __sveltets_1_store_get(uid);/*Ωignore_endΩ*/
+    let  foo1 = __sveltets_1_invalidate(() => getFoo1((__sveltets_1_store_get(uid), $uid)));/*Ωignore_startΩ*/;let $foo1 = __sveltets_1_store_get(foo1);/*Ωignore_endΩ*/
+    ;() => {$: console.log({ foo1: (__sveltets_1_store_get(foo1), $foo1) });}
+    
+    let  foo2 = __sveltets_1_invalidate(() => getFoo2((__sveltets_1_store_get(uid), $uid)))/*Ωignore_startΩ*/;let $foo2 = __sveltets_1_store_get(foo2);/*Ωignore_endΩ*/
+    ;() => {$: console.log({ foo2: (__sveltets_1_store_get(foo2), $foo2) })}
+
+    let  {foo3} = __sveltets_1_invalidate(() => getFoo3((__sveltets_1_store_get(uid), $uid)));/*Ωignore_startΩ*/;let $foo3 = __sveltets_1_store_get(foo3);/*Ωignore_endΩ*/
+    ;() => {$: console.log({ foo3: (__sveltets_1_store_get(foo3), $foo3) });}
+    
+    let  {foo4} = __sveltets_1_invalidate(() => getFoo4((__sveltets_1_store_get(uid), $uid)))/*Ωignore_startΩ*/;let $foo4 = __sveltets_1_store_get(foo4);/*Ωignore_endΩ*/
+    ;() => {$: console.log({ foo4: (__sveltets_1_store_get(foo4), $foo4) })}
+;
+() => (<></>);
+return { props: {}, slots: {}, getters: {}, events: {} }}
+
+export default class Input__SvelteComponent_ extends __sveltets_1_createSvelte2TsxComponent(__sveltets_1_partial(__sveltets_1_with_any_event(render()))) {
+}
\ No newline at end of file
diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/reactive-statements-store/input.svelte b/packages/svelte2tsx/test/svelte2tsx/samples/reactive-statements-store/input.svelte
new file mode 100644
index 00000000..3121658a
--- /dev/null
+++ b/packages/svelte2tsx/test/svelte2tsx/samples/reactive-statements-store/input.svelte
@@ -0,0 +1,14 @@
+<script lang="ts">
+    const uid = readable('')
+    $: foo1 = getFoo1($uid);
+    $: console.log({ foo1: $foo1 });
+    
+    $: foo2 = getFoo2($uid)
+    $: console.log({ foo2: $foo2 })
+
+    $: ({foo3} = getFoo3($uid));
+    $: console.log({ foo3: $foo3 });
+    
+    $: ({foo4} = getFoo4($uid))
+    $: console.log({ foo4: $foo4 })
+</script>
\ No newline at end of file