みどりのさるのエンジニア

ESLintをちゃんと理解する

2021年01月19日

ESLint は毎回プロジェクト開始の最初だけ設定して、その後あまり触ることが無く毎回なんとなくで設定して、新しく設定する時に設定項目を忘れてしまう事が多いので、各設定項目や導入プラグインの目的をちゃんと理解したいと思い、まとめました。

ESLintとは?

ESLint は EcmaScript/JavaScript のリンターツールです。チームでコードの利用規約を決め、各自がルールを守って開発するのは大変です。新しくメンバーが増えたときにも、その人がチームのルールに馴染むまで何度もコードレビューで指摘する必要があるかもしれません。ESLintを導入することで、コードのルールチェックを自動化して、統一性のあるコードを担保することができます。

ESLintの設定

ESLintは設定ファイルで全てルールのON/OFFなどを設定することができます。ESLintの基本的な設定項目について見ていきます。

Configuring ESLint - ESLint - Pluggable JavaScript linter

Rules

rules オプションはESLintがどのような構文をエラーとするか無視するかを設定するオプションです。デフォルトで定義されているルールとしては「文末にセミコロンを必須とする」 semi などがあります。ユーザーは設定ファイルでこれらのルールを設定することで、自分好みにリンターの挙動をカスタマイズすることができます。
ルールは off or 0 , warn or 1 , error or 2 を指定することで設定ができます。文字列でも数値でも、どちらを指定しても問題ありません。

ESLintは構文チェックで error となるルールが見つかった場合は 0以外のexit code で終了し、 warn の場合は警告として出力するが構文チェックとしては通過するようになっています。全てを error に設定するのも手ですが、Linterの構文エラーを解消するために思わず時間がかかる場合もあるので、チーム内で相談してルールの厳格性を決めると良いです。

次の例では "quotes": ["error", "single"] を指定して ' を使わない場合に構文エラーとしています。また "no-unused-var": "warn" と設定することで、未使用の変数を警告として扱うようにしています。 off は次で紹介する extends を使った場合などに併用する場合が多いです。

// error  Strings must use singlequote  quotes
const name = "taro"
// warning  'age' is assigned a value but never used  no-unused-vars
const age = 10
console.log(name)
// .esrintrc
{
	"rules": {
		"quotes": ["error", "single"],
        "no-unused-var": "warn"
    }
}

Extends

extends オプションではベースとなる設定を指定することで、その設定を継承することができます。先の rules オプションの例では、一つ一つルールを設定していました。しかし、毎回プロジェクトごとにルールを全て設定していくのは大変な作業です。そこで ESLint には公式が推奨しているプリセットが存在します。

extends オプションで eslint:recommended を指定することで、ベース設定を継承して一括で設定をすることができます。

どのようなルールが設定されるかは 公式ドキュメント を参照すれば確認することができます。

{
	"extends": "eslint:recommended"
}

ベースとなる設定で一部のルールを無効にしたい場合には、先ほど説明した rules オプションで off を指定することで設定できます。

{
	"extends": [
		"eslint:recommended"
	],
	"rules": {
        "no-ununsed-vars": "off"
    }
}

extends は配列で複数指定することが可能です。 no-unsued-vars などのルール設定が重複していた場合は、後から指定した設定で上書きされます。
次の例では、 eslint:recommended で定義されている設定が plugin:react/recommended で定義されている設定で上書きされます。

{
	"extends": [
        "eslint:recommended",
        "plugin:react/recommended"
    ]
}

Parser Options

ESLintはデフォルトでES5の構文をサポートしています。ES6以降の構文やJSXの構文を対象にする場合は parserOptions で設定します。

ここでは次の3つの項目について説明します。

  • ecmaVersion
  • sourceType
  • ecmaFeatures

ecmaVersion はEcmaScriptのバージョンを指定します。デフォルトの値は 5 です。
デフォルトの設定で次のコードを構文チェックした場合は The keyword 'const' is reserved という構文エラーが発生します。

// error  Parsing error: The keyword 'const' is reserved
const hello = () => {
    console.log("hello");
};

ecmaVersion のを指定すれば、構文チェックする EcmaScript のバージョンを変更できます。

{
	"parserOptions": {
		"ecmaVersion": 2020
	}
}

