diff --git a/.changeset/grumpy-insects-sleep.md b/.changeset/grumpy-insects-sleep.md new file mode 100644 index 0000000000..e6c2fea660 --- /dev/null +++ b/.changeset/grumpy-insects-sleep.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: disallow using `let:` directives with component render tags diff --git a/packages/svelte/messages/shared-errors/errors.md b/packages/svelte/messages/shared-errors/errors.md index b7b2e8432f..d693b35e05 100644 --- a/packages/svelte/messages/shared-errors/errors.md +++ b/packages/svelte/messages/shared-errors/errors.md @@ -4,7 +4,7 @@ ## render_tag_invalid_argument -> 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 ...}` +> 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 ...}` ## snippet_used_as_component diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js index b46c17696f..8e545e2d0d 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js @@ -1864,7 +1864,11 @@ export const template_visitors = { let snippet_function = /** @type {import('estree').Expression} */ (context.visit(callee)); if (context.state.options.dev) { - snippet_function = b.call('$.validate_snippet', snippet_function); + snippet_function = b.call( + '$.validate_snippet', + snippet_function, + args.length ? b.id('$$props') : undefined + ); } if (node.metadata.dynamic) { diff --git a/packages/svelte/src/internal/shared/errors.js b/packages/svelte/src/internal/shared/errors.js index 984b86a54d..880b37bc71 100644 --- a/packages/svelte/src/internal/shared/errors.js +++ b/packages/svelte/src/internal/shared/errors.js @@ -20,12 +20,12 @@ export function lifecycle_outside_component(name) { } /** - * 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 ...}` + * 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 ...}` * @returns {never} */ export function render_tag_invalid_argument() { if (DEV) { - 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 ...}\``); + 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 ...}\``); error.name = 'Svelte error'; throw error; diff --git a/packages/svelte/src/internal/shared/validate.js b/packages/svelte/src/internal/shared/validate.js index 399ace8b58..1fb1644d97 100644 --- a/packages/svelte/src/internal/shared/validate.js +++ b/packages/svelte/src/internal/shared/validate.js @@ -15,9 +15,10 @@ export function add_snippet_symbol(fn) { /** * Validate that the function handed to `{@render ...}` is a snippet function, and not some other kind of function. * @param {any} snippet_fn + * @param {Record | undefined} $$props Only passed if render tag receives arguments */ -export function validate_snippet(snippet_fn) { - if (snippet_fn && snippet_fn[snippet_symbol] !== true) { +export function validate_snippet(snippet_fn, $$props) { + if ($$props?.$$slots?.default || (snippet_fn && snippet_fn[snippet_symbol] !== true)) { e.render_tag_invalid_argument(); } diff --git a/packages/svelte/tests/runtime-legacy/shared.ts b/packages/svelte/tests/runtime-legacy/shared.ts index 6d98e06c16..40c9d62a39 100644 --- a/packages/svelte/tests/runtime-legacy/shared.ts +++ b/packages/svelte/tests/runtime-legacy/shared.ts @@ -117,7 +117,9 @@ export function runtime_suite(runes: boolean) { (!config.test_ssr && config.html === undefined && config.ssrHtml === undefined && - config.error === undefined) + config.error === undefined && + config.runtime_error === undefined && + !config.mode?.includes('server')) ) { return 'no-test'; } diff --git a/packages/svelte/tests/runtime-runes/samples/snippet-slot-let-error/_config.js b/packages/svelte/tests/runtime-runes/samples/snippet-slot-let-error/_config.js new file mode 100644 index 0000000000..2fbcf3eb10 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/snippet-slot-let-error/_config.js @@ -0,0 +1,8 @@ +import { test } from '../../test'; + +export default test({ + compileOptions: { + dev: true + }, + runtime_error: 'render_tag_invalid_argument' +}); diff --git a/packages/svelte/tests/runtime-runes/samples/snippet-slot-let-error/inner.svelte b/packages/svelte/tests/runtime-runes/samples/snippet-slot-let-error/inner.svelte new file mode 100644 index 0000000000..c0d85745dd --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/snippet-slot-let-error/inner.svelte @@ -0,0 +1,5 @@ + + +{@render children(true)} diff --git a/packages/svelte/tests/runtime-runes/samples/snippet-slot-let-error/main.svelte b/packages/svelte/tests/runtime-runes/samples/snippet-slot-let-error/main.svelte new file mode 100644 index 0000000000..6bac8819c6 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/snippet-slot-let-error/main.svelte @@ -0,0 +1,7 @@ + + + + {foo} + diff --git a/packages/svelte/tests/runtime-runes/samples/snippet-validation-error-1/_config.js b/packages/svelte/tests/runtime-runes/samples/snippet-validation-error-1/_config.js index f99c948dec..288edc26a6 100644 --- a/packages/svelte/tests/runtime-runes/samples/snippet-validation-error-1/_config.js +++ b/packages/svelte/tests/runtime-runes/samples/snippet-validation-error-1/_config.js @@ -4,8 +4,5 @@ export default test({ compileOptions: { dev: true }, - error: - 'render_tag_invalid_argument\n' + - '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 ...}`' + error: 'render_tag_invalid_argument' });