Skip to content

Commit f846cb4

Browse files
authored
fix: disallow using let: directives with component render tags (#12400)
closes #12087
1 parent fd7b950 commit f846cb4

File tree

10 files changed

+40
-11
lines changed

10 files changed

+40
-11
lines changed

.changeset/grumpy-insects-sleep.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: disallow using `let:` directives with component render tags

packages/svelte/messages/shared-errors/errors.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
55
## render_tag_invalid_argument
66

7-
> The argument to `{@render ...}` must be a snippet function, not a component or some other kind of function. If you want to dynamically render one snippet or another, use `$derived` and pass its result to `{@render ...}`
7+
> The argument to `{@render ...}` must be a snippet function, not a component or a slot with a `let:` directive or some other kind of function. If you want to dynamically render one snippet or another, use `$derived` and pass its result to `{@render ...}`
88
99
## snippet_used_as_component
1010

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1864,7 +1864,11 @@ export const template_visitors = {
18641864

18651865
let snippet_function = /** @type {import('estree').Expression} */ (context.visit(callee));
18661866
if (context.state.options.dev) {
1867-
snippet_function = b.call('$.validate_snippet', snippet_function);
1867+
snippet_function = b.call(
1868+
'$.validate_snippet',
1869+
snippet_function,
1870+
args.length ? b.id('$$props') : undefined
1871+
);
18681872
}
18691873

18701874
if (node.metadata.dynamic) {

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,12 @@ export function lifecycle_outside_component(name) {
2020
}
2121

2222
/**
23-
* The argument to `{@render ...}` must be a snippet function, not a component or some other kind of function. If you want to dynamically render one snippet or another, use `$derived` and pass its result to `{@render ...}`
23+
* The argument to `{@render ...}` must be a snippet function, not a component or a slot with a `let:` directive or some other kind of function. If you want to dynamically render one snippet or another, use `$derived` and pass its result to `{@render ...}`
2424
* @returns {never}
2525
*/
2626
export function render_tag_invalid_argument() {
2727
if (DEV) {
28-
const error = new Error(`render_tag_invalid_argument\nThe argument to \`{@render ...}\` must be a snippet function, not a component or some other kind of function. If you want to dynamically render one snippet or another, use \`$derived\` and pass its result to \`{@render ...}\``);
28+
const error = new Error(`render_tag_invalid_argument\nThe argument to \`{@render ...}\` must be a snippet function, not a component or a slot with a \`let:\` directive or some other kind of function. If you want to dynamically render one snippet or another, use \`$derived\` and pass its result to \`{@render ...}\``);
2929

3030
error.name = 'Svelte error';
3131
throw error;

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,10 @@ 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
1819
*/
19-
export function validate_snippet(snippet_fn) {
20-
if (snippet_fn && snippet_fn[snippet_symbol] !== true) {
20+
export function validate_snippet(snippet_fn, $$props) {
21+
if ($$props?.$$slots?.default || (snippet_fn && snippet_fn[snippet_symbol] !== true)) {
2122
e.render_tag_invalid_argument();
2223
}
2324

packages/svelte/tests/runtime-legacy/shared.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,9 @@ export function runtime_suite(runes: boolean) {
117117
(!config.test_ssr &&
118118
config.html === undefined &&
119119
config.ssrHtml === undefined &&
120-
config.error === undefined)
120+
config.error === undefined &&
121+
config.runtime_error === undefined &&
122+
!config.mode?.includes('server'))
121123
) {
122124
return 'no-test';
123125
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { test } from '../../test';
2+
3+
export default test({
4+
compileOptions: {
5+
dev: true
6+
},
7+
runtime_error: 'render_tag_invalid_argument'
8+
});
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: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<script>
2+
import Inner from './inner.svelte';
3+
</script>
4+
5+
<Inner let:foo>
6+
{foo}
7+
</Inner>

packages/svelte/tests/runtime-runes/samples/snippet-validation-error-1/_config.js

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,5 @@ export default test({
44
compileOptions: {
55
dev: true
66
},
7-
error:
8-
'render_tag_invalid_argument\n' +
9-
'The argument to `{@render ...}` must be a snippet function, not a component or some other kind of function. ' +
10-
'If you want to dynamically render one snippet or another, use `$derived` and pass its result to `{@render ...}`'
7+
error: 'render_tag_invalid_argument'
118
});

0 commit comments

Comments
 (0)