diff --git a/.changeset/good-pianos-jump.md b/.changeset/good-pianos-jump.md new file mode 100644 index 0000000000..0ae35e5937 --- /dev/null +++ b/.changeset/good-pianos-jump.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: check that snippet is not rendered as a 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 b830367776..66ec08d782 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 @@ -850,7 +850,14 @@ function serialize_inline_component(node, component_name, context) { b.thunk(b.array(props_and_spreads.map((p) => (Array.isArray(p) ? b.object(p) : p)))) ); /** @param {import('estree').Identifier} node_id */ - let fn = (node_id) => b.call(component_name, node_id, props_expression); + let fn = (node_id) => + b.call( + context.state.options.dev + ? b.call('$.validate_component', b.id(component_name)) + : component_name, + node_id, + props_expression + ); if (bind_this !== null) { const prev = fn; diff --git a/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js b/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js index ffba6d09db..c0eebce99a 100644 --- a/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js +++ b/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js @@ -882,7 +882,12 @@ function serialize_inline_component(node, component_name, context) { /** @type {import('estree').Statement} */ let statement = b.stmt( (typeof component_name === 'string' ? b.call : b.maybe_call)( - component_name, + context.state.options.dev + ? b.call( + '$.validate_component', + typeof component_name === 'string' ? b.id(component_name) : component_name + ) + : component_name, b.id('$$payload'), props_expression ) diff --git a/packages/svelte/src/internal/client/validate.js b/packages/svelte/src/internal/client/validate.js index 5c0378920e..831ccd9014 100644 --- a/packages/svelte/src/internal/client/validate.js +++ b/packages/svelte/src/internal/client/validate.js @@ -102,13 +102,13 @@ export function loop_guard(timeout) { }; } -const symbol = Symbol.for('svelte.snippet'); +const snippet_symbol = Symbol.for('svelte.snippet'); /** * @param {any} fn */ export function add_snippet_symbol(fn) { - fn[symbol] = true; + fn[snippet_symbol] = true; return fn; } @@ -117,7 +117,7 @@ export function add_snippet_symbol(fn) { * @param {any} snippet_fn */ export function validate_snippet(snippet_fn) { - if (snippet_fn[symbol] !== true) { + if (snippet_fn[snippet_symbol] !== true) { throw new Error( '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 ...}`.' @@ -125,3 +125,14 @@ export function validate_snippet(snippet_fn) { } return snippet_fn; } + +/** + * Validate that the function behind `` isn't a snippet. + * @param {any} component_fn + */ +export function validate_component(component_fn) { + if (component_fn?.[snippet_symbol] === true) { + throw new Error('A snippet must be rendered with `{@render ...}`'); + } + return component_fn; +} diff --git a/packages/svelte/tests/runtime-runes/samples/snippet-validation-error/_config.js b/packages/svelte/tests/runtime-runes/samples/snippet-validation-error-1/_config.js similarity index 100% rename from packages/svelte/tests/runtime-runes/samples/snippet-validation-error/_config.js rename to packages/svelte/tests/runtime-runes/samples/snippet-validation-error-1/_config.js diff --git a/packages/svelte/tests/runtime-runes/samples/snippet-validation-error/main.svelte b/packages/svelte/tests/runtime-runes/samples/snippet-validation-error-1/main.svelte similarity index 100% rename from packages/svelte/tests/runtime-runes/samples/snippet-validation-error/main.svelte rename to packages/svelte/tests/runtime-runes/samples/snippet-validation-error-1/main.svelte diff --git a/packages/svelte/tests/runtime-runes/samples/snippet-validation-error-2/_config.js b/packages/svelte/tests/runtime-runes/samples/snippet-validation-error-2/_config.js new file mode 100644 index 0000000000..09bd836b0a --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/snippet-validation-error-2/_config.js @@ -0,0 +1,8 @@ +import { test } from '../../test'; + +export default test({ + compileOptions: { + dev: true + }, + error: 'A snippet must be rendered with `{@render ...}`' +}); diff --git a/packages/svelte/tests/runtime-runes/samples/snippet-validation-error-2/main.svelte b/packages/svelte/tests/runtime-runes/samples/snippet-validation-error-2/main.svelte new file mode 100644 index 0000000000..e24e9549e3 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/snippet-validation-error-2/main.svelte @@ -0,0 +1,5 @@ +{#snippet Foo()} +

hello

+{/snippet} + +