Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: makes it optional whether to parse runes. #536

Merged
merged 6 commits into from
Jun 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/shy-cups-visit.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"svelte-eslint-parser": minor
---

feat: makes it optional whether to parse runes.
48 changes: 46 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`:

Expand All @@ -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.
Expand All @@ -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.
Expand All @@ -292,20 +329,22 @@ 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"],
languageOptions: {
parser: svelteParser,
parserOptions: {
parser: "...",
svelteConfig,
/* ... */
},
},
Expand All @@ -315,6 +354,7 @@ export default [
languageOptions: {
parser: svelteParser,
parserOptions: {
svelteConfig,
/* ... */
},
},
Expand All @@ -325,6 +365,7 @@ export default [
parser: svelteParser,
parserOptions: {
parser: "...(ts parser)...",
svelteConfig,
/* ... */
},
},
Expand All @@ -342,13 +383,15 @@ For example in `.eslintrc.*`:
"parser": "svelte-eslint-parser",
"parserOptions": {
"parser": "...",
"svelteFeatures": { "runes": true },
/* ... */
},
},
{
"files": ["*.svelte.js"],
"parser": "svelte-eslint-parser",
"parserOptions": {
"svelteFeatures": { "runes": true },
/* ... */
},
},
Expand All @@ -357,6 +400,7 @@ For example in `.eslintrc.*`:
"parser": "svelte-eslint-parser",
"parserOptions": {
"parser": "...(ts parser)...",
"svelteFeatures": { "runes": true },
/* ... */
},
},
Expand Down
11 changes: 6 additions & 5 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 };

Expand Down
38 changes: 21 additions & 17 deletions src/parser/analyze-scope.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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);
}
}
}
}
Expand Down
30 changes: 21 additions & 9 deletions src/parser/globals.ts
Original file line number Diff line number Diff line change
@@ -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",
Expand All @@ -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 [];
}
Loading
Loading