Skip to content

Commit

Permalink
feat: makes it optional whether to parse runes. (#536)
Browse files Browse the repository at this point in the history
  • Loading branch information
ota-meshi authored Jun 17, 2024
1 parent 249deb6 commit 1a9ef3d
Show file tree
Hide file tree
Showing 30 changed files with 3,905 additions and 55 deletions.
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

0 comments on commit 1a9ef3d

Please sign in to comment.