Repository: sveltejs/language-tools
Pull request #1478 (2022-05-10 12:52:38)

(feat) support const tag in if block in the old transformation
#1470. Based upon #1474. We could merge that first. 

Supporting const tag in the if block with an IIFE. So typescript would still retain the control flow. Not sure if there's a case where it won't retain the control flow state. 


Patch:
diff --git a/packages/svelte2tsx/src/htmlxtojsx/nodes/if-else.ts b/packages/svelte2tsx/src/htmlxtojsx/nodes/if-else.ts
index 78ab7b81..e0f8074a 100644
--- a/packages/svelte2tsx/src/htmlxtojsx/nodes/if-else.ts
+++ b/packages/svelte2tsx/src/htmlxtojsx/nodes/if-else.ts
@@ -2,6 +2,7 @@ import MagicString from 'magic-string';
 import { IfScope } from './if-scope';
 import { BaseNode } from '../../interfaces';
 import { withTrailingPropertyAccess } from '../utils/node-utils';
+import { extractConstTags } from './const-tag';
 
 /**
  * {# if ...}...{/if}   --->   {() => {if(...){<>...</>}}}
@@ -13,33 +14,58 @@ export function handleIf(
     ifScope: IfScope
 ): void {
     const endIf = htmlx.lastIndexOf('{', ifBlock.end - 1);
+    const constTags = extractConstTags(ifBlock.children);
+    const ifConditionEnd = htmlx.indexOf('}', ifBlock.expression.end) + 1;
+    const hasConstTags = !!constTags.length;
+    const endIIFE = createEndIIFE(hasConstTags);
+    const startIIFE = createStartIIFE(hasConstTags);
+
+    if (hasConstTags) {
+        // {@const hi = exp} <div>{hi}> -> {(() => { const hi = exp; return <> <div>{hi}<div></> })}
+
+        constTags.forEach((constTag) => {
+            constTag(ifConditionEnd, str);
+        });
+
+        str.appendRight(ifConditionEnd, 'return <>');
+
+        if (ifBlock.else) {
+            // {:else} -> </>})()}</> : <>
+            const elseWord = htmlx.lastIndexOf(':else', ifBlock.else.start);
+            const elseStart = htmlx.lastIndexOf('{', elseWord);
+            str.appendLeft(elseStart, endIIFE);
+        }
+    }
 
     if (ifBlock.elseif) {
         // {:else if expr}  ->  : (expr) ? <>
+        // {:else if expr}{@const ...}  ->  : (expr) ? <>{(() => {const ...; return <>
         const elseIfStart = htmlx.lastIndexOf('{', ifBlock.expression.start);
-        const elseIfConditionEnd = htmlx.indexOf('}', ifBlock.expression.end) + 1;
-        str.overwrite(elseIfStart, ifBlock.expression.start, '</> : (', { contentOnly: true });
+        str.overwrite(elseIfStart, ifBlock.expression.start, '</> : (', {
+            contentOnly: true
+        });
         str.overwrite(
             withTrailingPropertyAccess(str.original, ifBlock.expression.end),
-            elseIfConditionEnd,
-            ') ? <>'
+            ifConditionEnd,
+            ') ? <>' + startIIFE
         );
 
         ifScope.addElseIf(ifBlock.expression, str);
 
         if (!ifBlock.else) {
-            str.appendLeft(endIf, '</> : <>');
+            str.appendLeft(endIf, endIIFE + '</> : <>');
         }
         return;
     }
 
     // {#if expr}  ->  {(expr) ? <>
+    // {#if expr}{@const ...} ->  {(expr) ? <>{(() => {const ...; return <>
     str.overwrite(ifBlock.start, ifBlock.expression.start, '{(', { contentOnly: true });
-    const end = htmlx.indexOf('}', ifBlock.expression.end);
+
     str.overwrite(
         withTrailingPropertyAccess(str.original, ifBlock.expression.end),
-        end + 1,
-        ') ? <>',
+        ifConditionEnd,
+        ') ? <>' + startIIFE,
         { contentOnly: true }
     );
 
@@ -50,12 +76,24 @@ export function handleIf(
         str.overwrite(endIf, ifBlock.end, '</> }', { contentOnly: true });
     } else {
         // {/if}  ->  </> : <></>}
-        str.overwrite(endIf, ifBlock.end, '</> : <></>}', { contentOnly: true });
+        // {@const ...} -> </>})()}</> : <></>}
+        str.overwrite(endIf, ifBlock.end, endIIFE + '</> : <></>}', {
+            contentOnly: true
+        });
     }
 }
 
+function createStartIIFE(hasConstTags: boolean) {
+    return hasConstTags ? '{(() => {' : '';
+}
+
+function createEndIIFE(hasConstTags: boolean) {
+    return hasConstTags ? '</>})()}' : '';
+}
+
 /**
  * {:else}   --->   </> : <>
+ * {:else} {@const ...} -> </> : <>{(() => { const ...; return<>
  */
 export function handleElse(
     htmlx: string,
@@ -70,10 +108,25 @@ export function handleElse(
     ) {
         return;
     }
+
     const elseEnd = htmlx.lastIndexOf('}', elseBlock.start);
     const elseword = htmlx.lastIndexOf(':else', elseEnd);
     const elseStart = htmlx.lastIndexOf('{', elseword);
-    str.overwrite(elseStart, elseEnd + 1, '</> : <>');
+    const constTags = extractConstTags(elseBlock.children);
+    const hasConstTags = !!constTags.length;
+
+    str.overwrite(elseStart, elseEnd + 1, '</> : <>' + createStartIIFE(hasConstTags));
 
     ifScope.addElse();
+
+    if (!hasConstTags) {
+        return;
+    }
+
+    constTags.forEach((constTag) => {
+        constTag(elseEnd + 1, str);
+    });
+
+    str.appendRight(elseEnd + 1, 'return <>');
+    str.appendLeft(elseBlock.end, createEndIIFE(true));
 }
