背景
ある開発者の環境ではLintエラーが発生するけど、自分の環境では発生しないという現象がきっかけでした。(原因は本記事とは関係のないところにあったのですが、、)
ESLint自体を見直す機会になったので、仕事がない分、勉強したんだぞという証拠を残しておきます。
環境
- OS
Windows WSL2 Ubuntu 20.04
- Node
$node -v
V18.12.1
$npm -v
8.19.2
- ESLint
.eslintrc.jsonで設定を記述します。
※V9.0.0で廃止されるスタイルのようです。知りませんでした。まぁでもコンセプトは変わらないので大丈夫
$npx eslint --version
V8.41.0
セットアップ
ゼロから作ってみると達成感が増すので、ドキュメントに沿って、設定してみます。
# 適当な作業フォルダを作成
$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": {
}
}
ESLintがコードチェックする際に、グローバル変数などを認識するために設定します。例えば、以下であれば、Browserが持つグローバル変数とES2021で定義されているグローバル変数を理解してくれます。
"env": {
"browser": true,
"es2021": true
},
これは、定義されていない変数が使われていないかをチェックするルールです。何気なく使っている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が理解できていないため、未定義変数として扱い、エラーが出力されていることが分かりますね。
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に配置しました。
このプロパティは、"特定のファイル"に対して、ルール実行の挙動を変更するものです。したがって、filesプロパティを指定することが必須となります。
全体ルールとしては、ダブルクォーテーションはエラーですが、overrides内で指定されているfilesに対してはシングルクォーテーションがエラーとなります。
{
"rules": {
"quotes": ["error", "double"]
},
"overrides": [
{
"files": ["bin/*.js", "lib/*.js"],
"excludedFiles": "*.test.js",
"rules": {
"quotes": ["error", "single"]
}
}
]
}
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が理解されてしまうので外しました。
ドキュメントでは、指定する必要が無いと記載されているので、継承されたものを暗黙的に利用するか、明示的に記載するかはどちらでも良いと思います。
JavaScript Syntaxをどのように解析するかを設定できます。また、これらの設定値を使う・使わないにかかわらず、parserプロパティに渡されます。
以下の設定では、最新のecmaサポートバージョンが設定され、ecmaモジュール(import、export形式)が問題なく解析されます。
"parserOptions": {
"ecmaVersion": "latest",
"sourceType": "module"
},
ただし、Syntaxを解析するだけなので、例えば、es2021で追加されたグローバル変数を理解するには、別途、envにて、以下のように指定する必要があります。
実は、envで以下のように設定すると、暗黙的にparserOptionsのecmaVersionにes2021が設定されるので、parserOptionsを再設定する必要はありません。
"env": {
"es2021": true
},
カスタム設定ファイルや、ルール、環境などが詰め込まれたもので、npm インストールにてプロジェクトに追加可能です。extendsに設定可能なルール群も同様にnpmパッケージにて共有されます。
pluginはpluginsへの設定だけでは効力が無く、plugins設定した上で、extendsやrulesに引用することができます。
extendsとpluginsを明確に切り分けようとすると混乱するので、pluginsはカスタム設定群で、その中からextendsにも設定できるし、rulesにも設定できる、柔軟性が高いものという認識でOKだと思います。
一応、違いについての参考記事です。
対比されているわけではなく、それぞれの特徴がピックアップされていますね。
// 以下のようにplugin:xxxxをextendsに設定することで、
// plugins内のカスタム設定ファイルを継承することができます。
"extends": [
"eslint:recommended",
"plugin:react/recommended",
"plugin:@typescript-eslint/recommended"
],
"plugins": [
"react",
"@typescript-eslint"
],
ここまでの動作チェックでは、rulesが空にも関わらず、ESLintが動いてくれていたと思います。それは、extendsに設定してあるファイルにrulesがちゃんと設定してあったからです。
rulesに記載することで、extendsで継承された設定ファイル内に記載があるrulesを上書きしたり、pluginsからカスタムルールを適用させることが可能です。もちろん、ビルトインルールもあるので、extendsやplugins無しでもそれらのルールを適用させることもできます。
以下はビルトインルールを適用させる場合のrulesです。
{
"rules": {
"quotes": ["error", "double"]
}
}
終わりに
今回は、あくまで公式ドキュメントをベースに動作チェックでざっくりと理解を深めました。
あとは、pluginsを自作すれば、完全な理解にいたりそうな気がしています。