Repository: ota-meshi/eslint-plugin-astro (ts)

Issue:
Support for Flat Config
### Description

- https://eslint.org/docs/latest/use/configure/configuration-files-new
- https://eslint.org/docs/latest/use/configure/migration-guide#start-using-flat-config-files

Pull request description:
feat: add support for flat config
close #241

Patch:
diff --git a/.changeset/silly-balloons-double.md b/.changeset/silly-balloons-double.md
new file mode 100644
index 0000000..9d9c470
--- /dev/null
+++ b/.changeset/silly-balloons-double.md
@@ -0,0 +1,5 @@
+---
+"eslint-plugin-astro": minor
+---
+
+feat: add support for flat config
diff --git a/.eslintrc.js b/.eslintrc.js
index 54c3ec6..9f4dcd4 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -191,5 +191,14 @@ module.exports = {
         "no-console": "off",
       },
     },
+    {
+      files: ["*.md/**", "**/*.md/**"],
+      parserOptions: {
+        sourceType: "module",
+      },
+      rules: {
+        "n/no-missing-import": "off",
+      },
+    },
   ],
 }
diff --git a/README.md b/README.md
index ef6fa04..2b61c20 100644
--- a/README.md
+++ b/README.md
@@ -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:
 
diff --git a/docs/user-guide.md b/docs/user-guide.md
index 63fce62..369a84c 100644
--- a/docs/user-guide.md
+++ b/docs/user-guide.md
@@ -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:
 
diff --git a/package.json b/package.json
index 4bdf8ad..89f1e67 100644
--- a/package.json
+++ b/package.json
@@ -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"
   },
diff --git a/src/a11y/configs.ts b/src/a11y/configs.ts
index cfed59b..3ead605 100644
--- a/src/a11y/configs.ts
+++ b/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> {
@@ -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() {
diff --git a/src/configs/flat/all.ts b/src/configs/flat/all.ts
new file mode 100644
index 0000000..2d02a96
--- /dev/null
+++ b/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,
+    },
+  },
+]
diff --git a/src/configs/flat/base.ts b/src/configs/flat/base.ts
new file mode 100644
index 0000000..3137a24
--- /dev/null
+++ b/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",
+    },
+  },
+]
diff --git a/src/configs/flat/recommended.ts b/src/configs/flat/recommended.ts
new file mode 100644
index 0000000..6dca72a
--- /dev/null
+++ b/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",
+    },
+  },
+]
diff --git a/src/configs/has-typescript-eslint-parser.ts b/src/configs/has-typescript-eslint-parser.ts
index dc177cc..7d70866 100644
--- a/src/configs/has-typescript-eslint-parser.ts
+++ b/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
diff --git a/src/index.ts b/src/index.ts
index 7fa1173..0f12c40 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -5,6 +5,9 @@ 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"
 
@@ -12,7 +15,9 @@ const configs = {
   base,
   recommended,
   all,
-  ...buildA11yConfigs(),
+  "flat/base": flatBase,
+  "flat/recommended": flatRecommended,
+  "flat/all": flatAll,
 }
 
 const a11yConfigs = buildA11yConfigs()
