From f8c83c8908a32b068781cadcb07d2529f8761dae Mon Sep 17 00:00:00 2001 From: paoloricciuti Date: Mon, 17 Mar 2025 23:54:59 +0100 Subject: [PATCH 1/3] fix: don't hoist listeners that access non hoistable snippets --- .changeset/thick-pans-fold.md | 5 +++++ .../compiler/phases/2-analyze/visitors/Attribute.js | 9 +++++++++ .../unhoist-function-accessing-snippet/_config.js | 12 ++++++++++++ .../unhoist-function-accessing-snippet/main.svelte | 12 ++++++++++++ 4 files changed, 38 insertions(+) create mode 100644 .changeset/thick-pans-fold.md create mode 100644 packages/svelte/tests/runtime-runes/samples/unhoist-function-accessing-snippet/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/unhoist-function-accessing-snippet/main.svelte diff --git a/.changeset/thick-pans-fold.md b/.changeset/thick-pans-fold.md new file mode 100644 index 000000000000..b5b5cee53ec5 --- /dev/null +++ b/.changeset/thick-pans-fold.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: don't hoist listeners that access non hoistable snippets diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/Attribute.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/Attribute.js index 9124a8822f58..660488b44684 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/Attribute.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/Attribute.js @@ -1,6 +1,7 @@ /** @import { ArrowFunctionExpression, Expression, FunctionDeclaration, FunctionExpression } from 'estree' */ /** @import { AST, DelegatedEvent } from '#compiler' */ /** @import { Context } from '../types' */ +import exp from 'node:constants'; import { cannot_be_set_statically, is_capture_event, is_delegated } from '../../../../utils.js'; import { get_attribute_chunks, @@ -183,6 +184,14 @@ function get_delegated_event(event_name, handler, context) { const binding = scope.get(reference); const local_binding = context.state.scope.get(reference); + if ( + local_binding !== null && + local_binding.initial?.type === 'SnippetBlock' && + !local_binding.initial.metadata.can_hoist + ) { + return unhoisted; + } + // If we are referencing a binding that is shadowed in another scope then bail out. if (local_binding !== null && binding !== null && local_binding.node !== binding.node) { return unhoisted; diff --git a/packages/svelte/tests/runtime-runes/samples/unhoist-function-accessing-snippet/_config.js b/packages/svelte/tests/runtime-runes/samples/unhoist-function-accessing-snippet/_config.js new file mode 100644 index 000000000000..b1229f5a8aad --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/unhoist-function-accessing-snippet/_config.js @@ -0,0 +1,12 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + async test({ assert, target, errors }) { + const button = target.querySelector('button'); + flushSync(() => { + button?.click(); + }); + assert.deepEqual(errors, []); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/unhoist-function-accessing-snippet/main.svelte b/packages/svelte/tests/runtime-runes/samples/unhoist-function-accessing-snippet/main.svelte new file mode 100644 index 000000000000..e909d77fd670 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/unhoist-function-accessing-snippet/main.svelte @@ -0,0 +1,12 @@ + + + + +{#snippet snip()} + snippet {x} +{/snippet} \ No newline at end of file From 4586de9170e4f3780421dbcb9bf5d79459de48b6 Mon Sep 17 00:00:00 2001 From: paoloricciuti Date: Mon, 17 Mar 2025 23:56:23 +0100 Subject: [PATCH 2/3] chore: add comment --- .../svelte/src/compiler/phases/2-analyze/visitors/Attribute.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/Attribute.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/Attribute.js index 660488b44684..b32d6cd80780 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/Attribute.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/Attribute.js @@ -184,6 +184,7 @@ function get_delegated_event(event_name, handler, context) { const binding = scope.get(reference); const local_binding = context.state.scope.get(reference); + // if the function access a snippet that can't be hoisted we bail out if ( local_binding !== null && local_binding.initial?.type === 'SnippetBlock' && From 57cba419ae1c3f3aff77a1c545f83e1e48adcbec Mon Sep 17 00:00:00 2001 From: Paolo Ricciuti Date: Tue, 18 Mar 2025 00:12:31 +0100 Subject: [PATCH 3/3] chore: fix auto import fumble --- .../svelte/src/compiler/phases/2-analyze/visitors/Attribute.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/Attribute.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/Attribute.js index b32d6cd80780..3ba81767cce3 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/Attribute.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/Attribute.js @@ -1,7 +1,6 @@ /** @import { ArrowFunctionExpression, Expression, FunctionDeclaration, FunctionExpression } from 'estree' */ /** @import { AST, DelegatedEvent } from '#compiler' */ /** @import { Context } from '../types' */ -import exp from 'node:constants'; import { cannot_be_set_statically, is_capture_event, is_delegated } from '../../../../utils.js'; import { get_attribute_chunks,