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[]} */
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
*/
function safe_push(id) {
function push_unique(id) {
if (!params.find((param) => param.name === id.name)) {
params.push(id);
}
}
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.kind === 'store_sub') {
// We need both the subscription for getting the value and the store for updating
safe_push(b.id(binding.node.name.slice(1)));
safe_push(b.id(binding.node.name));
} else if (
push_unique(b.id(binding.node.name));
binding = /** @type {import('#compiler').Binding} */ (
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.
binding.expression !== null &&
typeof binding.expression !== 'function' &&
@ -486,7 +489,7 @@ function get_hoistable_params(node, context) {
binding.expression.object.callee.name === '$.get' &&
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 (
// If we are referencing a simple $$props value, then we need to reference the object property instead
(binding.kind === 'prop' || binding.kind === 'bindable_prop') &&
@ -494,14 +497,10 @@ function get_hoistable_params(node, context) {
binding.initial === null &&
!context.state.analysis.accessors
) {
// Handle $$props.something use-cases
if (!added_props) {
added_props = true;
safe_push(b.id('$$props'));
}
push_unique(b.id('$$props'));
} else {
// 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