bulletproofのeslintrcをFlatConfigに変更

## 前提
>本記事は、以下の記事の一部を詳細に書いた記事になります

https://qiita.com/matsuik/items/052c4d17dfdaeffbf06d


リンターであるESlintの設定ファイルがFlatConfigという記法に統一されていくらしいです。
今後はeslintrc形式が全く使えなくなるとか。

正直、従来のeslintrc形式でも書いたことなかったので何の設定をしているかの解読から始まりました。
加えて、ESLintの設定にこだわる人が少ないのかFlatConfigへの変換に関する記事が比較的少なく、結局公式ドキュメントを読みながら進めた部分も多かったです。

## 本題

まず変更前と変更後のコードの全文を載せます

#### 変更前

```javascript:.eslintrc.js
module.exports = {
  root: true,
  env: {
    node: true,
    es6: true,
  },
  parserOptions: { ecmaVersion: 8, sourceType: 'module' },
  ignorePatterns: ['node_modules/*'],
  extends: ['eslint:recommended'],
  overrides: [
    {
      files: ['**/*.ts', '**/*.tsx'],
      parser: '@typescript-eslint/parser',
      settings: {
        react: { version: 'detect' },
        'import/resolver': {
          typescript: {},
        },
      },
      env: {
        browser: true,
        node: true,
        es6: true,
      },
      extends: [
        'eslint:recommended',
        'plugin:import/errors',
        'plugin:import/warnings',
        'plugin:import/typescript',
        'plugin:@typescript-eslint/recommended',
        'plugin:react/recommended',
        'plugin:react-hooks/recommended',
        'plugin:jsx-a11y/recommended',
        'plugin:prettier/recommended',
        'plugin:testing-library/react',
        'plugin:jest-dom/recommended',
      ],
      rules: {
        'no-restricted-imports': [
          'error',
          {
            patterns: ['@/features/*/*'],
          },
        ],
        'linebreak-style': ['error', 'unix'],
        'react/prop-types': 'off',

        'import/order': [
          'error',
          {
            groups: ['builtin', 'external', 'internal', 'parent', 'sibling', 'index', 'object'],
            'newlines-between': 'always',
            alphabetize: { order: 'asc', caseInsensitive: true },
          },
        ],
        'import/default': 'off',
        'import/no-named-as-default-member': 'off',
        'import/no-named-as-default': 'off',

        'react/react-in-jsx-scope': 'off',

        'jsx-a11y/anchor-is-valid': 'off',

        '@typescript-eslint/no-unused-vars': ['error'],

        '@typescript-eslint/explicit-function-return-type': ['off'],
        '@typescript-eslint/explicit-module-boundary-types': ['off'],
        '@typescript-eslint/no-empty-function': ['off'],
        '@typescript-eslint/no-explicit-any': ['off'],

        'prettier/prettier': ['error', {}, { usePrettierrc: true }],
      },
    },
  ],
};
```
#### 変更後

```javascript:eslint.config.mjs
import js from '@eslint/js';
import { FlatCompat } from '@eslint/eslintrc';
import typeScriptESLintParser from '@typescript-eslint/parser';
import globals from 'globals';

const compat = new FlatCompat();

export default [
  // 推奨設定
  js.configs.recommended,
  // lint対象外のファイル
  {
    ignores: ['**/node_modules/**'],
  },
  // lint対象ファイル
  {
    files: ['**/*.ts', '**/*.tsx'],
  },
  // lint設定
  {
    rules: {
      'no-restricted-imports': [
        'error',
        {
          patterns: ['@/features/*/*'],
        },
      ],
      'linebreak-style': ['error', 'unix'],
      'react/prop-types': 'off',
      'import/order': [
        'error',
        {
          groups: ['builtin', 'external', 'internal', 'parent', 'sibling', 'index', 'object'],
          'newlines-between': 'always',
          alphabetize: { order: 'asc', caseInsensitive: true },
        },
      ],
      'import/default': 'off',
      'import/no-named-as-default-member': 'off',
      'import/no-named-as-default': 'off',
      'react/react-in-jsx-scope': 'off',
      'jsx-a11y/anchor-is-valid': 'off',
      '@typescript-eslint/no-unused-vars': ['error'],
      '@typescript-eslint/explicit-function-return-type': ['off'],
      '@typescript-eslint/explicit-module-boundary-types': ['off'],
      '@typescript-eslint/no-empty-function': ['off'],
      '@typescript-eslint/no-explicit-any': ['off'],
      'prettier/prettier': ['error', {}, { usePrettierrc: true }],
    },
    languageOptions: {
      globals: {
        ...globals.browser,
        ...globals.node,
        ...globals.es6,
        myCustomGlobal: 'readonly',
      },
      parser: typeScriptESLintParser,
    },
  },
  ...compat.extends(
    'plugin:import/errors',
    'plugin:import/warnings',
    'plugin:import/typescript',
    'plugin:@typescript-eslint/recommended',
    'plugin:react/recommended',
    'plugin:react-hooks/recommended',
    'plugin:jsx-a11y/recommended',
    'plugin:prettier/recommended',
    'plugin:testing-library/react',
    'plugin:jest-dom/recommended'
  ),
];
```

## 解説

eslintrcではextendsとかoverridesとかを使って設定を書いているところを、
flatConfigでは追加したい設定を並列のリスト形式で書き連ねるだけになったということで、全体的に設定を書きやすい+読みやすくなっているみたいです。

>正直初見ではeslintrc何書いてあるか全然わからなかった....

### root

.eslintrc.jsの`root:true`は、eslintrc特有の設定なのでflatconfigでは使いません。

### 環境変数

```javascript:.eslintrc.js
  env: {
    node: true,
    es6: true,
  },
```


