From 28b99aa8e66e50c80d1b7fd77502333a7e02bc72 Mon Sep 17 00:00:00 2001 From: Nic Polumeyv Date: Mon, 28 Sep 2026 17:05:21 -0400 Subject: [PATCH] fix: error at compile time when a declaration in a snippet redeclares one of its parameters (#18842) `{const item = 1}` inside `{#snippet row(item)}` compiles, and the output declares `item` twice in one function, so it does not parse: ```js const row = ($$anchor, item = $.noop) => { const item = 1; ``` `{let}`, `{@const}` and a nested snippet with the parameter's name do the same. In `phases/scope.js`, `EachBlock` visits its body's children in the item's own scope, so a tag that reuses the item name is a `declaration_duplicate`. `SnippetBlock` calls `context.next({ scope: child_scope })`, the `Fragment` visitor opens another scope for the body, and the tag never meets the parameter. After visiting the body, `SnippetBlock` now reports any name in the body's scope that `child_scope` also declares. A declaration in a nested block or element still shadows the parameter. --------- Co-authored-by: Simon Holthausen --- .changeset/snippet-parameter-redeclared.md | 5 +++++ packages/svelte/src/compiler/phases/scope.js | 8 ++++++++ .../errors.json | 14 ++++++++++++++ .../input.svelte | 17 +++++++++++++++++ 4 files changed, 44 insertions(+) create mode 100644 .changeset/snippet-parameter-redeclared.md create mode 100644 packages/svelte/tests/validator/samples/declaration-tag-snippet-parameter/errors.json create mode 100644 packages/svelte/tests/validator/samples/declaration-tag-snippet-parameter/input.svelte diff --git a/.changeset/snippet-parameter-redeclared.md b/.changeset/snippet-parameter-redeclared.md new file mode 100644 index 0000000000..9bb14cf3af --- /dev/null +++ b/.changeset/snippet-parameter-redeclared.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: error at compile time when a declaration in a snippet redeclares one of its parameters diff --git a/packages/svelte/src/compiler/phases/scope.js b/packages/svelte/src/compiler/phases/scope.js index 77c9119364..0d8e570772 100644 --- a/packages/svelte/src/compiler/phases/scope.js +++ b/packages/svelte/src/compiler/phases/scope.js @@ -1343,6 +1343,14 @@ export function create_scopes(ast, root, allow_reactive_declarations, parent) { } context.next({ scope: child_scope }); + + // the parameters and what the body declares end up in the same function + const body_scope = /** @type {Scope} */ (scopes.get(node.body)); + for (const [name, binding] of body_scope.declarations) { + if (child_scope.declarations.has(name)) { + e.declaration_duplicate(binding.node, name); + } + } }, Fragment: (node, context) => { diff --git a/packages/svelte/tests/validator/samples/declaration-tag-snippet-parameter/errors.json b/packages/svelte/tests/validator/samples/declaration-tag-snippet-parameter/errors.json new file mode 100644 index 0000000000..45e51d5f01 --- /dev/null +++ b/packages/svelte/tests/validator/samples/declaration-tag-snippet-parameter/errors.json @@ -0,0 +1,14 @@ +[ + { + "code": "declaration_duplicate", + "message": "`item` has already been declared", + "start": { + "line": 13, + "column": 8 + }, + "end": { + "line": 13, + "column": 12 + } + } +] diff --git a/packages/svelte/tests/validator/samples/declaration-tag-snippet-parameter/input.svelte b/packages/svelte/tests/validator/samples/declaration-tag-snippet-parameter/input.svelte new file mode 100644 index 0000000000..b59496dda1 --- /dev/null +++ b/packages/svelte/tests/validator/samples/declaration-tag-snippet-parameter/input.svelte @@ -0,0 +1,17 @@ + +{#snippet row1(item)} +{#if item} +{const item = 1} +

{item}

+{/if} +{/snippet} + +{@render row1(5)} + + +{#snippet row2(item)} + {const item = 1} +

{item}

+{/snippet} + +{@render row2(5)}