diff --git a/.changeset/sour-lets-hide.md b/.changeset/sour-lets-hide.md new file mode 100644 index 0000000000..6175027cff --- /dev/null +++ b/.changeset/sour-lets-hide.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: error when using `let:` directives on a component with a `children` snippet diff --git a/documentation/docs/98-reference/.generated/compile-errors.md b/documentation/docs/98-reference/.generated/compile-errors.md index 02e762dc48..eea1be14c8 100644 --- a/documentation/docs/98-reference/.generated/compile-errors.md +++ b/documentation/docs/98-reference/.generated/compile-errors.md @@ -677,6 +677,12 @@ Cannot use `$$restProps` in runes mode `let:` directive at invalid position ``` +### let_directive_snippet_conflict + +``` +Cannot use `let:` directives on a component that has a `children` snippet. Use snippet parameters instead (e.g. `{#snippet children({ %name% })}`) +``` + ### mixed_event_handler_syntaxes ``` diff --git a/packages/svelte/messages/compile-errors/template.md b/packages/svelte/messages/compile-errors/template.md index 6d1ae6ccbf..f71119169f 100644 --- a/packages/svelte/messages/compile-errors/template.md +++ b/packages/svelte/messages/compile-errors/template.md @@ -279,6 +279,10 @@ The same applies to components: > `let:` directive at invalid position +## let_directive_snippet_conflict + +> Cannot use `let:` directives on a component that has a `children` snippet. Use snippet parameters instead (e.g. `{#snippet children({ %name% })}`) + ## mixed_event_handler_syntaxes > Mixing old (on:%name%) and new syntaxes for event handling is not allowed. Use only the on%name% syntax diff --git a/packages/svelte/src/compiler/errors.js b/packages/svelte/src/compiler/errors.js index ccb7fe833e..f2be746ff6 100644 --- a/packages/svelte/src/compiler/errors.js +++ b/packages/svelte/src/compiler/errors.js @@ -1213,6 +1213,16 @@ export function let_directive_invalid_placement(node) { e(node, 'let_directive_invalid_placement', `\`let:\` directive at invalid position\nhttps://svelte.dev/e/let_directive_invalid_placement`); } +/** + * Cannot use `let:` directives on a component that has a `children` snippet. Use snippet parameters instead (e.g. `{#snippet children({ %name% })}`) + * @param {null | number | NodeLike} node + * @param {string} name + * @returns {never} + */ +export function let_directive_snippet_conflict(node, name) { + e(node, 'let_directive_snippet_conflict', `Cannot use \`let:\` directives on a component that has a \`children\` snippet. Use snippet parameters instead (e.g. \`{#snippet children({ ${name} })}\`)\nhttps://svelte.dev/e/let_directive_snippet_conflict`); +} + /** * Mixing old (on:%name%) and new syntaxes for event handling is not allowed. Use only the on%name% syntax * @param {null | number | NodeLike} node diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/LetDirective.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/LetDirective.js index e9a56ead0c..2eccac9d8a 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/LetDirective.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/LetDirective.js @@ -1,6 +1,7 @@ /** @import { AST } from '#compiler' */ /** @import { Context } from '../types' */ import * as e from '../../../errors.js'; +import { determine_slot } from '../../../utils/slot.js'; /** * @param {AST.LetDirective} node @@ -21,4 +22,25 @@ export function LetDirective(node, context) { ) { e.let_directive_invalid_placement(node); } + + // an explicit `children` snippet replaces the default slot content, so the `let:` + // directive would never be provided. if the component has a `slot` attribute, the + // directive applies to the component itself instead, which is fine + if ( + (parent.type === 'Component' || + parent.type === 'SvelteComponent' || + parent.type === 'SvelteSelf') && + !determine_slot(parent) && + parent.fragment.nodes.some( + (child) => child.type === 'SnippetBlock' && child.expression.name === 'children' + ) + ) { + const { expression } = node; + const pattern = + expression === null || (expression.type === 'Identifier' && expression.name === node.name) + ? node.name + : `${node.name}: ${context.state.analysis.source.slice(expression.start, expression.end)}`; + + e.let_directive_snippet_conflict(node, pattern); + } } diff --git a/packages/svelte/tests/compiler-errors/samples/let-directive-children-snippet-expression/_config.js b/packages/svelte/tests/compiler-errors/samples/let-directive-children-snippet-expression/_config.js new file mode 100644 index 0000000000..bf56000196 --- /dev/null +++ b/packages/svelte/tests/compiler-errors/samples/let-directive-children-snippet-expression/_config.js @@ -0,0 +1,10 @@ +import { test } from '../../test'; + +export default test({ + error: { + code: 'let_directive_snippet_conflict', + message: + 'Cannot use `let:` directives on a component that has a `children` snippet. Use snippet parameters instead (e.g. `{#snippet children({ item: x })}`)', + position: [62, 74] + } +}); diff --git a/packages/svelte/tests/compiler-errors/samples/let-directive-children-snippet-expression/main.svelte b/packages/svelte/tests/compiler-errors/samples/let-directive-children-snippet-expression/main.svelte new file mode 100644 index 0000000000..eddd5f2308 --- /dev/null +++ b/packages/svelte/tests/compiler-errors/samples/let-directive-children-snippet-expression/main.svelte @@ -0,0 +1,7 @@ + + + + {#snippet children()}{x}{/snippet} + diff --git a/packages/svelte/tests/compiler-errors/samples/let-directive-children-snippet/_config.js b/packages/svelte/tests/compiler-errors/samples/let-directive-children-snippet/_config.js new file mode 100644 index 0000000000..27b6a88667 --- /dev/null +++ b/packages/svelte/tests/compiler-errors/samples/let-directive-children-snippet/_config.js @@ -0,0 +1,10 @@ +import { test } from '../../test'; + +export default test({ + error: { + code: 'let_directive_snippet_conflict', + message: + 'Cannot use `let:` directives on a component that has a `children` snippet. Use snippet parameters instead (e.g. `{#snippet children({ value })}`)', + position: [359, 368] + } +}); diff --git a/packages/svelte/tests/compiler-errors/samples/let-directive-children-snippet/main.svelte b/packages/svelte/tests/compiler-errors/samples/let-directive-children-snippet/main.svelte new file mode 100644 index 0000000000..bdfe3de9b5 --- /dev/null +++ b/packages/svelte/tests/compiler-errors/samples/let-directive-children-snippet/main.svelte @@ -0,0 +1,23 @@ + + + + + {value} + {#snippet foo()}x{/snippet} + + + {#snippet children({ value })}{value}{/snippet} + + + + {#snippet children()}{value}{/snippet} + + + + + + {#snippet children()}Value is: {value}{/snippet} +