diff --git a/.changeset/big-ligers-turn.md b/.changeset/big-ligers-turn.md new file mode 100644 index 00000000..48573e67 --- /dev/null +++ b/.changeset/big-ligers-turn.md @@ -0,0 +1,5 @@ +--- +"svelte-eslint-parser": patch +--- + +fix: assign actual `runes` value to `SvelteParseContext` diff --git a/src/parser/index.ts b/src/parser/index.ts index f7ad5aa8..877b1a64 100644 --- a/src/parser/index.ts +++ b/src/parser/index.ts @@ -50,7 +50,6 @@ import type { NormalizedParserOptions } from "./parser-options.js"; import { isTypeScript, normalizeParserOptions } from "./parser-options.js"; import { getFragmentFromRoot } from "./compat.js"; import { - isEnableRunes, resolveSvelteParseContextForSvelte, resolveSvelteParseContextForSvelteScript, type SvelteParseContext, @@ -117,20 +116,13 @@ export function parseForESLint(code: string, options?: any): ParseResult { const parserOptions = normalizeParserOptions(options); if ( - isEnableRunes(svelteConfig, parserOptions) && parserOptions.filePath && - !parserOptions.filePath.endsWith(".svelte") && - // If no `filePath` is set in ESLint, "" will be specified. - parserOptions.filePath !== "" + (parserOptions.filePath.endsWith(".svelte.js") || + parserOptions.filePath.endsWith(".svelte.ts")) ) { - const trimmed = code.trim(); - if (!trimmed.startsWith("<") && !trimmed.endsWith(">")) { - const svelteParseContext = resolveSvelteParseContextForSvelteScript( - svelteConfig, - parserOptions, - ); - return parseAsScript(code, parserOptions, svelteParseContext); - } + const svelteParseContext = + resolveSvelteParseContextForSvelteScript(svelteConfig); + return parseAsScript(code, parserOptions, svelteParseContext); } return parseAsSvelte(code, svelteConfig, parserOptions); diff --git a/src/parser/svelte-parse-context.ts b/src/parser/svelte-parse-context.ts index d28799eb..19871f00 100644 --- a/src/parser/svelte-parse-context.ts +++ b/src/parser/svelte-parse-context.ts @@ -1,8 +1,20 @@ import type * as Compiler from "./svelte-ast-types-for-v5.js"; import type * as SvAST from "./svelte-ast-types.js"; +import type * as ESTree from "estree"; import type { NormalizedParserOptions } from "./parser-options.js"; import { compilerVersion, svelteVersion } from "./svelte-version.js"; import type { SvelteConfig } from "../svelte-config/index.js"; +import { traverseNodes } from "../traverse.js"; + +const runeSymbols: string[] = [ + "$state", + "$derived", + "$effect", + "$props", + "$bindable", + "$inspect", + "$host", +] as const; /** The context for parsing. */ export type SvelteParseContext = { @@ -18,36 +30,13 @@ export type SvelteParseContext = { svelteConfig: SvelteConfig | null; }; -export function isEnableRunes( - svelteConfig: SvelteConfig | null, - parserOptions: NormalizedParserOptions, -): boolean { - if (!svelteVersion.gte(5)) return false; - if (parserOptions.svelteFeatures?.runes != null) { - return Boolean(parserOptions.svelteFeatures.runes); - } - if (svelteConfig?.compilerOptions?.runes != null) { - return Boolean(svelteConfig.compilerOptions.runes); - } - return true; -} - export function resolveSvelteParseContextForSvelte( svelteConfig: SvelteConfig | null, parserOptions: NormalizedParserOptions, svelteAst: Compiler.Root | SvAST.AstLegacy, ): SvelteParseContext { - const svelteOptions = (svelteAst as Compiler.Root).options; - if (svelteOptions?.runes != null) { - return { - runes: svelteOptions.runes, - compilerVersion, - svelteConfig, - }; - } - return { - runes: isEnableRunes(svelteConfig, parserOptions), + runes: isRunes(svelteConfig, parserOptions, svelteAst), compilerVersion, svelteConfig, }; @@ -55,18 +44,62 @@ export function resolveSvelteParseContextForSvelte( export function resolveSvelteParseContextForSvelteScript( svelteConfig: SvelteConfig | null, - parserOptions: NormalizedParserOptions, -): SvelteParseContext { - return resolveSvelteParseContext(svelteConfig, parserOptions); -} - -function resolveSvelteParseContext( - svelteConfig: SvelteConfig | null, - parserOptions: NormalizedParserOptions, ): SvelteParseContext { return { - runes: isEnableRunes(svelteConfig, parserOptions), + // .svelte.js files are always in Runes mode for Svelte 5. + runes: svelteVersion.gte(5), compilerVersion, svelteConfig, }; } + +function isRunes( + svelteConfig: SvelteConfig | null, + parserOptions: NormalizedParserOptions, + svelteAst: Compiler.Root | SvAST.AstLegacy, +): boolean { + // Svelte 3/4 does not support Runes mode. + if (!svelteVersion.gte(5)) { + return false; + } + + // Compiler option. + if (parserOptions.svelteFeatures?.runes != null) { + return parserOptions.svelteFeatures?.runes; + } + if (svelteConfig?.compilerOptions?.runes != null) { + return svelteConfig?.compilerOptions?.runes; + } + + // ``. + const svelteOptions = (svelteAst as Compiler.Root).options; + if (svelteOptions?.runes != null) { + return svelteOptions?.runes; + } + + // Static analysis. + const { module, instance } = svelteAst; + return ( + (module != null && hasRuneSymbol(module)) || + (instance != null && hasRuneSymbol(instance)) + ); +} + +function hasRuneSymbol(ast: Compiler.Script | SvAST.Script): boolean { + let hasRuneSymbol = false; + traverseNodes(ast as unknown as ESTree.Node, { + enterNode(node) { + if (hasRuneSymbol) { + return; + } + if (node.type === "Identifier" && runeSymbols.includes(node.name)) { + hasRuneSymbol = true; + } + }, + leaveNode() { + // do nothing + }, + }); + + return hasRuneSymbol; +} diff --git a/tests/fixtures/parser/ast/svelte5/svelte-options01-input.svelte b/tests/fixtures/parser/ast/svelte5/svelte-options/svelte-options01-input.svelte similarity index 100% rename from tests/fixtures/parser/ast/svelte5/svelte-options01-input.svelte rename to tests/fixtures/parser/ast/svelte5/svelte-options/svelte-options01-input.svelte diff --git a/tests/fixtures/parser/ast/svelte5/svelte-options01-output.json b/tests/fixtures/parser/ast/svelte5/svelte-options/svelte-options01-output.json similarity index 100% rename from tests/fixtures/parser/ast/svelte5/svelte-options01-output.json rename to tests/fixtures/parser/ast/svelte5/svelte-options/svelte-options01-output.json diff --git a/tests/fixtures/parser/ast/svelte5/svelte-options01-scope-output.json b/tests/fixtures/parser/ast/svelte5/svelte-options/svelte-options01-scope-output.json similarity index 100% rename from tests/fixtures/parser/ast/svelte5/svelte-options01-scope-output.json rename to tests/fixtures/parser/ast/svelte5/svelte-options/svelte-options01-scope-output.json diff --git a/tests/fixtures/parser/ast/svelte5/svelte-options02-input.svelte b/tests/fixtures/parser/ast/svelte5/svelte-options/svelte-options02-input.svelte similarity index 100% rename from tests/fixtures/parser/ast/svelte5/svelte-options02-input.svelte rename to tests/fixtures/parser/ast/svelte5/svelte-options/svelte-options02-input.svelte diff --git a/tests/fixtures/parser/ast/svelte5/svelte-options02-output.json b/tests/fixtures/parser/ast/svelte5/svelte-options/svelte-options02-output.json similarity index 100% rename from tests/fixtures/parser/ast/svelte5/svelte-options02-output.json rename to tests/fixtures/parser/ast/svelte5/svelte-options/svelte-options02-output.json diff --git a/tests/fixtures/parser/ast/svelte5/svelte-options02-scope-output.json b/tests/fixtures/parser/ast/svelte5/svelte-options/svelte-options02-scope-output.json similarity index 100% rename from tests/fixtures/parser/ast/svelte5/svelte-options02-scope-output.json rename to tests/fixtures/parser/ast/svelte5/svelte-options/svelte-options02-scope-output.json diff --git a/tests/fixtures/parser/ast/svelte5/svelte-options/svelte.config.js b/tests/fixtures/parser/ast/svelte5/svelte-options/svelte.config.js new file mode 100644 index 00000000..cbc7a6c8 --- /dev/null +++ b/tests/fixtures/parser/ast/svelte5/svelte-options/svelte.config.js @@ -0,0 +1,2 @@ +/** Config for testing */ +export default {}; pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy