ESLintの導入

# はじめに
個人的なESlintの使い方をメモしておく。

https://eslint.org/

# 本体とconfigとpluginのインストール
## `eslint --init` による初期設定

```shell
% yarn eslint --init
yarn run v1.22.18
$ /Users/hoge/Documents/fuga/piyo/node_modules/.bin/eslint --init
✔ How would you like to use ESLint? · style
✔ What type of modules does your project use? · esm
✔ Which framework does your project use? · react
✔ Does your project use TypeScript? · No / Yes
✔ Where does your code run? · browser
✔ How would you like to define a style for your project? · guide
✔ Which style guide do you want to follow? · airbnb
✔ What format do you want your config file to be in? · JSON
Checking peerDependencies of eslint-config-airbnb@latest
Local ESLint installation not found.
The config that you've selected requires the following dependencies:

eslint-plugin-react@^7.28.0 @typescript-eslint/eslint-plugin@latest eslint-config-airbnb@latest eslint@^7.32.0 || ^8.2.0 eslint-plugin-import@^2.25.3 eslint-plugin-jsx-a11y@^6.5.1 eslint-plugin-react-hooks@^4.3.0 @typescript-eslint/parser@latest
? Would you like to install them now with npm? › No / Yes
```

- [eslint](https://www.npmjs.com/package/eslint)：ESLint本体。
- [@typescript-eslint/parser](https://www.npmjs.com/package/@typescript-eslint/parser)：ESLintがTypeScriptのソースコードをリントできるようにするパーサー。
- [@typescript-eslint/eslint-plugin](https://www.npmjs.com/package/@typescript-eslint/eslint-plugin)：TypeScriptに特化した機能を持つルールを追加したり拡張したりするプラグイン。`@typescript-eslint/parser`と同じバージョンのものを使用する必要がある。
- [eslint-config-airbnb](https://www.npmjs.com/package/eslint-config-airbnb)：Airbnbの`.eslintrc`を拡張可能な共有設定として提供するもの。ES6以上とReactを含むESLintのほとんどのルールを含んでおり、`eslint`, `eslint-plugin-import`, `eslint-plugin-react`, `eslint-plugin-react-hook`s, `eslint-plugin-jsx-a11y`が必要。ただ、デフォルトではReact Hooksに関するルールは有効になっていないため、有効にしたい場合は`.eslintrc`の`extends`に`['airbnb', 'airbnb/hooks']`を追加する必要がある。
- [eslint-plugin-import](https://www.npmjs.com/package/eslint-plugin-import)：モジュールのimportとexportに関するルールを提供するプラグイン。
- [eslint-plugin-jsx-a11y](https://www.npmjs.com/package/eslint-plugin-jsx-a11y)：JSX構文におけるアクセシビリティに関するルールを提供するプラグイン。
- [eslint-plugin-react-hooks](https://www.npmjs.com/package/eslint-plugin-react-hooks)：React Hooksに関するルールを提供するプラグイン。
- [eslint-plugin-react](https://www.npmjs.com/package/eslint-plugin-react)：Reactに関するルールを提供するプラグイン。

## そもそも`extends`とは

https://eslint.org/docs/user-guide/getting-started#configuration

> Alternatively, you can use configurations that others have created by searching for "eslint-config" on npmjs.com. ESLint will not lint your code unless you extend from a shared configuration or explicitly turn rules on in your configuration.

ESLintは、[Shareable Configs](https://eslint.org/docs/developer-guide/shareable-configs)を利用して拡張するか、明示的にルールをオンにしない限り、コードをlintしません、とのこと。Sharable Configは誰かがまとめたESLintの設定のnpmパッケージであり、`extends`にそのモジュール名を記述することで、その設定値を利用できるものである。

## そもそも`plugins`とは

https://eslint.org/docs/user-guide/configuring/

> Plugins - which third-party plugins define additional rules, environments, configs, etc. for ESLint to use.

`plugins`は、ESLintが使用する追加のルール・環境・構成などを定義するサードパーティ製プラグインです、とのこと。

## prettierとの競合を回避
Prettierと衝突する可能性のあるルールを全てオフにする。他の設定を上書きするよう、`extends`配列の最後に記述するようにする。

https://www.npmjs.com/package/eslint-config-prettier

```shell
% npm i -D eslint-config-prettier
```

```json:.eslintrc.json
"extends": [
　　　　"...",
  "prettier"
]
```

# rules
## 未使用の変数禁止のエラー重複を解消
`eslint/no-unused-vars`と、これをTypeScript用に拡張子した`@typescript-eslint/no-unused-vars`が重複してエラーを出すため、本家のルールを無効化しておく。

https://typescript-eslint.io/rules/no-unused-vars/

```json:.eslintrc.json
"rules": {
  "no-unused-vars": "off"
}
```

## Reactのインポート強制を無効化
ちょっと前まではJSX構文を含むファイルではReactをインポートしないといけなかったが、React v17.0からインポートしなくて良くなった。でもESLintはReactをインポートしてないよってエラーを出しちゃうのでそれを無効化する。

https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/react-in-jsx-scope.md

```json:.eslintrc.json
"rules": {
  "react/react-in-jsx-scope": "off"
}
```

## 関数コンポーネントの形式を指定
名前付きのコンポーネントがアロー関数で書かれていなかったら警告を出すようにする。デフォルトでは、名前付きのコンポーネントが関数宣言、名前なしのコンポーネントが関数式で書かれていないとエラーが出る。

https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/function-component-definition.md

```json:.eslintrc.json
"rules": {
  "react/function-component-definition": [
    "warn",
    { "namedComponents": "arrow-function" }
  ],
}
```

## defaultPropsの使用強制を無効化
コンポーネントのpropsが必須ではない場合に`defaultProps`の使用を強制したくなければこのルールをオフにする。

https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/require-default-props.md

```json:.eslintrc.json
"rules": {
  "react/require-default-props": "off"
}
```

## labelとinputの関連付けに関するルールを拡張
labelタグとinputタグが関連付けられていないとエラーが出るが、labelタグと例えばinputタグを出力する`Material UI`の`TextField`コンポーネントを関連付けていてもエラーを出してしまうため、このルールを拡張する。

https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/label-has-associated-control.md

```json:.eslintrc.json
"rules": {
  "jsx-a11y/label-has-associated-control": [
    "error",
    {
      "labelComponents": ["label"],
      "controlComponents": ["TextField"]
    }
  ],
}
```

## defaultエクスポート強制を無効化
モジュールからのエクスポートが1つしかない場合に名前付きエクスポートを使っているとエラーになるので、これを無効化する。

https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/prefer-default-export.md

```json:.eslintrc.json
"rules": {
  "import/prefer-default-export": "off"
}
```

## JSXを含む可能性のあるファイル拡張子を制限
JSXを含むファイル拡張子の制限にこだわらない場合は、`react/jsx-filename-extension`はオフにしておく。

https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/jsx-filename-extension.md

```json:.eslintrc.json
"rules": {
  "react/jsx-filename-extension": "off"
}
```

## インポート時の拡張子省略を許可
一貫したファイル拡張子の使用を実現するために、 このルールで特定のファイル拡張子の使用を強制したり禁止したりすることができる。

https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/extensions.md#rule-details

```json:.eslintrc.json
"rules": {
  "import/extensions": [
    "error",
    "ignorePackages",
    {
      "js": "never",
      "jsx": "never",
      "ts": "never",
      "tsx": "never"
    }
  ]
}
```

## 定義される前の変数の使用を禁止
`typescript-eslint/no-use-before-define`を使うと`eslint/no-use-before-define`が不正なエラーを出す可能性があるため、無効にしておく。

https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/docs/rules/no-use-before-define.md#how-to-use

```json:.eslintrc.json
"rules": {
  "no-use-before-define": "off",
}
```

# settings
## モジュールとして解析されるファイル拡張子を追加
モジュールとして解析され、エクスポートのために検査されるファイル拡張子のリスト（デフォルトは`.js`のみ）を編集する。

https://github.com/import-js/eslint-plugin-import/blob/v2.26.0/README.md#settings

```json:.eslintrc.json
"settings": {
  "import/resolver": {
    "node": {
      "extensions": [".js", ".jsx", ".ts", ".tsx"]
    }
  }
}
```

# おわりに
最終的な個人的`.eslintrc.json`はこちら。

```json:.eslintrc.json
{
  "ignorePatterns": ["functions", "src/utils/graphql"],
  "env": {
    "browser": true,
    "es2021": true
  },
  "extends": [
    "plugin:react/recommended",
    "plugin:@typescript-eslint/recommended",
    "airbnb",
    "prettier"
  ],
  "parser": "@typescript-eslint/parser",
  "parserOptions": {
    "ecmaFeatures": {
      "jsx": true
    },
    "ecmaVersion": 12,
    "sourceType": "module"
  },
  "plugins": ["react", "@typescript-eslint"],
  "rules": {
    "no-unused-vars": "off",
    "no-use-before-define": "off",
    "react/jsx-filename-extension": "off",
    "react/react-in-jsx-scope": "off",
    "react/require-default-props": "off",
    "react/function-component-definition": [
      "warn",
      { "namedComponents": "arrow-function" }
    ],
    "jsx-a11y/label-has-associated-control": [
      "error",
      {
        "labelComponents": ["label"],
        "controlComponents": ["TextField"]
      }
    ],
    "import/prefer-default-export": "off",
    "import/extensions": [
      "error",
      "ignorePackages",
      {
        "js": "never",
        "jsx": "never",
        "ts": "never",
        "tsx": "never"
      }
    ]
  },
  "settings": {
    "import/resolver": {
      "node": {
        "extensions": [".js", ".jsx", ".ts", ".tsx"]
      }
    }
  }
}
```

参考にしていただけますと幸いです。