🧌

ESLintカスタムルールの作り方

2024/01/20に公開

はじめに

こんにちは!Cordeliaです。今回はESLintのカスタムルールの作り方についてご紹介します。プロジェクトのローカルパッケージとして作成し、それをプラグインとしてESLintに認識させる方法を取ります。参考になれば嬉しいです。


注意点

  • ESLintの詳細は説明しません。
  • TypeScriptは使いません。


ゴール

プロジェクトを作り、その中でESLintに警告を出してもらいます。
以下のコードにおいてbadという文字列リテラルが定義されていたら警告を出す簡単なルールを作成します。

// OK
const str1 = "good";

// NG
const str2 = "bad";


最終的なプロジェクト構成

myapp/
├── node_modules
├── src/
│   ├── lib/
│   │   └── eslint-plugin-custom-rules/
│   │       ├── no-bad-literal.js
│   │       ├── index.js
│   │       └── package.json
│   └── app.js
├── .eslintrc.js
├── package-lock.json
└── package.json


作成

まずはプロジェクトを作成します。

mkdir myapp
cd $_
npm init -y
npm i -D eslint


リント対象のファイルです。

app.js
const str1 = "good";
const str2 = "bad";


ルールを定義します。

no-bad-literal.js
module.exports = {
  meta: {
    type: "problem",
    docs: {
      description: "文字列リテラルの'bad'が使われていたら警告を出す",
    },
  },

  create(context) {
    return {
      Literal(node) {
        if (node.value && typeof node.value === "string") {
          if (node.value.includes("bad")) {
            context.report({
              node,
              message: "文字列リテラルに'bad'は使えません。",
            });
          }
        }
      },
    };
  },
};

metaはこのルールのメタデータを定義し、create()ではソースコードを検証してその結果を返します。ここではリテラルの存在チェックをした後に、それがbadという文字列なら警告メッセージを表示するという処理をしています。

ESLintはソースコードをASTという形で解析しています。ASTとは、抽象木構文(Abstract Syntax Tree)のことで、ソースコードを扱いやすいよう加工したデータ構造のことです。加工後のデータはJSONデータになっていて上のコードで言えばLiteralがそれに当たり、これをノードと言います。ソースコード中のリテラル情報はこのLiteralが持っているんですね。
ノードはそれ自身がメソッドになっていて、引数に自身が保持するデータを受け取ります。これを検査する訳です。

以下は const str2 = "bad"; をASTに変換したものです。

これは AST Explorer で確認できます。左側に解析したいコードを貼り付けると、右側にASTが表示されます。実際にコードを貼り付けてやってみてください。
ルールの処理をまとめると、

  1. ルールのメタデータを定義
  2. 必要なノードを見つけてそれを検査する
  3. 結果を返す

となります。

続いてプラグインのindex.jspackage.jsonも作成します。

eslint-plugin-custom-rules/package.json
{
  "name": "eslint-plugin-custom-rules",
  "version": "1.0.0",
  "private": true,
  "main": "index.js"
}

eslint-plugin-custom-rules"がプラグイン名です。

eslint-plugin-custom-rules/index.js
module.exports = {
  rules: { "no-bad-literal": require("./no-bad-literal") },
};

no-bad-literalがルール名です。


パッケージをプロジェクトにインストールします。

npm i -D ./src/lib/eslint-plugin-custom-rules

するとプロジェクトルートのpackage.jsonはこの様になっているはずです。

package.json
{
  // 一部抜粋
  "devDependencies": {
    "eslint": "^8.56.0",// ESLintのバージョンは状況により違うのでこの限りではありません。
    "eslint-plugin-custom-rules": "file:src/lib/eslint-plugin-custom-rules",
  },
}


次はESLintの設定ファイルを作成します。

.eslintrc.js
// 今回の動作に必要な項目のみ記述しています。
module.exports = {
  env: {
    es2021: true,
  },
  plugins: ["custom-rules"],
  rules: {
    "custom-rules/no-bad-literal": "warn",
  },
};

plugins: ["custom-rules"]は先ほどインストールしたeslint-plugin-custom-rulesからeslint-pluginの部分を省略した名前を記述し、"custom-rules/no-bad-literal"custom-rulesというパッケージのno-bad-literalというルールを使う、という意味です。

それではESLintを実行してみます。

npx eslint ./src/app.js
/Users/cordelia/Projects/myapp/src/app.js
  2:14  warning  'bad'という文字列リテラルは使えません。  custom-rules/no-bad-literal

✖ 1 problem (0 errors, 1 warning)

この様な結果が表示されれば成功です🎉


おわりに

本来であればルールの動作確認の為にテストを作成しますが、今回は割愛しました。テストの作成についてや、その他詳しい情報は公式ドキュメントを参照してください。またカスタムルールはeslint-plugin-local-rulesなどのpackageを使う方法や、最新のESLintではFlat configを使う方法もありますので、必要であればそちらも併せて情報を参照してください。

最後までお読みいただき、ありがとうございました。


参考情報

https://eslint.org/docs/latest/extend/custom-rule-tutorial

https://ronvalstar.nl/custom-local-eslint-rules

https://zenn.dev/kazuwombat/articles/2a870356528783

Discussion