Skip to content

Commit

Permalink
feat: add support for flat config (#324)
Browse files Browse the repository at this point in the history
* feat: add support for flat config

* Create silly-balloons-double.md

* fix

* fix
  • Loading branch information
ota-meshi committed Mar 20, 2024
1 parent c98c0ce commit 64abbb1
Show file tree
Hide file tree
Showing 17 changed files with 481 additions and 65 deletions.
5 changes: 5 additions & 0 deletions .changeset/silly-balloons-double.md
@@ -0,0 +1,5 @@
---
"eslint-plugin-astro": minor
---

feat: add support for flat config
9 changes: 9 additions & 0 deletions .eslintrc.js
Expand Up @@ -191,5 +191,14 @@ module.exports = {
"no-console": "off",
},
},
{
files: ["*.md/**", "**/*.md/**"],
parserOptions: {
sourceType: "module",
},
rules: {
"n/no-missing-import": "off",
},
},
],
}
36 changes: 35 additions & 1 deletion README.md
Expand Up @@ -80,7 +80,41 @@ npm install --save-dev eslint-plugin-jsx-a11y

### Configuration

Use `.eslintrc.*` file to configure rules. See also: [https://eslint.org/docs/user-guide/configuring](https://eslint.org/docs/user-guide/configuring).
#### New \(ESLint\>=v9\) Config \(Flat Config\)

Use `eslint.config.js` file to configure rules. See also: [https://eslint.org/docs/latest/use/configure/configuration-files-new](https://eslint.org/docs/latest/use/configure/configuration-files-new).

Example **eslint.config.js**:

```js
import eslintPluginAstro from 'eslint-plugin-astro';
export default [
// add more generic rule sets here, such as:
// js.configs.recommended,
...eslintPluginAstro.configs['flat/recommended'],
{
rules: {
// override/add rules settings here, such as:
// "astro/no-set-html-directive": "error"
}
}
];
```

This plugin provides configs:

- `*.configs['flat/base']` ... Minimal configuration to enable correct Astro component linting.
- `*.configs['flat/recommended']` ... Above, plus rules to prevent errors or unintended behavior.
- `*.configs['flat/all']` ... Configuration enables all astro rules. It's meant for testing, not for production use because it changes with every minor and major version of the plugin. Use it at your own risk.
- Extension of sharable configuration provided by [eslint-plugin-jsx-a11y]. You need to install [eslint-plugin-jsx-a11y] to use it.
- `*.configs['flat/jsx-a11y-recommended']` ... Similar to the [`"plugin:jsx-a11y/recommended"` configuration](https://github.com/jsx-eslint/eslint-plugin-jsx-a11y#rule-strictness-in-different-modes), but with the rules extended for Astro components enabled.
- `*.configs['flat/jsx-a11y-strict']` ... Similar to the [`"plugin:jsx-a11y/strict"` configuration](https://github.com/jsx-eslint/eslint-plugin-jsx-a11y#rule-strictness-in-different-modes), but with the rules extended for Astro components enabled.

See [the rule list](https://ota-meshi.github.io/eslint-plugin-astro/rules/) to get the `rules` that this plugin provides.

#### Legacy Config \(ESLint\<v9\)

Use `.eslintrc.*` file to configure rules. See also: [https://eslint.org/docs/latest/use/configure](https://eslint.org/docs/latest/use/configure).

Example **.eslintrc.js**. When using the shareable configuration provided by the plugin:

Expand Down
36 changes: 35 additions & 1 deletion docs/user-guide.md
Expand Up @@ -36,7 +36,41 @@ npm install --save-dev eslint-plugin-jsx-a11y

### Configuration

Use `.eslintrc.*` file to configure rules. See also: [https://eslint.org/docs/user-guide/configuring](https://eslint.org/docs/user-guide/configuring).
#### New \(ESLint\>=v9\) Config \(Flat Config\)

Use `eslint.config.js` file to configure rules. See also: [https://eslint.org/docs/latest/use/configure/configuration-files-new](https://eslint.org/docs/latest/use/configure/configuration-files-new).

Example **eslint.config.js**:

```js
import eslintPluginAstro from 'eslint-plugin-astro';
export default [
// add more generic rule sets here, such as:
// js.configs.recommended,
...eslintPluginAstro.configs['flat/recommended'],
{
rules: {
// override/add rules settings here, such as:
// "astro/no-set-html-directive": "error"
}
}
];
```

This plugin provides configs:

- `*.configs['flat/base']` ... Minimal configuration to enable correct Astro component linting.
- `*.configs['flat/recommended']` ... Above, plus rules to prevent errors or unintended behavior.
- `*.configs['flat/all']` ... Configuration enables all astro rules. It's meant for testing, not for production use because it changes with every minor and major version of the plugin. Use it at your own risk.
- Extension of sharable configuration provided by [eslint-plugin-jsx-a11y]. You need to install [eslint-plugin-jsx-a11y] to use it.
- `*.configs['flat/jsx-a11y-recommended']` ... Similar to the [`"plugin:jsx-a11y/recommended"` configuration](https://github.com/jsx-eslint/eslint-plugin-jsx-a11y#rule-strictness-in-different-modes), but with the rules extended for Astro components enabled.
- `*.configs['flat/jsx-a11y-strict']` ... Similar to the [`"plugin:jsx-a11y/strict"` configuration](https://github.com/jsx-eslint/eslint-plugin-jsx-a11y#rule-strictness-in-different-modes), but with the rules extended for Astro components enabled.

See [the rule list](./rules.md) to get the `rules` that this plugin provides.

#### Legacy Config \(ESLint\<v9\)

Use `.eslintrc.*` file to configure rules. See also: [https://eslint.org/docs/latest/use/configure](https://eslint.org/docs/latest/use/configure).

Example **.eslintrc.js**. When using the shareable configuration provided by the plugin:

Expand Down
3 changes: 2 additions & 1 deletion package.json
Expand Up @@ -57,7 +57,8 @@
"@jridgewell/sourcemap-codec": "^1.4.14",
"@typescript-eslint/types": "^5.25.0",
"astro-eslint-parser": "^0.16.3",
"eslint-compat-utils": "^0.4.0",
"eslint-compat-utils": "^0.5.0",
"globals": "^13.0.0",
"postcss": "^8.4.14",
"postcss-selector-parser": "^6.0.10"
},
Expand Down
24 changes: 24 additions & 0 deletions src/a11y/configs.ts
@@ -1,6 +1,7 @@
import { getPluginJsxA11y } from "./load"
import path from "path"
import { a11yConfigKeys } from "./keys"
import flatBase from "../configs/flat/base"

/** Build a11y configs */
export function buildConfigs(): Record<string, unknown> {
Expand All @@ -11,6 +12,29 @@ export function buildConfigs(): Record<string, unknown> {
const configs: Record<string, unknown> = {}

for (const configName of a11yConfigKeys) {
// flat config
Object.defineProperty(configs, `flat/jsx-a11y-${configName}`, {
enumerable: true,
get() {
const base = getPluginJsxA11y()
const baseConfig = base?.configs?.[configName] ?? {}

const baseRules = baseConfig.rules ?? {}
const newRules: Record<string, string | unknown[]> = {}
for (const ruleName of Object.keys(baseRules)) {
newRules[`astro/${ruleName}`] = baseRules[ruleName]
}

return [
...flatBase,
{
plugins: { "jsx-a11y": base },
rules: newRules,
},
]
},
})
// legacy config
Object.defineProperty(configs, `jsx-a11y-${configName}`, {
enumerable: true,
get() {
Expand Down
19 changes: 19 additions & 0 deletions src/configs/flat/all.ts
@@ -0,0 +1,19 @@
import recommended from "./recommended"
import { rules } from "../../utils/rules"

const all: Record<string, string> = {}
for (const rule of rules.filter(
(rule) => rule.meta.docs.available() && !rule.meta.deprecated,
)) {
all[rule.meta.docs.ruleId] = "error"
}

export default [
...recommended,
{
rules: {
...all,
...recommended[recommended.length - 1].rules,
},
},
]
74 changes: 74 additions & 0 deletions src/configs/flat/base.ts
@@ -0,0 +1,74 @@
// IMPORTANT!
// This file has been automatically generated,
// in order to update its content execute "npm run update"
import globals from "globals"
import type { ESLint } from "eslint"
import * as parser from "astro-eslint-parser"
import { tsESLintParser } from "../has-typescript-eslint-parser"
import { environments } from "../../environments/index"
export default [
{
plugins: {
get astro(): ESLint.Plugin {
// eslint-disable-next-line @typescript-eslint/no-require-imports -- ignore
return require("../../index")
},
},
},
{
files: ["*.astro", "**/*.astro"],
languageOptions: {
globals: {
...globals.node,
...environments.astro.globals,
},
parser,
// The script of Astro components uses ESM.
sourceType: "module",
parserOptions: {
parser: tsESLintParser ?? undefined,
extraFileExtensions: [".astro"],
},
},
rules: {
// eslint-plugin-astro rules
// Enable base rules
},
},
{
// Define the configuration for `<script>` tag.
// Script in `<script>` is assigned a virtual file name with the `.js` extension.
files: ["**/*.astro/*.js", "*.astro/*.js"],
languageOptions: {
globals: {
...globals.browser,
},
sourceType: "module",
},
rules: {
// If you are using "prettier/prettier" rule,
// you don't need to format inside <script> as it will be formatted as a `.astro` file.
"prettier/prettier": "off",
},
},
{
// Define the configuration for `<script>` tag when using `client-side-ts` processor.
// Script in `<script>` is assigned a virtual file name with the `.ts` extension.
files: ["**/*.astro/*.ts", "*.astro/*.ts"],
languageOptions: {
globals: {
...globals.browser,
},
parser: tsESLintParser ?? undefined,
sourceType: "module",
parserOptions: {
project: null,
},
},
rules: {
// If you are using "prettier/prettier" rule,
// you don't need to format inside <script> as it will be formatted as a `.astro` file.
"prettier/prettier": "off",
},
},
]
19 changes: 19 additions & 0 deletions src/configs/flat/recommended.ts
@@ -0,0 +1,19 @@
// IMPORTANT!
// This file has been automatically generated,
// in order to update its content execute "npm run update"
import base from "./base"
export default [
...base,
{
rules: {
// eslint-plugin-astro rules
"astro/no-conflict-set-directives": "error",
"astro/no-deprecated-astro-canonicalurl": "error",
"astro/no-deprecated-astro-fetchcontent": "error",
"astro/no-deprecated-astro-resolve": "error",
"astro/no-deprecated-getentrybyslug": "error",
"astro/no-unused-define-vars-in-style": "error",
"astro/valid-compile": "error",
},
},
]
4 changes: 3 additions & 1 deletion src/configs/has-typescript-eslint-parser.ts
@@ -1,12 +1,14 @@
import { createRequire } from "module"
import path from "path"
import type tsParser from "@typescript-eslint/parser"

export let hasTypescriptEslintParser = false
export let tsESLintParser: typeof tsParser | null = null

try {
const cwd = process.cwd()
const relativeTo = path.join(cwd, "__placeholder__.js")
if (createRequire(relativeTo)("@typescript-eslint/parser"))
if ((tsESLintParser = createRequire(relativeTo)("@typescript-eslint/parser")))
hasTypescriptEslintParser = true
} catch {
// noop
Expand Down
7 changes: 6 additions & 1 deletion src/index.ts
Expand Up @@ -5,14 +5,19 @@ import { environments } from "./environments"
import base from "./configs/base"
import recommended from "./configs/recommended"
import all from "./configs/all"
import flatBase from "./configs/flat/base"
import flatRecommended from "./configs/flat/recommended"
import flatAll from "./configs/flat/all"
import { buildA11yConfigs } from "./a11y"
import * as meta from "./meta"

const configs = {
base,
recommended,
all,
...buildA11yConfigs(),
"flat/base": flatBase,
"flat/recommended": flatRecommended,
"flat/all": flatAll,
}

const a11yConfigs = buildA11yConfigs()
Expand Down
92 changes: 92 additions & 0 deletions tests/src/config/recommended.ts
@@ -0,0 +1,92 @@
import assert from "assert"
import plugin from "../../../src/index"
import { LegacyESLint, FlatESLint } from "../../utils/eslint-compat"

const code = `---
const foo = 42
---
<style define:vars={{ foregroundColor, backgroundColor }}>
h1 {
background-color: var(--backgroundColor);
color: var(--foreground);
}
</style>
`
describe("`recommended` config", () => {
it("legacy `recommended` config should work. ", async () => {
const linter = new LegacyESLint({
plugins: {
toml: plugin as never,
},
baseConfig: {
parserOptions: {
ecmaVersion: 2020,
},
extends: ["plugin:astro/recommended"],
},
useEslintrc: false,
})
const result = await linter.lintText(code, { filePath: "test.astro" })
const messages = result[0].messages

assert.deepStrictEqual(
messages.map((m) => ({
ruleId: m.ruleId,
line: m.line,
message: m.message,
})),
[
{
line: 4,
message: "'foregroundColor' is defined but never used.",
ruleId: "astro/no-unused-define-vars-in-style",
},
],
)
})
it("`flat/recommended` config should work. ", async () => {
const linter = new FlatESLint({
// @ts-expect-error -- typing bug
overrideConfigFile: true,
// @ts-expect-error -- typing bug
overrideConfig: [...plugin.configs["flat/recommended"]],
})
const result = await linter.lintText(code, { filePath: "test.astro" })
const messages = result[0].messages

assert.deepStrictEqual(
messages.map((m) => ({
ruleId: m.ruleId,
line: m.line,
message: m.message,
})),
[
{
line: 4,
message: "'foregroundColor' is defined but never used.",
ruleId: "astro/no-unused-define-vars-in-style",
},
],
)
})
it("`flat/recommended` config with *.js should work. ", async () => {
const linter = new FlatESLint({
// @ts-expect-error -- typing bug
overrideConfigFile: true,
// @ts-expect-error -- typing bug
overrideConfig: [...plugin.configs["flat/recommended"]],
})

const result = await linter.lintText(";", { filePath: "test.js" })
const messages = result[0].messages

assert.deepStrictEqual(
messages.map((m) => ({
ruleId: m.ruleId,
line: m.line,
message: m.message,
})),
[],
)
})
})

0 comments on commit 64abbb1

Please sign in to comment.