Skip to content

Commit 1178032

Browse files
authored
fix: assign actual runes value to SvelteParseContext (#633)
1 parent 98d23f5 commit 1178032

10 files changed

+78
-46
lines changed

.changeset/big-ligers-turn.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"svelte-eslint-parser": patch
3+
---
4+
5+
fix: assign actual `runes` value to `SvelteParseContext`

src/parser/index.ts

+5-13
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,6 @@ import type { NormalizedParserOptions } from "./parser-options.js";
5050
import { isTypeScript, normalizeParserOptions } from "./parser-options.js";
5151
import { getFragmentFromRoot } from "./compat.js";
5252
import {
53-
isEnableRunes,
5453
resolveSvelteParseContextForSvelte,
5554
resolveSvelteParseContextForSvelteScript,
5655
type SvelteParseContext,
@@ -117,20 +116,13 @@ export function parseForESLint(code: string, options?: any): ParseResult {
117116
const parserOptions = normalizeParserOptions(options);
118117

119118
if (
120-
isEnableRunes(svelteConfig, parserOptions) &&
121119
parserOptions.filePath &&
122-
!parserOptions.filePath.endsWith(".svelte") &&
123-
// If no `filePath` is set in ESLint, "<input>" will be specified.
124-
parserOptions.filePath !== "<input>"
120+
(parserOptions.filePath.endsWith(".svelte.js") ||
121+
parserOptions.filePath.endsWith(".svelte.ts"))
125122
) {
126-
const trimmed = code.trim();
127-
if (!trimmed.startsWith("<") && !trimmed.endsWith(">")) {
128-
const svelteParseContext = resolveSvelteParseContextForSvelteScript(
129-
svelteConfig,
130-
parserOptions,
131-
);
132-
return parseAsScript(code, parserOptions, svelteParseContext);
133-
}
123+
const svelteParseContext =
124+
resolveSvelteParseContextForSvelteScript(svelteConfig);
125+
return parseAsScript(code, parserOptions, svelteParseContext);
134126
}
135127

136128
return parseAsSvelte(code, svelteConfig, parserOptions);

src/parser/svelte-parse-context.ts

+66-33
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,20 @@
11
import type * as Compiler from "./svelte-ast-types-for-v5.js";
22
import type * as SvAST from "./svelte-ast-types.js";
3+
import type * as ESTree from "estree";
34
import type { NormalizedParserOptions } from "./parser-options.js";
45
import { compilerVersion, svelteVersion } from "./svelte-version.js";
56
import type { SvelteConfig } from "../svelte-config/index.js";
7+
import { traverseNodes } from "../traverse.js";
8+
9+
const runeSymbols: string[] = [
10+
"$state",
11+
"$derived",
12+
"$effect",
13+
"$props",
14+
"$bindable",
15+
"$inspect",
16+
"$host",
17+
] as const;
618

719
/** The context for parsing. */
820
export type SvelteParseContext = {
@@ -18,55 +30,76 @@ export type SvelteParseContext = {
1830
svelteConfig: SvelteConfig | null;
1931
};
2032

21-
export function isEnableRunes(
22-
svelteConfig: SvelteConfig | null,
23-
parserOptions: NormalizedParserOptions,
24-
): boolean {
25-
if (!svelteVersion.gte(5)) return false;
26-
if (parserOptions.svelteFeatures?.runes != null) {
27-
return Boolean(parserOptions.svelteFeatures.runes);
28-
}
29-
if (svelteConfig?.compilerOptions?.runes != null) {
30-
return Boolean(svelteConfig.compilerOptions.runes);
31-
}
32-
return true;
33-
}
34-
3533
export function resolveSvelteParseContextForSvelte(
3634
svelteConfig: SvelteConfig | null,
3735
parserOptions: NormalizedParserOptions,
3836
svelteAst: Compiler.Root | SvAST.AstLegacy,
3937
): SvelteParseContext {
40-
const svelteOptions = (svelteAst as Compiler.Root).options;
41-
if (svelteOptions?.runes != null) {
42-
return {
43-
runes: svelteOptions.runes,
44-
compilerVersion,
45-
svelteConfig,
46-
};
47-
}
48-
4938
return {
50-
runes: isEnableRunes(svelteConfig, parserOptions),
39+
runes: isRunes(svelteConfig, parserOptions, svelteAst),
5140
compilerVersion,
5241
svelteConfig,
5342
};
5443
}
5544

5645
export function resolveSvelteParseContextForSvelteScript(
5746
svelteConfig: SvelteConfig | null,
58-
parserOptions: NormalizedParserOptions,
59-
): SvelteParseContext {
60-
return resolveSvelteParseContext(svelteConfig, parserOptions);
61-
}
62-
63-
function resolveSvelteParseContext(
64-
svelteConfig: SvelteConfig | null,
65-
parserOptions: NormalizedParserOptions,
6647
): SvelteParseContext {
6748
return {
68-
runes: isEnableRunes(svelteConfig, parserOptions),
49+
// .svelte.js files are always in Runes mode for Svelte 5.
50+
runes: svelteVersion.gte(5),
6951
compilerVersion,
7052
svelteConfig,
7153
};
7254
}
55+
56+
function isRunes(
57+
svelteConfig: SvelteConfig | null,
58+
parserOptions: NormalizedParserOptions,
59+
svelteAst: Compiler.Root | SvAST.AstLegacy,
60+
): boolean {
61+
// Svelte 3/4 does not support Runes mode.
62+
if (!svelteVersion.gte(5)) {
63+
return false;
64+
}
65+
66+
// Compiler option.
67+
if (parserOptions.svelteFeatures?.runes != null) {
68+
return parserOptions.svelteFeatures?.runes;
69+
}
70+
if (svelteConfig?.compilerOptions?.runes != null) {
71+
return svelteConfig?.compilerOptions?.runes;
72+
}
73+
74+
// `<svelte:options>`.
75+
const svelteOptions = (svelteAst as Compiler.Root).options;
76+
if (svelteOptions?.runes != null) {
77+
return svelteOptions?.runes;
78+
}
79+
80+
// Static analysis.
81+
const { module, instance } = svelteAst;
82+
return (
83+
(module != null && hasRuneSymbol(module)) ||
84+
(instance != null && hasRuneSymbol(instance))
85+
);
86+
}
87+
88+
function hasRuneSymbol(ast: Compiler.Script | SvAST.Script): boolean {
89+
let hasRuneSymbol = false;
90+
traverseNodes(ast as unknown as ESTree.Node, {
91+
enterNode(node) {
92+
if (hasRuneSymbol) {
93+
return;
94+
}
95+
if (node.type === "Identifier" && runeSymbols.includes(node.name)) {
96+
hasRuneSymbol = true;
97+
}
98+
},
99+
leaveNode() {
100+
// do nothing
101+
},
102+
});
103+
104+
return hasRuneSymbol;
105+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
/** Config for testing */
2+
export default {};

0 commit comments

Comments
 (0)