Skip to content

Commit 80fb13f

Browse files
committed
test: add test cases for runes
1 parent d72423a commit 80fb13f

File tree

124 files changed

+87329
-77
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

124 files changed

+87329
-77
lines changed

.eslintignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
/node_modules
55
/tests/fixtures/**/*.json
66
/tests/fixtures/**/*.svelte
7+
/tests/fixtures/**/*.js
8+
/tests/fixtures/**/*.ts
79
/explorer/dist
810
/explorer/node_modules
911
/explorer-v2/build

README.md

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,56 @@ module.exports = {
169169
}
170170
```
171171

172+
### parserOptions.runes
173+
174+
***This is an experimental feature. It may be changed or removed in minor versions without notice.***
175+
176+
If set to `true`, Rune symbols will be parsed. In this mode, the parser also parses files other than `*.svelte`.
177+
178+
```json
179+
{
180+
"parser": "svelte-eslint-parser",
181+
"parserOptions": {
182+
"runes": true
183+
}
184+
}
185+
```
186+
187+
When using this mode in an ESLint configuration, it is recommended to set it per file pattern as below.
188+
189+
```json
190+
{
191+
"overrides": [
192+
{
193+
"files": ["*.svelte"],
194+
"parser": "svelte-eslint-parser",
195+
"parserOptions": {
196+
"runes": true,
197+
"parser": "...",
198+
...
199+
}
200+
},
201+
{
202+
"files": ["*.svelte.js"],
203+
"parser": "svelte-eslint-parser",
204+
"parserOptions": {
205+
"runes": true,
206+
...
207+
}
208+
},
209+
{
210+
"files": ["*.svelte.ts"],
211+
"parser": "svelte-eslint-parser",
212+
"parserOptions": {
213+
"runes": true,
214+
"parser": "...(ts parser)...",
215+
...
216+
}
217+
}
218+
]
219+
}
220+
```
221+
172222
## :computer: Editor Integrations
173223

174224
### Visual Studio Code

src/parser/index.ts

Lines changed: 94 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import type {
1010
import type { Program } from "estree";
1111
import type { ScopeManager } from "eslint-scope";
1212
import { Variable } from "eslint-scope";
13-
import { parseScript } from "./script";
13+
import { parseScript, parseScriptInSvelte } from "./script";
1414
import type * as SvAST from "./svelte-ast-types";
1515
import { sortNodes } from "./sort";
1616
import { parseTemplate } from "./template";
@@ -58,39 +58,48 @@ export interface ESLintExtendedProgram {
5858
// The code used to parse the script.
5959
_virtualScriptCode?: string;
6060
}
61-
/**
62-
* Parse source code
63-
*/
64-
export function parseForESLint(
65-
code: string,
66-
options?: any,
67-
): {
61+
type ParseResult = {
6862
ast: SvelteProgram;
69-
services: Record<string, any> & {
70-
isSvelte: true;
71-
getSvelteHtmlAst: () => SvAST.Fragment;
72-
getStyleContext: () => StyleContext;
73-
};
63+
services: Record<string, any> &
64+
(
65+
| {
66+
isSvelte: true;
67+
svelteRunes: boolean;
68+
getSvelteHtmlAst: () => SvAST.Fragment;
69+
getStyleContext: () => StyleContext;
70+
}
71+
| { isSvelte: false; svelteRunes: boolean }
72+
);
7473
visitorKeys: { [type: string]: string[] };
7574
scopeManager: ScopeManager;
76-
} {
77-
const parserOptions = {
78-
ecmaVersion: 2020,
79-
sourceType: "module",
80-
loc: true,
81-
range: true,
82-
raw: true,
83-
tokens: true,
84-
comment: true,
85-
eslintVisitorKeys: true,
86-
eslintScopeManager: true,
87-
...(options || {}),
88-
};
89-
parserOptions.sourceType = "module";
90-
if (parserOptions.ecmaVersion <= 5 || parserOptions.ecmaVersion == null) {
91-
parserOptions.ecmaVersion = 2015;
75+
};
76+
/**
77+
* Parse source code
78+
*/
79+
export function parseForESLint(code: string, options?: any): ParseResult {
80+
const parserOptions = normalizeParserOptions(options);
81+
82+
if (
83+
parserOptions.filePath &&
84+
!parserOptions.filePath.endsWith(".svelte") &&
85+
parserOptions.runes
86+
) {
87+
const trimmed = code.trim();
88+
if (!trimmed.startsWith("<") && !trimmed.endsWith(">")) {
89+
return parseAsScript(code, parserOptions);
90+
}
9291
}
9392

93+
return parseAsSvelte(code, parserOptions);
94+
}
95+
96+
/**
97+
* Parse source code as svelte component
98+
*/
99+
function parseAsSvelte(
100+
code: string,
101+
parserOptions: NormalizedParserOptions,
102+
): ParseResult {
94103
const ctx = new Context(code, parserOptions);
95104
const resultTemplate = parseTemplate(
96105
ctx.sourceCode.template,
@@ -106,7 +115,7 @@ export function parseForESLint(
106115
parserOptions,
107116
{ slots: ctx.slots },
108117
)
109-
: parseScript(
118+
: parseScriptInSvelte(
110119
scripts.getCurrentVirtualCode(),
111120
scripts.attrs,
112121
parserOptions,
@@ -211,6 +220,61 @@ export function parseForESLint(
211220
return resultScript as any;
212221
}
213222

223+
/**
224+
* Parse source code as script
225+
*/
226+
function parseAsScript(
227+
code: string,
228+
parserOptions: NormalizedParserOptions,
229+
): ParseResult {
230+
const lang = parserOptions.filePath?.split(".").pop() || "js";
231+
// TODO support runes
232+
const resultScript = parseScript(code, { lang }, parserOptions);
233+
resultScript.services = Object.assign(resultScript.services || {}, {
234+
isSvelte: false,
235+
runes: parserOptions.runes,
236+
});
237+
resultScript.visitorKeys = Object.assign({}, KEYS, resultScript.visitorKeys);
238+
return resultScript as any;
239+
}
240+
241+
type NormalizedParserOptions = {
242+
ecmaVersion: number | "latest";
243+
sourceType: "module" | "script";
244+
loc: boolean;
245+
range: boolean;
246+
raw: boolean;
247+
tokens: boolean;
248+
comment: boolean;
249+
eslintVisitorKeys: boolean;
250+
eslintScopeManager: boolean;
251+
runes: boolean;
252+
filePath?: string;
253+
};
254+
255+
/** Normalize parserOptions */
256+
function normalizeParserOptions(options: any): NormalizedParserOptions {
257+
const parserOptions = {
258+
ecmaVersion: 2020,
259+
sourceType: "module",
260+
loc: true,
261+
range: true,
262+
raw: true,
263+
tokens: true,
264+
comment: true,
265+
eslintVisitorKeys: true,
266+
eslintScopeManager: true,
267+
rune: false,
268+
...(options || {}),
269+
};
270+
parserOptions.sourceType = "module";
271+
if (parserOptions.ecmaVersion <= 5 || parserOptions.ecmaVersion == null) {
272+
parserOptions.ecmaVersion = 2015;
273+
}
274+
275+
return parserOptions;
276+
}
277+
214278
/** Extract tokens */
215279
function extractTokens(ctx: Context) {
216280
const useRanges = sortNodes([...ctx.tokens, ...ctx.comments]).map(

src/parser/script.ts

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,23 +5,14 @@ import { getParser } from "./resolve-parser";
55
import { isEnhancedParserObject } from "./parser-object";
66

77
/**
8-
* Parse for script
8+
* Parse for <script>
99
*/
10-
export function parseScript(
10+
export function parseScriptInSvelte(
1111
code: string,
1212
attrs: Record<string, string | undefined>,
1313
parserOptions: any = {},
1414
): ESLintExtendedProgram {
15-
const result = parseScriptWithoutAnalyzeScopeFromVCode(
16-
code,
17-
attrs,
18-
parserOptions,
19-
);
20-
21-
if (!result.scopeManager) {
22-
const scopeManager = analyzeScope(result.ast, parserOptions);
23-
result.scopeManager = scopeManager;
24-
}
15+
const result = parseScript(code, attrs, parserOptions);
2516

2617
traverseNodes(result.ast, {
2718
visitorKeys: result.visitorKeys,
@@ -42,6 +33,27 @@ export function parseScript(
4233

4334
return result;
4435
}
36+
/**
37+
* Parse for script
38+
*/
39+
export function parseScript(
40+
code: string,
41+
attrs: Record<string, string | undefined>,
42+
parserOptions: any = {},
43+
): ESLintExtendedProgram {
44+
const result = parseScriptWithoutAnalyzeScopeFromVCode(
45+
code,
46+
attrs,
47+
parserOptions,
48+
);
49+
50+
if (!result.scopeManager) {
51+
const scopeManager = analyzeScope(result.ast, parserOptions);
52+
result.scopeManager = scopeManager;
53+
}
54+
55+
return result;
56+
}
4557

4658
/**
4759
* Parse for script without analyze scope

src/parser/typescript/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { ESLintExtendedProgram } from "..";
2-
import { parseScript } from "../script";
2+
import { parseScriptInSvelte } from "../script";
33
import type { AnalyzeTypeScriptContext } from "./analyze";
44
import { analyzeTypeScript } from "./analyze";
55
import type { TSESParseForESLintResult } from "./types";
@@ -15,7 +15,7 @@ export function parseTypeScript(
1515
): ESLintExtendedProgram {
1616
const tsCtx = analyzeTypeScript(code, attrs, parserOptions, context);
1717

18-
const result = parseScript(tsCtx.script, attrs, parserOptions);
18+
const result = parseScriptInSvelte(tsCtx.script, attrs, parserOptions);
1919

2020
tsCtx.restoreContext.restore(result as unknown as TSESParseForESLintResult);
2121

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"runes": true
3+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<script>
2+
let todos = $state([]);
3+
4+
function remaining(todos) {
5+
console.log('recalculating');
6+
return todos.filter(todo => !todo.done).length;
7+
}
8+
9+
function addTodo(event) {
10+
if (event.key !== 'Enter') return;
11+
12+
let done = $state(false);
13+
let text = $state(event.target.value);
14+
15+
todos = [...todos, {
16+
get done() { return done },
17+
set done(value) { done = value },
18+
get text() { return text },
19+
set text(value) { text = value }
20+
}];
21+
22+
event.target.value = '';
23+
}
24+
</script>
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
[
2+
{
3+
"ruleId": "no-undef",
4+
"code": "$state",
5+
"line": 2,
6+
"column": 14
7+
},
8+
{
9+
"ruleId": "no-undef",
10+
"code": "$state",
11+
"line": 12,
12+
"column": 14
13+
},
14+
{
15+
"ruleId": "no-undef",
16+
"code": "$state",
17+
"line": 13,
18+
"column": 14
19+
}
20+
]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
[
2+
{
3+
"ruleId": "no-unused-vars",
4+
"code": "remaining",
5+
"line": 4,
6+
"column": 11
7+
},
8+
{
9+
"ruleId": "no-unused-vars",
10+
"code": "addTodo",
11+
"line": 9,
12+
"column": 11
13+
}
14+
]

0 commit comments

Comments
 (0)