sourceType はモジュールのタイプを指定します。デフォルトの値は "script" です。

デフォルトの設定で ESLint を利用すると import/export などのESモジュールを利用すると構文エラーとなります。

// 'import' and 'export' may appear only with 'sourceType: module'
import { pick } from 'lodash';

import/export を利用する場合は "module" を指定します。

{
	"parserOptions": {
		"sourceType": "module"
	}
}

ecmaFeatures は JavaScript で有効にする追加の構文拡張を指定します。

例えば、JSX は JavaScript の構文拡張のため、デフォルトの設定では構文エラーとなります。

// error  Parsing error: Unexpected token <
const Hello = ({ name }) => <div>{name}</div>;

"jsx": true を指定することで、JSX も構文チェックができます。

{
    "parserOptions": {
        "ecmaVersion": 2020,
        "ecmaFeatures": {
            "jsx": true
        }
    }
}

Parser

ESLintはデフォルトで Espree を構文解析のパーサーとして利用しています。TypeScript を構文チェックする場合に type キーワードなどは ECMAScript の構文ではないなため構文解析に失敗します。そのため、 Parser オプションで別のパーサーを指定する必要があります。

// error  Parsing error: Unexpected token User
type User = {
    name: String;
}

TypeScriptの構文解析する場合は @typescript-eslint/parser をパーサーを指定します。

$ yann add -D typescript @typescript-eslint/parser
{
  "parser": "@typescript-eslint/parser"
}

Processor

一部のプラグインは特定の機能としてプロセッサーを提供しています。プロセッサーは Markdown ファイルから JavaScript のコードを抜き出して ESLint をかけたりできます。
例えば、特定のファイルパスで一部のプラグインを無効にする mradionov/eslint-plugin-disable では、次のように processer オプションでプロセッサーを指定します。

{
    "plugins": ["react", "disable"],
    "processor": "disable/disable",
    "overrides": [
        {
            "files": ["tests/**/*.test.js"],
            "settings": {
                "disable/plugins": ["react"]
            }
        }
    ]
}

Environments

ブラウザや jest などのグローバル変数や関数を定義済みとして認識するための設定です。
例えば、jest ではグローバル関数として describeit などの関数が定義されますが、ESLint はその関数が定義されていることを認識することができません。 no-undef のルールを有効にした状態で、jestのコードを構文チェックを未定義のエラーが発生します。

// error  'describe' is not defined  no-undef
describe("test", () => {
	// error  'it' is not defined  no-undef
    it("test", () => {});
});
{
    "parserOptions": {
        "ecmaVersion": 6
    },
    "rules": {
        "no-undef": "error"
    },
}

env オプションで jest を設定することで describe などのグローバル関数が定義済みであることを ESLint に伝えることで、構文エラーを解消することできます。
設定可能な環境の一覧は 公式のページ で確認できます。

{
  "env": {
    "jest": true,
  }
}

Globals

環境に依存しない独自に定義したグローバル変数をESLintに認識させるための設定です。
値として writable , readonly , off が設定できます。

// error  'bar' is not defined  no-undef
bar = "bar"
// error  'hoge' is not defined  no-undef
console.log(hoge)
{
    "rules": {
        "no-undef": "error",
    "no-global-assign": "error"
    },
    "env": {
        "browser": true
    }
}

writable, readonly は定義済みのグローバル変数に対しての可能な操作を表しています。グローバル変数を上書きする事はほぼ無いのと、そもそも上書き自体がバッドプラクティスなので、基本的には readonly を指定すれば大丈夫です。

// "readonly" と指定したグローバル変数を上書きしているので、まだ構文エラーが発生している。
// error  Read-only global 'bar' should not be modified  no-global-assign
bar = "bar"
console.log(hoge)
// .eslintrc
{
    // (省略)
	"globals": {
		"hoge": "readonly",
		"bar": "readonly"
    }
}

上の例では、no-global-assign を有効にしている状態で globals オプションで readonly を設定している bar のグローバル変数に対して上書きを実行しており、ESLint の構文エラーが発生しています。
グローバル変数を上書きする場合は writable を指定することで、構文チェックのエラーを解消できます。

