【実務・中級編】ESLint Flat Configでのプラグイン自作を簡略化:『eslint-plugin-local-rules』を使わない最新手法 – デバッグ・コード品質・テストツール生産性向上バイブル

ESLint Flat Configを「真の武器」に。local-rulesに頼らないカスタムプラグインの極意

テックリード諸君、日々増え続けるレガシーなルールや、チーム固有の「守るべき作法」に疲弊していないか?

これまで、プロジェクト固有の静的解析ルールを作るために `eslint-plugin-local-rules` を導入し、Flat Configへの移行でビルドエラーに頭を抱えた経験はないだろうか。結論から言おう。もう、外部パッケージに頼る必要はない。

ESLint Flat Configの本質は「単なるJSオブジェクトの集積」にある。この特性を活かせば、プロジェクトルートに置いたファイルを直接インポートするだけで、自作ルールを即座にCI/CDのパイプラインに組み込める。今日は、その「最もシンプルで、最も拡張性の高い」設計手法を授ける。

—

1. なぜ「外部プラグイン」を使わないのか?

従来の `eslint-plugin-local-rules` は、ルールを読み込むためのメタデータ管理が煩雑であり、何より「ESLintのライフサイクル」と「Node.jsのモジュール解決」の間に不要な抽象レイヤーを挟んでいた。

Flat Configの世界では、`plugins` フィールドにはオブジェクトを直接渡せる。つまり、プロジェクト内の特定のディレクトリをモジュールとして扱い、ESLintに直接読み込ませることで、以下のメリットが享受できる。

  • 型安全性の確保: ルール定義自体をTSで書き、型定義を共有できる。
  • テストの爆速化: `RuleTester` をそのままテストファイルにインポートするだけで、JestやVitestでの単体テストが即座に動く。
  • ゼロ依存: ツール側のアップデートを待つ必要がない。

—

2. 実践:プロジェクトルートでの自作ルール管理術

ディレクトリ構造を以下のように標準化する。

/my-project
├── eslint.config.js # メインのFlat Config
├── scripts/
│ └── eslint-rules/ # ここに自作ルールを格納
│ ├── index.js # プラグインの定義(後述)
│ └── no-console-in-prod.js # 個別のルール定義

ステップ1:ルール本体の作成 (`scripts/eslint-rules/no-console-in-prod.js`)

まずは、特定の条件でエラーを吐く単純なルールを作る。

// scripts/eslint-rules/no-console-in-prod.js
module.exports = {
meta: {
type: ‘problem’,
docs: { description: ‘本番環境でのconsole出力を禁止する’ },
fixable: ‘code’,
},
create(context) {
return {
CallExpression(node) {
if (node.callee.object?.name === ‘console’) {
context.report({ node, message: ‘本番コードにconsoleを残すな!’ });
}
},
};
},
};

ステップ2:プラグインとして統合 (`scripts/eslint-rules/index.js`)

// scripts/eslint-rules/index.js
const noConsoleInProd = require(‘./no-console-in-prod’);

// ESLintのプラグイン仕様に合わせてエクスポート
module.exports = {
rules: {
‘no-console-in-prod’: noConsoleInProd,
},
};

ステップ3:Flat Configでの読み込み (`eslint.config.js`)

ここがアーキテクチャの肝だ。`require` でモジュールを直読みし、`plugins` プロパティにマッピングする。

// eslint.config.js
const localRules = require(‘./scripts/eslint-rules/index’);

module.exports = [
{
plugins: {
// プレフィックスを ‘local’ と定義
local: localRules,
},
rules: {
// 適用したいルールを記述
‘local/no-console-in-prod’: ‘error’,
},
},
];

—

3. 生産性を極限まで高める「神」テクニック

A. テストを「開発体験」の核にする

`RuleTester` を使用する際、`eslint-plugin-local-rules` を経由するとテストが重くなるが、直読みなら `vitest` 等で高速に回せる。

// scripts/eslint-rules/__tests__/no-console-in-prod.test.js
const { RuleTester } = require(‘eslint’);
const rule = require(‘../no-console-in-prod’);

const tester = new RuleTester({ languageOptions: { ecmaVersion: 2022 } });
tester.run(‘no-console-in-prod’, rule, {
valid: [‘console.log()’], // ここは適切に調整
invalid: [{ code: ‘console.log()’, errors: [{ message: ‘…’ }] }],
});

B. 設定ファイルの「共有ルール」

チーム規模が拡大したら、`eslint.config.js` を関数化し、`baseConfig` として配布せよ。

// eslint.config.base.js
module.exports = (customRules) => [
{
plugins: { local: customRules },
rules: { … }
}
];

これを各マイクロサービスで `require` して拡張すれば、「全リポジトリで同じカスタムルールを維持する」という悪夢から解放される。

—

4. テックリードからの提言:なぜこれが「最強」なのか

多くのエンジニアがツールに依存しすぎることで、ESLint内部の構造をブラックボックス化させている。しかし、今回紹介した手法は、「ESLintとは単なるAST(抽象構文木)のトラバーサルエンジンである」という本質に立ち返るものだ。

  • パフォーマンス: 不要なファイル走査が消えるため、CLIの実行速度が向上する。
  • デバッグ: 異常が発生しても、`scripts/` 内のコードに `console.log` を仕込むだけで、解析プロセスを完全にトレースできる。

「ツールは使うな、飼い慣らせ。」

この知見が、君たちのチームのコード品質を次の次元へ引き上げる一助となれば幸いだ。さあ、今すぐ `local-rules` の依存を消し去り、自分たちの手で解析エンジンを制御する快感を味わってほしい。

タイトルとURLをコピーしました