Skip to content

Commit

Permalink
fix: correctly migrate $$slots with bracket member expressions & sl…
Browse files Browse the repository at this point in the history
…ots with static props (#13468)

- fix: correctly migrate `$$slots` with bracket member expressions
- fix: wrong type for JSDoc `$$restProps`
- fix: correctly migrate slots with static props
-  fix: `$$props` messing with `$$slots`

Fixes #13467, fixes #13466, fixes #13472
  • Loading branch information
paoloricciuti authored Oct 4, 2024
1 parent 785779a commit 49a0908
Show file tree
Hide file tree
Showing 10 changed files with 141 additions and 21 deletions.
5 changes: 5 additions & 0 deletions .changeset/nine-kids-whisper.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'svelte': patch
---

fix: correctly migrate `$$slots` with bracket member expressions & slots with static props
80 changes: 64 additions & 16 deletions packages/svelte/src/compiler/migrate/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ export function migrate(source) {
}
} else {
if (analysis.uses_props || analysis.uses_rest_props) {
type = `{Record<string, any>}`;
type = `Record<string, any>`;
} else {
type = `{${state.props
.map((prop) => {
Expand Down Expand Up @@ -294,7 +294,7 @@ export function migrate(source) {
* str: MagicString;
* analysis: ComponentAnalysis;
* indent: string;
* props: Array<{ local: string; exported: string; init: string; bindable: boolean; slot_name?: string; optional: boolean; type: string; comment?: string, type_only?: boolean }>;
* props: Array<{ local: string; exported: string; init: string; bindable: boolean; slot_name?: string; optional: boolean; type: string; comment?: string; type_only?: boolean; needs_refine_type?: boolean; }>;
* props_insertion_point: number;
* has_props_rune: boolean;
* end: number;
Expand Down Expand Up @@ -704,7 +704,7 @@ const template = {
handle_events(node, state);
next();
},
SlotElement(node, { state, next }) {
SlotElement(node, { state, next, visit }) {
if (state.analysis.custom_element) return;
let name = 'children';
let slot_name = 'default';
Expand All @@ -719,13 +719,22 @@ const template = {
} else {
const attr_value =
attr.value === true || Array.isArray(attr.value) ? attr.value : [attr.value];
const value =
attr_value !== true
? state.str.original.substring(
attr_value[0].start,
attr_value[attr_value.length - 1].end
)
: 'true';
let value = 'true';
if (attr_value !== true) {
const first = attr_value[0];
const last = attr_value[attr_value.length - 1];
for (const attr of attr_value) {
visit(attr);
}
value = state.str
.snip(
first.type === 'Text'
? first.start - 1
: /** @type {number} */ (first.expression.start),
last.type === 'Text' ? last.end + 1 : /** @type {number} */ (last.expression.end)
)
.toString();
}
slot_props += value === attr.name ? `${value}, ` : `${attr.name}: ${value}, `;
}
}
Expand Down Expand Up @@ -753,18 +762,25 @@ const template = {
slot_name,
type: `import('svelte').${slot_props ? 'Snippet<[any]>' : 'Snippet'}`
});
} else if (existing_prop.needs_refine_type) {
existing_prop.type = `import('svelte').${slot_props ? 'Snippet<[any]>' : 'Snippet'}`;
existing_prop.needs_refine_type = false;
}

if (node.fragment.nodes.length > 0) {
next();
state.str.update(
node.start,
node.fragment.nodes[0].start,
`{#if ${name}}{@render ${name}(${slot_props})}{:else}`
`{#if ${name}}{@render ${state.analysis.uses_props ? `${state.names.props}.` : ''}${name}(${slot_props})}{:else}`
);
state.str.update(node.fragment.nodes[node.fragment.nodes.length - 1].end, node.end, '{/if}');
} else {
state.str.update(node.start, node.end, `{@render ${name}?.(${slot_props})}`);
state.str.update(
node.start,
node.end,
`{@render ${state.analysis.uses_props ? `${state.names.props}.` : ''}${name}?.(${slot_props})}`
);
}
},
Comment(node, { state }) {
Expand Down Expand Up @@ -958,7 +974,7 @@ function handle_identifier(node, state, path) {
const parent = path.at(-1);
if (parent?.type === 'MemberExpression' && parent.property === node) return;

if (state.analysis.uses_props) {
if (state.analysis.uses_props && node.name !== '$$slots') {
if (node.name === '$$props' || node.name === '$$restProps') {
// not 100% correct for $$restProps but it'll do
state.str.update(
Expand All @@ -980,10 +996,42 @@ function handle_identifier(node, state, path) {
);
} else if (node.name === '$$slots' && state.analysis.uses_slots) {
if (parent?.type === 'MemberExpression') {
state.str.update(/** @type {number} */ (node.start), parent.property.start, '');
if (parent.property.name === 'default') {
state.str.update(parent.property.start, parent.property.end, 'children');
if (state.analysis.custom_element) return;

let name = parent.property.type === 'Literal' ? parent.property.value : parent.property.name;
let slot_name = name;
const existing_prop = state.props.find((prop) => prop.slot_name === name);
if (existing_prop) {
name = existing_prop.local;
} else if (name !== 'default') {
name = state.scope.generate(name);
}

name = name === 'default' ? 'children' : name;

if (!existing_prop) {
state.props.push({
local: name,
exported: name,
init: '',
bindable: false,
optional: true,
slot_name,
// if it's the first time we encounter this slot
// we start with any and delegate to when the slot
// is actually rendered (it might not happen in that case)
// any is still a safe bet
type: `import('svelte').Snippet<[any]>}`,
needs_refine_type: true
});
}

state.str.update(
/** @type {number} */ (node.start),
parent.property.start,
state.analysis.uses_props ? `${state.names.props}.` : ''
);
state.str.update(parent.property.start, parent.end, name);
}
// else passed as identifier, we don't know what to do here, so let it error
} else if (
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<script>
/** @type {{Record<string, any>}} */
/** @type {Record<string, any>} */
let { foo, ...rest } = $props();
</script>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,8 @@

{#if $$slots.default}foo{/if}

{#if $$slots['default']}foo{/if}

{#if $$slots['dashed-name']}foo{/if}

<slot name="dashed-name" />
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,15 @@
<slot name="foo" {foo} />
{/if}

{#if bar}
{#if $$slots.bar}
{$$slots}
<slot name="bar" />
{/if}

{#if children}foo{/if}
{#if $$slots.default}foo{/if}

{#if $$slots['default']}foo{/if}

{#if $$slots['dashed-name']}foo{/if}

<slot name="dashed-name" />
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<button><slot /></button>

{#if foo}
<slot name="foo" {foo} />
{/if}

{#if $$slots.bar}
{$$slots}
<slot name="bar" />
{/if}

{#if $$slots.default}foo{/if}

{#if $$slots['default']}foo{/if}

{#if $$slots['dashed-name']}foo{/if}

<slot name="dashed-name" />

<slot header="something" title={$$props.cool} {id} />
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<script>
/** @type {Record<string, any>} */
let {
...props
} = $props();
</script>

<button>{@render props.children?.()}</button>

{#if foo}
{@render props.foo_1?.({ foo, })}
{/if}

{#if props.bar}
{$$slots}
{@render props.bar?.()}
{/if}

{#if props.children}foo{/if}

{#if props.children}foo{/if}

{#if props.dashed_name}foo{/if}

{@render props.dashed_name?.()}

{@render props.children?.({ header: "something", title: props.cool, id, })}
6 changes: 6 additions & 0 deletions packages/svelte/tests/migrate/samples/slots/input.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,10 @@

{#if $$slots.default}foo{/if}

{#if $$slots['default']}foo{/if}

{#if $$slots['dashed-name']}foo{/if}

<slot name="dashed-name" />

<slot header="something" title={my_title} {id} />
8 changes: 7 additions & 1 deletion packages/svelte/tests/migrate/samples/slots/output.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,10 @@

{#if children}foo{/if}

{@render dashed_name?.()}
{#if children}foo{/if}

{#if dashed_name}foo{/if}

{@render dashed_name?.()}

{@render children?.({ header: "something", title: my_title, id, })}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<script>
/** @type {{Record<string, any>}} */
/** @type {Record<string, any>} */
let { ...rest } = $props();
let Component;
let fallback;
Expand Down

0 comments on commit 49a0908

Please sign in to comment.