Repository: sveltejs/language-tools
Pull request #1121 (2021-07-29 13:31:42)

(fix) infer slot/event type from literal string type props
bringing back the string props handling in #952 but change the handling method for multiple props values to a template literal. So it can cover template literal type. 

This would let the error in #1113 go away. But it would still have some problems with rename symbol. Because it's not properly ignored. 


Patch:
diff --git a/packages/svelte2tsx/src/htmlxtojsx/utils/node-utils.ts b/packages/svelte2tsx/src/htmlxtojsx/utils/node-utils.ts
index 6166bd95..9383c75b 100644
--- a/packages/svelte2tsx/src/htmlxtojsx/utils/node-utils.ts
+++ b/packages/svelte2tsx/src/htmlxtojsx/utils/node-utils.ts
@@ -120,8 +120,7 @@ function getNameValuePairsFromAttributes(
                     return { name, value: name, identifier: name };
                 }
                 if (val.type === 'Text') {
-                    // Value not important, just that it's typeof text
-                    return { name, value: '""' };
+                    return { name, value: `'${val.data || val.raw}'` };
                 }
                 if (val.type === 'MustacheTag') {
                     const valueStr = originalStr.substring(val.start + 1, val.end - 1);
@@ -130,18 +129,30 @@ function getNameValuePairsFromAttributes(
                     }
                     if (val.expression.type === 'Literal') {
                         const value =
-                            typeof val.expression.value === 'string' ? '""' : val.expression.value;
+                            typeof val.expression.value === 'string'
+                                ? val.expression.raw
+                                : val.expression.value;
                         return { name, value };
                     }
                     return { name, value: valueStr, complexExpression: true };
                 }
             }
 
-            // In the case of zero values, the user did attr="", which is the empty string.
-            // In the case of multiple values, the user did put in a property value
-            // like a="a{b}c" which is a template literal string. Since we only care
-            // about the type of the expression in the end, we can simplify this
-            return { name, value: '""' };
+            if (!attr.value.length) {
+                return { name, value: '""' };
+            }
+
+            const value = attr.value
+                .map((val) =>
+                    val.type === 'Text'
+                        ? val.raw
+                        : val.type === 'MustacheTag'
+                        ? '$' + originalStr.substring(val.start, val.end)
+                        : ''
+                )
+                .join('');
+
+            return { name, value: `\`${value}\`` };
         });
 }
 


