Skip to content

Commit 4dfa0e3

Browse files
authored
fix: tighten up export default validation (#14368)
through #14363 I noticed our `export default` validation wasn't quite right: - missed checking for derived/state exports - the "cannot have a default export" error was only thrown if you did `export default` from the instance script, but it shouldn't matter from which component part you export it; it's never ok
1 parent 4c98c2e commit 4dfa0e3

File tree

10 files changed

+83
-26
lines changed

10 files changed

+83
-26
lines changed

.changeset/new-houses-roll.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: tighten up `export default` validation

packages/svelte/src/compiler/phases/2-analyze/visitors/ExportDefaultDeclaration.js

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,18 @@
1-
/** @import { ExportDefaultDeclaration, Node } from 'estree' */
1+
/** @import { ExportDefaultDeclaration } from 'estree' */
22
/** @import { Context } from '../types' */
33
import * as e from '../../../errors.js';
4+
import { validate_export } from './shared/utils.js';
45

56
/**
67
* @param {ExportDefaultDeclaration} node
78
* @param {Context} context
89
*/
910
export function ExportDefaultDeclaration(node, context) {
10-
if (context.state.ast_type === 'instance') {
11+
if (!context.state.ast_type /* .svelte.js module */) {
12+
if (node.declaration.type === 'Identifier') {
13+
validate_export(node, context.state.scope, node.declaration.name);
14+
}
15+
} else {
1116
e.module_illegal_default_export(node);
1217
}
1318

Lines changed: 2 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
1-
/** @import { ExportSpecifier, Node } from 'estree' */
2-
/** @import { Binding } from '#compiler' */
1+
/** @import { ExportSpecifier } from 'estree' */
32
/** @import { Context } from '../types' */
4-
/** @import { Scope } from '../../scope' */
5-
import * as e from '../../../errors.js';
3+
import { validate_export } from './shared/utils.js';
64

75
/**
86
* @param {ExportSpecifier} node
@@ -30,22 +28,3 @@ export function ExportSpecifier(node, context) {
3028
validate_export(node, context.state.scope, local_name);
3129
}
3230
}
33-
34-
/**
35-
*
36-
* @param {Node} node
37-
* @param {Scope} scope
38-
* @param {string} name
39-
*/
40-
function validate_export(node, scope, name) {
41-
const binding = scope.get(name);
42-
if (!binding) return;
43-
44-
if (binding.kind === 'derived') {
45-
e.derived_invalid_export(node);
46-
}
47-
48-
if ((binding.kind === 'state' || binding.kind === 'raw_state') && binding.reassigned) {
49-
e.state_invalid_export(node);
50-
}
51-
}

packages/svelte/src/compiler/phases/2-analyze/visitors/shared/utils.js

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/** @import { AssignmentExpression, Expression, Literal, Pattern, PrivateIdentifier, Super, UpdateExpression, VariableDeclarator } from 'estree' */
1+
/** @import { AssignmentExpression, Expression, Literal, Node, Pattern, PrivateIdentifier, Super, UpdateExpression, VariableDeclarator } from 'estree' */
22
/** @import { AST, Binding } from '#compiler' */
33
/** @import { AnalysisState, Context } from '../../types' */
44
/** @import { Scope } from '../../../scope' */
@@ -263,3 +263,22 @@ export function validate_identifier_name(binding, function_depth) {
263263
}
264264
}
265265
}
266+
267+
/**
268+
* Checks that the exported name is not a derived or reassigned state variable.
269+
* @param {Node} node
270+
* @param {Scope} scope
271+
* @param {string} name
272+
*/
273+
export function validate_export(node, scope, name) {
274+
const binding = scope.get(name);
275+
if (!binding) return;
276+
277+
if (binding.kind === 'derived') {
278+
e.derived_invalid_export(node);
279+
}
280+
281+
if ((binding.kind === 'state' || binding.kind === 'raw_state') && binding.reassigned) {
282+
e.state_invalid_export(node);
283+
}
284+
}
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: 'derived_invalid_export',
6+
message:
7+
'Cannot export derived state from a module. To expose the current derived value, export a function returning its value',
8+
position: [61, 83]
9+
}
10+
});
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
let count = $state(0);
2+
3+
const double = $derived(count * 2);
4+
5+
export default double;
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: 'state_invalid_export',
6+
message:
7+
"Cannot export state from a module if it is reassigned. Either export a function returning the state value or only mutate the state value's properties",
8+
position: [93, 118]
9+
}
10+
});
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
let primitive = $state('nope');
2+
3+
export function update_primitive() {
4+
primitive = 'yep';
5+
}
6+
7+
export default primitive;
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
[
2+
{
3+
"code": "module_illegal_default_export",
4+
"message": "A component cannot have a default export",
5+
"start": {
6+
"line": 2,
7+
"column": 1
8+
},
9+
"end": {
10+
"line": 2,
11+
"column": 19
12+
}
13+
}
14+
]
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<script module>
2+
export default 42;
3+
</script>

0 commit comments

Comments
 (0)