Skip to content

Commit 51f12d2

Browse files
authored
fix: properly validate snippet/slot interop (#12421)
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 70cec4e commit 51f12d2

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
@@ -880,7 +880,12 @@ function serialize_inline_component(node, component_name, context, anchor = cont
880880
])
881881
);
882882

883-
if (slot_name === 'default' && !has_children_prop) {
883+
if (
884+
slot_name === 'default' &&
885+
!has_children_prop &&
886+
lets.length === 0 &&
887+
children.default.every((node) => node.type !== 'SvelteFragment')
888+
) {
884889
push_prop(
885890
b.init(
886891
'children',
@@ -1859,7 +1864,9 @@ export const template_visitors = {
18591864
snippet_function = b.call(
18601865
'$.validate_snippet',
18611866
snippet_function,
1862-
args.length ? b.id('$$props') : undefined
1867+
args.length && callee.type === 'Identifier' && callee.name === 'children'
1868+
? b.id('$$props')
1869+
: undefined
18631870
);
18641871
}
18651872

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)