ESLint 設定を Glob ベースでがっつり書き直した

Prettier や TypeScript を使うようになって、3年前に構築してぼちぼち保守してきた自分用の共有設定 eslint-config-mysticatea をそのまま使えないことが増えてきました。また、1年ほど前に Glob ベースで設定できるようになっている ので、この際がっつり書き直すことにしました。

ついでに、ESLint 共有設定を作る際のプラクティスのようなものがまとまっていないと感じたので、まとめてみます。改めて整理してみると、本当にバッドノウハウの塊だなぁ...。互換性に縛られずリライトしたいですね...。

新しい共有設定の紹介

新しい設定は eslint-plugin-mysticatea/lib/configs にあります (eslint-config-mysticatea ではない)。

shell
// まだ調整中なので @next が要る
$ npm install --save-dev eslint eslint-plugin-mysticatea@next
.eslintrc.yml
extends:
  - plugin:mysticatea/es2018 # es5, es2015, es2016, es2017 もある。目的に応じて。

中身は以下のようになっています。

  • バグっぽいコードを見つけるために ESLint コアルールと eslint-plugin-eslint-comments のルールを有効にしています。
  • 空白や改行については eslint-plugin-prettier 経由で Prettier を使っています。
  • .ts ファイルでは typescript-eslint-parsereslint-plugin-typescript のルールも有効にしています。
  • .vue ファイルでは vue-eslint-parsereslint-plugin-vue のルールも有効にしています。
  • test ディレクトリの中は mocha のグローバル変数を有効にしています。
  • scripts ディレクトリの中は Node.js 環境であるとします (eslint-plugin-node のルールも有効にして、.js ファイルはスクリプト、.mjs ファイルはモジュールとして扱う)。
  • .eslintrc.jswebpack.config.js 等の一部ファイルは Node.js 環境であるとします。

また、開発目的に応じて追加の設定を行うことができます。

.eslintrc.yml
extends:
  - plugin:mysticatea/es2018
  - plugin:mysticatea/+browser # ブラウザ向けのグローバル変数を定義する。
                               # ただし name, event, top 等、よく使うような名前は定義しない。
.eslintrc.yml
extends:
  - plugin:mysticatea/es2018
  - plugin:mysticatea/+node # 全体を Node.js 環境とする。
.eslintrc.yml
extends:
  - plugin:mysticatea/es2018
  - plugin:mysticatea/+eslint-plugin # eslint-plugin 開発向けの設定を追加する。
                                     # eslint-plugin-node と eslint-plugin-eslint-plugin の
                                     # ルールを追加。
.eslintrc.yml
extends:
  - plugin:mysticatea/es2018
  - plugin:mysticatea/+modules # ES modules のオプションと追加のルールを有効にする。
  - plugin:mysticatea/+browser

Glob-based な設定によって、いつも使っているディレクトリ構造について1行で設定できるようになりました。.vue .ts に関しても追加の設定なしで検証かけられて楽になりました。

共有設定のプラクティス

1. 共有設定機能を使わない

共有設定機能、つまり eslint-config-xxx のことです。この機能は ESLint のプラグイン機構が設計されるより前に作られたため、プラグインと共に使おうとすると不都合が多いです。

例えば、プラグインのパッケージは eslintrequire("eslint-plugin-xxx") で読み込める位置にインストールする必要がありますが、共有設定の dependencies にプラグインを追加しても期待通りにインストールできない場合が多いです。人気のある eslint-config-airbnb の Usage に複雑なインストール手順が書かれているのはそのため。

そこで、プラグイン機構 (eslint-plugin-xxx) を共有設定機能の代わりに使うのがおすすめです。
プラグインは、以下のように configs プロパティで共有設定を公開することができます。現在も多くのプラグインでは、これを利用して plugin:node/recommended のような推奨設定を提供していますね。

eslint-plugin-hoge/index.js
module.exports = {
    configs: {
        fuga: {
            // ここに設定を書く
        }
    },
    rules: {},
}
.eslintrc.yml
extends:
  - plugin:hoge/fuga

2. 他のプラグインを使うときはルールを再定義する

次の例は良くない例です。

eslint-plugin-hoge/index.js
module.exports = {
    configs: {
        fuga: {
            plugins: ["node", "hoge"],
            rules: {
                "node/no-unsupported-features": "error",
            },
        },
    },
    rules: {},
}

eslint-plugin-node を使う設定が書かれているので、この設定を使う場合は、前項の eslint-config-airbnb のようにインストール手順で eslint-plugin-node もインストールするよう要請する必要があります。

代わりに、自身の中にルールを再定義するのがおすすめです。

eslint-plugin-hoge/index.js
const nodePlugin = require("eslint-plugin-node")

module.exports = {
    configs: {
        fuga: {
            plugins: ["hoge"],
            rules: {
                "hoge/node/no-unsupported-features": "error",
            },
        },
    },
    rules: {
        "node/no-unsupported-features": nodePlugin.rules["no-unsupported-features"],
    },
}

こうすることで eslint-plugin-node プラグインを読み込む主体が ESLint から eslint-plugin-hoge に変わるので、dependencies に記述した他のプラグインを確実に利用できます。そのため、eslint-plugin-hoge の利用者は eslint-plugin-hoge (と eslint 本体) だけをインストールすればそれを使えるようになります。

これは、共有設定とプラグインに関する議論の中で ESLint の作者が提案した方法です (eslint/eslint#3458)。いわゆる菱形継承問題があって自動的に処理するのが難しいため、このような提案になっています。バッドノウハウ。

3. 基本設定と環境別の設定に分離する

JavaScript は様々な用途で使われています。Web サイト、Web アプリに、Node.js アプリ (ESLint もそのうちの1つ)、Electron アプリ、Windows Script Host (WSH) や AppleScript 等のオートメーションツール、などなど。用途によって必要な設定は変わってきます。また、同じ環境でも使用するフレームワーク・ライブラリによって有用な検証ルールを提供してくれていたりします。

そこで、共有設定を作る場合には環境によらない基本的な設定と、環境ごとに特化した設定を分けておくと良いです。

私の場合は ECMAScript 言語仕様の範疇にある検証ルールを plugin:mysticatea/es2018 のような設定にまとめ、環境ごとに plugin:mysticatea/+browser, plugin:mysticatea/+node, plugin:mysticatea/+eslint-plugin のような追加設定を書いています。

eslint-config-airbnb では基本設定と React 用の追加設定とに分かれていますね。

4. テストを書く

作成した共有設定を保守していくために CI テストを書くと良いです。

  • eslint.CLIEngine オブジェクトを作ってプラグインを読み込み、正常動作することを確認する。
  • eslint-find-rules のようなツールを使って足りないルールを確認する。

などすると良いでしょう。

5. セルフホストする

プラグイン自身のコードの静的検証にそのプラグインを使えると格好いいです。

eslint-plugin-mysticatea/.eslintrc.js#L10-L19 のように、.eslintrc.js ファイルでシンボリック リンクを作ると自分自身を使えたりします。