diff --git a/.changeset/shy-cups-visit.md b/.changeset/shy-cups-visit.md new file mode 100644 index 00000000..5e160db6 --- /dev/null +++ b/.changeset/shy-cups-visit.md @@ -0,0 +1,5 @@ +--- +"svelte-eslint-parser": minor +--- + +feat: makes it optional whether to parse runes. diff --git a/README.md b/README.md index b90865f3..d27cabc2 100644 --- a/README.md +++ b/README.md @@ -245,9 +245,34 @@ For example in `.eslintrc.*`: } ``` +### parserOptions.svelteConfig + +If you are using `eslint.config.js`, you can provide a `svelte.config.js` in the `parserOptions.svelteConfig` property. + +For example: + +```js +import svelteConfig from "./svelte.config.js"; +export default [ + { + files: ["**/*.svelte", "*.svelte"], + languageOptions: { + parser: svelteParser, + parserOptions: { + svelteConfig: svelteConfig, + }, + }, + }, +]; +``` + +If `parserOptions.svelteConfig` is not specified, some config will be statically parsed from the `svelte.config.js` file. + +The `.eslintrc.*` style configuration cannot load `svelte.config.js` because it cannot use ESM. We recommend using the `eslint.config.js` style configuration. + ### parserOptions.svelteFeatures -You can use `parserOptions.svelteFeatures` property to specify how to parse related to Svelte features. For example: +You can use `parserOptions.svelteFeatures` property to specify how to parse related to Svelte features. For example in `eslint.config.js`: @@ -259,6 +284,12 @@ export default [ parser: svelteParser, parserOptions: { svelteFeatures: { + /* -- Experimental Svelte Features -- */ + /* It may be changed or removed in minor versions without notice. */ + // If true, it will analyze Runes. + // By default, it will try to read `compilerOptions.runes` from `svelte.config.js`. + // However, note that if `parserOptions.svelteConfig` is not specified and the file cannot be parsed by static analysis, it will behave as `false`. + runes: false, /* -- Experimental Svelte Features -- */ /* It may be changed or removed in minor versions without notice. */ // Whether to parse the `generics` attribute. @@ -278,6 +309,12 @@ For example in `.eslintrc.*`: "parser": "svelte-eslint-parser", "parserOptions": { "svelteFeatures": { + /* -- Experimental Svelte Features -- */ + /* It may be changed or removed in minor versions without notice. */ + // If true, it will analyze Runes. + // By default, it will try to read `compilerOptions.runes` from `svelte.config.js`. + // However, note that if the file cannot be parsed by static analysis, it will behave as false. + "runes": false, /* -- Experimental Svelte Features -- */ /* It may be changed or removed in minor versions without notice. */ // Whether to parse the `generics` attribute. @@ -292,13 +329,14 @@ For example in `.eslintrc.*`: **_This is an experimental feature. It may be changed or removed in minor versions without notice._** -If you install Svelte v5 the parser will be able to parse runes, and will also be able to parse `*.js` and `*.ts` files. +If you install Svelte v5 and turn on runes (`compilerOptions.runes` in `svelte.config.js` or `parserOptions.svelteFeatures.runes` in ESLint config is `true`), the parser will be able to parse runes, and will also be able to parse `*.js` and `*.ts` files. When using this mode in an ESLint configuration, it is recommended to set it per file pattern as below. For example in `eslint.config.js`: ```js +import svelteConfig from "./svelte.config.js"; export default [ { files: ["**/*.svelte", "*.svelte"], @@ -306,6 +344,7 @@ export default [ parser: svelteParser, parserOptions: { parser: "...", + svelteConfig, /* ... */ }, }, @@ -315,6 +354,7 @@ export default [ languageOptions: { parser: svelteParser, parserOptions: { + svelteConfig, /* ... */ }, }, @@ -325,6 +365,7 @@ export default [ parser: svelteParser, parserOptions: { parser: "...(ts parser)...", + svelteConfig, /* ... */ }, }, @@ -342,6 +383,7 @@ For example in `.eslintrc.*`: "parser": "svelte-eslint-parser", "parserOptions": { "parser": "...", + "svelteFeatures": { "runes": true }, /* ... */ }, }, @@ -349,6 +391,7 @@ For example in `.eslintrc.*`: "files": ["*.svelte.js"], "parser": "svelte-eslint-parser", "parserOptions": { + "svelteFeatures": { "runes": true }, /* ... */ }, }, @@ -357,6 +400,7 @@ For example in `.eslintrc.*`: "parser": "svelte-eslint-parser", "parserOptions": { "parser": "...(ts parser)...", + "svelteFeatures": { "runes": true }, /* ... */ }, }, diff --git a/src/index.ts b/src/index.ts index 08290598..3cb39f11 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,14 +4,15 @@ import { KEYS } from "./visitor-keys"; import { ParseError } from "./errors"; export { parseForESLint, - StyleContext, - StyleContextNoStyleElement, - StyleContextParseError, - StyleContextSuccess, - StyleContextUnknownLang, + type StyleContext, + type StyleContextNoStyleElement, + type StyleContextParseError, + type StyleContextSuccess, + type StyleContextUnknownLang, } from "./parser"; export * as meta from "./meta"; export { name } from "./meta"; +export type { SvelteConfig } from "./svelte-config"; export { AST, ParseError }; diff --git a/src/parser/analyze-scope.ts b/src/parser/analyze-scope.ts index 12bd2a4c..c0700dc2 100644 --- a/src/parser/analyze-scope.ts +++ b/src/parser/analyze-scope.ts @@ -10,6 +10,7 @@ import type { import { addReference, addVariable, getScopeFromNode } from "../scope"; import { addElementToSortedArray } from "../utils"; import type { NormalizedParserOptions } from "./parser-options"; +import type { SvelteParseContext } from "./svelte-parse-context"; /** * Analyze scope */ @@ -160,6 +161,7 @@ export function analyzeStoreScope(scopeManager: ScopeManager): void { export function analyzePropsScope( body: SvelteScriptElement, scopeManager: ScopeManager, + svelteParseContext: SvelteParseContext, ): void { const moduleScope = scopeManager.scopes.find( (scope) => scope.type === "module", @@ -187,23 +189,25 @@ export function analyzePropsScope( } } } else if (node.type === "VariableDeclaration") { - // Process for Svelte v5 Runes props. e.g. `let { x = $bindable() } = $props()`; - for (const decl of node.declarations) { - if ( - decl.init?.type === "CallExpression" && - decl.init.callee.type === "Identifier" && - decl.init.callee.name === "$props" && - decl.id.type === "ObjectPattern" - ) { - for (const pattern of extractPattern(decl.id)) { - if ( - pattern.type === "AssignmentPattern" && - pattern.left.type === "Identifier" && - pattern.right.type === "CallExpression" && - pattern.right.callee.type === "Identifier" && - pattern.right.callee.name === "$bindable" - ) { - addPropReference(pattern.left, moduleScope); + if (svelteParseContext.runes) { + // Process for Svelte v5 Runes props. e.g. `let { x = $bindable() } = $props()`; + for (const decl of node.declarations) { + if ( + decl.init?.type === "CallExpression" && + decl.init.callee.type === "Identifier" && + decl.init.callee.name === "$props" && + decl.id.type === "ObjectPattern" + ) { + for (const pattern of extractPattern(decl.id)) { + if ( + pattern.type === "AssignmentPattern" && + pattern.left.type === "Identifier" && + pattern.right.type === "CallExpression" && + pattern.right.callee.type === "Identifier" && + pattern.right.callee.name === "$bindable" + ) { + addPropReference(pattern.left, moduleScope); + } } } } diff --git a/src/parser/globals.ts b/src/parser/globals.ts index 9ae23b3a..29ac3c9c 100644 --- a/src/parser/globals.ts +++ b/src/parser/globals.ts @@ -1,6 +1,6 @@ -import { svelteVersion } from "./svelte-version"; +import type { SvelteParseContext } from "./svelte-parse-context"; -const globalsForSvelte4 = ["$$slots", "$$props", "$$restProps"] as const; +const globalsForSvelte = ["$$slots", "$$props", "$$restProps"] as const; export const globalsForRunes = [ "$state", "$derived", @@ -10,10 +10,22 @@ export const globalsForRunes = [ "$inspect", "$host", ] as const; -const globalsForSvelte5 = [...globalsForSvelte4, ...globalsForRunes]; -export const globals = svelteVersion.gte(5) - ? globalsForSvelte5 - : globalsForSvelte4; -export const globalsForSvelteScript = svelteVersion.gte(5) - ? globalsForRunes - : []; +type Global = + | (typeof globalsForSvelte)[number] + | (typeof globalsForRunes)[number]; +export function getGlobalsForSvelte( + svelteParseContext: SvelteParseContext, +): readonly Global[] { + if (svelteParseContext.runes) { + return [...globalsForSvelte, ...globalsForRunes]; + } + return globalsForSvelte; +} +export function getGlobalsForSvelteScript( + svelteParseContext: SvelteParseContext, +): readonly Global[] { + if (svelteParseContext.runes) { + return globalsForRunes; + } + return []; +} diff --git a/src/parser/index.ts b/src/parser/index.ts index 0a8cdf1c..981f2f4f 100644 --- a/src/parser/index.ts +++ b/src/parser/index.ts @@ -34,11 +34,18 @@ import { styleNodeLoc, styleNodeRange, } from "./style-context"; -import { globals, globalsForSvelteScript } from "./globals"; -import { svelteVersion } from "./svelte-version"; +import { getGlobalsForSvelte, getGlobalsForSvelteScript } from "./globals"; import type { NormalizedParserOptions } from "./parser-options"; import { isTypeScript, normalizeParserOptions } from "./parser-options"; import { getFragmentFromRoot } from "./compat"; +import { + isEnableRunes, + resolveSvelteParseContextForSvelte, + resolveSvelteParseContextForSvelteScript, + type SvelteParseContext, +} from "./svelte-parse-context"; +import type { SvelteConfig } from "../svelte-config"; +import { resolveSvelteConfigFromOption } from "../svelte-config"; export { StyleContext, @@ -74,8 +81,13 @@ type ParseResult = { isSvelteScript: false; getSvelteHtmlAst: () => SvAST.Fragment | Compiler.Fragment; getStyleContext: () => StyleContext; + svelteParseContext: SvelteParseContext; + } + | { + isSvelte: false; + isSvelteScript: true; + svelteParseContext: SvelteParseContext; } - | { isSvelte: false; isSvelteScript: true } ); visitorKeys: { [type: string]: string[] }; scopeManager: ScopeManager; @@ -84,10 +96,11 @@ type ParseResult = { * Parse source code */ export function parseForESLint(code: string, options?: any): ParseResult { + const svelteConfig = resolveSvelteConfigFromOption(options); const parserOptions = normalizeParserOptions(options); if ( - svelteVersion.hasRunes && + isEnableRunes(svelteConfig, parserOptions) && parserOptions.filePath && !parserOptions.filePath.endsWith(".svelte") && // If no `filePath` is set in ESLint, "" will be specified. @@ -95,11 +108,15 @@ export function parseForESLint(code: string, options?: any): ParseResult { ) { const trimmed = code.trim(); if (!trimmed.startsWith("<") && !trimmed.endsWith(">")) { - return parseAsScript(code, parserOptions); + const svelteParseContext = resolveSvelteParseContextForSvelteScript( + svelteConfig, + parserOptions, + ); + return parseAsScript(code, parserOptions, svelteParseContext); } } - return parseAsSvelte(code, parserOptions); + return parseAsSvelte(code, svelteConfig, parserOptions); } /** @@ -107,6 +124,7 @@ export function parseForESLint(code: string, options?: any): ParseResult { */ function parseAsSvelte( code: string, + svelteConfig: SvelteConfig | null, parserOptions: NormalizedParserOptions, ): ParseResult { const ctx = new Context(code, parserOptions); @@ -115,6 +133,11 @@ function parseAsSvelte( ctx, parserOptions, ); + const svelteParseContext = resolveSvelteParseContextForSvelte( + svelteConfig, + parserOptions, + resultTemplate.svelteAst, + ); const scripts = ctx.sourceCode.scripts; const resultScript = ctx.isTypeScript() @@ -122,7 +145,7 @@ function parseAsSvelte( scripts.getCurrentVirtualCodeInfo(), scripts.attrs, parserOptions, - { slots: ctx.slots }, + { slots: ctx.slots, svelteParseContext }, ) : parseScriptInSvelte( scripts.getCurrentVirtualCode(), @@ -141,7 +164,10 @@ function parseAsSvelte( analyzeSnippetsScope(ctx.snippets, resultScript.scopeManager!); // Add $$xxx variable - addGlobalVariables(resultScript.scopeManager!, globals); + addGlobalVariables( + resultScript.scopeManager!, + getGlobalsForSvelte(svelteParseContext), + ); const ast = resultTemplate.ast; @@ -177,7 +203,7 @@ function parseAsSvelte( attr.value[0].value === "module", ) ) { - analyzePropsScope(body, resultScript.scopeManager!); + analyzePropsScope(body, resultScript.scopeManager!, svelteParseContext); } } if (statements.length) { @@ -208,6 +234,7 @@ function parseAsSvelte( }, styleNodeLoc, styleNodeRange, + svelteParseContext, }); resultScript.visitorKeys = Object.assign({}, KEYS, resultScript.visitorKeys); @@ -220,18 +247,23 @@ function parseAsSvelte( function parseAsScript( code: string, parserOptions: NormalizedParserOptions, + svelteParseContext: SvelteParseContext, ): ParseResult { const lang = parserOptions.filePath?.split(".").pop(); const resultScript = isTypeScript(parserOptions, lang) - ? parseTypeScript(code, { lang }, parserOptions) + ? parseTypeScript(code, { lang }, parserOptions, svelteParseContext) : parseScript(code, { lang }, parserOptions); // Add $$xxx variable - addGlobalVariables(resultScript.scopeManager!, globalsForSvelteScript); + addGlobalVariables( + resultScript.scopeManager!, + getGlobalsForSvelteScript(svelteParseContext), + ); resultScript.services = Object.assign(resultScript.services || {}, { isSvelte: false, isSvelteScript: true, + svelteParseContext, }); resultScript.visitorKeys = Object.assign({}, KEYS, resultScript.visitorKeys); return resultScript as any; diff --git a/src/parser/parser-options.ts b/src/parser/parser-options.ts index 7b10badd..3bfc818d 100644 --- a/src/parser/parser-options.ts +++ b/src/parser/parser-options.ts @@ -20,6 +20,10 @@ export type NormalizedParserOptions = { [key: string]: any; }; svelteFeatures?: { + // If true, it will analyze Runes. + // By default, it will try to read `compilerOptions.runes` from `svelte.config.js`. + // However, note that if it cannot be resolved due to static analysis, it will behave as false. + runes?: boolean; /* -- Experimental Svelte Features -- */ // Whether to parse the `generics` attribute. // See https://github.com/sveltejs/rfcs/pull/38 diff --git a/src/parser/svelte-parse-context.ts b/src/parser/svelte-parse-context.ts new file mode 100644 index 00000000..33ec8ee0 --- /dev/null +++ b/src/parser/svelte-parse-context.ts @@ -0,0 +1,71 @@ +import type * as Compiler from "svelte/compiler"; +import type * as SvAST from "./svelte-ast-types"; +import type { NormalizedParserOptions } from "./parser-options"; +import { compilerVersion, svelteVersion } from "./svelte-version"; +import type { SvelteConfig } from "../svelte-config"; + +/** The context for parsing. */ +export type SvelteParseContext = { + /** + * Whether to use Runes mode. + * May be `true` if the user is using Svelte v5. + * Resolved from `svelte.config.js` or `parserOptions`, but may be overridden by ``. + */ + runes: boolean; + /** The version of "svelte/compiler". */ + compilerVersion: string; + /** The result of static analysis of `svelte.config.js`. */ + 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); + } else if (svelteConfig?.compilerOptions?.runes != null) { + return Boolean(svelteConfig.compilerOptions.runes); + } + return false; +} + +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), + compilerVersion, + svelteConfig, + }; +} + +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), + compilerVersion, + svelteConfig, + }; +} diff --git a/src/parser/svelte-version.ts b/src/parser/svelte-version.ts index 9c859aee..3257aa72 100644 --- a/src/parser/svelte-version.ts +++ b/src/parser/svelte-version.ts @@ -1,10 +1,11 @@ -import { VERSION as SVELTE_VERSION } from "svelte/compiler"; +import { VERSION as compilerVersion } from "svelte/compiler"; -const verStrings = SVELTE_VERSION.split("."); +export { compilerVersion }; + +const verStrings = compilerVersion.split("."); export const svelteVersion = { gte(v: number): boolean { return Number(verStrings[0]) >= v; }, - hasRunes: Number(verStrings[0]) >= 5, }; diff --git a/src/parser/typescript/analyze/index.ts b/src/parser/typescript/analyze/index.ts index bb27f6f7..0e1d8bbe 100644 --- a/src/parser/typescript/analyze/index.ts +++ b/src/parser/typescript/analyze/index.ts @@ -16,12 +16,14 @@ import { VirtualTypeScriptContext } from "../context"; import type { TSESParseForESLintResult } from "../types"; import type ESTree from "estree"; import type { SvelteAttribute, SvelteHTMLElement } from "../../../ast"; -import { globals, globalsForRunes } from "../../../parser/globals"; import type { NormalizedParserOptions } from "../../parser-options"; import { setParent } from "../set-parent"; +import { getGlobalsForSvelte, globalsForRunes } from "../../globals"; +import type { SvelteParseContext } from "../../svelte-parse-context"; export type AnalyzeTypeScriptContext = { slots: Set; + svelteParseContext: SvelteParseContext; }; type TransformInfo = { @@ -57,14 +59,22 @@ export function analyzeTypeScriptInSvelte( ctx._beforeResult = result; - analyzeStoreReferenceNames(result, ctx); + analyzeStoreReferenceNames(result, context.svelteParseContext, ctx); - analyzeDollarDollarVariables(result, ctx, context.slots); + analyzeDollarDollarVariables( + result, + ctx, + context.svelteParseContext, + context.slots, + ); - analyzeRuneVariables(result, ctx); + analyzeRuneVariables(result, ctx, context.svelteParseContext); applyTransforms( - [...analyzeReactiveScopes(result), ...analyzeDollarDerivedScopes(result)], + [ + ...analyzeReactiveScopes(result), + ...analyzeDollarDerivedScopes(result, context.svelteParseContext), + ], ctx, ); @@ -83,6 +93,7 @@ export function analyzeTypeScript( code: string, attrs: Record, parserOptions: NormalizedParserOptions, + svelteParseContext: SvelteParseContext, ): VirtualTypeScriptContext { const ctx = new VirtualTypeScriptContext(code); ctx.appendOriginal(/^\s*/u.exec(code)![0].length); @@ -95,9 +106,12 @@ export function analyzeTypeScript( ctx._beforeResult = result; - analyzeRuneVariables(result, ctx); + analyzeRuneVariables(result, ctx, svelteParseContext); - applyTransforms([...analyzeDollarDerivedScopes(result)], ctx); + applyTransforms( + [...analyzeDollarDerivedScopes(result, svelteParseContext)], + ctx, + ); ctx.appendOriginalToEnd(); @@ -110,8 +124,10 @@ export function analyzeTypeScript( */ function analyzeStoreReferenceNames( result: TSESParseForESLintResult, + svelteParseContext: SvelteParseContext, ctx: VirtualTypeScriptContext, ) { + const globals = getGlobalsForSvelte(svelteParseContext); const scopeManager = result.scopeManager; const programScope = getProgramScope(scopeManager as ScopeManager); const maybeStoreRefNames = new Set(); @@ -198,8 +214,10 @@ function analyzeStoreReferenceNames( function analyzeDollarDollarVariables( result: TSESParseForESLintResult, ctx: VirtualTypeScriptContext, + svelteParseContext: SvelteParseContext, slots: Set, ) { + const globals = getGlobalsForSvelte(svelteParseContext); const scopeManager = result.scopeManager; for (const globalName of globals) { if ( @@ -308,7 +326,9 @@ function analyzeDollarDollarVariables( function analyzeRuneVariables( result: TSESParseForESLintResult, ctx: VirtualTypeScriptContext, + svelteParseContext: SvelteParseContext, ) { + if (!svelteParseContext.runes) return; const scopeManager = result.scopeManager; for (const globalName of globalsForRunes) { if ( @@ -511,7 +531,9 @@ function* analyzeReactiveScopes( */ function* analyzeDollarDerivedScopes( result: TSESParseForESLintResult, + svelteParseContext: SvelteParseContext, ): Iterable { + if (!svelteParseContext.runes) return; const scopeManager = result.scopeManager; const derivedReferences = scopeManager.globalScope!.through.filter( (reference) => reference.identifier.name === "$derived", diff --git a/src/parser/typescript/index.ts b/src/parser/typescript/index.ts index 4f2f2e3d..91abd973 100644 --- a/src/parser/typescript/index.ts +++ b/src/parser/typescript/index.ts @@ -1,6 +1,7 @@ import type { ESLintExtendedProgram } from ".."; import type { NormalizedParserOptions } from "../parser-options"; import { parseScript, parseScriptInSvelte } from "../script"; +import type { SvelteParseContext } from "../svelte-parse-context"; import type { AnalyzeTypeScriptContext } from "./analyze"; import { analyzeTypeScript, analyzeTypeScriptInSvelte } from "./analyze"; import { setParent } from "./set-parent"; @@ -30,8 +31,14 @@ export function parseTypeScript( code: string, attrs: Record, parserOptions: NormalizedParserOptions, + svelteParseContext: SvelteParseContext, ): ESLintExtendedProgram { - const tsCtx = analyzeTypeScript(code, attrs, parserOptions); + const tsCtx = analyzeTypeScript( + code, + attrs, + parserOptions, + svelteParseContext, + ); const result = parseScript(tsCtx.script, attrs, parserOptions); setParent(result); diff --git a/src/scope/index.ts b/src/scope/index.ts index d43de84f..a3c3cb13 100644 --- a/src/scope/index.ts +++ b/src/scope/index.ts @@ -80,6 +80,26 @@ export function getScopeFromNode( const global = scopeManager.globalScope; return global; } + +/** + * Find the variable of a given identifier. + */ +export function findVariable( + scopeManager: ScopeManager, + node: ESTree.Identifier, +): Variable | null { + let scope: Scope | null = getScopeFromNode(scopeManager, node); + + while (scope != null) { + const variable = scope.set.get(node.name); + if (variable != null) { + return variable; + } + scope = scope.upper; + } + + return null; +} /** * Gets the scope for the Program node */ diff --git a/src/svelte-config/index.ts b/src/svelte-config/index.ts new file mode 100644 index 00000000..ac35a8c8 --- /dev/null +++ b/src/svelte-config/index.ts @@ -0,0 +1,145 @@ +import path from "path"; +import fs from "fs"; +import { parseConfig } from "./parser"; +import type * as Compiler from "svelte/compiler"; + +export type SvelteConfig = { + compilerOptions?: Compiler.CompileOptions; + extensions?: string[]; + kit?: KitConfig; + preprocess?: unknown; + vitePlugin?: unknown; + onwarn?: ( + warning: Compiler.Warning, + defaultHandler: (warning: Compiler.Warning) => void, + ) => void; + [key: string]: unknown; +}; + +interface KitConfig { + adapter?: unknown; + alias?: Record; + appDir?: string; + csp?: { + mode?: "hash" | "nonce" | "auto"; + directives?: unknown; + reportOnly?: unknown; + }; + csrf?: { + checkOrigin?: boolean; + }; + embedded?: boolean; + env?: { + dir?: string; + publicPrefix?: string; + privatePrefix?: string; + }; + files?: { + assets?: string; + hooks?: { + client?: string; + server?: string; + universal?: string; + }; + lib?: string; + params?: string; + routes?: string; + serviceWorker?: string; + appTemplate?: string; + errorTemplate?: string; + }; + inlineStyleThreshold?: number; + moduleExtensions?: string[]; + outDir?: string; + output?: { + preloadStrategy?: "modulepreload" | "preload-js" | "preload-mjs"; + }; + paths?: { + assets?: "" | `http://${string}` | `https://${string}`; + base?: "" | `/${string}`; + relative?: boolean; + }; + prerender?: { + concurrency?: number; + crawl?: boolean; + entries?: ("*" | `/${string}`)[]; + handleHttpError?: unknown; + handleMissingId?: unknown; + handleEntryGeneratorMismatch?: unknown; + origin?: string; + }; + serviceWorker?: { + register?: boolean; + files?(filepath: string): boolean; + }; + typescript?: { + config?: (config: Record) => Record | void; + }; + version?: { + name?: string; + pollInterval?: number; + }; +} + +const caches = new Map(); + +/** + * Resolves svelte.config. + */ +export function resolveSvelteConfigFromOption( + options: any, +): SvelteConfig | null { + if (options?.svelteConfig) { + return options.svelteConfig; + } + return resolveSvelteConfig(options?.filePath); +} + +/** + * Resolves `svelte.config.js`. + * It searches the parent directories of the given file to find `svelte.config.js`, + * and returns the static analysis result for it. + */ +function resolveSvelteConfig( + filePath: string | undefined, +): SvelteConfig | null { + const cwd = + filePath && fs.existsSync(filePath) + ? path.dirname(filePath) + : process.cwd(); + const configFilePath = findConfigFilePath(cwd); + if (!configFilePath) return null; + + if (caches.has(configFilePath)) { + return caches.get(configFilePath) || null; + } + + const code = fs.readFileSync(configFilePath, "utf8"); + const config = parseConfig(code); + caches.set(configFilePath, config); + return config; +} + +/** + * Searches from the current working directory up until finding the config filename. + * @param {string} cwd The current working directory to search from. + * @returns {string|undefined} The file if found or `undefined` if not. + */ +function findConfigFilePath(cwd: string) { + let directory = path.resolve(cwd); + const { root } = path.parse(directory); + const stopAt = path.resolve(directory, root); + while (directory !== stopAt) { + const target = path.resolve(directory, "svelte.config.js"); + const stat = fs.existsSync(target) + ? fs.statSync(target, { + throwIfNoEntry: false, + }) + : null; + if (stat?.isFile()) { + return target; + } + directory = path.dirname(directory); + } + return null; +} diff --git a/src/svelte-config/parser.ts b/src/svelte-config/parser.ts new file mode 100644 index 00000000..451072d1 --- /dev/null +++ b/src/svelte-config/parser.ts @@ -0,0 +1,328 @@ +import type { SvelteConfig } from "."; +import type * as ESTree from "estree"; +import type { Scope } from "eslint"; +import type { ScopeManager } from "eslint-scope"; +import { getFallbackKeys, traverseNodes } from "../traverse"; +import { getEspree } from "../parser/espree"; +import { analyze } from "eslint-scope"; +import { findVariable } from "../scope"; + +export function parseConfig(code: string): SvelteConfig | null { + const espree = getEspree(); + const ast = espree.parse(code, { + range: true, + loc: true, + ecmaVersion: espree.latestEcmaVersion, + sourceType: "module", + }); + // Set parent nodes. + traverseNodes(ast, { + enterNode(node, parent) { + (node as any).parent = parent; + }, + leaveNode() { + /* do nothing */ + }, + }); + // Analyze scopes. + const scopeManager = analyze(ast, { + ignoreEval: true, + nodejsScope: false, + ecmaVersion: espree.latestEcmaVersion, + sourceType: "module", + fallback: getFallbackKeys, + }); + return parseAst(ast, scopeManager); +} + +function parseAst( + ast: ESTree.Program, + scopeManager: ScopeManager, +): SvelteConfig { + const edd = ast.body.find( + (node): node is ESTree.ExportDefaultDeclaration => + node.type === "ExportDefaultDeclaration", + ); + if (!edd) return {}; + const decl = edd.declaration; + if (decl.type === "ClassDeclaration" || decl.type === "FunctionDeclaration") + return {}; + return parseSvelteConfigExpression(decl, scopeManager); +} + +function parseSvelteConfigExpression( + node: ESTree.Expression, + scopeManager: ScopeManager, +): SvelteConfig { + const evaluated = evaluateExpression(node, scopeManager); + if (evaluated?.type !== EvaluatedType.object) return {}; + const result: SvelteConfig = {}; + // Returns only known properties. + const compilerOptions = evaluated.getProperty("compilerOptions"); + if (compilerOptions?.type === EvaluatedType.object) { + result.compilerOptions = {}; + const runes = compilerOptions.getProperty("runes")?.getStatic(); + if (runes) { + result.compilerOptions.runes = Boolean(runes.value); + } + } + const kit = evaluated.getProperty("kit"); + if (kit?.type === EvaluatedType.object) { + result.kit = {}; + const files = kit.getProperty("files")?.getStatic(); + if (files) result.kit.files = files.value as never; + } + return result; +} + +const enum EvaluatedType { + literal, + object, +} + +type Evaluated = EvaluatedLiteral | EvaluatedObject; + +class EvaluatedLiteral { + public readonly type = EvaluatedType.literal; + + public value: unknown; + + public constructor(value: unknown) { + this.value = value; + } + + public getStatic() { + return this; + } +} + +/** Evaluating an object expression. */ +class EvaluatedObject { + public readonly type = EvaluatedType.object; + + private readonly cached = new Map(); + + private readonly node: ESTree.ObjectExpression; + + private readonly parseExpression: ( + node: ESTree.Expression | ESTree.Pattern | ESTree.PrivateIdentifier, + ) => Evaluated | null; + + public constructor( + node: ESTree.ObjectExpression, + parseExpression: ( + node: ESTree.Expression | ESTree.Pattern | ESTree.PrivateIdentifier, + ) => Evaluated | null, + ) { + this.node = node; + this.parseExpression = parseExpression; + } + + /** Gets the evaluated value of the property with the given name. */ + public getProperty(key: string): Evaluated | null { + return this.withCache(key, () => { + let unknown = false; + for (const prop of [...this.node.properties].reverse()) { + if (prop.type === "Property") { + const name = this.getKey(prop); + if (name === key) return this.parseExpression(prop.value); + if (name == null) unknown = true; + } else if (prop.type === "SpreadElement") { + const evaluated = this.parseExpression(prop.argument); + if (evaluated?.type === EvaluatedType.object) { + const value = evaluated.getProperty(key); + if (value) return value; + } + unknown = true; + } + } + return unknown ? null : new EvaluatedLiteral(undefined); + }); + } + + public getStatic(): { value: Record } | null { + const object: Record = {}; + for (const prop of this.node.properties) { + if (prop.type === "Property") { + const name = this.getKey(prop); + if (name == null) return null; + const evaluated = this.withCache(name, () => + this.parseExpression(prop.value), + )?.getStatic(); + if (!evaluated) return null; + object[name] = evaluated.value; + } else if (prop.type === "SpreadElement") { + const evaluated = this.parseExpression(prop.argument)?.getStatic(); + if (!evaluated) return null; + Object.assign(object, evaluated.value); + } + } + return { value: object }; + } + + private withCache( + key: string, + parse: () => Evaluated | null, + ): Evaluated | null { + if (this.cached.has(key)) return this.cached.get(key) || null; + const evaluated = parse(); + this.cached.set(key, evaluated); + return evaluated; + } + + private getKey(node: ESTree.Property): string | null { + if (!node.computed && node.key.type === "Identifier") return node.key.name; + const evaluatedKey = this.parseExpression(node.key)?.getStatic(); + if (evaluatedKey) return String(evaluatedKey.value); + return null; + } +} + +function evaluateExpression( + node: ESTree.Expression, + scopeManager: ScopeManager, +): Evaluated | null { + const tracked = new Map(); + return parseExpression(node); + + function parseExpression( + node: ESTree.Expression | ESTree.Pattern | ESTree.PrivateIdentifier, + ): Evaluated | null { + if (node.type === "Literal") { + return new EvaluatedLiteral(node.value); + } + if (node.type === "Identifier") { + return parseIdentifier(node); + } + if (node.type === "ObjectExpression") { + return new EvaluatedObject(node, parseExpression); + } + + return null; + } + + function parseIdentifier(node: ESTree.Identifier): Evaluated | null { + const defs = getIdentifierDefinitions(node); + if (defs.length !== 1) { + if (defs.length === 0 && node.name === "undefined") + return new EvaluatedLiteral(undefined); + return null; + } + const def = defs[0]; + if (def.type !== "Variable") return null; + if (def.parent.kind !== "const" || !def.node.init) return null; + const evaluated = parseExpression(def.node.init); + if (!evaluated) return null; + const assigns = parsePatternAssign(def.name, def.node.id); + let result = evaluated; + while (assigns.length) { + const assign = assigns.shift()!; + if (assign.type === "member") { + if (result.type !== EvaluatedType.object) return null; + const next = result.getProperty(assign.name); + if (!next) return null; + result = next; + } else if (assign.type === "assignment") { + if ( + result.type === EvaluatedType.literal && + result.value === undefined + ) { + const next = parseExpression(assign.node.right); + if (!next) return null; + result = next; + } + } + } + return result; + } + + function getIdentifierDefinitions( + node: ESTree.Identifier, + ): Scope.Definition[] { + if (tracked.has(node)) return tracked.get(node)!; + tracked.set(node, []); + const defs = findVariable(scopeManager, node)?.defs; + if (!defs) return []; + tracked.set(node, defs); + if (defs.length !== 1) { + const def = defs[0]; + if ( + def.type === "Variable" && + def.parent.kind === "const" && + def.node.id.type === "Identifier" && + def.node.init?.type === "Identifier" + ) { + const newDef = getIdentifierDefinitions(def.node.init); + tracked.set(node, newDef); + return newDef; + } + } + return defs; + } +} + +/** + * Returns the assignment path. + * For example, + * `let {a: {target}} = {}` + * -> `[{type: "member", name: 'a'}, {type: "member", name: 'target'}]`. + * `let {a: {target} = foo} = {}` + * -> `[{type: "member", name: 'a'}, {type: "assignment"}, {type: "member", name: 'target'}]`. + */ +function parsePatternAssign( + node: ESTree.Pattern, + root: ESTree.Pattern, +): ( + | { type: "member"; name: string } + | { type: "assignment"; node: ESTree.AssignmentPattern } +)[] { + return parse(root) || []; + + function parse( + target: ESTree.Pattern, + ): + | ( + | { type: "member"; name: string } + | { type: "assignment"; node: ESTree.AssignmentPattern } + )[] + | null { + if (node === target) { + return []; + } + if (target.type === "Identifier") { + return null; + } + if (target.type === "AssignmentPattern") { + const left = parse(target.left); + if (!left) return null; + return [{ type: "assignment", node: target }, ...left]; + } + if (target.type === "ObjectPattern") { + for (const prop of target.properties) { + if (prop.type === "Property") { + const name = + !prop.computed && prop.key.type === "Identifier" + ? prop.key.name + : prop.key.type === "Literal" + ? String(prop.key.value) + : null; + if (!name) continue; + const value = parse(prop.value); + if (!value) return null; + return [{ type: "member", name }, ...value]; + } + } + return null; + } + if (target.type === "ArrayPattern") { + for (const [index, element] of target.elements.entries()) { + if (!element) continue; + const value = parse(element); + if (!value) return null; + return [{ type: "member", name: String(index) }, ...value]; + } + return null; + } + return null; + } +} diff --git a/tests/fixtures/parser/ast/svelte.config.js b/tests/fixtures/parser/ast/svelte.config.js new file mode 100644 index 00000000..f24eca30 --- /dev/null +++ b/tests/fixtures/parser/ast/svelte.config.js @@ -0,0 +1,10 @@ +/** Config for testing */ + +/** @type {import('svelte/compiler').CompileOptions} */ +const options = { + runes: true +}; + +export default { + compilerOptions: options +}; diff --git a/tests/fixtures/parser/ast/svelte5/$props-without-runes01-config.json b/tests/fixtures/parser/ast/svelte5/$props-without-runes01-config.json new file mode 100644 index 00000000..64e36863 --- /dev/null +++ b/tests/fixtures/parser/ast/svelte5/$props-without-runes01-config.json @@ -0,0 +1,5 @@ +{ + "svelteFeatures": { + "runes": false + } +} diff --git a/tests/fixtures/parser/ast/svelte5/$props-without-runes01-input.svelte b/tests/fixtures/parser/ast/svelte5/$props-without-runes01-input.svelte new file mode 100644 index 00000000..60106664 --- /dev/null +++ b/tests/fixtures/parser/ast/svelte5/$props-without-runes01-input.svelte @@ -0,0 +1,5 @@ + + +{p} diff --git a/tests/fixtures/parser/ast/svelte5/$props-without-runes01-no-undef-result.json b/tests/fixtures/parser/ast/svelte5/$props-without-runes01-no-undef-result.json new file mode 100644 index 00000000..5cb69474 --- /dev/null +++ b/tests/fixtures/parser/ast/svelte5/$props-without-runes01-no-undef-result.json @@ -0,0 +1,8 @@ +[ + { + "ruleId": "no-undef", + "code": "$props", + "line": 2, + "column": 16 + } +] \ No newline at end of file diff --git a/tests/fixtures/parser/ast/svelte5/$props-without-runes01-output.json b/tests/fixtures/parser/ast/svelte5/$props-without-runes01-output.json new file mode 100644 index 00000000..0ccd50d3 --- /dev/null +++ b/tests/fixtures/parser/ast/svelte5/$props-without-runes01-output.json @@ -0,0 +1,860 @@ +{ + "type": "Program", + "body": [ + { + "type": "SvelteScriptElement", + "name": { + "type": "SvelteName", + "name": "script", + "range": [ + 1, + 7 + ], + "loc": { + "start": { + "line": 1, + "column": 1 + }, + "end": { + "line": 1, + "column": 7 + } + } + }, + "startTag": { + "type": "SvelteStartTag", + "attributes": [], + "selfClosing": false, + "range": [ + 0, + 8 + ], + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 8 + } + } + }, + "body": [ + { + "type": "VariableDeclaration", + "kind": "const", + "declarations": [ + { + "type": "VariableDeclarator", + "id": { + "type": "ObjectPattern", + "properties": [ + { + "type": "Property", + "kind": "init", + "computed": false, + "key": { + "type": "Identifier", + "name": "p", + "range": [ + 18, + 19 + ], + "loc": { + "start": { + "line": 2, + "column": 9 + }, + "end": { + "line": 2, + "column": 10 + } + } + }, + "method": false, + "shorthand": true, + "value": { + "type": "Identifier", + "name": "p", + "range": [ + 18, + 19 + ], + "loc": { + "start": { + "line": 2, + "column": 9 + }, + "end": { + "line": 2, + "column": 10 + } + } + }, + "range": [ + 18, + 19 + ], + "loc": { + "start": { + "line": 2, + "column": 9 + }, + "end": { + "line": 2, + "column": 10 + } + } + } + ], + "range": [ + 16, + 21 + ], + "loc": { + "start": { + "line": 2, + "column": 7 + }, + "end": { + "line": 2, + "column": 12 + } + } + }, + "init": { + "type": "CallExpression", + "arguments": [], + "callee": { + "type": "Identifier", + "name": "$props", + "range": [ + 24, + 30 + ], + "loc": { + "start": { + "line": 2, + "column": 15 + }, + "end": { + "line": 2, + "column": 21 + } + } + }, + "optional": false, + "range": [ + 24, + 32 + ], + "loc": { + "start": { + "line": 2, + "column": 15 + }, + "end": { + "line": 2, + "column": 23 + } + } + }, + "range": [ + 16, + 32 + ], + "loc": { + "start": { + "line": 2, + "column": 7 + }, + "end": { + "line": 2, + "column": 23 + } + } + } + ], + "range": [ + 10, + 33 + ], + "loc": { + "start": { + "line": 2, + "column": 1 + }, + "end": { + "line": 2, + "column": 24 + } + } + } + ], + "endTag": { + "type": "SvelteEndTag", + "range": [ + 34, + 43 + ], + "loc": { + "start": { + "line": 3, + "column": 0 + }, + "end": { + "line": 3, + "column": 9 + } + } + }, + "range": [ + 0, + 43 + ], + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 3, + "column": 9 + } + } + }, + { + "type": "SvelteText", + "value": "\n\n", + "range": [ + 43, + 45 + ], + "loc": { + "start": { + "line": 3, + "column": 9 + }, + "end": { + "line": 5, + "column": 0 + } + } + }, + { + "type": "SvelteElement", + "kind": "html", + "name": { + "type": "SvelteName", + "name": "span", + "range": [ + 46, + 50 + ], + "loc": { + "start": { + "line": 5, + "column": 1 + }, + "end": { + "line": 5, + "column": 5 + } + } + }, + "startTag": { + "type": "SvelteStartTag", + "attributes": [], + "selfClosing": false, + "range": [ + 45, + 51 + ], + "loc": { + "start": { + "line": 5, + "column": 0 + }, + "end": { + "line": 5, + "column": 6 + } + } + }, + "children": [ + { + "type": "SvelteMustacheTag", + "kind": "text", + "expression": { + "type": "Identifier", + "name": "p", + "range": [ + 52, + 53 + ], + "loc": { + "start": { + "line": 5, + "column": 7 + }, + "end": { + "line": 5, + "column": 8 + } + } + }, + "range": [ + 51, + 54 + ], + "loc": { + "start": { + "line": 5, + "column": 6 + }, + "end": { + "line": 5, + "column": 9 + } + } + } + ], + "endTag": { + "type": "SvelteEndTag", + "range": [ + 54, + 61 + ], + "loc": { + "start": { + "line": 5, + "column": 9 + }, + "end": { + "line": 5, + "column": 16 + } + } + }, + "range": [ + 45, + 61 + ], + "loc": { + "start": { + "line": 5, + "column": 0 + }, + "end": { + "line": 5, + "column": 16 + } + } + } + ], + "sourceType": "module", + "comments": [], + "tokens": [ + { + "type": "Punctuator", + "value": "<", + "range": [ + 0, + 1 + ], + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 1 + } + } + }, + { + "type": "HTMLIdentifier", + "value": "script", + "range": [ + 1, + 7 + ], + "loc": { + "start": { + "line": 1, + "column": 1 + }, + "end": { + "line": 1, + "column": 7 + } + } + }, + { + "type": "Punctuator", + "value": ">", + "range": [ + 7, + 8 + ], + "loc": { + "start": { + "line": 1, + "column": 7 + }, + "end": { + "line": 1, + "column": 8 + } + } + }, + { + "type": "Keyword", + "value": "const", + "range": [ + 10, + 15 + ], + "loc": { + "start": { + "line": 2, + "column": 1 + }, + "end": { + "line": 2, + "column": 6 + } + } + }, + { + "type": "Punctuator", + "value": "{", + "range": [ + 16, + 17 + ], + "loc": { + "start": { + "line": 2, + "column": 7 + }, + "end": { + "line": 2, + "column": 8 + } + } + }, + { + "type": "Identifier", + "value": "p", + "range": [ + 18, + 19 + ], + "loc": { + "start": { + "line": 2, + "column": 9 + }, + "end": { + "line": 2, + "column": 10 + } + } + }, + { + "type": "Punctuator", + "value": "}", + "range": [ + 20, + 21 + ], + "loc": { + "start": { + "line": 2, + "column": 11 + }, + "end": { + "line": 2, + "column": 12 + } + } + }, + { + "type": "Punctuator", + "value": "=", + "range": [ + 22, + 23 + ], + "loc": { + "start": { + "line": 2, + "column": 13 + }, + "end": { + "line": 2, + "column": 14 + } + } + }, + { + "type": "Identifier", + "value": "$props", + "range": [ + 24, + 30 + ], + "loc": { + "start": { + "line": 2, + "column": 15 + }, + "end": { + "line": 2, + "column": 21 + } + } + }, + { + "type": "Punctuator", + "value": "(", + "range": [ + 30, + 31 + ], + "loc": { + "start": { + "line": 2, + "column": 21 + }, + "end": { + "line": 2, + "column": 22 + } + } + }, + { + "type": "Punctuator", + "value": ")", + "range": [ + 31, + 32 + ], + "loc": { + "start": { + "line": 2, + "column": 22 + }, + "end": { + "line": 2, + "column": 23 + } + } + }, + { + "type": "Punctuator", + "value": ";", + "range": [ + 32, + 33 + ], + "loc": { + "start": { + "line": 2, + "column": 23 + }, + "end": { + "line": 2, + "column": 24 + } + } + }, + { + "type": "Punctuator", + "value": "<", + "range": [ + 34, + 35 + ], + "loc": { + "start": { + "line": 3, + "column": 0 + }, + "end": { + "line": 3, + "column": 1 + } + } + }, + { + "type": "Punctuator", + "value": "/", + "range": [ + 35, + 36 + ], + "loc": { + "start": { + "line": 3, + "column": 1 + }, + "end": { + "line": 3, + "column": 2 + } + } + }, + { + "type": "HTMLIdentifier", + "value": "script", + "range": [ + 36, + 42 + ], + "loc": { + "start": { + "line": 3, + "column": 2 + }, + "end": { + "line": 3, + "column": 8 + } + } + }, + { + "type": "Punctuator", + "value": ">", + "range": [ + 42, + 43 + ], + "loc": { + "start": { + "line": 3, + "column": 8 + }, + "end": { + "line": 3, + "column": 9 + } + } + }, + { + "type": "HTMLText", + "value": "\n\n", + "range": [ + 43, + 45 + ], + "loc": { + "start": { + "line": 3, + "column": 9 + }, + "end": { + "line": 5, + "column": 0 + } + } + }, + { + "type": "Punctuator", + "value": "<", + "range": [ + 45, + 46 + ], + "loc": { + "start": { + "line": 5, + "column": 0 + }, + "end": { + "line": 5, + "column": 1 + } + } + }, + { + "type": "HTMLIdentifier", + "value": "span", + "range": [ + 46, + 50 + ], + "loc": { + "start": { + "line": 5, + "column": 1 + }, + "end": { + "line": 5, + "column": 5 + } + } + }, + { + "type": "Punctuator", + "value": ">", + "range": [ + 50, + 51 + ], + "loc": { + "start": { + "line": 5, + "column": 5 + }, + "end": { + "line": 5, + "column": 6 + } + } + }, + { + "type": "Punctuator", + "value": "{", + "range": [ + 51, + 52 + ], + "loc": { + "start": { + "line": 5, + "column": 6 + }, + "end": { + "line": 5, + "column": 7 + } + } + }, + { + "type": "Identifier", + "value": "p", + "range": [ + 52, + 53 + ], + "loc": { + "start": { + "line": 5, + "column": 7 + }, + "end": { + "line": 5, + "column": 8 + } + } + }, + { + "type": "Punctuator", + "value": "}", + "range": [ + 53, + 54 + ], + "loc": { + "start": { + "line": 5, + "column": 8 + }, + "end": { + "line": 5, + "column": 9 + } + } + }, + { + "type": "Punctuator", + "value": "<", + "range": [ + 54, + 55 + ], + "loc": { + "start": { + "line": 5, + "column": 9 + }, + "end": { + "line": 5, + "column": 10 + } + } + }, + { + "type": "Punctuator", + "value": "/", + "range": [ + 55, + 56 + ], + "loc": { + "start": { + "line": 5, + "column": 10 + }, + "end": { + "line": 5, + "column": 11 + } + } + }, + { + "type": "HTMLIdentifier", + "value": "span", + "range": [ + 56, + 60 + ], + "loc": { + "start": { + "line": 5, + "column": 11 + }, + "end": { + "line": 5, + "column": 15 + } + } + }, + { + "type": "Punctuator", + "value": ">", + "range": [ + 60, + 61 + ], + "loc": { + "start": { + "line": 5, + "column": 15 + }, + "end": { + "line": 5, + "column": 16 + } + } + } + ], + "range": [ + 0, + 62 + ], + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 6, + "column": 0 + } + } +} \ No newline at end of file diff --git a/tests/fixtures/parser/ast/svelte5/$props-without-runes01-scope-output.json b/tests/fixtures/parser/ast/svelte5/$props-without-runes01-scope-output.json new file mode 100644 index 00000000..3adbd737 --- /dev/null +++ b/tests/fixtures/parser/ast/svelte5/$props-without-runes01-scope-output.json @@ -0,0 +1,445 @@ +{ + "type": "global", + "variables": [ + { + "name": "$$slots", + "identifiers": [], + "defs": [], + "references": [] + }, + { + "name": "$$props", + "identifiers": [], + "defs": [], + "references": [] + }, + { + "name": "$$restProps", + "identifiers": [], + "defs": [], + "references": [] + } + ], + "references": [], + "childScopes": [ + { + "type": "module", + "variables": [ + { + "name": "p", + "identifiers": [ + { + "type": "Identifier", + "name": "p", + "range": [ + 18, + 19 + ], + "loc": { + "start": { + "line": 2, + "column": 9 + }, + "end": { + "line": 2, + "column": 10 + } + } + } + ], + "defs": [ + { + "type": "Variable", + "name": { + "type": "Identifier", + "name": "p", + "range": [ + 18, + 19 + ], + "loc": { + "start": { + "line": 2, + "column": 9 + }, + "end": { + "line": 2, + "column": 10 + } + } + }, + "node": { + "type": "VariableDeclarator", + "id": { + "type": "ObjectPattern", + "properties": [ + { + "type": "Property", + "kind": "init", + "computed": false, + "key": { + "type": "Identifier", + "name": "p", + "range": [ + 18, + 19 + ], + "loc": { + "start": { + "line": 2, + "column": 9 + }, + "end": { + "line": 2, + "column": 10 + } + } + }, + "method": false, + "shorthand": true, + "value": { + "type": "Identifier", + "name": "p", + "range": [ + 18, + 19 + ], + "loc": { + "start": { + "line": 2, + "column": 9 + }, + "end": { + "line": 2, + "column": 10 + } + } + }, + "range": [ + 18, + 19 + ], + "loc": { + "start": { + "line": 2, + "column": 9 + }, + "end": { + "line": 2, + "column": 10 + } + } + } + ], + "range": [ + 16, + 21 + ], + "loc": { + "start": { + "line": 2, + "column": 7 + }, + "end": { + "line": 2, + "column": 12 + } + } + }, + "init": { + "type": "CallExpression", + "arguments": [], + "callee": { + "type": "Identifier", + "name": "$props", + "range": [ + 24, + 30 + ], + "loc": { + "start": { + "line": 2, + "column": 15 + }, + "end": { + "line": 2, + "column": 21 + } + } + }, + "optional": false, + "range": [ + 24, + 32 + ], + "loc": { + "start": { + "line": 2, + "column": 15 + }, + "end": { + "line": 2, + "column": 23 + } + } + }, + "range": [ + 16, + 32 + ], + "loc": { + "start": { + "line": 2, + "column": 7 + }, + "end": { + "line": 2, + "column": 23 + } + } + } + } + ], + "references": [ + { + "identifier": { + "type": "Identifier", + "name": "p", + "range": [ + 18, + 19 + ], + "loc": { + "start": { + "line": 2, + "column": 9 + }, + "end": { + "line": 2, + "column": 10 + } + } + }, + "from": "module", + "init": true, + "resolved": { + "type": "Identifier", + "name": "p", + "range": [ + 18, + 19 + ], + "loc": { + "start": { + "line": 2, + "column": 9 + }, + "end": { + "line": 2, + "column": 10 + } + } + } + }, + { + "identifier": { + "type": "Identifier", + "name": "p", + "range": [ + 52, + 53 + ], + "loc": { + "start": { + "line": 5, + "column": 7 + }, + "end": { + "line": 5, + "column": 8 + } + } + }, + "from": "module", + "init": null, + "resolved": { + "type": "Identifier", + "name": "p", + "range": [ + 18, + 19 + ], + "loc": { + "start": { + "line": 2, + "column": 9 + }, + "end": { + "line": 2, + "column": 10 + } + } + } + } + ] + } + ], + "references": [ + { + "identifier": { + "type": "Identifier", + "name": "p", + "range": [ + 18, + 19 + ], + "loc": { + "start": { + "line": 2, + "column": 9 + }, + "end": { + "line": 2, + "column": 10 + } + } + }, + "from": "module", + "init": true, + "resolved": { + "type": "Identifier", + "name": "p", + "range": [ + 18, + 19 + ], + "loc": { + "start": { + "line": 2, + "column": 9 + }, + "end": { + "line": 2, + "column": 10 + } + } + } + }, + { + "identifier": { + "type": "Identifier", + "name": "$props", + "range": [ + 24, + 30 + ], + "loc": { + "start": { + "line": 2, + "column": 15 + }, + "end": { + "line": 2, + "column": 21 + } + } + }, + "from": "module", + "init": null, + "resolved": null + }, + { + "identifier": { + "type": "Identifier", + "name": "p", + "range": [ + 52, + 53 + ], + "loc": { + "start": { + "line": 5, + "column": 7 + }, + "end": { + "line": 5, + "column": 8 + } + } + }, + "from": "module", + "init": null, + "resolved": { + "type": "Identifier", + "name": "p", + "range": [ + 18, + 19 + ], + "loc": { + "start": { + "line": 2, + "column": 9 + }, + "end": { + "line": 2, + "column": 10 + } + } + } + } + ], + "childScopes": [], + "through": [ + { + "identifier": { + "type": "Identifier", + "name": "$props", + "range": [ + 24, + 30 + ], + "loc": { + "start": { + "line": 2, + "column": 15 + }, + "end": { + "line": 2, + "column": 21 + } + } + }, + "from": "module", + "init": null, + "resolved": null + } + ] + } + ], + "through": [ + { + "identifier": { + "type": "Identifier", + "name": "$props", + "range": [ + 24, + 30 + ], + "loc": { + "start": { + "line": 2, + "column": 15 + }, + "end": { + "line": 2, + "column": 21 + } + } + }, + "from": "module", + "init": null, + "resolved": null + } + ] +} \ No newline at end of file diff --git a/tests/fixtures/parser/ast/svelte5/$props-without-runes02-config.json b/tests/fixtures/parser/ast/svelte5/$props-without-runes02-config.json new file mode 100644 index 00000000..2a0441e5 --- /dev/null +++ b/tests/fixtures/parser/ast/svelte5/$props-without-runes02-config.json @@ -0,0 +1,5 @@ +{ + "svelteConfig": { + "runes": false + } +} diff --git a/tests/fixtures/parser/ast/svelte5/$props-without-runes02-input.svelte b/tests/fixtures/parser/ast/svelte5/$props-without-runes02-input.svelte new file mode 100644 index 00000000..60106664 --- /dev/null +++ b/tests/fixtures/parser/ast/svelte5/$props-without-runes02-input.svelte @@ -0,0 +1,5 @@ + + +{p} diff --git a/tests/fixtures/parser/ast/svelte5/$props-without-runes02-no-undef-result.json b/tests/fixtures/parser/ast/svelte5/$props-without-runes02-no-undef-result.json new file mode 100644 index 00000000..5cb69474 --- /dev/null +++ b/tests/fixtures/parser/ast/svelte5/$props-without-runes02-no-undef-result.json @@ -0,0 +1,8 @@ +[ + { + "ruleId": "no-undef", + "code": "$props", + "line": 2, + "column": 16 + } +] \ No newline at end of file diff --git a/tests/fixtures/parser/ast/svelte5/$props-without-runes02-output.json b/tests/fixtures/parser/ast/svelte5/$props-without-runes02-output.json new file mode 100644 index 00000000..0ccd50d3 --- /dev/null +++ b/tests/fixtures/parser/ast/svelte5/$props-without-runes02-output.json @@ -0,0 +1,860 @@ +{ + "type": "Program", + "body": [ + { + "type": "SvelteScriptElement", + "name": { + "type": "SvelteName", + "name": "script", + "range": [ + 1, + 7 + ], + "loc": { + "start": { + "line": 1, + "column": 1 + }, + "end": { + "line": 1, + "column": 7 + } + } + }, + "startTag": { + "type": "SvelteStartTag", + "attributes": [], + "selfClosing": false, + "range": [ + 0, + 8 + ], + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 8 + } + } + }, + "body": [ + { + "type": "VariableDeclaration", + "kind": "const", + "declarations": [ + { + "type": "VariableDeclarator", + "id": { + "type": "ObjectPattern", + "properties": [ + { + "type": "Property", + "kind": "init", + "computed": false, + "key": { + "type": "Identifier", + "name": "p", + "range": [ + 18, + 19 + ], + "loc": { + "start": { + "line": 2, + "column": 9 + }, + "end": { + "line": 2, + "column": 10 + } + } + }, + "method": false, + "shorthand": true, + "value": { + "type": "Identifier", + "name": "p", + "range": [ + 18, + 19 + ], + "loc": { + "start": { + "line": 2, + "column": 9 + }, + "end": { + "line": 2, + "column": 10 + } + } + }, + "range": [ + 18, + 19 + ], + "loc": { + "start": { + "line": 2, + "column": 9 + }, + "end": { + "line": 2, + "column": 10 + } + } + } + ], + "range": [ + 16, + 21 + ], + "loc": { + "start": { + "line": 2, + "column": 7 + }, + "end": { + "line": 2, + "column": 12 + } + } + }, + "init": { + "type": "CallExpression", + "arguments": [], + "callee": { + "type": "Identifier", + "name": "$props", + "range": [ + 24, + 30 + ], + "loc": { + "start": { + "line": 2, + "column": 15 + }, + "end": { + "line": 2, + "column": 21 + } + } + }, + "optional": false, + "range": [ + 24, + 32 + ], + "loc": { + "start": { + "line": 2, + "column": 15 + }, + "end": { + "line": 2, + "column": 23 + } + } + }, + "range": [ + 16, + 32 + ], + "loc": { + "start": { + "line": 2, + "column": 7 + }, + "end": { + "line": 2, + "column": 23 + } + } + } + ], + "range": [ + 10, + 33 + ], + "loc": { + "start": { + "line": 2, + "column": 1 + }, + "end": { + "line": 2, + "column": 24 + } + } + } + ], + "endTag": { + "type": "SvelteEndTag", + "range": [ + 34, + 43 + ], + "loc": { + "start": { + "line": 3, + "column": 0 + }, + "end": { + "line": 3, + "column": 9 + } + } + }, + "range": [ + 0, + 43 + ], + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 3, + "column": 9 + } + } + }, + { + "type": "SvelteText", + "value": "\n\n", + "range": [ + 43, + 45 + ], + "loc": { + "start": { + "line": 3, + "column": 9 + }, + "end": { + "line": 5, + "column": 0 + } + } + }, + { + "type": "SvelteElement", + "kind": "html", + "name": { + "type": "SvelteName", + "name": "span", + "range": [ + 46, + 50 + ], + "loc": { + "start": { + "line": 5, + "column": 1 + }, + "end": { + "line": 5, + "column": 5 + } + } + }, + "startTag": { + "type": "SvelteStartTag", + "attributes": [], + "selfClosing": false, + "range": [ + 45, + 51 + ], + "loc": { + "start": { + "line": 5, + "column": 0 + }, + "end": { + "line": 5, + "column": 6 + } + } + }, + "children": [ + { + "type": "SvelteMustacheTag", + "kind": "text", + "expression": { + "type": "Identifier", + "name": "p", + "range": [ + 52, + 53 + ], + "loc": { + "start": { + "line": 5, + "column": 7 + }, + "end": { + "line": 5, + "column": 8 + } + } + }, + "range": [ + 51, + 54 + ], + "loc": { + "start": { + "line": 5, + "column": 6 + }, + "end": { + "line": 5, + "column": 9 + } + } + } + ], + "endTag": { + "type": "SvelteEndTag", + "range": [ + 54, + 61 + ], + "loc": { + "start": { + "line": 5, + "column": 9 + }, + "end": { + "line": 5, + "column": 16 + } + } + }, + "range": [ + 45, + 61 + ], + "loc": { + "start": { + "line": 5, + "column": 0 + }, + "end": { + "line": 5, + "column": 16 + } + } + } + ], + "sourceType": "module", + "comments": [], + "tokens": [ + { + "type": "Punctuator", + "value": "<", + "range": [ + 0, + 1 + ], + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 1 + } + } + }, + { + "type": "HTMLIdentifier", + "value": "script", + "range": [ + 1, + 7 + ], + "loc": { + "start": { + "line": 1, + "column": 1 + }, + "end": { + "line": 1, + "column": 7 + } + } + }, + { + "type": "Punctuator", + "value": ">", + "range": [ + 7, + 8 + ], + "loc": { + "start": { + "line": 1, + "column": 7 + }, + "end": { + "line": 1, + "column": 8 + } + } + }, + { + "type": "Keyword", + "value": "const", + "range": [ + 10, + 15 + ], + "loc": { + "start": { + "line": 2, + "column": 1 + }, + "end": { + "line": 2, + "column": 6 + } + } + }, + { + "type": "Punctuator", + "value": "{", + "range": [ + 16, + 17 + ], + "loc": { + "start": { + "line": 2, + "column": 7 + }, + "end": { + "line": 2, + "column": 8 + } + } + }, + { + "type": "Identifier", + "value": "p", + "range": [ + 18, + 19 + ], + "loc": { + "start": { + "line": 2, + "column": 9 + }, + "end": { + "line": 2, + "column": 10 + } + } + }, + { + "type": "Punctuator", + "value": "}", + "range": [ + 20, + 21 + ], + "loc": { + "start": { + "line": 2, + "column": 11 + }, + "end": { + "line": 2, + "column": 12 + } + } + }, + { + "type": "Punctuator", + "value": "=", + "range": [ + 22, + 23 + ], + "loc": { + "start": { + "line": 2, + "column": 13 + }, + "end": { + "line": 2, + "column": 14 + } + } + }, + { + "type": "Identifier", + "value": "$props", + "range": [ + 24, + 30 + ], + "loc": { + "start": { + "line": 2, + "column": 15 + }, + "end": { + "line": 2, + "column": 21 + } + } + }, + { + "type": "Punctuator", + "value": "(", + "range": [ + 30, + 31 + ], + "loc": { + "start": { + "line": 2, + "column": 21 + }, + "end": { + "line": 2, + "column": 22 + } + } + }, + { + "type": "Punctuator", + "value": ")", + "range": [ + 31, + 32 + ], + "loc": { + "start": { + "line": 2, + "column": 22 + }, + "end": { + "line": 2, + "column": 23 + } + } + }, + { + "type": "Punctuator", + "value": ";", + "range": [ + 32, + 33 + ], + "loc": { + "start": { + "line": 2, + "column": 23 + }, + "end": { + "line": 2, + "column": 24 + } + } + }, + { + "type": "Punctuator", + "value": "<", + "range": [ + 34, + 35 + ], + "loc": { + "start": { + "line": 3, + "column": 0 + }, + "end": { + "line": 3, + "column": 1 + } + } + }, + { + "type": "Punctuator", + "value": "/", + "range": [ + 35, + 36 + ], + "loc": { + "start": { + "line": 3, + "column": 1 + }, + "end": { + "line": 3, + "column": 2 + } + } + }, + { + "type": "HTMLIdentifier", + "value": "script", + "range": [ + 36, + 42 + ], + "loc": { + "start": { + "line": 3, + "column": 2 + }, + "end": { + "line": 3, + "column": 8 + } + } + }, + { + "type": "Punctuator", + "value": ">", + "range": [ + 42, + 43 + ], + "loc": { + "start": { + "line": 3, + "column": 8 + }, + "end": { + "line": 3, + "column": 9 + } + } + }, + { + "type": "HTMLText", + "value": "\n\n", + "range": [ + 43, + 45 + ], + "loc": { + "start": { + "line": 3, + "column": 9 + }, + "end": { + "line": 5, + "column": 0 + } + } + }, + { + "type": "Punctuator", + "value": "<", + "range": [ + 45, + 46 + ], + "loc": { + "start": { + "line": 5, + "column": 0 + }, + "end": { + "line": 5, + "column": 1 + } + } + }, + { + "type": "HTMLIdentifier", + "value": "span", + "range": [ + 46, + 50 + ], + "loc": { + "start": { + "line": 5, + "column": 1 + }, + "end": { + "line": 5, + "column": 5 + } + } + }, + { + "type": "Punctuator", + "value": ">", + "range": [ + 50, + 51 + ], + "loc": { + "start": { + "line": 5, + "column": 5 + }, + "end": { + "line": 5, + "column": 6 + } + } + }, + { + "type": "Punctuator", + "value": "{", + "range": [ + 51, + 52 + ], + "loc": { + "start": { + "line": 5, + "column": 6 + }, + "end": { + "line": 5, + "column": 7 + } + } + }, + { + "type": "Identifier", + "value": "p", + "range": [ + 52, + 53 + ], + "loc": { + "start": { + "line": 5, + "column": 7 + }, + "end": { + "line": 5, + "column": 8 + } + } + }, + { + "type": "Punctuator", + "value": "}", + "range": [ + 53, + 54 + ], + "loc": { + "start": { + "line": 5, + "column": 8 + }, + "end": { + "line": 5, + "column": 9 + } + } + }, + { + "type": "Punctuator", + "value": "<", + "range": [ + 54, + 55 + ], + "loc": { + "start": { + "line": 5, + "column": 9 + }, + "end": { + "line": 5, + "column": 10 + } + } + }, + { + "type": "Punctuator", + "value": "/", + "range": [ + 55, + 56 + ], + "loc": { + "start": { + "line": 5, + "column": 10 + }, + "end": { + "line": 5, + "column": 11 + } + } + }, + { + "type": "HTMLIdentifier", + "value": "span", + "range": [ + 56, + 60 + ], + "loc": { + "start": { + "line": 5, + "column": 11 + }, + "end": { + "line": 5, + "column": 15 + } + } + }, + { + "type": "Punctuator", + "value": ">", + "range": [ + 60, + 61 + ], + "loc": { + "start": { + "line": 5, + "column": 15 + }, + "end": { + "line": 5, + "column": 16 + } + } + } + ], + "range": [ + 0, + 62 + ], + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 6, + "column": 0 + } + } +} \ No newline at end of file diff --git a/tests/fixtures/parser/ast/svelte5/$props-without-runes02-scope-output.json b/tests/fixtures/parser/ast/svelte5/$props-without-runes02-scope-output.json new file mode 100644 index 00000000..3adbd737 --- /dev/null +++ b/tests/fixtures/parser/ast/svelte5/$props-without-runes02-scope-output.json @@ -0,0 +1,445 @@ +{ + "type": "global", + "variables": [ + { + "name": "$$slots", + "identifiers": [], + "defs": [], + "references": [] + }, + { + "name": "$$props", + "identifiers": [], + "defs": [], + "references": [] + }, + { + "name": "$$restProps", + "identifiers": [], + "defs": [], + "references": [] + } + ], + "references": [], + "childScopes": [ + { + "type": "module", + "variables": [ + { + "name": "p", + "identifiers": [ + { + "type": "Identifier", + "name": "p", + "range": [ + 18, + 19 + ], + "loc": { + "start": { + "line": 2, + "column": 9 + }, + "end": { + "line": 2, + "column": 10 + } + } + } + ], + "defs": [ + { + "type": "Variable", + "name": { + "type": "Identifier", + "name": "p", + "range": [ + 18, + 19 + ], + "loc": { + "start": { + "line": 2, + "column": 9 + }, + "end": { + "line": 2, + "column": 10 + } + } + }, + "node": { + "type": "VariableDeclarator", + "id": { + "type": "ObjectPattern", + "properties": [ + { + "type": "Property", + "kind": "init", + "computed": false, + "key": { + "type": "Identifier", + "name": "p", + "range": [ + 18, + 19 + ], + "loc": { + "start": { + "line": 2, + "column": 9 + }, + "end": { + "line": 2, + "column": 10 + } + } + }, + "method": false, + "shorthand": true, + "value": { + "type": "Identifier", + "name": "p", + "range": [ + 18, + 19 + ], + "loc": { + "start": { + "line": 2, + "column": 9 + }, + "end": { + "line": 2, + "column": 10 + } + } + }, + "range": [ + 18, + 19 + ], + "loc": { + "start": { + "line": 2, + "column": 9 + }, + "end": { + "line": 2, + "column": 10 + } + } + } + ], + "range": [ + 16, + 21 + ], + "loc": { + "start": { + "line": 2, + "column": 7 + }, + "end": { + "line": 2, + "column": 12 + } + } + }, + "init": { + "type": "CallExpression", + "arguments": [], + "callee": { + "type": "Identifier", + "name": "$props", + "range": [ + 24, + 30 + ], + "loc": { + "start": { + "line": 2, + "column": 15 + }, + "end": { + "line": 2, + "column": 21 + } + } + }, + "optional": false, + "range": [ + 24, + 32 + ], + "loc": { + "start": { + "line": 2, + "column": 15 + }, + "end": { + "line": 2, + "column": 23 + } + } + }, + "range": [ + 16, + 32 + ], + "loc": { + "start": { + "line": 2, + "column": 7 + }, + "end": { + "line": 2, + "column": 23 + } + } + } + } + ], + "references": [ + { + "identifier": { + "type": "Identifier", + "name": "p", + "range": [ + 18, + 19 + ], + "loc": { + "start": { + "line": 2, + "column": 9 + }, + "end": { + "line": 2, + "column": 10 + } + } + }, + "from": "module", + "init": true, + "resolved": { + "type": "Identifier", + "name": "p", + "range": [ + 18, + 19 + ], + "loc": { + "start": { + "line": 2, + "column": 9 + }, + "end": { + "line": 2, + "column": 10 + } + } + } + }, + { + "identifier": { + "type": "Identifier", + "name": "p", + "range": [ + 52, + 53 + ], + "loc": { + "start": { + "line": 5, + "column": 7 + }, + "end": { + "line": 5, + "column": 8 + } + } + }, + "from": "module", + "init": null, + "resolved": { + "type": "Identifier", + "name": "p", + "range": [ + 18, + 19 + ], + "loc": { + "start": { + "line": 2, + "column": 9 + }, + "end": { + "line": 2, + "column": 10 + } + } + } + } + ] + } + ], + "references": [ + { + "identifier": { + "type": "Identifier", + "name": "p", + "range": [ + 18, + 19 + ], + "loc": { + "start": { + "line": 2, + "column": 9 + }, + "end": { + "line": 2, + "column": 10 + } + } + }, + "from": "module", + "init": true, + "resolved": { + "type": "Identifier", + "name": "p", + "range": [ + 18, + 19 + ], + "loc": { + "start": { + "line": 2, + "column": 9 + }, + "end": { + "line": 2, + "column": 10 + } + } + } + }, + { + "identifier": { + "type": "Identifier", + "name": "$props", + "range": [ + 24, + 30 + ], + "loc": { + "start": { + "line": 2, + "column": 15 + }, + "end": { + "line": 2, + "column": 21 + } + } + }, + "from": "module", + "init": null, + "resolved": null + }, + { + "identifier": { + "type": "Identifier", + "name": "p", + "range": [ + 52, + 53 + ], + "loc": { + "start": { + "line": 5, + "column": 7 + }, + "end": { + "line": 5, + "column": 8 + } + } + }, + "from": "module", + "init": null, + "resolved": { + "type": "Identifier", + "name": "p", + "range": [ + 18, + 19 + ], + "loc": { + "start": { + "line": 2, + "column": 9 + }, + "end": { + "line": 2, + "column": 10 + } + } + } + } + ], + "childScopes": [], + "through": [ + { + "identifier": { + "type": "Identifier", + "name": "$props", + "range": [ + 24, + 30 + ], + "loc": { + "start": { + "line": 2, + "column": 15 + }, + "end": { + "line": 2, + "column": 21 + } + } + }, + "from": "module", + "init": null, + "resolved": null + } + ] + } + ], + "through": [ + { + "identifier": { + "type": "Identifier", + "name": "$props", + "range": [ + 24, + 30 + ], + "loc": { + "start": { + "line": 2, + "column": 15 + }, + "end": { + "line": 2, + "column": 21 + } + } + }, + "from": "module", + "init": null, + "resolved": null + } + ] +} \ No newline at end of file diff --git a/tests/fixtures/parser/ast/svelte5/svelte-options02-input.svelte b/tests/fixtures/parser/ast/svelte5/svelte-options02-input.svelte new file mode 100644 index 00000000..abfb3298 --- /dev/null +++ b/tests/fixtures/parser/ast/svelte5/svelte-options02-input.svelte @@ -0,0 +1 @@ + diff --git a/tests/fixtures/parser/ast/svelte5/svelte-options02-output.json b/tests/fixtures/parser/ast/svelte5/svelte-options02-output.json new file mode 100644 index 00000000..2bd30457 --- /dev/null +++ b/tests/fixtures/parser/ast/svelte5/svelte-options02-output.json @@ -0,0 +1,388 @@ +{ + "type": "Program", + "body": [ + { + "type": "SvelteElement", + "kind": "special", + "name": { + "type": "SvelteName", + "name": "svelte:options", + "range": [ + 1, + 15 + ], + "loc": { + "start": { + "line": 1, + "column": 1 + }, + "end": { + "line": 1, + "column": 15 + } + } + }, + "startTag": { + "type": "SvelteStartTag", + "attributes": [ + { + "type": "SvelteAttribute", + "key": { + "type": "SvelteName", + "name": "runes", + "range": [ + 16, + 21 + ], + "loc": { + "start": { + "line": 1, + "column": 16 + }, + "end": { + "line": 1, + "column": 21 + } + } + }, + "boolean": false, + "value": [ + { + "type": "SvelteMustacheTag", + "kind": "text", + "expression": { + "type": "Literal", + "raw": "false", + "value": false, + "range": [ + 23, + 28 + ], + "loc": { + "start": { + "line": 1, + "column": 23 + }, + "end": { + "line": 1, + "column": 28 + } + } + }, + "range": [ + 22, + 29 + ], + "loc": { + "start": { + "line": 1, + "column": 22 + }, + "end": { + "line": 1, + "column": 29 + } + } + } + ], + "range": [ + 16, + 29 + ], + "loc": { + "start": { + "line": 1, + "column": 16 + }, + "end": { + "line": 1, + "column": 29 + } + } + } + ], + "selfClosing": false, + "range": [ + 0, + 31 + ], + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 31 + } + } + }, + "children": [], + "endTag": { + "type": "SvelteEndTag", + "range": [ + 31, + 48 + ], + "loc": { + "start": { + "line": 1, + "column": 31 + }, + "end": { + "line": 1, + "column": 48 + } + } + }, + "range": [ + 0, + 48 + ], + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 48 + } + } + } + ], + "sourceType": "module", + "comments": [], + "tokens": [ + { + "type": "Punctuator", + "value": "<", + "range": [ + 0, + 1 + ], + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 1 + } + } + }, + { + "type": "HTMLIdentifier", + "value": "svelte:options", + "range": [ + 1, + 15 + ], + "loc": { + "start": { + "line": 1, + "column": 1 + }, + "end": { + "line": 1, + "column": 15 + } + } + }, + { + "type": "HTMLIdentifier", + "value": "runes", + "range": [ + 16, + 21 + ], + "loc": { + "start": { + "line": 1, + "column": 16 + }, + "end": { + "line": 1, + "column": 21 + } + } + }, + { + "type": "Punctuator", + "value": "=", + "range": [ + 21, + 22 + ], + "loc": { + "start": { + "line": 1, + "column": 21 + }, + "end": { + "line": 1, + "column": 22 + } + } + }, + { + "type": "Punctuator", + "value": "{", + "range": [ + 22, + 23 + ], + "loc": { + "start": { + "line": 1, + "column": 22 + }, + "end": { + "line": 1, + "column": 23 + } + } + }, + { + "type": "Boolean", + "value": "false", + "range": [ + 23, + 28 + ], + "loc": { + "start": { + "line": 1, + "column": 23 + }, + "end": { + "line": 1, + "column": 28 + } + } + }, + { + "type": "Punctuator", + "value": "}", + "range": [ + 28, + 29 + ], + "loc": { + "start": { + "line": 1, + "column": 28 + }, + "end": { + "line": 1, + "column": 29 + } + } + }, + { + "type": "Punctuator", + "value": ">", + "range": [ + 30, + 31 + ], + "loc": { + "start": { + "line": 1, + "column": 30 + }, + "end": { + "line": 1, + "column": 31 + } + } + }, + { + "type": "Punctuator", + "value": "<", + "range": [ + 31, + 32 + ], + "loc": { + "start": { + "line": 1, + "column": 31 + }, + "end": { + "line": 1, + "column": 32 + } + } + }, + { + "type": "Punctuator", + "value": "/", + "range": [ + 32, + 33 + ], + "loc": { + "start": { + "line": 1, + "column": 32 + }, + "end": { + "line": 1, + "column": 33 + } + } + }, + { + "type": "HTMLIdentifier", + "value": "svelte:options", + "range": [ + 33, + 47 + ], + "loc": { + "start": { + "line": 1, + "column": 33 + }, + "end": { + "line": 1, + "column": 47 + } + } + }, + { + "type": "Punctuator", + "value": ">", + "range": [ + 47, + 48 + ], + "loc": { + "start": { + "line": 1, + "column": 47 + }, + "end": { + "line": 1, + "column": 48 + } + } + } + ], + "range": [ + 0, + 49 + ], + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 2, + "column": 0 + } + } +} \ No newline at end of file diff --git a/tests/fixtures/parser/ast/svelte5/svelte-options02-scope-output.json b/tests/fixtures/parser/ast/svelte5/svelte-options02-scope-output.json new file mode 100644 index 00000000..d392ca4b --- /dev/null +++ b/tests/fixtures/parser/ast/svelte5/svelte-options02-scope-output.json @@ -0,0 +1,34 @@ +{ + "type": "global", + "variables": [ + { + "name": "$$slots", + "identifiers": [], + "defs": [], + "references": [] + }, + { + "name": "$$props", + "identifiers": [], + "defs": [], + "references": [] + }, + { + "name": "$$restProps", + "identifiers": [], + "defs": [], + "references": [] + } + ], + "references": [], + "childScopes": [ + { + "type": "module", + "variables": [], + "references": [], + "childScopes": [], + "through": [] + } + ], + "through": [] +} \ No newline at end of file diff --git a/tests/src/parser/typescript/index.ts b/tests/src/parser/typescript/index.ts index 6bfd733f..49051278 100644 --- a/tests/src/parser/typescript/index.ts +++ b/tests/src/parser/typescript/index.ts @@ -1,6 +1,7 @@ import { Context } from "../../../../src/context"; import type { NormalizedParserOptions } from "../../../../src/parser/parser-options"; import { parseScriptInSvelte } from "../../../../src/parser/script"; +import { compilerVersion } from "../../../../src/parser/svelte-version"; import { parseTemplate } from "../../../../src/parser/template"; import { parseTypeScriptInSvelte } from "../../../../src/parser/typescript"; import { generateParserOptions, listupFixtures } from "../test-utils"; @@ -49,6 +50,11 @@ describe("Check for typescript analyze result.", () => { parserOptions, { slots: new Set(), + svelteParseContext: { + runes: true, + compilerVersion, + svelteConfig: null, + }, }, ); const result = parseScriptInSvelte( diff --git a/tests/src/svelte-config/parser.ts b/tests/src/svelte-config/parser.ts new file mode 100644 index 00000000..7f97721d --- /dev/null +++ b/tests/src/svelte-config/parser.ts @@ -0,0 +1,69 @@ +import assert from "assert"; +import { parseConfig } from "../../../src/svelte-config/parser"; + +describe("parseConfig", () => { + const testCases = [ + { + code: `export default {compilerOptions:{runes:true}}`, + output: { compilerOptions: { runes: true } }, + }, + { + code: ` + const opt = {compilerOptions:{runes:true}} + export default opt + `, + output: { compilerOptions: { runes: true } }, + }, + { + code: ` + const compilerOptions = {runes:true} + export default {compilerOptions} + `, + output: { compilerOptions: { runes: true } }, + }, + { + code: ` + const kit = {files:{routes:"src/custom"}} + const compilerOptions = {runes:false} + export default {compilerOptions,kit} + `, + output: { + compilerOptions: { runes: false }, + kit: { files: { routes: "src/custom" } }, + }, + }, + { + code: ` + const opt = {compilerOptions:{runes:true}} + export default {...opt} + `, + output: { compilerOptions: { runes: true } }, + }, + { + code: ` + const key = "compilerOptions" + export default {[key]:{runes:false}} + `, + output: { compilerOptions: { runes: false } }, + }, + { + code: ` + const {compilerOptions} = {compilerOptions:{runes:true}} + export default {compilerOptions} + `, + output: { compilerOptions: { runes: true } }, + }, + { + code: ` + const {compilerOptions = {runes:true}} = {} + export default {compilerOptions} + `, + output: { compilerOptions: { runes: true } }, + }, + ]; + for (const { code, output } of testCases) { + it(code, () => { + assert.deepStrictEqual(parseConfig(code), output); + }); + } +});