fix: bail out if slot name changes and $$slots assigned to variable (#13678)

pull/13650/head
Paolo Ricciuti 3 months ago committed by GitHub
parent ab9eeb46fe
commit 18c5a5ba2d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: bail out if slot name changes and $$slots assigned to variable

@ -418,7 +418,7 @@ const instance_script = {
state.str.remove(/** @type {number} */ (node.start), /** @type {number} */ (node.end)); state.str.remove(/** @type {number} */ (node.start), /** @type {number} */ (node.end));
} }
}, },
VariableDeclaration(node, { state, path, visit }) { VariableDeclaration(node, { state, path, visit, next }) {
if (state.scope !== state.analysis.instance.scope) { if (state.scope !== state.analysis.instance.scope) {
return; return;
} }
@ -439,12 +439,14 @@ const instance_script = {
bindings = state.scope.get_bindings(declarator); bindings = state.scope.get_bindings(declarator);
} catch (e) { } catch (e) {
// no bindings, so we can skip this // no bindings, so we can skip this
next();
continue; continue;
} }
const has_state = bindings.some((binding) => binding.kind === 'state'); const has_state = bindings.some((binding) => binding.kind === 'state');
const has_props = bindings.some((binding) => binding.kind === 'bindable_prop'); const has_props = bindings.some((binding) => binding.kind === 'bindable_prop');
if (!has_state && !has_props) { if (!has_state && !has_props) {
next();
continue; continue;
} }
@ -491,25 +493,31 @@ const instance_script = {
const prop = state.props.find((prop) => prop.exported === (binding.prop_alias || name)); const prop = state.props.find((prop) => prop.exported === (binding.prop_alias || name));
if (prop) { if (prop) {
next();
// $$Props type was used // $$Props type was used
prop.init = declarator.init prop.init = declarator.init
? state.str.original.substring( ? state.str
/** @type {number} */ (declarator.init.start), .snip(
/** @type {number} */ (declarator.init.end) /** @type {number} */ (declarator.init.start),
) /** @type {number} */ (declarator.init.end)
)
.toString()
: ''; : '';
prop.bindable = binding.updated; prop.bindable = binding.updated;
prop.exported = binding.prop_alias || name; prop.exported = binding.prop_alias || name;
prop.type_only = false; prop.type_only = false;
} else { } else {
next();
state.props.push({ state.props.push({
local: name, local: name,
exported: binding.prop_alias ? binding.prop_alias : name, exported: binding.prop_alias ? binding.prop_alias : name,
init: declarator.init init: declarator.init
? state.str.original.substring( ? state.str
/** @type {number} */ (declarator.init.start), .snip(
/** @type {number} */ (declarator.init.end) /** @type {number} */ (declarator.init.start),
) /** @type {number} */ (declarator.init.end)
)
.toString()
: '', : '',
optional: !!declarator.init, optional: !!declarator.init,
bindable: binding.updated, bindable: binding.updated,
@ -1036,6 +1044,11 @@ const template = {
name = existing_prop.local; name = existing_prop.local;
} else if (slot_name !== 'default') { } else if (slot_name !== 'default') {
name = state.scope.generate(slot_name); name = state.scope.generate(slot_name);
if (name !== slot_name) {
throw new Error(
'This migration would change the name of a slot making the component unusable'
);
}
} }
if (!existing_prop) { if (!existing_prop) {
@ -1462,7 +1475,12 @@ function handle_identifier(node, state, path) {
if (existing_prop) { if (existing_prop) {
name = existing_prop.local; name = existing_prop.local;
} else if (name !== 'default') { } else if (name !== 'default') {
name = state.scope.generate(name); let new_name = state.scope.generate(name);
if (new_name !== name) {
throw new Error(
'This migration would change the name of a slot making the component unusable'
);
}
} }
name = name === 'default' ? 'children' : name; name = name === 'default' ? 'children' : name;
@ -1479,7 +1497,7 @@ function handle_identifier(node, state, path) {
// we start with any and delegate to when the slot // we start with any and delegate to when the slot
// is actually rendered (it might not happen in that case) // is actually rendered (it might not happen in that case)
// any is still a safe bet // any is still a safe bet
type: `import('svelte').Snippet<[any]>}`, type: `import('svelte').Snippet<[any]>`,
needs_refine_type: true needs_refine_type: true
}); });
} }

@ -0,0 +1,10 @@
<script>
let showMessage = $$slots.message;
$: extraTitle = $$slots.extra;
</script>
{#if showMessage}
<slot name="message" />
{/if}
{$$props}

@ -0,0 +1,12 @@
<script>
/** @type {{message?: import('svelte').Snippet, extra?: import('svelte').Snippet<[any]>, [key: string]: any}} */
let { ...props } = $props();
let showMessage = props.message;
let extraTitle = $derived(props.extra);
</script>
{#if showMessage}
{@render props.message?.()}
{/if}
{props}

@ -0,0 +1,11 @@
<script>
export let showMessage = $$slots.message;
let showTitle = $$slots.title;
$: extraTitle = $$slots.extra;
</script>
{#if showMessage}
<slot name="message" />
{/if}

@ -0,0 +1,17 @@
<script>
/** @type {{message?: import('svelte').Snippet, showMessage?: any, title?: import('svelte').Snippet<[any]>, extra?: import('svelte').Snippet<[any]>}} */
let {
message,
showMessage = message,
title,
extra
} = $props();
let showTitle = title;
let extraTitle = $derived(extra);
</script>
{#if showMessage}
{@render message?.()}
{/if}

@ -0,0 +1,7 @@
import { test } from '../../test';
export default test({
logs: [
'One or more `@migration-task` comments were added to `output.svelte`, please check them and complete the migration manually.'
]
});

@ -0,0 +1,5 @@
<script>
let body;
</script>
<slot name="body"></slot>

@ -0,0 +1,6 @@
<!-- @migration-task Error while migrating Svelte code: This migration would change the name of a slot making the component unusable -->
<script>
let body;
</script>
<slot name="body"></slot>

@ -0,0 +1,7 @@
import { test } from '../../test';
export default test({
logs: [
'One or more `@migration-task` comments were added to `output.svelte`, please check them and complete the migration manually.'
]
});

@ -0,0 +1,2 @@
<!-- @migration-task Error while migrating Svelte code: This migration would change the name of a slot making the component unusable -->
<slot name="dashed-name"></slot>

@ -1,5 +1,2 @@
<button><slot /></button> <button><slot /></button>
<button><slot /></button> <button><slot /></button>
<slot name="dashed-name" />
<slot name="dashed-name" />

@ -1,10 +1,7 @@
<script> <script>
/** @type {{children?: import('svelte').Snippet, dashed_name?: import('svelte').Snippet}} */ /** @type {{children?: import('svelte').Snippet}} */
let { children, dashed_name } = $props(); let { children } = $props();
</script> </script>
<button>{@render children?.()}</button> <button>{@render children?.()}</button>
<button>{@render children?.()}</button> <button>{@render children?.()}</button>
{@render dashed_name?.()}
{@render dashed_name?.()}

@ -1,7 +1,7 @@
<button><slot /></button> <button><slot /></button>
{#if foo} {#if foos}
<slot name="foo" {foo} /> <slot name="foo" foo={foos} />
{/if} {/if}
{#if $$slots.bar} {#if $$slots.bar}
@ -13,8 +13,4 @@
{#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} /> <slot header="something" title={$$props.cool} {id} />

@ -1,14 +1,12 @@
<script> <script>
/** @type {{children?: import('svelte').Snippet, foo_1?: import('svelte').Snippet<[any]>, bar?: import('svelte').Snippet, dashed_name?: import('svelte').Snippet, [key: string]: any}} */ /** @type {{children?: import('svelte').Snippet, foo?: import('svelte').Snippet<[any]>, bar?: import('svelte').Snippet, [key: string]: any}} */
let { let { ...props } = $props();
...props
} = $props();
</script> </script>
<button>{@render props.children?.()}</button> <button>{@render props.children?.()}</button>
{#if foo} {#if foos}
{@render props.foo_1?.({ foo, })} {@render props.foo?.({ foo: foos, })}
{/if} {/if}
{#if props.bar} {#if props.bar}
@ -20,8 +18,4 @@
{#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, })} {@render props.children?.({ header: "something", title: props.cool, id, })}

@ -1,7 +1,7 @@
<button><slot /></button> <button><slot /></button>
{#if foo} {#if foos}
<slot name="foo" {foo} /> <slot name="foo" foo={foos} />
{/if} {/if}
{#if $$slots.bar} {#if $$slots.bar}
@ -13,8 +13,4 @@
{#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} /> <slot header="something" title={my_title} {id} />

@ -1,17 +1,12 @@
<script> <script>
/** @type {{children?: import('svelte').Snippet, foo_1?: import('svelte').Snippet<[any]>, bar?: import('svelte').Snippet, dashed_name?: import('svelte').Snippet}} */ /** @type {{children?: import('svelte').Snippet, foo?: import('svelte').Snippet<[any]>, bar?: import('svelte').Snippet}} */
let { let { children, foo, bar } = $props();
children,
foo_1,
bar,
dashed_name
} = $props();
</script> </script>
<button>{@render children?.()}</button> <button>{@render children?.()}</button>
{#if foo} {#if foos}
{@render foo_1?.({ foo, })} {@render foo?.({ foo: foos, })}
{/if} {/if}
{#if bar} {#if bar}
@ -23,8 +18,4 @@
{#if children}foo{/if} {#if children}foo{/if}
{#if dashed_name}foo{/if}
{@render dashed_name?.()}
{@render children?.({ header: "something", title: my_title, id, })} {@render children?.({ header: "something", title: my_title, id, })}
Loading…
Cancel
Save