fix: handle default parameters scope leaks (#17788)

Use separate scopes for function declarations/expressions and function
bodies. This prevents variable declarations from leaking into default
parameter initialization expressions.

Closes #17785.

### Before submitting the PR, please make sure you do the following

- [x] It's really useful if your PR references an issue where it is
discussed ahead of time. In many cases, features are absent for a
reason. For large changes, please create an RFC:
https://github.com/sveltejs/rfcs
- [x] Prefix your PR title with `feat:`, `fix:`, `chore:`, or `docs:`.
- [x] This message body should clearly illustrate what problems it
solves.
- [x] Ideally, include a test that fails without this PR but passes with
it.
- [x] If this PR changes code within `packages/svelte/src`, add a
changeset (`npx changeset`).

### Tests and linting

- [x] Run the tests with `pnpm test` and lint the project with `pnpm
lint`
pull/17786/head
Maurício Kishi 6 months ago committed by GitHub
parent fef7e0f742
commit 3a289797bd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: handle default parameters scope leaks

@ -1098,25 +1098,19 @@ export function create_scopes(ast, root, allow_reactive_declarations, parent) {
},
FunctionExpression(node, { state, next }) {
const scope = state.scope.child();
const scope = state.scope.child(true);
scopes.set(node, scope);
if (node.id) {
scopes.set(node.id, state.scope); // so that declarations within with the same name are not confused with the function name
scope.declare(node.id, 'normal', 'function');
}
if (node.id) scope.declare(node.id, 'normal', 'function');
add_params(scope, node.params);
next({ scope });
},
FunctionDeclaration(node, { state, next }) {
if (node.id) {
scopes.set(node.id, state.scope); // so that declarations within with the same name are not confused with the function name
state.scope.declare(node.id, 'normal', 'function', node);
}
if (node.id) state.scope.declare(node.id, 'normal', 'function', node);
const scope = state.scope.child();
const scope = state.scope.child(true);
scopes.set(node, scope);
add_params(scope, node.params);
@ -1124,7 +1118,7 @@ export function create_scopes(ast, root, allow_reactive_declarations, parent) {
},
ArrowFunctionExpression(node, { state, next }) {
const scope = state.scope.child();
const scope = state.scope.child(true);
scopes.set(node, scope);
add_params(scope, node.params);
@ -1142,8 +1136,11 @@ export function create_scopes(ast, root, allow_reactive_declarations, parent) {
parent?.type === 'FunctionExpression' ||
parent?.type === 'ArrowFunctionExpression'
) {
// We already created a new scope for the function
context.next();
// The scopes created for the function nodes above handle the function identifier and
// parameters, but the block statement itself holds the non-porous function scope
const scope = context.state.scope.child();
scopes.set(node, scope);
context.next({ scope });
} else {
create_block_scope(node, context);
}

@ -0,0 +1,7 @@
import { test } from '../../test';
export default test({
test({ assert, logs }) {
assert.deepEqual(logs, [42, 43]);
}
});

@ -0,0 +1,12 @@
<script>
let value = $state(42);
function shadow(output = value) {
const value = 1337;
return output;
}
console.log(shadow());
value += 1;
console.log(shadow());
</script>
Loading…
Cancel
Save