Skip to content

Commit 1c43be1

Browse files
committed
test: add test cases for runes
1 parent ff242c4 commit 1c43be1

File tree

123 files changed

+87327
-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.

123 files changed

+87327
-77
lines changed

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";
@@ -59,39 +59,48 @@ export interface ESLintExtendedProgram {
5959
// The code used to parse the script.
6060
_virtualScriptCode?: string;
6161
}
62-
/**
63-
* Parse source code
64-
*/
65-
export function parseForESLint(
66-
code: string,
67-
options?: any,
68-
): {
62+
type ParseResult = {
6963
ast: SvelteProgram;
70-
services: Record<string, any> & {
71-
isSvelte: true;
72-
getSvelteHtmlAst: () => SvAST.Fragment;
73-
getStyleContext: () => StyleContext;
74-
};
64+
services: Record<string, any> &
65+
(
66+
| {
67+
isSvelte: true;
68+
svelteRunes: boolean;
69+
getSvelteHtmlAst: () => SvAST.Fragment;
70+
getStyleContext: () => StyleContext;
71+
}
72+
| { isSvelte: false; svelteRunes: boolean }
73+
);
7574
visitorKeys: { [type: string]: string[] };
7675
scopeManager: ScopeManager;
77-
} {
78-
const parserOptions = {
79-
ecmaVersion: 2020,
80-
sourceType: "module",
81-
loc: true,
82-
range: true,
83-
raw: true,
84-
tokens: true,
85-
comment: true,
86-
eslintVisitorKeys: true,
87-
eslintScopeManager: true,
88-
...(options || {}),
89-
};
90-
parserOptions.sourceType = "module";
91-
if (parserOptions.ecmaVersion <= 5 || parserOptions.ecmaVersion == null) {
92-
parserOptions.ecmaVersion = 2015;
76+
};
77+
/**
78+
* Parse source code
79+
*/
80+
export function parseForESLint(code: string, options?: any): ParseResult {
81+
const parserOptions = normalizeParserOptions(options);
82+
83+
if (
84+
parserOptions.filePath &&
85+
!parserOptions.filePath.endsWith(".svelte") &&
86+
parserOptions.runes
87+
) {
88+
const trimmed = code.trim();
89+
if (!trimmed.startsWith("<") && !trimmed.endsWith(">")) {
90+
return parseAsScript(code, parserOptions);
91+
}
9392
}
9493

94+
return parseAsSvelte(code, parserOptions);
95+
}
96+
97+
/**
98+
* Parse source code as svelte component
99+
*/
100+
function parseAsSvelte(
101+
code: string,
102+
parserOptions: NormalizedParserOptions,
103+
): ParseResult {
95104
const ctx = new Context(code, parserOptions);
96105
const resultTemplate = parseTemplate(
97106
ctx.sourceCode.template,
@@ -107,7 +116,7 @@ export function parseForESLint(
107116
parserOptions,
108117
{ slots: ctx.slots },
109118
)
110-
: parseScript(
119+
: parseScriptInSvelte(
111120
scripts.getCurrentVirtualCode(),
112121
scripts.attrs,
113122
parserOptions,
@@ -212,6 +221,61 @@ export function parseForESLint(
212221
return resultScript as any;
213222
}
214223

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