Skip to content

Commit 194570d

Browse files
fix: error at compile time on unsupported TypeScript language features (#12982)
part of #11502 - to close it completely, we also need to look at using and possibly implement heuristics within bundler plugins to give more details --------- Co-authored-by: Simon Holthausen <[email protected]>
1 parent 81b32d8 commit 194570d

File tree

16 files changed

+184
-9
lines changed

16 files changed

+184
-9
lines changed

.changeset/perfect-cooks-shop.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'svelte': patch
3+
---
4+
5+
fix: error at compile time on unsupported TypeScript language features

packages/svelte/messages/compile-errors/script.md

+4
Original file line numberDiff line numberDiff line change
@@ -159,3 +159,7 @@
159159
> Cannot reference store value outside a `.svelte` file
160160
161161
Using a `$` prefix to refer to the value of a store is only possible inside `.svelte` files, where Svelte can automatically create subscriptions when a component is mounted and unsubscribe when the component is unmounted. Consider migrating to runes instead.
162+
163+
## typescript_invalid_feature
164+
165+
> TypeScript language features like %feature% are not natively supported, and their use is generally discouraged. Outside of `<script>` tags, these features are not supported. For use within `<script>` tags, you will need to use a preprocessor to convert it to JavaScript before it gets passed to the Svelte compiler. If you are using `vitePreprocess`, make sure to specifically enable preprocessing script tags (`vitePreprocess({ script: true })`)

packages/svelte/src/compiler/errors.js

+10
Original file line numberDiff line numberDiff line change
@@ -434,6 +434,16 @@ export function store_invalid_subscription_module(node) {
434434
e(node, "store_invalid_subscription_module", "Cannot reference store value outside a `.svelte` file");
435435
}
436436

437+
/**
438+
* TypeScript language features like %feature% are not natively supported, and their use is generally discouraged. Outside of `<script>` tags, these features are not supported. For use within `<script>` tags, you will need to use a preprocessor to convert it to JavaScript before it gets passed to the Svelte compiler. If you are using `vitePreprocess`, make sure to specifically enable preprocessing script tags (`vitePreprocess({ script: true })`)
439+
* @param {null | number | NodeLike} node
440+
* @param {string} feature
441+
* @returns {never}
442+
*/
443+
export function typescript_invalid_feature(node, feature) {
444+
e(node, "typescript_invalid_feature", `TypeScript language features like ${feature} are not natively supported, and their use is generally discouraged. Outside of \`<script>\` tags, these features are not supported. For use within \`<script>\` tags, you will need to use a preprocessor to convert it to JavaScript before it gets passed to the Svelte compiler. If you are using \`vitePreprocess\`, make sure to specifically enable preprocessing script tags (\`vitePreprocess({ script: true })\`)`);
445+
}
446+
437447
/**
438448
* Declaration cannot be empty
439449
* @param {null | number | NodeLike} node

packages/svelte/src/compiler/phases/1-parse/remove_typescript_nodes.js

+45-4
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
/** @import { FunctionExpression, FunctionDeclaration } from 'estree' */
33
import { walk } from 'zimmerframe';
44
import * as b from '../../utils/builders.js';
5+
import * as e from '../../errors.js';
56

67
/**
78
* @param {FunctionExpression | FunctionDeclaration} node
@@ -16,6 +17,9 @@ function remove_this_param(node, context) {
1617

1718
/** @type {Visitors<any, null>} */
1819
const visitors = {
20+
Decorator(node) {
21+
e.typescript_invalid_feature(node, 'decorators (related TSC proposal is not stage 4 yet)');
22+
},
1923
ImportDeclaration(node) {
2024
if (node.importKind === 'type') return b.empty;
2125

@@ -52,6 +56,14 @@ const visitors = {
5256
if (node.exportKind === 'type') return b.empty;
5357
return node;
5458
},
59+
PropertyDefinition(node) {
60+
if (node.accessor) {
61+
e.typescript_invalid_feature(
62+
node,
63+
'accessor fields (related TSC proposal is not stage 4 yet)'
64+
);
65+
}
66+
},
5567
TSAsExpression(node, context) {
5668
return context.visit(node.expression);
5769
},
@@ -73,10 +85,13 @@ const visitors = {
7385
TSTypeParameterInstantiation() {
7486
return b.empty;
7587
},
76-
TSEnumDeclaration() {
77-
return b.empty;
88+
TSEnumDeclaration(node) {
89+
e.typescript_invalid_feature(node, 'enums');
7890
},
79-
TSParameterProperty(node) {
91+
TSParameterProperty(node, context) {
92+
if (node.accessibility && context.path.at(-2)?.kind === 'constructor') {
93+
e.typescript_invalid_feature(node, 'accessibility modifiers on constructor parameters');
94+
}
8095
return node.parameter;
8196
},
8297
Identifier(node) {
@@ -89,7 +104,33 @@ const visitors = {
89104
return node;
90105
},
91106
FunctionExpression: remove_this_param,
92-
FunctionDeclaration: remove_this_param
107+
FunctionDeclaration: remove_this_param,
108+
TSDeclareFunction() {
109+
return b.empty;
110+
},
111+
ClassDeclaration(node, context) {
112+
if (node.declare) {
113+
return b.empty;
114+
}
115+
return context.next();
116+
},
117+
VariableDeclaration(node, context) {
118+
if (node.declare) {
119+
return b.empty;
120+
}
121+
return context.next();
122+
},
123+
TSModuleDeclaration(node, context) {
124+
if (!node.body) return b.empty;
125+
126+
// namespaces can contain non-type nodes
127+
const cleaned = /** @type {any[]} */ (node.body.body).map((entry) => context.visit(entry));
128+
if (cleaned.some((entry) => entry !== b.empty)) {
129+
e.typescript_invalid_feature(node, 'namespaces with non-type nodes');
130+
}
131+
132+
return b.empty;
133+
}
93134
};
94135

95136
/**

packages/svelte/tests/runtime-runes/samples/typescript/main.svelte

+12-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,18 @@
99
}
1010
1111
class Foo {
12-
constructor(public readonly name: string) {}
12+
constructor(readonly name: string) {}
13+
}
14+
15+
declare const declared_const: number;
16+
declare function declared_fn(): void;
17+
declare class declared_class {
18+
foo: number;
19+
}
20+
21+
declare module 'foobar' {}
22+
namespace SomeNamespace {
23+
export type Foo = true
1324
}
1425
1526
export type { Hello };
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
[
2+
{
3+
"code": "typescript_invalid_feature",
4+
"message": "TypeScript language features like accessor fields (related TSC proposal is not stage 4 yet) are not natively supported, and their use is generally discouraged. Outside of `<script>` tags, these features are not supported. For use within `<script>` tags, you will need to use a preprocessor to convert it to JavaScript before it gets passed to the Svelte compiler. If you are using `vitePreprocess`, make sure to specifically enable preprocessing script tags (`vitePreprocess({ script: true })`)",
5+
"start": {
6+
"line": 3,
7+
"column": 2
8+
},
9+
"end": {
10+
"line": 3,
11+
"column": 17
12+
}
13+
}
14+
]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<script lang="ts">
2+
class Foo {
3+
accessor y = 1;
4+
}
5+
</script>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
[
2+
{
3+
"code": "typescript_invalid_feature",
4+
"message": "TypeScript language features like decorators (related TSC proposal is not stage 4 yet) are not natively supported, and their use is generally discouraged. Outside of `<script>` tags, these features are not supported. For use within `<script>` tags, you will need to use a preprocessor to convert it to JavaScript before it gets passed to the Svelte compiler. If you are using `vitePreprocess`, make sure to specifically enable preprocessing script tags (`vitePreprocess({ script: true })`)",
5+
"start": {
6+
"line": 2,
7+
"column": 4
8+
},
9+
"end": {
10+
"line": 2,
11+
"column": 10
12+
}
13+
}
14+
]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<script lang="ts">
2+
@foo()
3+
class Foo {}
4+
</script>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
[
2+
{
3+
"code": "typescript_invalid_feature",
4+
"message": "TypeScript language features like enums are not natively supported, and their use is generally discouraged. Outside of `<script>` tags, these features are not supported. For use within `<script>` tags, you will need to use a preprocessor to convert it to JavaScript before it gets passed to the Svelte compiler. If you are using `vitePreprocess`, make sure to specifically enable preprocessing script tags (`vitePreprocess({ script: true })`)",
5+
"start": {
6+
"line": 2,
7+
"column": 1
8+
},
9+
"end": {
10+
"line": 4,
11+
"column": 2
12+
}
13+
}
14+
]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<script lang="ts">
2+
enum Foo {
3+
bar = 1
4+
}
5+
</script>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
[
2+
{
3+
"code": "typescript_invalid_feature",
4+
"message": "TypeScript language features like accessibility modifiers on constructor parameters are not natively supported, and their use is generally discouraged. Outside of `<script>` tags, these features are not supported. For use within `<script>` tags, you will need to use a preprocessor to convert it to JavaScript before it gets passed to the Svelte compiler. If you are using `vitePreprocess`, make sure to specifically enable preprocessing script tags (`vitePreprocess({ script: true })`)",
5+
"start": {
6+
"line": 3,
7+
"column": 14
8+
},
9+
"end": {
10+
"line": 3,
11+
"column": 31
12+
}
13+
}
14+
]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<script lang="ts">
2+
class Foo {
3+
constructor(private x: number) {}
4+
}
5+
</script>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
[
2+
{
3+
"code": "typescript_invalid_feature",
4+
"message": "TypeScript language features like namespaces with non-type nodes are not natively supported, and their use is generally discouraged. Outside of `<script>` tags, these features are not supported. For use within `<script>` tags, you will need to use a preprocessor to convert it to JavaScript before it gets passed to the Svelte compiler. If you are using `vitePreprocess`, make sure to specifically enable preprocessing script tags (`vitePreprocess({ script: true })`)",
5+
"start": {
6+
"line": 2,
7+
"column": 1
8+
},
9+
"end": {
10+
"line": 4,
11+
"column": 2
12+
}
13+
}
14+
]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<script module lang="ts">
2+
namespace SomeNamespace {
3+
export const foo = true;
4+
}
5+
</script>

packages/svelte/tests/validator/test.ts

+14-4
Original file line numberDiff line numberDiff line change
@@ -53,10 +53,20 @@ const { test, run } = suite<ValidatorTest>(async (config, cwd) => {
5353
const expected = expected_errors && expected_errors[0];
5454

5555
if (error && expected) {
56-
assert.equal(error.code, expected.code);
57-
assert.equal(error.message, expected.message);
58-
assert.deepEqual({ line: error.start?.line, column: error.start?.column }, expected.start);
59-
assert.deepEqual({ line: error.end?.line, column: error.end?.column }, expected.end);
56+
assert.deepEqual(
57+
{
58+
code: error.code,
59+
message: error.message,
60+
start: { line: error.start?.line, column: error.start?.column },
61+
end: { line: error.end?.line, column: error.end?.column }
62+
},
63+
{
64+
code: expected.code,
65+
message: expected.message,
66+
start: expected.start,
67+
end: expected.end
68+
}
69+
);
6070
} else if (expected) {
6171
throw new Error(`Expected an error: ${expected.message}`);
6272
} else if (error) {

0 commit comments

Comments
 (0)