8000 feat: add explicit `auto` option for rune mode. · sveltejs/svelte-eslint-parser@953f687 · GitHub
[go: up one dir, main page]

Skip to content

Commit 953f687

Browse files
committed
feat: add explicit auto option for rune mode.
1 parent 10d6af6 commit 953f687

22 files changed

+2784
-54
lines changed

.changeset/runes-auto.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"svelte-eslint-parser": minor
3+
---
4+
5+
feat: add explicit `auto` option for rune mode.

README.md

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -286,11 +286,7 @@ export default [
286286
svelteFeatures: {
287287
/* -- Experimental Svelte Features -- */
288288
/* It may be changed or removed in minor versions without notice. */
289-
// This option is for Svelte 5. The default value is `true`.
290-
// If `false`, ESLint will not recognize rune symbols.
291-
// If not configured this option, The parser will try to read the option from `compilerOptions.runes` from `svelte.config.js`.
292-
// If `parserOptions.svelteConfig` is not specified and the file cannot be parsed by static analysis, it will behave as `true`.
293-
runes: true,
289+
runes: "auto", // or `true` or `false`
294290
/* -- Experimental Svelte Features -- */
295291
/* It may be changed or removed in minor versions without notice. */
296292
// Whether to parse the `generics` attribute.
@@ -312,11 +308,7 @@ For example in `.eslintrc.*`:
312308
"svelteFeatures": {
313309
/* -- Experimental Svelte Features -- */
314310
/* It may be changed or removed in minor versions without notice. */
315-
// This option is for Svelte 5. The default value is `true`.
316-
// If `false`, ESLint will not recognize rune symbols.
317-
// If not configured this option, The parser will try to read the option from `compilerOptions.runes` from `svelte.config.js`.
318-
// If `parserOptions.svelteConfig` is not specified and the file cannot be parsed by static analysis, it will behave as `true`.
319-
"runes": true,
311+
"runes": "auto", // or `true` or `false`
320312
/* -- Experimental Svelte Features -- */
321313
/* It may be changed or removed in minor versions without notice. */
322314
// Whether to parse the `generics` attribute.
@@ -327,6 +319,17 @@ For example in `.eslintrc.*`:
327319
}
328320
```
329321

322+
#### parserOptions.svelteFeatures.runes
323+
324+
Configures whether ESLint recognizes rune symbols.
325+
326+
- `true` ... The parser recognizes rune symbols, and always passes `parserServices.svelteParseContext.runes` as `true`.
327+
- `"auto"` ... The parser recognizes rune symbols, and passes `true` to `parserServices.svelteParseContext.runes` if the file contains rune symbols, `false` if it does not.
328+
- `false` ... The parser does not recognize rune symbols, and always passes `parserServices.svelteParseContext.runes` as `false`.
329+
330+
If not configured this option, The parser will try to read the option from `compilerOptions.runes` from `svelte.config.js`.
331+
If `parserOptions.svelteConfig` is not specified and the file cannot be analyzed by static analysis, or neither has `compilerOptions.runes`, it will behave as `"auto"`.
332+
330333
### Runes support
331334

332335
**_This is an experimental feature. It may be changed or removed in minor versions without notice._**

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ export {
1313
export * as meta from "./meta";
1414
export { name } from "./meta";
1515
export type { SvelteConfig } from "./svelte-config";
16+
export type { PublicSvelteParseContext as SvelteParseContext } from "./parser/svelte-parse-context";
1617

1718
export { AST, ParseError };
1819

src/parser/index.ts

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,10 @@ import type { NormalizedParserOptions } from "./parser-options";
3939
import { isTypeScript, normalizeParserOptions } from "./parser-options";
4040
import { getFragmentFromRoot } from "./compat";
4141
import {
42-
isEnableRunes,
42+
isMaybeEnableRunes,
4343
resolveSvelteParseContextForSvelte,
4444
resolveSvelteParseContextForSvelteScript,
45+
type PublicSvelteParseContext,
4546
type SvelteParseContext,
4647
} from "./svelte-parse-context";
4748
import type { SvelteConfig } from "../svelte-config";
@@ -81,12 +82,12 @@ type ParseResult = {
8182
isSvelteScript: false;
8283
getSvelteHtmlAst: () => SvAST.Fragment | Compiler.Fragment;
8384
getStyleContext: () => StyleContext;
84-
svelteParseContext: SvelteParseContext;
85+
svelteParseContext: PublicSvelteParseContext;
8586
}
8687
| {
8788
isSvelte: false;
8889
isSvelteScript: true;
89-
svelteParseContext: SvelteParseContext;
90+
svelteParseContext: PublicSvelteParseContext;
9091
}
9192
);
9293
visitorKeys: { [type: string]: string[] };
@@ -100,7 +101,7 @@ export function parseForESLint(code: string, options?: any): ParseResult {
100101
const parserOptions = normalizeParserOptions(options);
101102

102103
if (
103-
isEnableRunes(svelteConfig, parserOptions) &&
104+
isMaybeEnableRunes(svelteConfig, parserOptions) &&
104105
parserOptions.filePath &&
105106
!parserOptions.filePath.endsWith(".svelte") &&
106107
// If no `filePath` is set in ESLint, "<input>" will be specified.
@@ -152,6 +153,8 @@ function parseAsSvelte(
152153
scripts.attrs,
153154
parserOptions,
154155
);
156+
svelteParseContext.analyzeRunesMode(resultScript.scopeManager!);
157+
155158
ctx.scriptLet.restore(resultScript);
156159
ctx.tokens.push(...resultScript.ast.tokens);
157160
ctx.comments.push(...resultScript.ast.comments);
@@ -234,7 +237,7 @@ function parseAsSvelte(
234237
},
235238
styleNodeLoc,
236239
styleNodeRange,
237-
svelteParseContext,
240+
svelteParseContext: svelteParseContext.toPublic(),
238241
});
239242
resultScript.visitorKeys = Object.assign({}, KEYS, resultScript.visitorKeys);
240243

@@ -263,7 +266,7 @@ function parseAsScript(
263266
resultScript.services = Object.assign(resultScript.services || {}, {
264267
isSvelte: false,
265268
isSvelteScript: true,
266-
svelteParseContext,
269+
svelteParseContext: svelteParseContext.toPublic(),
267270
});
268271
resultScript.visitorKeys = Object.assign({}, KEYS, resultScript.visitorKeys);
269272
return resultScript as any;

src/parser/parser-options.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,8 @@ export type NormalizedParserOptions = {
2121
};
2222
svelteFeatures?: {
2323
/* -- Experimental Svelte Features -- */
24-
// This option is for Svelte 5. The default value is `true`.
25-
// If `false`, ESLint will not recognize rune symbols.
26-
// If not configured this option, The parser will try to read the option from `compilerOptions.runes` from `svelte.config.js`.
27-
// If `parserOptions.svelteConfig` is not specified and the file cannot be parsed by static analysis, it will behave as `true`.
28-
runes?: boolean;
24+
// Configures whether ESLint recognizes rune symbols.
25+
runes?: boolean | "auto";
2926
// Whether to parse the `generics` attribute.
3027
// See https://github.com/sveltejs/rfcs/pull/38
3128
experimentalGenerics?: boolean;

src/parser/svelte-parse-context.ts

Lines changed: 66 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@ import type * as SvAST from "./svelte-ast-types";
33
import type { NormalizedParserOptions } from "./parser-options";
44
import { compilerVersion, svelteVersion } from "./svelte-version";
55
import type { SvelteConfig } from "../svelte-config";
6+
import type { ScopeManager } from "eslint-scope";
7+
import { globalsForRunes } from "./globals";
68

79
/** The context for parsing. */
8-
export type SvelteParseContext = {
10+
export type PublicSvelteParseContext = {
911
/**
1012
* Whether to use Runes mode.
1113
* May be `true` if the user is using Svelte v5.
@@ -18,18 +20,68 @@ export type SvelteParseContext = {
1820
svelteConfig: SvelteConfig | null;
1921
};
2022

21-
export function isEnableRunes(
23+
export const enum RunesMode {
24+
off,
25+
on,
26+
auto,
27+
}
28+
29+
export class SvelteParseContext {
30+
private runesMode: RunesMode;
31+
32+
private readonly svelteConfig: SvelteConfig | null;
33+
34+
public constructor(runesMode: RunesMode, svelteConfig: SvelteConfig | null) {
35+
this.runesMode = runesMode;
36+
this.svelteConfig = svelteConfig;
37+
}
38+
39+
public get runes(): boolean {
40+
if (this.runesMode === RunesMode.auto)
41+
throw new Error("Runes mode is auto");
42+
return this.runesMode === RunesMode.on;
43+
}
44+
45+
public analyzeRunesMode(scopeManager: ScopeManager): void {
46+
if (this.runesMode !== RunesMode.auto) return;
47+
this.runesMode = scopeManager.globalScope.through.some((reference) =>
48+
globalsForRunes.includes(reference.identifier.name as never),
49+
)
50+
? RunesMode.on
51+
: RunesMode.off;
52+
}
53+
54+
/** Convert it into a format provided by the parser service. */
55+
public toPublic(): PublicSvelteParseContext {
56+
return {
57+
runes: this.runes,
58+
compilerVersion,
59+
svelteConfig: this.svelteConfig,
60+
};
61+
}
62+
}
63+
64+
function getRunesMode(
2265
svelteConfig: SvelteConfig | null,
2366
parserOptions: NormalizedParserOptions,
24-
): boolean {
25-
if (!svelteVersion.gte(5)) return false;
67+
): RunesMode {
68+
if (!svelteVersion.gte(5)) return RunesMode.off;
2669
if (parserOptions.svelteFeatures?.runes != null) {
27-
return Boolean(parserOptions.svelteFeatures.runes);
70+
if (parserOptions.svelteFeatures.runes === "auto") return RunesMode.auto;
71+
return parserOptions.svelteFeatures.runes ? RunesMode.on : RunesMode.off;
2872
}
2973
if (svelteConfig?.compilerOptions?.runes != null) {
30-
return Boolean(svelteConfig.compilerOptions.runes);
74+
return svelteConfig.compilerOptions.runes ? RunesMode.on : RunesMode.off;
3175
}
32-
return true;
76+
return RunesMode.auto;
77+
}
78+
79+
export function isMaybeEnableRunes(
80+
svelteConfig: SvelteConfig | null,
81+
parserOptions: NormalizedParserOptions,
82+
): boolean {
83+
const mode = getRunesMode(svelteConfig, parserOptions);
84+
return mode === RunesMode.on || mode === RunesMode.auto;
3385
}
3486

3587
export function resolveSvelteParseContextForSvelte(
@@ -39,18 +91,12 @@ export function resolveSvelteParseContextForSvelte(
3991
): SvelteParseContext {
4092
const svelteOptions = (svelteAst as Compiler.Root).options;
4193
if (svelteOptions?.runes != null) {
42-
return {
43-
runes: svelteOptions.runes,
44-
compilerVersion,
94+
return new SvelteParseContext(
95+
svelteOptions.runes ? RunesMode.on : RunesMode.off,
4596
svelteConfig,
46-
};
97+
);
4798
}
48-
49-
return {
50-
runes: isEnableRunes(svelteConfig, parserOptions),
51-
compilerVersion,
52-
svelteConfig,
53-
};
99+
return resolveSvelteParseContext(svelteConfig, parserOptions);
54100
}
55101

56102
export function resolveSvelteParseContextForSvelteScript(
@@ -64,9 +110,8 @@ function resolveSvelteParseContext(
64110
svelteConfig: SvelteConfig | null,
65111
parserOptions: NormalizedParserOptions,
66112
): SvelteParseContext {
67-
return {
68-
runes: isEnableRunes(svelteConfig, parserOptions),
69-
compilerVersion,
113+
return new SvelteParseContext(
114+
getRunesMode(svelteConfig, parserOptions),
70115
svelteConfig,
71-
};
116+
);
72117
}

src/parser/typescript/analyze/index.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import type { SvelteAttribute, SvelteHTMLElement } from "../../../ast";
1919
import type { NormalizedParserOptions } from "../../parser-options";
2020
import { setParent } from "../set-parent";
2121
import { getGlobalsForSvelte, globalsForRunes } from "../../globals";
22-
import type { SvelteParseContext } from "../../svelte-parse-context";
22+
import { type SvelteParseContext } from "../../svelte-parse-context";
2323

2424
export type AnalyzeTypeScriptContext = {
2525
slots: Set<SvelteHTMLElement>;
@@ -56,6 +56,7 @@ export function analyzeTypeScriptInSvelte(
5656
project: null,
5757
},
5858
) as unknown as TSESParseForESLintResult;
59+
context.svelteParseContext.analyzeRunesMode(result.scopeManager as never);
5960

6061
ctx._beforeResult = result;
6162

@@ -103,6 +104,7 @@ export function analyzeTypeScript(
103104
// Without typings
104105
project: null,
105106
}) as unknown as TSESParseForESLintResult;
107+
svelteParseContext.analyzeRunesMode(result.scopeManager as never);
106108

107109
ctx._beforeResult = result;
108110

tests/fixtures/integrations/snippet-scope/ts-snippet-hoist-scope-setup.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ export function getConfig() {
1414
parser: "svelte-eslint-parser",
1515
parserOptions: {
1616
...generateParserOptions(),
17-
svelteFeatures: { runes: true },
1817
},
1918
rules: {
2019
"@typescript-eslint/no-unused-vars": "error",

tests/fixtures/integrations/type-info-tests/$derived-setup.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ export function getConfig() {
3030
parser: "svelte-eslint-parser",
3131
parserOptions: {
3232
...generateParserOptions(),
33-
svelteFeatures: { runes: true },
3433
},
3534
rules: {
3635
"@typescript-eslint/no-unsafe-argument": "error",

tests/fixtures/integrations/type-info-tests/$derived-ts-setup.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ export function getConfig() {
3030
parser: "svelte-eslint-parser",
3131
parserOptions: {
3232
...generateParserOptions(),
33-
svelteFeatures: { runes: true },
3433
},
3534
rules: {
3635
"@typescript-eslint/no-unsafe-argument": "error",

tests/fixtures/integrations/type-info-tests/$derived2-setup.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ export function getConfig() {
3030
parser: "svelte-eslint-parser",
3131
parserOptions: {
3232
...generateParserOptions(),
33-
svelteFeatures: { runes: true },
3433
},
3534
rules: {
3635
"@typescript-eslint/no-unsafe-argument": "error",

tests/fixtures/integrations/type-info-tests/$derived2-ts-setup.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ export function getConfig() {
3030
parser: "svelte-eslint-parser",
3131
parserOptions: {
3232
...generateParserOptions(),
33-
svelteFeatures: { runes: true },
3433
},
3534
rules: {
3635
"@typescript-eslint/no-unsafe-argument": "error",
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"svelteFeatures": {
3+
"runes": "auto"
4+
}
5+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<script>
2+
const { p } = $props();
3+
</script>
4+
5+
<span>{p}</span>

0 commit comments

Comments
 (0)
0