fix: store from props hoist wrong param (#11367)

Fixes #11355

---------

Co-authored-by: Simon Holthausen <simon.holthausen@vercel.com>
pull/11369/head
Paolo Ricciuti 8 months ago committed by GitHub
parent 68071f7c06
commit c7bdef595b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,5 @@
---
"svelte": patch
---
fix: ensure store from props is hoisted correctly

@ -456,27 +456,30 @@ function get_hoistable_params(node, context) {
/** @type {import('estree').Identifier[]} */ /** @type {import('estree').Identifier[]} */
const params = []; const params = [];
let added_props = false;
/** /**
* we only want to push if it's not already present to avoid name clashing * We only want to push if it's not already present to avoid name clashing
* @param {import('estree').Identifier} id * @param {import('estree').Identifier} id
*/ */
function safe_push(id) { function push_unique(id) {
if (!params.find((param) => param.name === id.name)) { if (!params.find((param) => param.name === id.name)) {
params.push(id); params.push(id);
} }
} }
for (const [reference] of scope.references) { for (const [reference] of scope.references) {
const binding = scope.get(reference); let binding = scope.get(reference);
if (binding !== null && !scope.declarations.has(reference) && binding.initial !== node) { if (binding !== null && !scope.declarations.has(reference) && binding.initial !== node) {
if (binding.kind === 'store_sub') { if (binding.kind === 'store_sub') {
// We need both the subscription for getting the value and the store for updating // We need both the subscription for getting the value and the store for updating
safe_push(b.id(binding.node.name.slice(1))); push_unique(b.id(binding.node.name));
safe_push(b.id(binding.node.name)); binding = /** @type {import('#compiler').Binding} */ (
} else if ( scope.get(binding.node.name.slice(1))
);
}
if (
// If it's a destructured derived binding, then we can extract the derived signal reference and use that. // If it's a destructured derived binding, then we can extract the derived signal reference and use that.
binding.expression !== null && binding.expression !== null &&
typeof binding.expression !== 'function' && typeof binding.expression !== 'function' &&
@ -486,7 +489,7 @@ function get_hoistable_params(node, context) {
binding.expression.object.callee.name === '$.get' && binding.expression.object.callee.name === '$.get' &&
binding.expression.object.arguments[0].type === 'Identifier' binding.expression.object.arguments[0].type === 'Identifier'
) { ) {
safe_push(b.id(binding.expression.object.arguments[0].name)); push_unique(b.id(binding.expression.object.arguments[0].name));
} else if ( } else if (
// If we are referencing a simple $$props value, then we need to reference the object property instead // If we are referencing a simple $$props value, then we need to reference the object property instead
(binding.kind === 'prop' || binding.kind === 'bindable_prop') && (binding.kind === 'prop' || binding.kind === 'bindable_prop') &&
@ -494,14 +497,10 @@ function get_hoistable_params(node, context) {
binding.initial === null && binding.initial === null &&
!context.state.analysis.accessors !context.state.analysis.accessors
) { ) {
// Handle $$props.something use-cases push_unique(b.id('$$props'));
if (!added_props) {
added_props = true;
safe_push(b.id('$$props'));
}
} else { } else {
// create a copy to remove start/end tags which would mess up source maps // create a copy to remove start/end tags which would mess up source maps
safe_push(b.id(binding.node.name)); push_unique(b.id(binding.node.name));
} }
} }
} }

@ -0,0 +1,18 @@
import { test } from '../../test';
export default test({
compileOptions: {
dev: true
},
async test({ assert, target }) {
const button = target.querySelector('button');
await button?.click();
assert.htmlEqual(
target.innerHTML,
`
<button>1</button>
`
);
}
});

@ -0,0 +1,8 @@
<script>
const { attrs } = $props();
function increment() {
$attrs.count++;
}
</script>
<button onclick={increment}>{$attrs.count}</button>

@ -0,0 +1,7 @@
<script>
import { writable } from "svelte/store";
import Child from "./child.svelte";
const attrs = writable({ count: 0 });
</script>
<Child {attrs} />
Loading…
Cancel
Save