ESLintの「カスタムルール」をテストせよ:Lint運用をブラックボックス化させない最強の守護術
チーム開発において、ESLintのカスタムルールを作成することは「特定の設計思想を強制する強力な武器」になります。しかし、そのルール自体が不完全であれば、CIで誤検知を連発し、開発者の信頼を失い、かえって生産性を阻害します。
本稿では、`RuleTester`を用いたカスタムルールの品質保証術を軸に、ESLintの真の実力を引き出すアーキテクチャ設計について伝授します。
—
1. なぜ「RuleTester」を避けて通れないのか
カスタムルールはAST(抽象構文木)を操作する性質上、予期せぬエッジケースが頻発します。手動でプロジェクトに組み込んでLintを走らせるようなデバッグは、開発スピードを著しく低下させます。
`RuleTester`はESLintが公式に提供するテストユーティリティであり、「特定のコードが期待通りに検証されるか」を分離して検証するための最高峰のツールです。
RuleTesterの基本形と設計思想
単に「エラーが出るか」を確認するだけでは不十分です。以下の3つの観点を必ず網羅してください。
- Valid (正常系): ルールが適用されるべきでないコードが、正しくスルーされるか。
- Invalid (異常系): ルールが適用されるべきコードが、意図したエラーメッセージ・修正案を出すか。
- Fixer (自動修正): `fixable`なルールの場合、修正後のコードが構文として正しいか。
// rule-testerの導入例
const { RuleTester } = require(“eslint”);
const rule = require(“./my-custom-rule”);
const ruleTester = new RuleTester({
// パーサー設定をテスト環境と同期させる(TSの場合は必須)
parserOptions: { ecmaVersion: 2020, sourceType: “module” }
});
ruleTester.run(“my-custom-rule”, rule, {
valid: [“const validVariable = 1;”], // 正常系:静的解析でエラーが出ないはずのコード
invalid: [
{
code: “const var = 1;”, // 異常系:エラーを誘発するコード
errors: [{ messageId: “unexpectedVar” }], // 検証すべきエラーID
output: “const validVar = 1;” // 自動修正後の期待値(Fixerテスト)
}
]
});
—
2. 開発効率を爆速化する「隠れた」設定とテクニック
VS Codeの神設定:Lintのフィードバックループを最短にする
`eslint.rules.customizations` を `settings.json` に追記することで、ルール開発中のストレスが激減します。
// .vscode/settings.json
{
“eslint.validate”: [“javascript”, “typescript”],
// エラーの重要度を強制的にオーバーライド
“eslint.rules.customizations”: [
{ “rule”: “my-plugin/my-custom-rule”, “severity”: “warn” }
],
// ファイル保存時にPrettierとESLintを競合させず実行する
“editor.codeActionsOnSave”: {
“source.fixAll.eslint”: “always”
}
}
チーム開発の黄金律:「設定の継承」と「シェアード設定」
個別のリポジトリに巨大な `eslintrc` を置くのはアンチパターンです。「Core Plugin」と「Project Config」を分離し、npmパッケージ経由で配布してください。
- `eslint-config-company-base`: 共通のコーディング規約。
- `eslint-plugin-company-internal`: ビジネスロジックに関連するカスタムルール。
これにより、ルールを修正した際に全プロダクトへ一括適用が可能になります。
—
3. 実践:複雑なルールの品質を保証する「テストケース設計術」
正規表現やASTの深掘りロジックを含むルールでは、以下の「境界値テスト」を実装してください。
1. ネストの深さ: `IfStatement`の中に`IfStatement`がある場合の挙動。
2. スコープの隔離: 同じ変数名でもブロックが異なれば別物として扱えるか(`scope.through`や`scope.variables`の確認)。
3. コメントの無視: コード内に含まれるコメントが解析に悪影響を及ぼさないか。
実践的なテストコードの構築例
// テストケースのデータ構造化
const testCases = [
{
name: “ネストされた関数内でも正しく警告を出す”,
code: “function outer() { function inner() { var x = 1; } }”,
errors: [{ messageId: “avoidVar” }]
},
{
name: “コメントアウトされたコードは無視する”,
code: “// var x = 1;”,
errors: []
}
];
// ループでテストを実行し、可読性と保守性を担保する
testCases.forEach(({ name, code, errors }) => {
ruleTester.run(name, rule, {
valid: errors.length === 0 ? [code] : [],
invalid: errors.length > 0 ? [{ code, errors }] : []
});
});
—
4. アーキテクトからの提言:ルールは「ドキュメント」である
最後に、最も重要なことを伝えます。「ESLintのルールは、チームに対するドキュメントである」と認識してください。
ルールが違反した際に表示するメッセージには、「なぜそれがダメなのか」「どう修正すべきか」を明確に記述しましょう。テストコードの中で `errors` を検証する際、単にメッセージの存在確認をするのではなく、具体的なフィードバック内容までテスト対象に含めてください。
まとめ:明日からすべきこと
1. 既存のカスタムルールに `RuleTester` を適用する。
2. `Fixer` のテストを追加し、自動修正の安全性を担保する。
3. `eslint-plugin` を分離し、全プロジェクトで共有可能なパッケージを作る。
これらを徹底することで、あなたのチームのコードベースは「規律ある美しい状態」を維持し続け、レビューコストは劇的に削減されるはずです。技術とは、自動化することではなく、「人間が判断しなくていいことを機械に託すこと」なのです。