Skip to content

Commit dab0a43

Browse files
authored
fix: stricter validation for component exports (#10430)
disallow exporting props, derived and reassigned state from within components closes #10310, closes #10311, closes #10293
1 parent 0e011ad commit dab0a43

File tree

11 files changed

+75
-8
lines changed

11 files changed

+75
-8
lines changed

.changeset/odd-taxis-retire.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"svelte": patch
3+
---
4+
5+
fix: disallow exporting props, derived and reassigned state from within components

packages/svelte/src/compiler/errors.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -169,8 +169,12 @@ const runes = {
169169
'invalid-legacy-export': () => `Cannot use \`export let\` in runes mode — use $props instead`,
170170
/** @param {string} rune */
171171
'invalid-rune-usage': (rune) => `Cannot use ${rune} rune in non-runes mode`,
172-
'invalid-state-export': () => `Cannot export state if it is reassigned`,
173-
'invalid-derived-export': () => `Cannot export derived state`,
172+
'invalid-state-export': () =>
173+
`Cannot export state if it is reassigned. Either export a function returning the state value or only mutate the state value's properties`,
174+
'invalid-derived-export': () =>
175+
`Cannot export derived state. To expose the current derived value, export a function returning its value`,
176+
'invalid-prop-export': () =>
177+
`Cannot export properties. To expose the current value of a property, export a function returning its value`,
174178
'invalid-props-id': () => `$props() can only be used with an object destructuring pattern`,
175179
'invalid-props-pattern': () =>
176180
`$props() assignment must not contain nested properties or computed keys`,

packages/svelte/src/compiler/phases/2-analyze/validation.js

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -710,6 +710,10 @@ function validate_export(node, scope, name) {
710710
const binding = scope.get(name);
711711
if (!binding) return;
712712

713+
if (binding.kind === 'prop') {
714+
error(node, 'invalid-prop-export');
715+
}
716+
713717
if (binding.kind === 'derived') {
714718
error(node, 'invalid-derived-export');
715719
}
@@ -941,10 +945,20 @@ export const validation_runes = merge(validation, a11y_validators, {
941945
if (node.label.name !== '$' || path.at(-1)?.type !== 'Program') return;
942946
error(node, 'invalid-legacy-reactive-statement');
943947
},
944-
ExportNamedDeclaration(node, { state }) {
948+
ExportNamedDeclaration(node, { state, next }) {
945949
if (node.declaration?.type !== 'VariableDeclaration') return;
946-
if (node.declaration.kind !== 'let') return;
950+
951+
// visit children, so bindings are correctly initialised
952+
next();
953+
954+
for (const declarator of node.declaration.declarations) {
955+
for (const id of extract_identifiers(declarator.id)) {
956+
validate_export(node, state.scope, id.name);
957+
}
958+
}
959+
947960
if (state.analysis.instance.scope !== state.scope) return;
961+
if (node.declaration.kind !== 'let') return;
948962
error(node, 'invalid-legacy-export');
949963
},
950964
ExportSpecifier(node, { state }) {

packages/svelte/tests/compiler-errors/samples/export-derived-state/_config.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { test } from '../../test';
33
export default test({
44
error: {
55
code: 'invalid-derived-export',
6-
message: 'Cannot export derived state',
7-
position: [24, 66]
6+
message:
7+
'Cannot export derived state. To expose the current derived value, export a function returning its value'
88
}
99
});
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<script>
2+
let count = $state(0);
3+
export const double = $derived(count * 2);
4+
</script>
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { test } from '../../test';
2+
3+
export default test({
4+
error: {
5+
code: 'invalid-state-export',
6+
message:
7+
"Cannot export state if it is reassigned. Either export a function returning the state value or only mutate the state value's properties",
8+
position: [59, 99]
9+
}
10+
});
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<script>
2+
export const object = $state({
3+
ok: true
4+
});
5+
6+
export const primitive = $state('nope');
7+
8+
export function update_object() {
9+
object.ok = !object.ok;
10+
}
11+
12+
export function update_primitive() {
13+
primitive = 'yep';
14+
}
15+
</script>

packages/svelte/tests/compiler-errors/samples/export-state/_config.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ import { test } from '../../test';
33
export default test({
44
error: {
55
code: 'invalid-state-export',
6-
message: 'Cannot export state if it is reassigned',
6+
message:
7+
"Cannot export state if it is reassigned. Either export a function returning the state value or only mutate the state value's properties",
78
position: [46, 86]
89
}
910
});

packages/svelte/tests/compiler-errors/samples/runes-export-named-state/_config.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ import { test } from '../../test';
33
export default test({
44
error: {
55
code: 'invalid-state-export',
6-
message: 'Cannot export state if it is reassigned',
6+
message:
7+
"Cannot export state if it is reassigned. Either export a function returning the state value or only mutate the state value's properties",
78
position: [28, 53]
89
}
910
});
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { test } from '../../test';
2+
3+
export default test({
4+
error: {
5+
code: 'invalid-prop-export',
6+
message:
7+
'Cannot export properties. To expose the current value of a property, export a function returning its value'
8+
}
9+
});
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<script>
2+
let { foo } = $props();
3+
export { foo };
4+
</script>

0 commit comments

Comments
 (0)