hoge = 'new hoge'
console.log(hoge)
// .eslintrc
{
    // (省略)
	"globals": {
		"hoge": "readonly",
		"bar": "writable"
    }
}

Plugins

ESLintでは「JSX が記述されている場合は React モジュールがインポートされていること」などの独自ルールをプラグインという形で組み込むことができます。
plugins オプションでは利用したいプラグインを配列の形で指定します。また、プラグインを利用する場合は、事前にパッケージとしてインストールしておく必要があります。

例として、React の構文チェックプラグインである yannickcr/eslint-plugin-react を導入してみます。

最初にプラグインをインストールします。

$ yarn add -D eslint-plugin-react

次にインストールしたプラグインを plugins オプションで指定して、使いたいルールを有効化します。
プラグイン名を指定するときに eslint-plugin のプレフィックは省略可能です。

{
    // eslint-plugin-react でもOK
	"plugins": ["react"],
	"parserOptions": {
		"ecmaFeatures": {
			"jsx": true
		}
	},
	"rules": {
		"react/jsx-uses-react": "error",
        "react/jsx-uses-vars": "error"
    }
}

先の例では plugins オプションの例を示すために、敢えて冗長な書き方をしています。プラグインが提供するプリセットの設定をそのまま適用する場合には、次のように extends オプションで一括指定することも可能です。

{
	"extends": [
	    "eslint:recommended",
	    "plugin:react/recommended"
    ]
}

Shared Settings

settins オプションはESLintのルール実行時に参照される共通の設定値です。独自のカスタムルールから共通の値に参照したい場合などに利用できます。

yannickcr/eslint-plugin-react を利用する場合に次の設定を書く事でReactのバージョンを指定することができます。

{
	"plugins": ["react"],
	"settings": {
		"react": {
			"version": "16.3"
        }
    }
}

Prettierとの併用

Prettier とはコードの自動整形ツールです。

Prettier は eslint --fix では整形されないコードも整形してくれるため、ESLint よりも整形ツールとして優れているという利点があります。 次のようなコードは eslint --fix は自動整形してくれませんが、 Prettier はコードを自動整形してくれます。

// eslint --fix ではこのコードは整形されない
const getAllPostsImpl: GetAllPosts = new GetAllPostsImpl(postsRepository, directoryRepository);
// prettier だと整形される
const getAllPostsImpl: GetAllPosts = new GetAllPostsImpl(
	postsRepository, 
	directoryRepository
);

ESLint と Prettier の併用方法をインターネットで検索すると eslint-plugin-prettier をインストールして、ESLint 上で Prettier を実行する方法を見かけることがあります。しかし、この方法では Prettier を直接実行するよりも処理に時間がかかったり、自動整形されるコードが構文エラーとして表示される問題があり 公式のドキュメント で非推奨とされています。

対応方法としては eslint-config-prettier を導入してPrettierと競合するルールをオフにして、prettier && eslint と先に Prettier でコードを整形をした後に ESLint で構文チェックを実行します。

{
	"extends": [
		"eslint:recommended",
		"prettier"
	]
}
# 実行
$ prettier && eslint

Reactの設定例

インストールするパッケージ

React v17.0 以降を利用する場合は Reactのモジュールをインポートする必要がなくなったので react/react-in-jsx-scopeoff にしておくことをオススメします。

設定ファイル

{
    "plugins": [
        "react-hooks"
    ],
    "extends": [
        "eslint:recommended",
        "plugin:react/recommended",
    ],
    "parserOptions": {
        "sourceType": "module",
        "ecmaVersion": 2020
    },
    "env": {
        "browser": true,
        "jest": true
    },
    "rules": {
        "react-hooks/rules-of-hooks": "error",
        "react-hooks/exhaustive-deps": "warn",
        "react/react-in-jsx-scope": "off",
        "react/prop-types": "off",
        "react/display-name": "off",
    }
}

TypeScriptの設定例

インストールするパッケージ

$ yarn add -D @typescript-eslint/parser @typescript-eslint/eslint-plugin

設定ファイル

{
    "extends": [
        "eslint:recommended",
        "plugin:@typescript-eslint/recommended"
    ],
    "parserOptions": {
        "sourceType": "module",
        "ecmaVersion": 2020
    },
    "env": {
        "browser": true,
        "jest": true
    }
}