Repository: sveltejs/language-tools
Pull request #1410 (2022-03-12 10:28:54)

untyped and `any` should have the same behaviour
If a component that contains `<script lang="ts">` exports a prop that is not typed, `TypeScript` treats it as `any` and this is also true for the usage in the component itself. But it shows up as `undefined` in other components.

I think these two props should have the same behaviour:
```svelte
<script lang="ts">
    export let untyped;
    export let typedAsAny: any;
</script>
```

I'm not sure where I can fix this, so I just started by writing tests.
Any suggestion where this needs to be fixed?

dummdidumm: @jasonlyu123 you mentioned this in #1378 and now it came up through this PR. What do you think about also initializing the prop in this case? Doesn't hurt in my opinion, it also gets rid of some "properly undefined" errors when using an uninitialized prop during component initialization.
jasonlyu123: Looks ok. I also think it wouldn't hurt. Can't think of other dropbacks. 

Patch:
diff --git a/packages/svelte2tsx/src/svelte2tsx/nodes/ExportedNames.ts b/packages/svelte2tsx/src/svelte2tsx/nodes/ExportedNames.ts
index 7614f265..98e0d363 100644
--- a/packages/svelte2tsx/src/svelte2tsx/nodes/ExportedNames.ts
+++ b/packages/svelte2tsx/src/svelte2tsx/nodes/ExportedNames.ts
@@ -112,24 +112,28 @@ export class ExportedNames {
             const type = tsType || jsDocType;
 
             if (
-                !ts.isIdentifier(identifier) ||
-                (!type &&
+                ts.isIdentifier(identifier) &&
+                // Ensure initialization for proper control flow and to avoid "possibly undefined" type errors.
+                // Also ensure prop is typed as any with a type annotation in TS strict mode
+                (!declaration.initializer ||
+                    // Widen the type, else it's narrowed to the initializer
+                    type ||
                     // Edge case: TS infers `export let bla = false` to type `false`.
                     // prevent that by adding the any-wrap in this case, too.
-                    ![ts.SyntaxKind.FalseKeyword, ts.SyntaxKind.TrueKeyword].includes(
-                        declaration.initializer?.kind
-                    ))
+                    (!type &&
+                        [ts.SyntaxKind.FalseKeyword, ts.SyntaxKind.TrueKeyword].includes(
+                            declaration.initializer.kind
+                        )))
             ) {
-                return;
+                const name = identifier.getText();
+                const end = declaration.end + this.astOffset;
+
+                preprendStr(
+                    this.str,
+                    end,
+                    surroundWithIgnoreComments(`;${name} = __sveltets_1_any(${name});`)
+                );
             }
-            const name = identifier.getText();
-            const end = declaration.end + this.astOffset;
-
-            preprendStr(
-                this.str,
-                end,
-                surroundWithIgnoreComments(`;${name} = __sveltets_1_any(${name});`)
-            );
         };
 
         const findComma = (target: ts.Node) =>


Test patch:
diff --git a/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/props-untyped/expected.json b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/props-untyped/expected.json
new file mode 100644
index 00000000..fe51488c
--- /dev/null
+++ b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/props-untyped/expected.json
@@ -0,0 +1,1 @@
+[]
diff --git a/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/props-untyped/expectedv2.json b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/props-untyped/expectedv2.json
new file mode 100644
index 00000000..fe51488c
--- /dev/null
+++ b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/props-untyped/expectedv2.json
@@ -0,0 +1,1 @@
+[]
diff --git a/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/props-untyped/input.svelte b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/props-untyped/input.svelte
new file mode 100644
index 00000000..14d13c70
--- /dev/null
+++ b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/props-untyped/input.svelte
@@ -0,0 +1,13 @@
+<script lang="ts">
+	import Component from './untyped-ts.svelte'
+</script>
+
+<Component typedAsAny={undefined} untyped={undefined} />
+<Component typedAsAny={null} untyped={null} />
+<Component typedAsAny={true} untyped={true} />
+<Component typedAsAny={123} untyped={123} />
+<Component typedAsAny='string' untyped='string' />
+<Component typedAsAny={{some: 'object'}} untyped={{some: 'object'}} />
+<Component typedAsAny={['string', 'array']} untyped={['string', 'array']} />
+<Component typedAsAny={['array', 123, false]} untyped={['array', 123, false]} />
+<Component typedAsAny={['array', 123, false]} untyped={['array', 123, false]} />
diff --git a/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/props-untyped/untyped-ts.svelte b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/props-untyped/untyped-ts.svelte
new file mode 100644
index 00000000..c00abb9f
--- /dev/null
+++ b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/props-untyped/untyped-ts.svelte
@@ -0,0 +1,4 @@
+<script lang="ts">
+    export let untyped;
+    export let typedAsAny: any;
+</script>
\ No newline at end of file
diff --git a/packages/svelte2tsx/test/sourcemaps/samples/large-sample-1/mappings.jsx b/packages/svelte2tsx/test/sourcemaps/samples/large-sample-1/mappings.jsx
index 3ca70f65..20f5b55f 100644
--- a/packages/svelte2tsx/test/sourcemaps/samples/large-sample-1/mappings.jsx
+++ b/packages/svelte2tsx/test/sourcemaps/samples/large-sample-1/mappings.jsx
@@ -118,15 +118,17 @@ s
 ------------------------------------------------------------------------------------------------------------------------------------------------------ */}
                                                                                                                                                       {/**
 ------------------------------------------------------------------------------------------------------------------------------------------------------ */}
