ESLintのenv、extends、overrides、parser、parserOptions、plugins、rulesをざっくり理解する

## 背景

ある開発者の環境ではLintエラーが発生するけど、自分の環境では発生しないという現象がきっかけでした。（原因は[本記事とは関係のないところ](https://qiita.com/i2i3i/items/abfbb340473b6e00c972#%E7%B5%90%E8%AB%96)にあったのですが、、）

ESLint自体を見直す機会になったので、仕事がない分、勉強したんだぞという証拠を残しておきます。


## 環境

- OS

Windows WSL2 Ubuntu 20.04

- Node

```
$node -v
V18.12.1

$npm -v
8.19.2
```    

- ESLint

.eslintrc.jsonで設定を記述します。
※[V9.0.0で廃止されるスタイル](https://eslint.org/docs/latest/use/configure/configuration-files)のようです。知りませんでした。まぁでもコンセプトは変わらないので大丈夫:innocent:

```
$npx eslint --version 
V8.41.0
```

## セットアップ

ゼロから作ってみると達成感が増すので、[ドキュメント](https://eslint.org/docs/latest/use/getting-started#quick-start)に沿って、設定してみます。

```
# 適当な作業フォルダを作成
$cd ~/work
$mkdir eslint-practice
$cd eslint-practice

# node プロジェクトを生成
$npm init -y

# eslint の初期化処理と選択肢は以下を選んでいます。
$npm init @eslint/config
# 1. To check syntax and find problems
# 2. JavaScript modules
# 3. React
# 4. Yes(TypeScript)
# 5. Browser
# 6. JSON
# 7. Yes(Install packages)
# 8. npm

# package.jsonの中身を確認してみると、TypeScriptとReactに関連するeslintパッケージもInstallされていることが分かります。
$cat package.json
{
  "name": "eslint-practice",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@typescript-eslint/eslint-plugin": "^5.59.8",
    "@typescript-eslint/parser": "^5.59.8",
    "eslint": "^8.41.0",
    "eslint-plugin-react": "^7.32.2"
  }
}

# TypeScriptがnode_modulesに入るのですが、parserとの互換性エラーがでるので、明示的にVersionを指定してInstallします。
$ npm install -D typescript@5.0.4

# eslintも動作することが確認できます。
$npx eslint --version
v8.41.0

# 設定ファイルも生成されています
$ ls -a
.eslintrc.json  node_modules  package-lock.json  package.json
```

## 設定を確認

ここからが本題です。デフォルトで生成された生成ファイルを解剖していきましょう。

```
$ cat .eslintrc.json
{
    "env": {
        "browser": true,
        "es2021": true
    },
    "extends": [
        "eslint:recommended",
        "plugin:react/recommended",
        "plugin:@typescript-eslint/recommended"
    ],
    "overrides": [
    ],
    "parser": "@typescript-eslint/parser",
    "parserOptions": {
        "ecmaVersion": "latest",
        "sourceType": "module"
    },
    "plugins": [
        "react",
        "@typescript-eslint"
    ],
    "rules": {
    }
}
```

<br/>

- [env](https://eslint.org/docs/latest/use/configure/language-options#specifying-environments)

ESLintがコードチェックする際に、グローバル変数などを認識するために設定します。例えば、以下であれば、Browserが持つグローバル変数とES2021で定義されているグローバル変数を理解してくれます。

```
    "env": {
        "browser": true,
        "es2021": true
    },
```

例）[no-undefルールを適用している場合](https://eslint.org/docs/latest/rules/no-undef#environments)

これは、定義されていない変数が使われていないかをチェックするルールです。何気なく使っているconsole.logなどは、Browserのグローバル変数として定義されています。

実際にチェックしてみましょう。
eslint-practiceプロジェクト配下にindex.tsを以下の内容で生成します。また.eslintrc.jsonも単純化します。

index.ts
```
console.log('test')
```

.eslintrc.json
```
{
    "env": {
        "browser": true
    },
    "extends": [
        "eslint:recommended"
    ]
}
```

Lintingを実行

```
# Lintingを実行
$ npx eslint index.ts
# 特にエラーは発生しません。

# 次に、"browser": trueを削除して実行してみてください。consoleが定義されていない旨のエラーメッセージが表示されます。
$ npx eslint index.ts
  1:1  error  'console' is not defined  no-undef

✖ 1 problem (1 error, 0 warnings)
```

consoleがどこから来たものなのかをESLintが理解できていないため、未定義変数として扱い、エラーが出力されていることが分かりますね。

<br/>

- [extends](https://eslint.org/docs/latest/use/configure/configuration-files#extending-configuration-files)

ESLintルール群を継承することができます。「車輪の再開発はしない」が鉄則の業界なので、必ず便利なベストプラクティスが存在します。それを流用することができるプロパティとなります。npm packageとしてルール群をインストールすることも可能ですし、単純に相対パスを指定して、別の.eslintrc.jsonファイルを継承することも可能です。

以下は、eslintが推奨するルール群と、後ほど出てきますが、pluginsとして入れたルール群を継承していることを示します。

```
    "extends": [
        "eslint:recommended",
        "plugin:react/recommended",
        "plugin:@typescript-eslint/recommended"
    ],
```

動作はenvのときに実は確認済みで、no-undefというルールはeslint:recommendedに定義されているルールが利用されていました。

また上記の動作チェック時に、.eslintrc.jsonを簡略化した理由として、plugin:@typescript-eslint/recommendedが内部的にno-undefをoffにしていたため、eslint:recommendedのみをextendsに配置しました。

<br/>

- [overrides](https://eslint.org/docs/latest/use/configure/configuration-files#how-do-overrides-work)

このプロパティは、"特定のファイル"に対して、ルール実行の挙動を変更するものです。したがって、filesプロパティを指定することが必須となります。

例）[quotesルールの挙動を特定ファイルで変更したい場合](https://eslint.org/docs/latest/use/configure/configuration-files#how-do-overrides-work)

全体ルールとしては、ダブルクォーテーションはエラーですが、overrides内で指定されているfilesに対してはシングルクォーテーションがエラーとなります。

```
{
  "rules": {
    "quotes": ["error", "double"]
  },

  "overrides": [
    {
      "files": ["bin/*.js", "lib/*.js"],
      "excludedFiles": "*.test.js",
      "rules": {
        "quotes": ["error", "single"]
      }
    }
  ]
}
```
<br/>

- [parser](https://eslint.org/docs/latest/use/configure/parser#configure-a-custom-parser)

ESlintでチェックする対象をどのように解析するかを設定します。以下では、TypeScriptを解析できるように設定をおこなっています。Defaultでは、JavaScript ParserのEspreeが設定されています。

```
    "parser": "@typescript-eslint/parser",
```

例）TypeScriptのSyntaxをparser設定なしでチェックした場合

以下のようにindex.tsと.eslintrc.jsonを簡略化し、Lintingを実行してみてください。

index.ts
```
var str: string = 'test';
```

.eslintrc.json
```
{
    "extends": [
        "eslint:recommended"
    ],
    "parser": "@typescript-eslint/parser"
}
```

Lintingを実行

```
# TypeScriptが理解され、内容がチェックされていることが分かります。
$ npx eslint index.ts
  1:5  error  'str' is assigned a value but never used  no-unused-vars

✖ 1 problem (1 error, 0 warnings)

# 次に、"parser": "@typescript-eslint/parser"を削除して実行してみてください。
# 内容を解析できずにTypeを指定する":"でエラーが発生していることが分かります。
$ npx eslint index.ts
  1:8  error  Parsing error: Unexpected token :

✖ 1 problem (1 error, 0 warnings)
```

実はここでも、.eslintrc.jsonを簡略化した理由があります。
extendsにplugin:@typescript-eslint/recommendedが入っていると、この中の@typescript-eslint/parserが動作し、.eslintrc.jsonのparserの有無が関係なくTypeScriptのSyntaxが理解されてしまうので外しました。

[ドキュメント](https://eslint.org/docs/latest/use/core-concepts#parsers)では、指定する必要が無いと記載されているので、継承されたものを暗黙的に利用するか、明示的に記載するかはどちらでも良いと思います。

<br/>

- [pserserOptions](https://eslint.org/docs/latest/use/configure/language-options#specifying-parser-options)

JavaScript Syntaxをどのように解析するかを設定できます。また、これらの設定値を使う・使わないにかかわらず、parserプロパティに渡されます。

以下の設定では、最新のecmaサポートバージョンが設定され、ecmaモジュール（import、export形式）が問題なく解析されます。

```
    "parserOptions": {
        "ecmaVersion": "latest",
        "sourceType": "module"
    },
```

ただし、Syntaxを解析するだけなので、例えば、es2021で追加されたグローバル変数を理解するには、別途、envにて、以下のように指定する必要があります。

実は、envで以下のように設定すると、暗黙的にparserOptionsのecmaVersionにes2021が設定されるので、parserOptionsを再設定する必要はありません。

```
    "env": {
        "es2021": true
    },
```

<br/>

- [plugins](https://eslint.org/docs/latest/use/configure/plugins)

カスタム設定ファイルや、ルール、環境などが詰め込まれたもので、npm インストールにてプロジェクトに追加可能です。extendsに設定可能なルール群も同様にnpmパッケージにて共有されます。

pluginはpluginsへの設定だけでは効力が無く、plugins設定した上で、extendsやrulesに引用することができます。

extendsとpluginsを明確に切り分けようとすると混乱するので、pluginsはカスタム設定群で、その中からextendsにも設定できるし、rulesにも設定できる、柔軟性が高いものという認識でOKだと思います。

一応、違いについての[参考記事](https://stackoverflow.com/questions/53189200/whats-the-difference-between-plugins-and-extends-in-eslint)です。
対比されているわけではなく、それぞれの特徴がピックアップされていますね。

```
    // 以下のようにplugin:xxxxをextendsに設定することで、
    // plugins内のカスタム設定ファイルを継承することができます。
    "extends": [
        "eslint:recommended",
        "plugin:react/recommended",
        "plugin:@typescript-eslint/recommended"
    ],
    "plugins": [
        "react",
        "@typescript-eslint"
    ],
```

<br/>

- [rules](https://eslint.org/docs/latest/use/configure/rules)

ここまでの動作チェックでは、rulesが空にも関わらず、ESLintが動いてくれていたと思います。それは、extendsに設定してあるファイルにrulesがちゃんと設定してあったからです。

rulesに記載することで、extendsで継承された設定ファイル内に記載があるrulesを上書きしたり、pluginsからカスタムルールを適用させることが可能です。もちろん、ビルトインルールもあるので、extendsやplugins無しでもそれらのルールを適用させることもできます。

以下はビルトインルールを適用させる場合のrulesです。

```
{
    "rules": {
        "quotes": ["error", "double"]
    }
}
```

<br/>

## 終わりに

今回は、あくまで公式ドキュメントをベースに動作チェックでざっくりと理解を深めました。
あとは、pluginsを自作すれば、完全な理解にいたりそうな気がしています。

## 参考

[ESlint公式ドキュメント](https://eslint.org/docs/latest/use/configure/)

[extendsとplugins](https://stackoverflow.com/questions/53189200/whats-the-difference-between-plugins-and-extends-in-eslint)