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);
+ });
+ }
+});