```javascript:eslint.config.mjs
  {
    languageOptions: {
      globals: {
        ...globals.node,
        ...globals.es6,
      },
    },
  }
```

### ignoreファイル
リンターの対象にしないファイルを指定することができます。

```javascript:.eslintrc.js:L2
  ignorePatterns: ['node_modules/*'],
```


```javascript:eslint.config.mjs
  ignores: ['**/node_modules/**'],
```

### 対象ファイル
リンターの対象にするファイルを指定することができます。
以下の設定では、typescriptファイルのみを指定しています。

```javascript:.eslintrc.js:L2
  files: ['**/*.ts', '**/*.tsx'],
```


```javascript:eslint.config.mjs
  {
    files: ['**/*.ts', '**/*.tsx'],
  }
```

### overrides
従来オーバーライドして追加していた設定を、並列して追記するだけになっています。
rulesの中身はコピペしていて、parserやenvなどはlanguageOptionsに書くよう仕様変更されています。

```javascript:.eslintrc.js:L2
  overrides: [
    {
      parser: '@typescript-eslint/parser',
      settings: {
        react: { version: 'detect' },
        'import/resolver': {
          typescript: {},
        },
      },
      env: {
        browser: true,
        node: true,
        es6: true,
      },
      rules: {
        'no-restricted-imports': [
          'error',
          {
            patterns: ['@/features/*/*'],
          },
        ],
        'linebreak-style': ['error', 'unix'],
        'react/prop-types': 'off',
        'import/order': [
          'error',
          {
            groups: ['builtin', 'external', 'internal', 'parent', 'sibling', 'index', 'object'],
            'newlines-between': 'always',
            alphabetize: { order: 'asc', caseInsensitive: true },
          },
        ],
        'import/default': 'off',
        'import/no-named-as-default-member': 'off',
        'import/no-named-as-default': 'off',
        'react/react-in-jsx-scope': 'off',
        'jsx-a11y/anchor-is-valid': 'off',
        '@typescript-eslint/no-unused-vars': ['error'],
        '@typescript-eslint/explicit-function-return-type': ['off'],
        '@typescript-eslint/explicit-module-boundary-types': ['off'],
        '@typescript-eslint/no-empty-function': ['off'],
        '@typescript-eslint/no-explicit-any': ['off'],
        'prettier/prettier': ['error', {}, { usePrettierrc: true }],
      },
    },
  ],,
```


```javascript:eslint.config.mjs
　　languageOptions: {
      globals: {
        ...globals.browser,
        ...globals.node,
        ...globals.es6,
        myCustomGlobal: 'readonly',
      },
      parser: typeScriptESLintParser,
    },
    rules: {
      'no-restricted-imports': [
        'error',
        {
          patterns: ['@/features/*/*'],
        },
      ],
      'linebreak-style': ['error', 'unix'],
      'react/prop-types': 'off',
      'import/order': [
        'error',
        {
          groups: ['builtin', 'external', 'internal', 'parent', 'sibling', 'index', 'object'],
          'newlines-between': 'always',
          alphabetize: { order: 'asc', caseInsensitive: true },
        },
      ],
      'import/default': 'off',
      'import/no-named-as-default-member': 'off',
      'import/no-named-as-default': 'off',
      'react/react-in-jsx-scope': 'off',
      'jsx-a11y/anchor-is-valid': 'off',
      '@typescript-eslint/no-unused-vars': ['error'],
      '@typescript-eslint/explicit-function-return-type': ['off'],
      '@typescript-eslint/explicit-module-boundary-types': ['off'],
      '@typescript-eslint/no-empty-function': ['off'],
      '@typescript-eslint/no-explicit-any': ['off'],
      'prettier/prettier': ['error', {}, { usePrettierrc: true }],
    },
  },
```

### プラグイン

Flatconfigに対応している場合は以下のようにimportしてリストに追加するだけです。

```javascript:.eslintrc.js:L2
  extends: ['eslint:recommended'],
```


```javascript:eslint.config.mjs
import js from '@eslint/js';

export default [
  js.configs.recommended,
  ...

```


しかし調べてみたところ現在はFlatconfigに対応していないプラグインも多く、その場合はcompat.extendsというものを使うと利用できるようです。

```javascript:.eslintrc.js:L2
  　　　　extends: [
        'eslint:recommended',
        'plugin:import/errors',
        'plugin:import/warnings',
        'plugin:import/typescript',
        'plugin:@typescript-eslint/recommended',
        'plugin:react/recommended',
        'plugin:react-hooks/recommended',
        'plugin:jsx-a11y/recommended',
        'plugin:prettier/recommended',
        'plugin:testing-library/react',
        'plugin:jest-dom/recommended',
      ]
```


```javascript:eslint.config.mjs
import { FlatCompat } from '@eslint/eslintrc';

const compat = new FlatCompat();

export default [
  {},
  {},
　　 ...compat.extends(
    'plugin:import/errors',
    'plugin:import/warnings',
    'plugin:import/typescript',
    'plugin:@typescript-eslint/recommended',
    'plugin:react/recommended',
    'plugin:react-hooks/recommended',
    'plugin:jsx-a11y/recommended',
    'plugin:prettier/recommended',
    'plugin:testing-library/react',
    'plugin:jest-dom/recommended'
  ),

```

## 終わりに

とりあえずESlintの設定ファイルをベストプラクティスに沿って作成することができました！

調べながら変換しているので、もしかしたら誤っている部分もあるかもしれません。
ぜひご指摘頂けると幸いです。

## 参考サイト

https://zenn.dev/makotot/articles/0d9184f3dde858

https://qiita.com/Shilaca/items/c494e4dc6b536a5231de