Repository: sveltejs/language-tools
Pull request #1292 (2021-12-23 15:17:24)

(fix) handle slot spread
#1291


Patch:
diff --git a/packages/svelte2tsx/src/svelte2tsx/createRenderFunction.ts b/packages/svelte2tsx/src/svelte2tsx/createRenderFunction.ts
index d24cbf99..b663d8f5 100644
--- a/packages/svelte2tsx/src/svelte2tsx/createRenderFunction.ts
+++ b/packages/svelte2tsx/src/svelte2tsx/createRenderFunction.ts
@@ -88,7 +88,11 @@ export function createRenderFunction({
           Array.from(slots.entries())
               .map(([name, attrs]) => {
                   const attrsAsString = Array.from(attrs.entries())
-                      .map(([exportName, expr]) => `${exportName}:${expr}`)
+                      .map(([exportName, expr]) =>
+                          exportName.startsWith('__spread__')
+                              ? `...${expr}`
+                              : `${exportName}:${expr}`
+                      )
                       .join(', ');
                   return `'${name}': {${attrsAsString}}`;
               })
diff --git a/packages/svelte2tsx/src/svelte2tsx/nodes/slot.ts b/packages/svelte2tsx/src/svelte2tsx/nodes/slot.ts
index 4cd6f130..3ef5b650 100644
--- a/packages/svelte2tsx/src/svelte2tsx/nodes/slot.ts
+++ b/packages/svelte2tsx/src/svelte2tsx/nodes/slot.ts
@@ -61,6 +61,12 @@ export class SlotHandler {
         return resolved;
     }
 
+    /**
+     * Returns a string which expresses the given identifier unpacked to
+     * the top level in order to express the slot types correctly later on.
+     *
+     * Example: {#each items as item} ---> __sveltets_1_unwrapArr(items)
+     */
     private getResolveExpressionStr(
         identifierDef: SvelteIdentifier,
         scope: TemplateScope,
@@ -168,6 +174,10 @@ export class SlotHandler {
         }));
     }
 
+    /**
+     * Resolves the slot expression to a string that can be used
+     * in the props-object in the return type of the render function
+     */
     private resolveExpression(expression: Node, scope: TemplateScope) {
         let resolved = this.resolvedExpression.get(expression);
         if (resolved) {
@@ -234,6 +244,14 @@ export class SlotHandler {
             if (attr.name == 'name') {
                 continue;
             }
+
+            if (attr.type === 'Spread') {
+                const rawName = attr.expression.name;
+                const init = scope.getInit(rawName);
+                const name = init ? this.resolved.get(init) : rawName;
+                attributes.set(`__spread__${name}`, name);
+            }
+
             if (!attr.value?.length) {
                 continue;
             }
@@ -268,6 +286,7 @@ export class SlotHandler {
         if (attrVal.type == 'MustacheTag') {
             return this.resolveExpression(attrVal.expression, scope);
         }
+
         throw Error('Unknown attribute value type:' + attrVal.type);
     }
 }


Test patch:
diff --git a/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/slot-typechecks/diagnostics-slots-imported.svelte b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/slot-typechecks/diagnostics-slots-imported.svelte
index 2e7530a2..f5e41474 100644
--- a/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/slot-typechecks/diagnostics-slots-imported.svelte
+++ b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/slot-typechecks/diagnostics-slots-imported.svelte
@@ -2,8 +2,10 @@
     export let prop: any;
     const defaultSlotProp = 1;
     const namedSlotProp = true;
+    const spread = {a: true, b: ''};
 </script>
 
 {prop}
 <slot {defaultSlotProp} />
 <slot name="named" {namedSlotProp} />
+<slot name="spread" {...spread} />
diff --git a/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/slot-typechecks/expected.json b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/slot-typechecks/expected.json
index 91997a60..b6f2a953 100644
--- a/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/slot-typechecks/expected.json
+++ b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/slot-typechecks/expected.json
@@ -33,5 +33,27 @@
         "message": "Cannot find name 'namedSlotProp'.",
         "code": 2304,
         "tags": []
+    },
+    {
+        "range": {
+            "start": { "line": 13, "character": 39 },
+            "end": { "line": 13, "character": 40 }
+        },
+        "severity": 1,
+        "source": "ts",
+        "message": "Property 'd' does not exist on type '{ a: boolean; b: string; }'.",
+        "code": 2339,
+        "tags": []
+    },
+    {
+        "range": {
+            "start": { "line": 16, "character": 5 },
+            "end": { "line": 16, "character": 13 }
+        },
+        "severity": 1,
+        "source": "ts",
+        "message": "This condition will always return 'false' since the types 'boolean' and 'string' have no overlap.",
+        "code": 2367,
+        "tags": []
     }
 ]