diff --git a/packages/typescript-plugin/src/language-service/find-references.ts b/packages/typescript-plugin/src/language-service/find-references.ts
index f951963c..7d1c4ecf 100644
--- a/packages/typescript-plugin/src/language-service/find-references.ts
+++ b/packages/typescript-plugin/src/language-service/find-references.ts
@@ -24,7 +24,10 @@ function _decorateFindReferences(
             ?.map((reference) => {
                 const snapshot = snapshotManager.get(reference.definition.fileName);
                 if (!isSvelteFilePath(reference.definition.fileName) || !snapshot) {
-                    return reference;
+                    return {
+                        ...reference,
+                        references: mapReferences(reference.references, snapshotManager, logger)
+                    };
                 }
 
                 const textSpan = snapshot.getOriginalTextSpan(reference.definition.textSpan);


Test patch:
diff --git a/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/const-tag-if/expected.json b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/const-tag-if/expected.json
new file mode 100644
index 00000000..8018999c
--- /dev/null
+++ b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/const-tag-if/expected.json
@@ -0,0 +1,46 @@
+[
+    {
+        "range": {
+            "start": { "line": 21, "character": 19 },
+            "end": { "line": 21, "character": 26 }
+        },
+        "severity": 1,
+        "source": "ts",
+        "message": "Property 'toFixed' does not exist on type 'string'. Did you mean 'fixed'?",
+        "code": 2551,
+        "tags": []
+    },
+    {
+        "range": {
+            "start": { "line": 21, "character": 40 },
+            "end": { "line": 21, "character": 47 }
+        },
+        "severity": 1,
+        "source": "ts",
+        "message": "Property 'toFixed' does not exist on type 'string'. Did you mean 'fixed'?",
+        "code": 2551,
+        "tags": []
+    },
+    {
+        "range": {
+            "start": { "line": 23, "character": 11 },
+            "end": { "line": 23, "character": 20 }
+        },
+        "severity": 1,
+        "source": "ts",
+        "message": "Property 'substring' does not exist on type 'number'.",
+        "code": 2339,
+        "tags": []
+    },
+    {
+        "range": {
+            "start": { "line": 25, "character": 11 },
+            "end": { "line": 25, "character": 18 }
+        },
+        "severity": 1,
+        "source": "ts",
+        "message": "Property 'toFixed' does not exist on type 'boolean'.",
+        "code": 2339,
+        "tags": []
+    }
+]
diff --git a/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/const-tag-if/expectedv2.json b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/const-tag-if/expectedv2.json
new file mode 100644
index 00000000..8018999c
--- /dev/null
+++ b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/const-tag-if/expectedv2.json
@@ -0,0 +1,46 @@
+[
+    {
+        "range": {
+            "start": { "line": 21, "character": 19 },
+            "end": { "line": 21, "character": 26 }
+        },
+        "severity": 1,
+        "source": "ts",
+        "message": "Property 'toFixed' does not exist on type 'string'. Did you mean 'fixed'?",
+        "code": 2551,
+        "tags": []
+    },
+    {
+        "range": {
+            "start": { "line": 21, "character": 40 },
+            "end": { "line": 21, "character": 47 }
+        },
+        "severity": 1,
+        "source": "ts",
+        "message": "Property 'toFixed' does not exist on type 'string'. Did you mean 'fixed'?",
+        "code": 2551,
+        "tags": []
+    },
+    {
+        "range": {
+            "start": { "line": 23, "character": 11 },
+            "end": { "line": 23, "character": 20 }
+        },
+        "severity": 1,
+        "source": "ts",
+        "message": "Property 'substring' does not exist on type 'number'.",
+        "code": 2339,
+        "tags": []
+    },
+    {
+        "range": {
+            "start": { "line": 25, "character": 11 },
+            "end": { "line": 25, "character": 18 }
+        },
+        "severity": 1,
+        "source": "ts",
+        "message": "Property 'toFixed' does not exist on type 'boolean'.",
+        "code": 2339,
+        "tags": []
+    }
+]
diff --git a/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/const-tag-if/input.svelte b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/const-tag-if/input.svelte
new file mode 100644
index 00000000..0682ac5c
--- /dev/null
+++ b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/const-tag-if/input.svelte
@@ -0,0 +1,27 @@
+<script lang="ts">
+    let value: string | number | boolean;
+</script>
+
+<!-- valid -->
+{#if typeof value === 'string'}
+    {@const valueStr = value}
+    {@const valueStr2 = valueStr}
+
+    <div>{valueStr.substring(0)}{valueStr2.substring(0)}</div>
+
+    <button on:click={() => {valueStr.substring(0)}}></button>
+{:else if typeof value === 'number'}
+    {value.toFixed()}
+{/if}
+
+<!-- invalid -->
+{#if typeof value === 'string'}
+    {@const valueStr = value}
+    {@const valueStr2 = valueStr}
+
+    <div>{valueStr.toFixed()}{valueStr2.toFixed()}</div>
+{:else if typeof value === 'number'}
+    {value.substring(0)}
+{:else}
+    {value.toFixed()}
+{/if}
\ No newline at end of file
diff --git a/packages/svelte2tsx/test/htmlx2jsx/samples/if-block-const/expected.jsx b/packages/svelte2tsx/test/htmlx2jsx/samples/if-block-const/expected.jsx
index 93732331..7f13b9b3 100644
--- a/packages/svelte2tsx/test/htmlx2jsx/samples/if-block-const/expected.jsx
+++ b/packages/svelte2tsx/test/htmlx2jsx/samples/if-block-const/expected.jsx
@@ -1,10 +1,28 @@
-<>{(name == "world") ? <>
-    {@const hello = name}
+<>{(name == "world") ? <>{(() => {const hello = name;return <>
+    { }
     <h1>Hello {hello}</h1>
-</> : (true) ? <>
-    {@const hello = name}
+</>})()}</> : (true) ? <>{(() => {const hello = name;return <>
+    { }
     <h1>Hello {hello}</h1>
-</> : <>
-    {@const hello = name}
+</>})()}</> : <>{(() => {const hello = name;return <>
+    { }
     <h1>Hello {hello}</h1>
-</> }</>
\ No newline at end of file
+</>})()}</> }
+
+{(typeof a === 'string') ? <>{(() => {const aStr = a;const aStr2 = aStr;return <>
+    { }
+    { }
+
+    {a}
+</>})()}</> : (typeof a === 'number') ? <>{(() => {const aNum = a;return <>
+    { }
+</>})()}</> : <></> }
+
+{(typeof a === 'string') ? <>{(() => {const aStr = a;return <>
+    { }
+</>})()}</> : <></>}
+
+{(typeof a === 'string') ? <>{(() => {const aStr = a;return <>
+    { }
+</>})()}</> : <>
+</> }</>
diff --git a/packages/svelte2tsx/test/htmlx2jsx/samples/if-block-const/expectedv2.js b/packages/svelte2tsx/test/htmlx2jsx/samples/if-block-const/expectedv2.js
index 33291a88..c1a0e3eb 100644
--- a/packages/svelte2tsx/test/htmlx2jsx/samples/if-block-const/expectedv2.js
+++ b/packages/svelte2tsx/test/htmlx2jsx/samples/if-block-const/expectedv2.js
@@ -7,4 +7,22 @@ if(name == "world"){
 }else{
     const hello = name;
      { svelteHTML.createElement("h1", {}); hello; }
-}
\ No newline at end of file
+}
+
+if(typeof a === 'string'){
+    const aStr = a;
+    const aStr2 = aStr;
+
+    a;
+} else if (typeof a === 'number'){
+    const aNum = a;
+}
+
+if(typeof a === 'string'){
+    const aStr = a;
+}
+
+if(typeof a === 'string'){
+    const aStr = a;
+}else{
+}
diff --git a/packages/svelte2tsx/test/htmlx2jsx/samples/if-block-const/input.svelte b/packages/svelte2tsx/test/htmlx2jsx/samples/if-block-const/input.svelte
index 05417176..8044633c 100644
--- a/packages/svelte2tsx/test/htmlx2jsx/samples/if-block-const/input.svelte
+++ b/packages/svelte2tsx/test/htmlx2jsx/samples/if-block-const/input.svelte
@@ -8,3 +8,21 @@
     {@const hello = name}
     <h1>Hello {hello}</h1>
 {/if}
+
+{#if typeof a === 'string'}
+    {@const aStr = a}
+    {@const aStr2 = aStr}
+
+    {a}
+{:else if typeof a === 'number'}
+    {@const aNum = a}
+{/if}
+
+{#if typeof a === 'string'}
+    {@const aStr = a}
+{/if}
+
+{#if typeof a === 'string'}
+    {@const aStr = a}
+{:else}
+{/if}