【ESLint】 Flat Config で書いてみた

ESLint の v9.0.0 以降では、Flat Config(eslint.config.js) がデフォルトになったみたいです。
書き方が分からなくて、色々と試してみて、なんとかできたっぽいので、結果だけ残しておきたいと思います。

# 前提
Vite から、React, TypeScript でプロジェクトを生成しています

# 追加でインストールしたもの
- eslint-plugin-react
- eslint-plugin-jsx-a11y

# 作成･変更したファイル
## frontend/eslint.config.js(変更)
元々あったファイルから割と大胆に書き換えてしまったので差分表示はしていないです。
```js
import eslint from '@eslint/js';
import globals from 'globals';
import reactHooks from 'eslint-plugin-react-hooks';
import reactRefresh from 'eslint-plugin-react-refresh';
import tseslint from 'typescript-eslint';
import react from 'eslint-plugin-react';
import jsxA11y from 'eslint-plugin-jsx-a11y';

/** @typedef {import('./config-helper').ConfigWithExtends} ConfigWithExtends */

/** @type {ConfigWithExtends} */
const tsConfig = {
  ignores: ['dist'],

  extends: [
    eslint.configs.recommended,
    ...tseslint.configs.recommended,
    jsxA11y.flatConfigs.recommended,
  ],

  files: ['src/**/*.{ts,tsx}'],

  languageOptions: {
    ecmaVersion: 2022,
    globals: globals.browser,
  },

  plugins: {
    'react-hooks': reactHooks,
    'react-refresh': reactRefresh,
    react,
    tseslint,
  },

  rules: {
    ...reactHooks.configs.recommended.rules,
    'react-refresh/only-export-components': [
      'warn',
      { allowConstantExport: true },
    ],
    ...react.configs.recommended.rules,
    ...react.configs['jsx-runtime'].rules,
    '@typescript-eslint/no-unused-vars': [
      'error',
      {
        argsIgnorePattern: '^_',
        varsIgnorePattern: '^_',
      },
    ],
  },

  settings: { react: { version: 'detect' } },
};

export default tseslint.config(tsConfig);
```

## frontend/types/global.d.ts(新規作成)
```typescript
// types/eslint-plugin-jsx-a11y.d.ts
declare module 'eslint-plugin-jsx-a11y' {
  const jsxA11y: {
    flatConfigs: {
      recommended: any;
    };
  };
  export default jsxA11y;
}

// types/eslint-plugin-react.d.ts
declare module 'eslint-plugin-react' {
  const react: {
    configs: {
      recommended: {
        rules: Record<string, any>;
      };
    };
  };
  export default react;
}
```

## frontend/tsconfig.app.json(変更)
```diff_json
{
  "compilerOptions": {
    "target": "ES2020",
    "useDefineForClassFields": true,
    "lib": ["ES2020", "DOM", "DOM.Iterable"],
    "module": "ESNext",
    "skipLibCheck": true,

    /* Bundler mode */
    "moduleResolution": "bundler",
    "allowImportingTsExtensions": true,
    "isolatedModules": true,
    "moduleDetection": "force",
    "noEmit": true,
    "jsx": "react-jsx",

    /* Linting */
    "strict": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noFallthroughCasesInSwitch": true
  },
- "include": ["src"]
+ "include": ["src", "types"]
}
```

## frontend/package.json(変更)
```diff_json
{
  "name": "frontend",
  "private": true,
  "version": "0.0.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "tsc -b && vite build",
    "lint": "eslint 'src/**/*.{ts,tsx}' --fix",
    "lint-config": "eslint --inspect-config",
    "preview": "vite preview"
  },
  "dependencies": {
    "react": "^18.3.1",
    "react-dom": "^18.3.1"
  },
  "devDependencies": {
    "@eslint/js": "^9.11.1",
    "@types/react": "^18.3.10",
    "@types/react-dom": "^18.3.0",
    "@vitejs/plugin-react-swc": "^3.5.0",
    "eslint": "^9.11.1",
+   "eslint-plugin-jsx-a11y": "^6.10.1",
+   "eslint-plugin-react": "^7.37.1",
    "eslint-plugin-react-hooks": "^5.1.0-rc.0",
    "eslint-plugin-react-refresh": "^0.4.12",
    "globals": "^15.9.0",
    "typescript": "^5.5.3",
    "typescript-eslint": "^8.7.0",
    "vite": "^5.4.8"
  }
}
```

# おわりに
eslint.config.js は Vite でプロジェクトを作成した時点で既に warning が出ていたので、JSDoc を使って extends されている型も読めるようにしました。
また、追記したルールで warning が出てしまったので、global.d.ts に型情報を書いてみました。
この方法が良いのか悪いのかはよくわからないですが・・・。とりあえず構文エラーも warning もない状態なので、ひとまずよきかなと思っています。

# おまけ
[ESLint Config Inspector](https://eslint.org/blog/2024/04/eslint-config-inspector/) に助けられました。
lint の構文があっているのか、今の設定でどのルールが有効になっているのかなど確認することができます。