fix: stricter validation for component exports (#10430)

disallow exporting props, derived and reassigned state from within components
closes #10310, closes #10311, closes #10293
pull/10431/head
Simon H 2 years ago committed by GitHub
parent 0e011add4e
commit dab0a43693
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,5 @@
---
"svelte": patch
---
fix: disallow exporting props, derived and reassigned state from within components

@ -169,8 +169,12 @@ const runes = {
'invalid-legacy-export': () => `Cannot use \`export let\` in runes mode — use $props instead`, 'invalid-legacy-export': () => `Cannot use \`export let\` in runes mode — use $props instead`,
/** @param {string} rune */ /** @param {string} rune */
'invalid-rune-usage': (rune) => `Cannot use ${rune} rune in non-runes mode`, 'invalid-rune-usage': (rune) => `Cannot use ${rune} rune in non-runes mode`,
'invalid-state-export': () => `Cannot export state if it is reassigned`, 'invalid-state-export': () =>
'invalid-derived-export': () => `Cannot export derived state`, `Cannot export state if it is reassigned. Either export a function returning the state value or only mutate the state value's properties`,
'invalid-derived-export': () =>
`Cannot export derived state. To expose the current derived value, export a function returning its value`,
'invalid-prop-export': () =>
`Cannot export properties. To expose the current value of a property, export a function returning its value`,
'invalid-props-id': () => `$props() can only be used with an object destructuring pattern`, 'invalid-props-id': () => `$props() can only be used with an object destructuring pattern`,
'invalid-props-pattern': () => 'invalid-props-pattern': () =>
`$props() assignment must not contain nested properties or computed keys`, `$props() assignment must not contain nested properties or computed keys`,

@ -710,6 +710,10 @@ function validate_export(node, scope, name) {
const binding = scope.get(name); const binding = scope.get(name);
if (!binding) return; if (!binding) return;
if (binding.kind === 'prop') {
error(node, 'invalid-prop-export');
}
if (binding.kind === 'derived') { if (binding.kind === 'derived') {
error(node, 'invalid-derived-export'); error(node, 'invalid-derived-export');
} }
@ -941,10 +945,20 @@ export const validation_runes = merge(validation, a11y_validators, {
if (node.label.name !== '$' || path.at(-1)?.type !== 'Program') return; if (node.label.name !== '$' || path.at(-1)?.type !== 'Program') return;
error(node, 'invalid-legacy-reactive-statement'); error(node, 'invalid-legacy-reactive-statement');
}, },
ExportNamedDeclaration(node, { state }) { ExportNamedDeclaration(node, { state, next }) {
if (node.declaration?.type !== 'VariableDeclaration') return; if (node.declaration?.type !== 'VariableDeclaration') return;
if (node.declaration.kind !== 'let') return;
// visit children, so bindings are correctly initialised
next();
for (const declarator of node.declaration.declarations) {
for (const id of extract_identifiers(declarator.id)) {
validate_export(node, state.scope, id.name);
}
}
if (state.analysis.instance.scope !== state.scope) return; if (state.analysis.instance.scope !== state.scope) return;
if (node.declaration.kind !== 'let') return;
error(node, 'invalid-legacy-export'); error(node, 'invalid-legacy-export');
}, },
ExportSpecifier(node, { state }) { ExportSpecifier(node, { state }) {

@ -3,7 +3,7 @@ import { test } from '../../test';
export default test({ export default test({
error: { error: {
code: 'invalid-derived-export', code: 'invalid-derived-export',
message: 'Cannot export derived state', message:
position: [24, 66] 'Cannot export derived state. To expose the current derived value, export a function returning its value'
} }
}); });

@ -0,0 +1,4 @@
<script>
let count = $state(0);
export const double = $derived(count * 2);
</script>

@ -0,0 +1,10 @@
import { test } from '../../test';
export default test({
error: {
code: 'invalid-state-export',
message:
"Cannot export state if it is reassigned. Either export a function returning the state value or only mutate the state value's properties",
position: [59, 99]
}
});

@ -0,0 +1,15 @@
<script>
export const object = $state({
ok: true
});
export const primitive = $state('nope');
export function update_object() {
object.ok = !object.ok;
}
export function update_primitive() {
primitive = 'yep';
}
</script>

@ -3,7 +3,8 @@ import { test } from '../../test';
export default test({ export default test({
error: { error: {
code: 'invalid-state-export', code: 'invalid-state-export',
message: 'Cannot export state if it is reassigned', message:
"Cannot export state if it is reassigned. Either export a function returning the state value or only mutate the state value's properties",
position: [46, 86] position: [46, 86]
} }
}); });

@ -3,7 +3,8 @@ import { test } from '../../test';
export default test({ export default test({
error: { error: {
code: 'invalid-state-export', code: 'invalid-state-export',
message: 'Cannot export state if it is reassigned', message:
"Cannot export state if it is reassigned. Either export a function returning the state value or only mutate the state value's properties",
position: [28, 53] position: [28, 53]
} }
}); });

@ -0,0 +1,9 @@
import { test } from '../../test';
export default test({
error: {
code: 'invalid-prop-export',
message:
'Cannot export properties. To expose the current value of a property, export a function returning its value'
}
});

@ -0,0 +1,4 @@
<script>
let { foo } = $props();
export { foo };
</script>
Loading…
Cancel
Save