-     let slug;                                                                                                                                        {/**
-   ╚•let•slug;↲          [generated] line 35                                                                                                          
+     let slug/*Ωignore_startΩ*/;slug = __sveltets_1_any(slug);/*Ωignore_endΩ*/;                                                                       {/**
+   ╚•let•slug/*Ωignore_startΩ*/;slug•=•__sveltets_1_any(slug);/*Ωignore_endΩ*/;↲    [generated] line 35                                               
+   ╚•let•slug;                                                                 ↲                                                                      
    ╚      •let•slug;↲                                                                                                                                 
-   ╚export•let•slug;↲    [original] line 29                                                                                                           
+   ╚export•let•slug;↲                                                               [original] line 29                                                
 ------------------------------------------------------------------------------------------------------------------------------------------------------ */}
-     let chapter;                                                                                                                                     {/**
-   ╚•let•chapter;↲          [generated] line 36                                                                                                       
+     let chapter/*Ωignore_startΩ*/;chapter = __sveltets_1_any(chapter);/*Ωignore_endΩ*/;                                                              {/**
+   ╚•let•chapter/*Ωignore_startΩ*/;chapter•=•__sveltets_1_any(chapter);/*Ωignore_endΩ*/;↲    [generated] line 36                                      
+   ╚•let•chapter;                                                                       ↲                                                             
    ╚      •let•chapter;↲                                                                                                                              
-   ╚export•let•chapter;↲    [original] line 30                                                                                                        
+   ╚export•let•chapter;↲                                                                     [original] line 30                                       
 ------------------------------------------------------------------------------------------------------------------------------------------------------ */}
 
     const { sections } = getContext('tutorial');
diff --git a/packages/svelte2tsx/test/sourcemaps/samples/reactive-statements/mappings.jsx b/packages/svelte2tsx/test/sourcemaps/samples/reactive-statements/mappings.jsx
index 59015592..990843b0 100644
--- a/packages/svelte2tsx/test/sourcemaps/samples/reactive-statements/mappings.jsx
+++ b/packages/svelte2tsx/test/sourcemaps/samples/reactive-statements/mappings.jsx
@@ -12,12 +12,12 @@
         ↲                                                                                                                                             
 <script>↲    [original] line 1 (rest generated at line 2)                                                                                             
 ------------------------------------------------------------------------------------------------------------------------------------------------------ */}
-     let prop/*Ωignore_startΩ*/;let $prop = __sveltets_1_store_get(prop);/*Ωignore_endΩ*/                                                             {/**
-   ╚•let•prop/*Ωignore_startΩ*/;let•$prop•=•__sveltets_1_store_get(prop);/*Ωignore_endΩ*/↲    [generated] line 4                                      
-   ╚•let•prop                                                                            ↲                                                            
-   ╚      •let•prop↲                                                                                                                                  
-   ╚export•let•prop↲                                                                          [original] line 2                                       
------------------------------------------------------------------------------------------------------------------------------------------------------- */}
+     let prop/*Ωignore_startΩ*/;let $prop = __sveltets_1_store_get(prop);/*Ωignore_endΩ*//*Ωignore_startΩ*/;prop = __sveltets_1_any(prop);/*Ωignore_endΩ*/
+   ╚•let•prop/*Ωignore_startΩ*/;let•$prop•=•__sveltets_1_store_get(prop);/*Ωignore_endΩ*//*Ωignore_startΩ*/;prop•=•__sveltets_1_any(prop);/*Ωignore_endΩ*/↲    [generated] line 4
+   ╚•let•prop                                                                            ↲                                                                     
+   ╚      •let•prop↲                                                                                                                                           
+   ╚export•let•prop↲                                                                                                                                           [original] line 2 
+//----------------------------------------------------------------------------------------------------------------------------------------------------
     let  foo = __sveltets_1_invalidate(() => prop);                                                                                                   {/**
    ╚let••foo•=•__sveltets_1_invalidate(()•=>•prop);↲    [generated] line 5                                                                            
    ╚    •foo•=•                              prop; ↲                                                                                                  
diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/export-doc/expected.tsx b/packages/svelte2tsx/test/svelte2tsx/samples/export-doc/expected.tsx
index 47024ccb..b3856c23 100644
--- a/packages/svelte2tsx/test/svelte2tsx/samples/export-doc/expected.tsx
+++ b/packages/svelte2tsx/test/svelte2tsx/samples/export-doc/expected.tsx
@@ -4,23 +4,23 @@
     /**
      * DOCS!
      */
-     let a;
+     let a/*Ωignore_startΩ*/;a = __sveltets_1_any(a);/*Ωignore_endΩ*/;
     /**
      * not this
      */
     /**
      * MORE DOCS!
      */
-     let b;
-     let c;
+     let b/*Ωignore_startΩ*/;b = __sveltets_1_any(b);/*Ωignore_endΩ*/;
+     let c/*Ωignore_startΩ*/;c = __sveltets_1_any(c);/*Ωignore_endΩ*/;
     /**
      * d
      */
-     let d;let 
+     let d/*Ωignore_startΩ*/;d = __sveltets_1_any(d);/*Ωignore_endΩ*/;let 
     /**
      * e
      */
-     e;
+     e/*Ωignore_startΩ*/;e = __sveltets_1_any(e);/*Ωignore_endΩ*/;
 ;
 () => (<></>);
 return { props: {
diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/export-doc/expectedv2.ts b/packages/svelte2tsx/test/svelte2tsx/samples/export-doc/expectedv2.ts
index 2dc08f85..96d2a4ed 100644
--- a/packages/svelte2tsx/test/svelte2tsx/samples/export-doc/expectedv2.ts
+++ b/packages/svelte2tsx/test/svelte2tsx/samples/export-doc/expectedv2.ts
@@ -4,23 +4,23 @@
     /**
      * DOCS!
      */
-     let a;
+     let a/*Ωignore_startΩ*/;a = __sveltets_1_any(a);/*Ωignore_endΩ*/;
     /**
      * not this
      */
     /**
      * MORE DOCS!
      */
-     let b;
-     let c;
+     let b/*Ωignore_startΩ*/;b = __sveltets_1_any(b);/*Ωignore_endΩ*/;
+     let c/*Ωignore_startΩ*/;c = __sveltets_1_any(c);/*Ωignore_endΩ*/;
     /**
      * d
      */
-     let d;let 
+     let d/*Ωignore_startΩ*/;d = __sveltets_1_any(d);/*Ωignore_endΩ*/;let 
     /**
      * e
      */
-     e;
+     e/*Ωignore_startΩ*/;e = __sveltets_1_any(e);/*Ωignore_endΩ*/;
 ;
 async () => {};
 return { props: {
diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/export-js-required-props/expected.tsx b/packages/svelte2tsx/test/svelte2tsx/samples/export-js-required-props/expected.tsx
index 5b3dc9bc..b411a41e 100644
--- a/packages/svelte2tsx/test/svelte2tsx/samples/export-js-required-props/expected.tsx
+++ b/packages/svelte2tsx/test/svelte2tsx/samples/export-js-required-props/expected.tsx
@@ -1,8 +1,8 @@
 ///<reference types="svelte" />
 <></>;function render() {
 
-     let a;
-     let b;
+     let a/*Ωignore_startΩ*/;a = __sveltets_1_any(a);/*Ωignore_endΩ*/;
+     let b/*Ωignore_startΩ*/;b = __sveltets_1_any(b);/*Ωignore_endΩ*/;
      let c = 123;
 ;
 () => (<></>);
diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/export-js-required-props/expectedv2.ts b/packages/svelte2tsx/test/svelte2tsx/samples/export-js-required-props/expectedv2.ts
index 73152523..0f983efc 100644
--- a/packages/svelte2tsx/test/svelte2tsx/samples/export-js-required-props/expectedv2.ts
+++ b/packages/svelte2tsx/test/svelte2tsx/samples/export-js-required-props/expectedv2.ts
@@ -1,8 +1,8 @@
 ///<reference types="svelte" />
 ;function render() {
 
-     let a;
-     let b;
+     let a/*Ωignore_startΩ*/;a = __sveltets_1_any(a);/*Ωignore_endΩ*/;
+     let b/*Ωignore_startΩ*/;b = __sveltets_1_any(b);/*Ωignore_endΩ*/;
      let c = 123;
 ;
 async () => {};
diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/export-list/expected.tsx b/packages/svelte2tsx/test/svelte2tsx/samples/export-list/expected.tsx
index 8b428b18..0e5e8c53 100644
--- a/packages/svelte2tsx/test/svelte2tsx/samples/export-list/expected.tsx
+++ b/packages/svelte2tsx/test/svelte2tsx/samples/export-list/expected.tsx
@@ -2,10 +2,10 @@
 <></>;function render() {
 
     let name1 = "world"
-    let name2
+    let name2/*Ωignore_startΩ*/;name2 = __sveltets_1_any(name2);/*Ωignore_endΩ*/
 
     let rename1 = '';
-    let rename2;
+    let rename2/*Ωignore_startΩ*/;rename2 = __sveltets_1_any(rename2);/*Ωignore_endΩ*/;
 
     class Foo {}
     function bar() {}
diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/export-list/expectedv2.ts b/packages/svelte2tsx/test/svelte2tsx/samples/export-list/expectedv2.ts
index 4a35466f..e1e36f57 100644
--- a/packages/svelte2tsx/test/svelte2tsx/samples/export-list/expectedv2.ts
+++ b/packages/svelte2tsx/test/svelte2tsx/samples/export-list/expectedv2.ts
@@ -2,10 +2,10 @@
 ;function render() {
 
     let name1 = "world"
-    let name2
+    let name2/*Ωignore_startΩ*/;name2 = __sveltets_1_any(name2);/*Ωignore_endΩ*/
 
     let rename1 = '';
-    let rename2;
+    let rename2/*Ωignore_startΩ*/;rename2 = __sveltets_1_any(rename2);/*Ωignore_endΩ*/;
 
     class Foo {}
     function bar() {}
diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/ts-export-doc-typedef/expected.tsx b/packages/svelte2tsx/test/svelte2tsx/samples/ts-export-doc-typedef/expected.tsx
index 08760bf8..94f14027 100644
--- a/packages/svelte2tsx/test/svelte2tsx/samples/ts-export-doc-typedef/expected.tsx
+++ b/packages/svelte2tsx/test/svelte2tsx/samples/ts-export-doc-typedef/expected.tsx
@@ -9,7 +9,7 @@
      * DOCS!
      * @typedef {'ABC'} ABC
      */
-     let a;
+     let a/*Ωignore_startΩ*/;a = __sveltets_1_any(a);/*Ωignore_endΩ*/;
 ;
 () => (<></>);
 return { props: {
diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/ts-export-doc-typedef/expectedv2.ts b/packages/svelte2tsx/test/svelte2tsx/samples/ts-export-doc-typedef/expectedv2.ts
index 0170a3f5..4b2e6c5c 100644
--- a/packages/svelte2tsx/test/svelte2tsx/samples/ts-export-doc-typedef/expectedv2.ts
+++ b/packages/svelte2tsx/test/svelte2tsx/samples/ts-export-doc-typedef/expectedv2.ts
@@ -9,7 +9,7 @@
      * DOCS!
      * @typedef {'ABC'} ABC
      */
-     let a;
+     let a/*Ωignore_startΩ*/;a = __sveltets_1_any(a);/*Ωignore_endΩ*/;
 ;
 async () => {};
 return { props: {
diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/ts-export-doc/expected.tsx b/packages/svelte2tsx/test/svelte2tsx/samples/ts-export-doc/expected.tsx
index 3f2cd1a7..277a2b5f 100644
--- a/packages/svelte2tsx/test/svelte2tsx/samples/ts-export-doc/expected.tsx
+++ b/packages/svelte2tsx/test/svelte2tsx/samples/ts-export-doc/expected.tsx
@@ -9,10 +9,10 @@
      * MORE DOCS!
      */
      let b = 1;
-     let c;
+     let c/*Ωignore_startΩ*/;c = __sveltets_1_any(c);/*Ωignore_endΩ*/;
 
     // not this one
-     let d;
+     let d/*Ωignore_startΩ*/;d = __sveltets_1_any(d);/*Ωignore_endΩ*/;
 ;
 () => (<></>);
 return { props: {a: a , b: b , c: c , d: d} as {
diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/ts-export-doc/expectedv2.ts b/packages/svelte2tsx/test/svelte2tsx/samples/ts-export-doc/expectedv2.ts
index 45c7528c..17b91a0c 100644
--- a/packages/svelte2tsx/test/svelte2tsx/samples/ts-export-doc/expectedv2.ts
+++ b/packages/svelte2tsx/test/svelte2tsx/samples/ts-export-doc/expectedv2.ts
@@ -9,10 +9,10 @@
      * MORE DOCS!
      */
      let b = 1;
-     let c;
+     let c/*Ωignore_startΩ*/;c = __sveltets_1_any(c);/*Ωignore_endΩ*/;
 
     // not this one
-     let d;
+     let d/*Ωignore_startΩ*/;d = __sveltets_1_any(d);/*Ωignore_endΩ*/;
 ;
 async () => {};
 return { props: {a: a , b: b , c: c , d: d} as {