Skip to content

fix: correctly migrate $$slots with bracket member expressions & slots with static props #13468

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Oct 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -191,7 +191,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 @@ -286,7 +286,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 @@ -696,7 +696,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 @@ -711,13 +711,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 @@ -745,18 +754,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 @@ -950,7 +966,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 @@ -972,10 +988,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