diff --git a/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/slot-typechecks/input.svelte b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/slot-typechecks/input.svelte
index dbbf396d..ec93ebbf 100644
--- a/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/slot-typechecks/input.svelte
+++ b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/slot-typechecks/input.svelte
@@ -11,4 +11,10 @@
     {defaultSlotProp}
   </p>
   {namedSlotProp}
+  <p slot="spread" let:a let:b={c} let:d>
+    {a === true}
+    {c === ''}
+    {a === ''}
+    {d}
+  </p>
 </Slots>
diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/component-slot-object-key/expected.tsx b/packages/svelte2tsx/test/svelte2tsx/samples/component-slot-object-key/expected.tsx
index f1218a3d..7ab84580 100644
--- a/packages/svelte2tsx/test/svelte2tsx/samples/component-slot-object-key/expected.tsx
+++ b/packages/svelte2tsx/test/svelte2tsx/samples/component-slot-object-key/expected.tsx
@@ -2,9 +2,9 @@
 <></>;function render() {
 /*Ωignore_startΩ*/;const __sveltets_ensureSlot = __sveltets_1_createEnsureSlot();/*Ωignore_endΩ*/
 <>{__sveltets_1_each(items, (item) => <>
-    <slot a={__sveltets_ensureSlot("default","a",item)} b={__sveltets_ensureSlot("default","b",{ item })} c={__sveltets_ensureSlot("default","c",{ item: 'abc' }.item)} d={__sveltets_ensureSlot("default","d",{ item: item })} e={__sveltets_ensureSlot("default","e",(__sveltets_1_store_get(item), $item))} f={__sveltets_ensureSlot("default","f",(__sveltets_1_store_get(item), $item))}>Hello</slot>
+    <slot a={__sveltets_ensureSlot("default","a",item)} b={__sveltets_ensureSlot("default","b",{ item })} c={__sveltets_ensureSlot("default","c",{ item: 'abc' }.item)} d={__sveltets_ensureSlot("default","d",{ item: item })} e={__sveltets_ensureSlot("default","e",(__sveltets_1_store_get(item), $item))} f={__sveltets_ensureSlot("default","f",(__sveltets_1_store_get(item), $item))} {...g} {...item}>Hello</slot>
 </>)}</>
-return { props: {}, slots: {'default': {a:__sveltets_1_unwrapArr(items), b:{ item:__sveltets_1_unwrapArr(items) }, c:{ item: 'abc' }.item, d:{ item: __sveltets_1_unwrapArr(items) }, e:$item, f:$item}}, getters: {}, events: {} }}
+return { props: {}, slots: {'default': {a:__sveltets_1_unwrapArr(items), b:{ item:__sveltets_1_unwrapArr(items) }, c:{ item: 'abc' }.item, d:{ item: __sveltets_1_unwrapArr(items) }, e:$item, f:$item, ...g, ...__sveltets_1_unwrapArr(items)}}, 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/component-slot-object-key/input.svelte b/packages/svelte2tsx/test/svelte2tsx/samples/component-slot-object-key/input.svelte
index 684b9027..5bd80cfb 100644
--- a/packages/svelte2tsx/test/svelte2tsx/samples/component-slot-object-key/input.svelte
+++ b/packages/svelte2tsx/test/svelte2tsx/samples/component-slot-object-key/input.svelte
@@ -1,3 +1,3 @@
 {#each items as item}
-    <slot a={item} b={{ item }} c={{ item: 'abc' }.item} d={{ item: item }} e={$item} f="{$item}">Hello</slot>
+    <slot a={item} b={{ item }} c={{ item: 'abc' }.item} d={{ item: item }} e={$item} f="{$item}" {...g} {...item}>Hello</slot>
 {/each}