Skip to content

Commit 01683b4

Browse files
committed
fix: properly validate snippet/slot interop
The previous validation for checking if slots with let directives were rendered with `{@render children(...)}` had false positives - threw an error even if the other side didn't make use of the arguments, i.e. wasn't actually using a let directive - didn't check that the rendered snippet actually was the children property This fixes the validation by only applying it to children render tags, and by adding the slot to `$$slots.default` instead of `$$props.children` in more cases (when it's using `<svelte:fragment>` or `let:` directives, which both mean you're using old slot syntax) Fixes #12414
1 parent 36a6a6b commit 01683b4

File tree

7 files changed

+49
-6
lines changed

7 files changed

+49
-6
lines changed

.changeset/shy-scissors-smile.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'svelte': patch
3+
---
4+
5+
fix: properly validate snippet/slot interop

packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -887,7 +887,12 @@ function serialize_inline_component(node, component_name, context, anchor = cont
887887
])
888888
);
889889

890-
if (slot_name === 'default' && !has_children_prop) {
890+
if (
891+
slot_name === 'default' &&
892+
!has_children_prop &&
893+
lets.length === 0 &&
894+
children.default.every((node) => node.type !== 'SvelteFragment')
895+
) {
891896
push_prop(
892897
b.init(
893898
'children',
@@ -1867,7 +1872,9 @@ export const template_visitors = {
18671872
snippet_function = b.call(
18681873
'$.validate_snippet',
18691874
snippet_function,
1870-
args.length ? b.id('$$props') : undefined
1875+
args.length && callee.type === 'Identifier' && callee.name === 'children'
1876+
? b.id('$$props')
1877+
: undefined
18711878
);
18721879
}
18731880

packages/svelte/src/compiler/phases/3-transform/server/transform-server.js

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -959,7 +959,12 @@ function serialize_inline_component(node, expression, context) {
959959
])
960960
);
961961

962-
if (slot_name === 'default' && !has_children_prop) {
962+
if (
963+
slot_name === 'default' &&
964+
!has_children_prop &&
965+
lets.length === 0 &&
966+
children.default.every((node) => node.type !== 'SvelteFragment')
967+
) {
963968
push_prop(
964969
b.prop(
965970
'init',
@@ -1202,7 +1207,13 @@ const template_visitors = {
12021207

12031208
const expression = /** @type {import('estree').Expression} */ (context.visit(callee));
12041209
const snippet_function = context.state.options.dev
1205-
? b.call('$.validate_snippet', expression)
1210+
? b.call(
1211+
'$.validate_snippet',
1212+
expression,
1213+
raw_args.length && callee.type === 'Identifier' && callee.name === 'children'
1214+
? b.id('$$props')
1215+
: undefined
1216+
)
12061217
: expression;
12071218

12081219
const snippet_args = raw_args.map((arg) => {

packages/svelte/src/internal/shared/validate.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,13 @@ export function add_snippet_symbol(fn) {
1515
/**
1616
* Validate that the function handed to `{@render ...}` is a snippet function, and not some other kind of function.
1717
* @param {any} snippet_fn
18-
* @param {Record<string, any> | undefined} $$props Only passed if render tag receives arguments
18+
* @param {Record<string, any> | undefined} $$props Only passed if render tag receives arguments and is for the children prop
1919
*/
2020
export function validate_snippet(snippet_fn, $$props) {
21-
if ($$props?.$$slots?.default || (snippet_fn && snippet_fn[snippet_symbol] !== true)) {
21+
if (
22+
($$props?.$$slots?.default && typeof $$props.$$slots.default !== 'boolean') ||
23+
(snippet_fn && snippet_fn[snippet_symbol] !== true)
24+
) {
2225
e.render_tag_invalid_argument();
2326
}
2427

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { test } from '../../test';
2+
3+
export default test({
4+
compileOptions: {
5+
dev: true
6+
}
7+
});
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<script>
2+
let { children } = $props();
3+
</script>
4+
5+
{@render children(true)}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<script>
2+
import Inner from './inner.svelte';
3+
</script>
4+
5+
<Inner>I don't need to use the argument if I don't want to</Inner>

0 commit comments

Comments
 (0)