diff --git a/tools/update-rulesets.ts b/tools/update-rulesets.ts
index 9555844..1289af6 100644
--- a/tools/update-rulesets.ts
+++ b/tools/update-rulesets.ts
@@ -93,6 +93,92 @@ export = {
 `,
 )
 
+void formatAndSave(
+  path.resolve(__dirname, "../src/configs/flat/base.ts"),
+  `/*
+ * 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
+      ${baseRules
+        .map((rule) => {
+          const conf = rule.meta.docs.default || "error"
+          return `"${rule.meta.docs.ruleId}": "${conf}"`
+        })
+        .join(",\n        ")}
+    },
+  },
+  {
+    // 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",
+    },
+  },
+]`,
+)
+
 const recommendedRules = rules.filter(
   (rule) => rule.meta.docs.recommended && !rule.meta.deprecated,
 )
@@ -122,3 +208,28 @@ export = {
 }
 `,
 )
+
+void formatAndSave(
+  path.resolve(__dirname, "../src/configs/flat/recommended.ts"),
+  `/*
+ * 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
+      ${recommendedRules
+        .map((rule) => {
+          const conf = rule.meta.docs.default || "error"
+          return `"${rule.meta.docs.ruleId}": "${conf}"`
+        })
+        .join(",\n    ")}
+    },
+  }
+]
+`,
+)


Test patch:
diff --git a/tests/src/config/recommended.ts b/tests/src/config/recommended.ts
new file mode 100644
index 0000000..913dcf6
--- /dev/null
+++ b/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,
+      })),
+      [],
+    )
+  })
+})
diff --git a/tests/src/integration/client-typescript.ts b/tests/src/integration/client-typescript.ts
index d585cfd..eeb9262 100644
--- a/tests/src/integration/client-typescript.ts
+++ b/tests/src/integration/client-typescript.ts
@@ -3,7 +3,7 @@ import astroPlugin from "../../../src/index"
 import assert from "assert"
 import Module from "module"
 import semver from "semver"
-import { convertFlatConfig } from "../../utils/eslint-compat"
+import { tsESLintParser } from "../../../src/configs/has-typescript-eslint-parser"
 
 describe("Integration test for client-side ts", () => {
   // @ts-expect-error -- ignore
@@ -22,35 +22,50 @@ describe("Integration test for client-side ts", () => {
     Module._load = originalLoad
   })
   it("should work with client-side-ts processor", async () => {
-    const overrideConfig = {
-      extends: ["plugin:astro/base"],
-      parser: "@typescript-eslint/parser",
-      rules: {
-        "no-restricted-syntax": ["error", "TSTypeAnnotation"],
-      } as Record<string, any>,
-      overrides: [
-        {
-          files: ["*.astro"],
-          parser: "astro-eslint-parser",
-          parserOptions: {
-            parser: "@typescript-eslint/parser",
-            extraFileExtensions: [".astro"],
-          },
-          processor: "astro/client-side-ts",
-        },
-      ],
-    }
     const eslint = semver.lt(ESLint.version, "9.0.0-0")
       ? new ESLint({
           plugins: {
             astro: astroPlugin as any,
           },
           useEslintrc: false,
-          overrideConfig,
+          overrideConfig: {
+            extends: ["plugin:astro/base"],
+            parser: "@typescript-eslint/parser",
+            rules: {
+              "no-restricted-syntax": ["error", "TSTypeAnnotation"],
+            } as Record<string, any>,
+            overrides: [
+              {
+                files: ["*.astro"],
+                parser: "astro-eslint-parser",
+                parserOptions: {
+                  parser: "@typescript-eslint/parser",
+                  extraFileExtensions: [".astro"],
+                },
+                processor: "astro/client-side-ts",
+              },
+            ],
+          },
         })
       : new ESLint({
           overrideConfigFile: true as any,
-          overrideConfig: convertFlatConfig(overrideConfig),
+          // @ts-expect-error -- typing bug
+          overrideConfig: [
+            ...astroPlugin.configs["flat/base"],
+            {
+              files: ["*.ts", "**/*.ts"],
+              languageOptions: {
+                parser: tsESLintParser,
+              },
+              rules: {
+                "no-restricted-syntax": ["error", "TSTypeAnnotation"],
+              } as Record<string, any>,
+            },
+            {
+              files: ["*.astro", "**/*.astro"],
+              processor: "astro/client-side-ts",
+            },
+          ],
         })
 
     const result = await eslint.lintText(
diff --git a/tests/src/integration/config-for-a11y.ts b/tests/src/integration/config-for-a11y.ts
index bf03f8a..33160a9 100644
--- a/tests/src/integration/config-for-a11y.ts
+++ b/tests/src/integration/config-for-a11y.ts
@@ -5,7 +5,6 @@ import astroPlugin from "../../../src/index"
 import assert from "assert"
 import Module from "module"
 import semver from "semver"
-import { convertFlatConfig } from "../../utils/eslint-compat"
 
 describe("Integration test for a11y config", () => {
   // @ts-expect-error -- ignore
@@ -37,9 +36,8 @@ describe("Integration test for a11y config", () => {
         })
       : new ESLint({
           overrideConfigFile: true as any,
-          overrideConfig: convertFlatConfig({
-            extends: ["plugin:astro/jsx-a11y-strict"],
-          }),
+          // @ts-expect-error -- typing bug
+          overrideConfig: astroPlugin.configs["flat/jsx-a11y-strict"],
         })
 
     const result = await eslint.lintText(
@@ -87,9 +85,8 @@ const src = 'icon.png'
         })
       : new ESLint({
           overrideConfigFile: true as any,
-          overrideConfig: convertFlatConfig({
-            extends: ["plugin:astro/jsx-a11y-recommended"],
-          }),
+          // @ts-expect-error -- typing bug
+          overrideConfig: astroPlugin.configs["flat/jsx-a11y-recommended"],
         })
 
     const result = await eslint.lintText(
diff --git a/tests/src/integration/parse-error.ts b/tests/src/integration/parse-error.ts
index afbe588..cf4d598 100644
--- a/tests/src/integration/parse-error.ts
+++ b/tests/src/integration/parse-error.ts
@@ -3,7 +3,6 @@ import astroPlugin from "../../../src/index"
 import assert from "assert"
 import Module from "module"
 import semver from "semver"
-import { convertFlatConfig } from "../../utils/eslint-compat"
 
 describe("Integration test for parse error", () => {
   // @ts-expect-error -- ignore
@@ -34,9 +33,8 @@ describe("Integration test for parse error", () => {
         })
       : new ESLint({
           overrideConfigFile: true as any,
-          overrideConfig: convertFlatConfig({
-            extends: ["plugin:astro/base"],
-          }),
+          // @ts-expect-error -- typing bug
+          overrideConfig: astroPlugin.configs["flat/base"],
         })
 
     const result = await eslint.lintText(
diff --git a/tests/utils/eslint-compat.ts b/tests/utils/eslint-compat.ts
index 79d7da0..13a2287 100644
--- a/tests/utils/eslint-compat.ts
+++ b/tests/utils/eslint-compat.ts
@@ -1,34 +1,11 @@
-import { ESLint as OriginalESLint } from "eslint"
+import type { ESLint as OriginalESLint } from "eslint"
 import { getRuleIdPrefix, getRuleTester } from "eslint-compat-utils/rule-tester"
-// @ts-expect-error -- missing type
-import { FlatCompat } from "@eslint/eslintrc"
-import astroPlugin from "../../src/index"
+import { getLegacyESLint, getESLint } from "eslint-compat-utils/eslint"
 
 // eslint-disable-next-line @typescript-eslint/naming-convention -- class name
-export const LegacyESLint: typeof OriginalESLint =
-  getUnsupported().LegacyESLint || OriginalESLint
+export const LegacyESLint: typeof OriginalESLint = getLegacyESLint()
 // eslint-disable-next-line @typescript-eslint/naming-convention -- class name
 export const RuleTester = getRuleTester()
 export const testRuleIdPrefix = getRuleIdPrefix()
-
-function getUnsupported() {
-  try {
-    // eslint-disable-next-line @typescript-eslint/no-require-imports -- ignore
-    return require("eslint/use-at-your-own-risk")
-  } catch {
-    return {}
-  }
-}
-
-export function convertFlatConfig(originalConfig: any): any {
-  const compat = new FlatCompat()
-  return compat.config(originalConfig).map((config: any) => {
-    if (!config.plugins?.astro) {
-      return config
-    }
-    return {
-      ...config,
-      plugins: { ...config.plugins, astro: astroPlugin },
-    }
-  })
-}
+// eslint-disable-next-line @typescript-eslint/naming-convention -- class name
+export const FlatESLint: typeof OriginalESLint = getESLint()