Test patch:
diff --git a/packages/language-server/test/plugins/typescript/features/DiagnosticsProvider.test.ts b/packages/language-server/test/plugins/typescript/features/DiagnosticsProvider.test.ts
index e4677856..24c9d2f8 100644
--- a/packages/language-server/test/plugins/typescript/features/DiagnosticsProvider.test.ts
+++ b/packages/language-server/test/plugins/typescript/features/DiagnosticsProvider.test.ts
@@ -1223,7 +1223,7 @@ describe('DiagnosticsProvider', () => {
             {
                 code: 2322,
                 message:
-                    'Type \'"asd"\' is not assignable to type \'number | unique symbol | "toString" | "charAt" | "charCodeAt" | "concat" | "indexOf" | "lastIndexOf" | "localeCompare" | "match" | "replace" | "search" | "slice" | "split" | "substring" | ... 34 more ... | "replaceAll"\'.',
+                    'Type \'"asd"\' is not assignable to type \'number | unique symbol | "anchor" | "toString" | "charAt" | "charCodeAt" | "concat" | "indexOf" | "lastIndexOf" | "localeCompare" | "match" | "replace" | "search" | "slice" | ... 35 more ... | "replaceAll"\'.',
                 range: {
                     start: {
                         character: 25,
@@ -1290,6 +1290,24 @@ describe('DiagnosticsProvider', () => {
                 severity: 1,
                 source: 'ts',
                 tags: []
+            },
+            {
+                code: 2367,
+                message:
+                    "This condition will always return 'false' since the types '\"anchor\"' and '\"big\"' have no overlap.",
+                range: {
+                    end: {
+                        character: 16,
+                        line: 15
+                    },
+                    start: {
+                        character: 5,
+                        line: 15
+                    }
+                },
+                tags: [],
+                severity: 1,
+                source: 'ts'
             }
         ]);
     });
diff --git a/packages/language-server/test/plugins/typescript/testfiles/diagnostics/diagnostics-generics.svelte b/packages/language-server/test/plugins/typescript/testfiles/diagnostics/diagnostics-generics.svelte
index 00b88e97..f652b03e 100644
--- a/packages/language-server/test/plugins/typescript/testfiles/diagnostics/diagnostics-generics.svelte
+++ b/packages/language-server/test/plugins/typescript/testfiles/diagnostics/diagnostics-generics.svelte
@@ -3,11 +3,15 @@
 </script>
 
 <!-- valid -->
-<Generics a={['a', 'b']} b={'anchor'} c={false} on:b={(e) => e.detail === 'str'} let:a>
-  {a === 'str'}
+<Generics a={['a', 'b']} b={'anchor'} c={false} on:b={(e) => e.detail === 'str'} let:a let:b>
+  {a === 'str'}{b === 'anchor'}
 </Generics>
 
 <!-- invalid -->
 <Generics a={['a', 'b']} b={'asd'} c={''} on:b={(e) => e.detail === true} let:a>
   {a === true}
 </Generics>
+
+<Generics a={['a', 'b']} b={'anchor'} c={false} let:b>
+    {b === 'big'}
+</Generics>
diff --git a/packages/language-server/test/plugins/typescript/testfiles/diagnostics/generics.svelte b/packages/language-server/test/plugins/typescript/testfiles/diagnostics/generics.svelte
index 03baef7c..adaa14a5 100644
--- a/packages/language-server/test/plugins/typescript/testfiles/diagnostics/generics.svelte
+++ b/packages/language-server/test/plugins/typescript/testfiles/diagnostics/generics.svelte
@@ -13,4 +13,4 @@
   dispatch('b', a[0]);
 </script>
 
-<slot a={a[0]} />
+<slot a={a[0]} {b} />
diff --git a/packages/svelte2tsx/test/htmlx2jsx/samples/component-slot-infer-props/expected.jsx b/packages/svelte2tsx/test/htmlx2jsx/samples/component-slot-infer-props/expected.jsx
index c98ecdf4..dfff877e 100644
--- a/packages/svelte2tsx/test/htmlx2jsx/samples/component-slot-infer-props/expected.jsx
+++ b/packages/svelte2tsx/test/htmlx2jsx/samples/component-slot-infer-props/expected.jsx
@@ -1,5 +1,5 @@
-<><Parent bare shorthand={shorthand} text1="val1" text2="val2" text3={`a${a}b${b}`} textEmpty="" literal={true} strLiteral={'foo'} complex={{a}} a-dashed-complex={{a}} {...__sveltets_1_cssProp({"--custom-cssprop": `foo`})} >{() => {/*Ωignore_startΩ*/const Ψcomplex={a},Ψa_dashed_complex={a};/*Ωignore_endΩ*/() => { let {foo} = /*Ωignore_startΩ*/new Parent({target: __sveltets_1_any(''), props: {'bare':true, 'shorthand':shorthand, 'text1':"", 'text2':"", 'text3':"", 'textEmpty':"", 'literal':true, 'strLiteral':"", 'complex':Ψcomplex, 'a-dashed-complex':Ψa_dashed_complex}})/*Ωignore_endΩ*/.$$slot_def['default'];<>
-    {() => { let {bar} = /*Ωignore_startΩ*/new Parent({target: __sveltets_1_any(''), props: {'bare':true, 'shorthand':shorthand, 'text1':"", 'text2':"", 'text3':"", 'textEmpty':"", 'literal':true, 'strLiteral':"", 'complex':Ψcomplex, 'a-dashed-complex':Ψa_dashed_complex}})/*Ωignore_endΩ*/.$$slot_def['named'];<><Component  >
+<><Parent bare shorthand={shorthand} text1="val1" text2="val2" text3={`a${a}b${b}`} textEmpty="" literal={true} strLiteral={'foo'} complex={{a}} a-dashed-complex={{a}} {...__sveltets_1_cssProp({"--custom-cssprop": `foo`})} >{() => {/*Ωignore_startΩ*/const Ψcomplex={a},Ψa_dashed_complex={a};/*Ωignore_endΩ*/() => { let {foo} = /*Ωignore_startΩ*/new Parent({target: __sveltets_1_any(''), props: {'bare':true, 'shorthand':shorthand, 'text1':'val1', 'text2':'val2', 'text3':`a${a}b${b}`, 'textEmpty':"", 'literal':true, 'strLiteral':'foo', 'complex':Ψcomplex, 'a-dashed-complex':Ψa_dashed_complex}})/*Ωignore_endΩ*/.$$slot_def['default'];<>
+    {() => { let {bar} = /*Ωignore_startΩ*/new Parent({target: __sveltets_1_any(''), props: {'bare':true, 'shorthand':shorthand, 'text1':'val1', 'text2':'val2', 'text3':`a${a}b${b}`, 'textEmpty':"", 'literal':true, 'strLiteral':'foo', 'complex':Ψcomplex, 'a-dashed-complex':Ψa_dashed_complex}})/*Ωignore_endΩ*/.$$slot_def['named'];<><Component  >
         {foo} {bar}
     </Component></>}}
     <div>
diff --git a/packages/svelte2tsx/test/htmlx2jsx/samples/component-slot-shadowed-prop/expected.jsx b/packages/svelte2tsx/test/htmlx2jsx/samples/component-slot-shadowed-prop/expected.jsx
index 2968b7e9..d12bed32 100644
--- a/packages/svelte2tsx/test/htmlx2jsx/samples/component-slot-shadowed-prop/expected.jsx
+++ b/packages/svelte2tsx/test/htmlx2jsx/samples/component-slot-shadowed-prop/expected.jsx
@@ -10,10 +10,10 @@
     
     
     
->{() => {/*Ωignore_startΩ*/const Ψsubthing=subthing,Ψshadowed1=shadowed1,Ψshadowed_2=shadowed2,Ψcomplex={complex};/*Ωignore_endΩ*/() => { let {name:n, shadowed1, shadowed2, subthing} = /*Ωignore_startΩ*/new Component({target: __sveltets_1_any(''), props: {'unshadowed1':unshadowed1, 'foo':unshadowed2, 'subthing':Ψsubthing, 'shadowed1':Ψshadowed1, 'shadowed-2':Ψshadowed_2, 'templateString':"", 'complex':Ψcomplex}})/*Ωignore_endΩ*/.$$slot_def['default'];<>
-    {() => { let {subthing} = /*Ωignore_startΩ*/new Component({target: __sveltets_1_any(''), props: {'unshadowed1':unshadowed1, 'foo':unshadowed2, 'subthing':Ψsubthing, 'shadowed1':Ψshadowed1, 'shadowed-2':Ψshadowed_2, 'templateString':"", 'complex':Ψcomplex}})/*Ωignore_endΩ*/.$$slot_def['sub1'];<><p  >{thing}{subthing}</p></>}}
+>{() => {/*Ωignore_startΩ*/const Ψsubthing=subthing,Ψshadowed1=shadowed1,Ψshadowed_2=shadowed2,Ψcomplex={complex};/*Ωignore_endΩ*/() => { let {name:n, shadowed1, shadowed2, subthing} = /*Ωignore_startΩ*/new Component({target: __sveltets_1_any(''), props: {'unshadowed1':unshadowed1, 'foo':unshadowed2, 'subthing':Ψsubthing, 'shadowed1':Ψshadowed1, 'shadowed-2':Ψshadowed_2, 'templateString':` ${complex} `, 'complex':Ψcomplex}})/*Ωignore_endΩ*/.$$slot_def['default'];<>
+    {() => { let {subthing} = /*Ωignore_startΩ*/new Component({target: __sveltets_1_any(''), props: {'unshadowed1':unshadowed1, 'foo':unshadowed2, 'subthing':Ψsubthing, 'shadowed1':Ψshadowed1, 'shadowed-2':Ψshadowed_2, 'templateString':` ${complex} `, 'complex':Ψcomplex}})/*Ωignore_endΩ*/.$$slot_def['sub1'];<><p  >{thing}{subthing}</p></>}}
     
-    {() => { let {subthing, othersubthing} = /*Ωignore_startΩ*/new Component({target: __sveltets_1_any(''), props: {'unshadowed1':unshadowed1, 'foo':unshadowed2, 'subthing':Ψsubthing, 'shadowed1':Ψshadowed1, 'shadowed-2':Ψshadowed_2, 'templateString':"", 'complex':Ψcomplex}})/*Ωignore_endΩ*/.$$slot_def['sub2'];<><Sub  subthing={subthing}  >{thing}{subthing}</Sub></>}}
+    {() => { let {subthing, othersubthing} = /*Ωignore_startΩ*/new Component({target: __sveltets_1_any(''), props: {'unshadowed1':unshadowed1, 'foo':unshadowed2, 'subthing':Ψsubthing, 'shadowed1':Ψshadowed1, 'shadowed-2':Ψshadowed_2, 'templateString':` ${complex} `, 'complex':Ψcomplex}})/*Ωignore_endΩ*/.$$slot_def['sub2'];<><Sub  subthing={subthing}  >{thing}{subthing}</Sub></>}}
     
     <Sub subthing={subthing}  >{() => {/*Ωignore_startΩ*/const Ψsubthing=subthing;/*Ωignore_endΩ*/() => { let {subthing, othersubthing} = /*Ωignore_startΩ*/new Sub({target: __sveltets_1_any(''), props: {'subthing':Ψsubthing}})/*Ωignore_endΩ*/.$$slot_def['default'];<>{thing}{subthing}</>}}}</Sub>
 </>}}}</Component></>
\ No newline at end of file
diff --git a/packages/svelte2tsx/test/htmlx2jsx/samples/event-handler-component-infer-props/expected.jsx b/packages/svelte2tsx/test/htmlx2jsx/samples/event-handler-component-infer-props/expected.jsx
index 2b5c95f0..349713e7 100644
--- a/packages/svelte2tsx/test/htmlx2jsx/samples/event-handler-component-infer-props/expected.jsx
+++ b/packages/svelte2tsx/test/htmlx2jsx/samples/event-handler-component-infer-props/expected.jsx
@@ -1,1 +1,1 @@
-<><Component bare shorthand={shorthand} text1="val1" text2="val2" text3={`a${a}b${b}`} textEmpty="" literal={true} strLiteral={'foo'} complex={{a}} a-dashed-complex={{a}} {...__sveltets_1_cssProp({"--custom-cssprop": `foo`})}  />{/*Ωignore_startΩ*/new Component({target: __sveltets_1_any(''), props: {'bare':true, 'shorthand':shorthand, 'text1':"", 'text2':"", 'text3':"", 'textEmpty':"", 'literal':true, 'strLiteral':"", 'complex':{a}, 'a-dashed-complex':{a}}})/*Ωignore_endΩ*/.$on('click', e => e)}</>
\ No newline at end of file
+<><Component bare shorthand={shorthand} text1="val1" text2="val2" text3={`a${a}b${b}`} textEmpty="" literal={true} strLiteral={'foo'} complex={{a}} a-dashed-complex={{a}} {...__sveltets_1_cssProp({"--custom-cssprop": `foo`})}  />{/*Ωignore_startΩ*/new Component({target: __sveltets_1_any(''), props: {'bare':true, 'shorthand':shorthand, 'text1':'val1', 'text2':'val2', 'text3':`a${a}b${b}`, 'textEmpty':"", 'literal':true, 'strLiteral':'foo', 'complex':{a}, 'a-dashed-complex':{a}}})/*Ωignore_endΩ*/.$on('click', e => e)}</>
\ 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 32bee9cb..e98ebbef 100644
--- a/packages/svelte2tsx/test/sourcemaps/samples/large-sample-1/mappings.jsx
+++ b/packages/svelte2tsx/test/sourcemaps/samples/large-sample-1/mappings.jsx
@@ -429,17 +429,17 @@ s
                 injectedJS={mapbox_setup}
                 relaxed                                                                                                                               {/**
 ------------------------------------------------------------------------------------------------------------------------------------------------------ */}
-            />{/*Ωignore_startΩ*/new Repl({target: __sveltets_1_any(''), props: {'workersUrl':"", 'svelteUrl':svelteUrl, 'rollupUrl':rollupUrl, 'orientation':mobile ? 'columns' : 'rows', 'fixed':mobile, 'injectedJS':mapbox_setup, 'relaxed':true}})/*Ωignore_endΩ*/.$on('change', handle_change)}{/**
-         ╚╚╚/>{/*Ωignore_startΩ*/new•Repl({target:•__sveltets_1_any(''),•props:•{'workersUrl':"",•'svelteUrl':svelteUrl,•'rollupUrl':rollupUrl,•'orientation':mobile•?•'columns'•:•'rows',•'fixed':mobile,•'injectedJS':mapbox_setup,•'relaxed':true}})/*Ωignore_endΩ*/.$on('change',•handle_change)}↲    [generated] line 172
-                                                                                                                                                                                                                                                                         on('change',•handle_change)}     [generated] subset
-                                                                                                                                                                                                                                                                         on: change=  handle_change}      
-             on:change= handle_change}                                                                                                                                                                                                                                                                    
-         ╚╚╚╚on:change={handle_change}↲                                                                                                                                                                                                                                                                   [original] line 309 (rest generated at line 169)
-                                                                                                                                                                                                                                                                                                          
-         ╚╚╚/>{/*Ωignore_startΩ*/new•Repl({target:•__sveltets_1_any(''),•props:•{'workersUrl':"",•'svelteUrl':svelteUrl,•'rollupUrl':rollupUrl,•'orientation':mobile•?•'columns'•:•'rows',•'fixed':mobile,•'injectedJS':mapbox_setup,•'relaxed':true}})/*Ωignore_endΩ*/.$on('change',•handle_change)}↲    [generated] line 172
-         ╚╚╚/>{/*Ωignore_startΩ*/new•Repl({target:•__sveltets_1_any(''),•props:•{'workersUrl':"",•'svelteUrl':svelteUrl,•'rollupUrl':rollupUrl,•'orientation':mobile•?•'columns'•:•'rows',•'fixed':mobile,•'injectedJS':mapbox_setup,•'relaxed':true}})/*Ωignore_endΩ*/.$                            ↲    [generated] subset
-         ╚╚╚/>                                                                                                                                                                                                                                                                                       ↲    
-         ╚╚╚/>↲                                                                                                                                                                                                                                                                                           [original] line 312 
+            />{/*Ωignore_startΩ*/new Repl({target: __sveltets_1_any(''), props: {'workersUrl':'workers', 'svelteUrl':svelteUrl, 'rollupUrl':rollupUrl, 'orientation':mobile ? 'columns' : 'rows', 'fixed':mobile, 'injectedJS':mapbox_setup, 'relaxed':true}})/*Ωignore_endΩ*/.$on('change', handle_change)}{/**
+         ╚╚╚/>{/*Ωignore_startΩ*/new•Repl({target:•__sveltets_1_any(''),•props:•{'workersUrl':'workers',•'svelteUrl':svelteUrl,•'rollupUrl':rollupUrl,•'orientation':mobile•?•'columns'•:•'rows',•'fixed':mobile,•'injectedJS':mapbox_setup,•'relaxed':true}})/*Ωignore_endΩ*/.$on('change',•handle_change)}↲    [generated] line 172
+                                                                                                                                                                                                                                                                                on('change',•handle_change)}     [generated] subset
+                                                                                                                                                                                                                                                                                on: change=  handle_change}      
+             on:change= handle_change}                                                                                                                                                                                                                                                                           
+         ╚╚╚╚on:change={handle_change}↲                                                                                                                                                                                                                                                                          [original] line 309 (rest generated at line 169)
+                                                                                                                                                                                                                                                                                                                 
+         ╚╚╚/>{/*Ωignore_startΩ*/new•Repl({target:•__sveltets_1_any(''),•props:•{'workersUrl':'workers',•'svelteUrl':svelteUrl,•'rollupUrl':rollupUrl,•'orientation':mobile•?•'columns'•:•'rows',•'fixed':mobile,•'injectedJS':mapbox_setup,•'relaxed':true}})/*Ωignore_endΩ*/.$on('change',•handle_change)}↲    [generated] line 172
+         ╚╚╚/>{/*Ωignore_startΩ*/new•Repl({target:•__sveltets_1_any(''),•props:•{'workersUrl':'workers',•'svelteUrl':svelteUrl,•'rollupUrl':rollupUrl,•'orientation':mobile•?•'columns'•:•'rows',•'fixed':mobile,•'injectedJS':mapbox_setup,•'relaxed':true}})/*Ωignore_endΩ*/.$                            ↲    [generated] subset
+         ╚╚╚/>                                                                                                                                                                                                                                                                                              ↲    
+         ╚╚╚/>↲                                                                                                                                                                                                                                                                                                  [original] line 312 
 ------------------------------------------------------------------------------------------------------------------------------------------------------ */}
         </div>
     </div>
diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/component-slot-forward-with-props/expected.tsx b/packages/svelte2tsx/test/svelte2tsx/samples/component-slot-forward-with-props/expected.tsx
index 501fb929..c75da30f 100644
--- a/packages/svelte2tsx/test/svelte2tsx/samples/component-slot-forward-with-props/expected.tsx
+++ b/packages/svelte2tsx/test/svelte2tsx/samples/component-slot-forward-with-props/expected.tsx
@@ -1,7 +1,7 @@
 ///<reference types="svelte" />
 <></>;function render() {
 /*Ωignore_startΩ*/;const __sveltets_ensureSlot = __sveltets_1_createEnsureSlot();/*Ωignore_endΩ*/
-<><Parent propA propB={propB} propC="val1" propD="val2" propE={`a${a}b${b}`} >{() => { let {foo} = /*Ωignore_startΩ*/new Parent({target: __sveltets_1_any(''), props: {'propA':true, 'propB':propB, 'propC':"", 'propD':"", 'propE':""}})/*Ωignore_endΩ*/.$$slot_def['default'];<>
+<><Parent propA propB={propB} propC="val1" propD="val2" propE={`a${a}b${b}`} >{() => { let {foo} = /*Ωignore_startΩ*/new Parent({target: __sveltets_1_any(''), props: {'propA':true, 'propB':propB, 'propC':'val1', 'propD':'val2', 'propE':`a${a}b${b}`}})/*Ωignore_endΩ*/.$$slot_def['default'];<>
     <slot foo={__sveltets_ensureSlot("default","foo",foo)} />
 </>}}</Parent></>
 return { props: {}, slots: {'default': {foo:__sveltets_1_instanceOf(Parent).$$slot_def['default'].foo}}, getters: {}, events: {} }}