12k
All articles

eslint-plugin-unicornによるJavaScriptコード品質の向上

ESLint 10向けのeslint-plugin-unicorn: flat configで導入し、recommendedまたはunopinionatedプリセットと自動修正可能なルールでJSとTSの品質を向上します。

OpenReplay Team
OpenReplay Team
eslint-plugin-unicornによるJavaScriptコード品質の向上

eslint-plugin-unicornは、300以上のルールを持つ大規模かつ主観的なESLintプラグインです。モダンなJavaScriptおよびTypeScriptのイディオムを推進し、微妙な品質バグを検出します。そのほとんどのルールは自動修正可能です。ESLint >=10.4、フラット設定、ESMが必要であるため、旧来の.eslintrcextends: 'plugin:unicorn/recommended'による設定はもはや機能しません。基本的な設定でESLintを実行しており、カスタムルールを自作せずにコード品質を向上させたい場合、このプラグインが最も手軽な方法です。

ただし、「主観的」であることには両面があります。一部のルールは意図せず二乗オーダーになるループや実際の正確性バグを防ぎますが、他のルールは既存のコードベースと相容れないスタイル上の好みを強制します。本ガイドではこの二つを分けて解説します。プラグインが何を強制するのか、どのルールが採用に値するのか、どれをダウングレードまたは無効化すべきか、そして現在のツールチェーンで動作するコピー&ペースト可能なeslint.config.mjsを紹介します。

重要なポイント

  • eslint-plugin-unicornは300以上のほぼ自動修正可能なルールを提供しており、v69(2026年6月)時点でESLint ≥10.4、フラット設定、ESMが必要です。ESLint 10がレガシーのeslintrcシステムを完全に削除したため、.eslintrcは機能しなくなりました。
  • 最短の導入手順:unicorn/recommendedを継承し、eslint --fixを実行した後、コードベースと相容れない少数のルールを無効化します。あるいはunopinionatedプリセットから始めると、主観的なルールがあらかじめ無効化されています。
  • プラグインはrecommendedunopinionated(recommendedから主観的なルールを除いたもの)、allの3つのプリセットをエクスポートします。
  • prefer-set-hasは、ループ内で意図せず二乗オーダーになる.includes()によるルックアップをO(n)のSet.has()チェックに変換します。またno-array-for-each.forEach()for…ofに書き換え、TypeScriptの型絞り込みを改善します。
  • @typescript-eslint/recommended-type-checkedと組み合わせることで、イディオムの強制と型を考慮したリンティングを統合できます。

eslint-plugin-unicornとはどのようなプラグインか

eslint-plugin-unicornは、コードをモダンでエラーの起きにくいJavaScript・TypeScriptパターンへと誘導するESLintルールのコレクションです。配列スキャンよりもSetによるメンバーシップ確認、.forEach()よりもfor…of、ベアモジュール名よりもnode:インポート指定子などを優先します。ESLint >=10.4、フラット設定、ESMが必要です。リポジトリでは300以上のルールを提供していると説明されており、最新バージョンは69.0.0で、ほぼ毎月リリースされています。

導入方法を左右する設計上の事実が2つあります。第一に、ほとんどのルールが自動修正可能であるため、多くの違反は手動編集ではなくeslint --fixで解消されます。第二に、プラグインはプリセット設定を提供しているため、ルールを一つずつ有効化する必要がありません。プラグインはグッドプラクティスを強制するrecommended設定と、非推奨のものを除くすべてのルールを有効化するall設定を別々にエクスポートしています。ルール一覧では絵文字でステータスを示しています。✅はrecommended設定に含まれるルール、☑️はunopinionated設定に含まれるルール、🔧は--fix CLIオプションで自動修正可能なルールを表します。

対象範囲は主にJavaScriptとTypeScriptですが、有用な補足もあります。ほとんどのルールはJavaScriptとTypeScriptを対象としていますが、@eslint/css@eslint/json@eslint/markdown@html-eslint/eslint-pluginなどの対応するESLint言語プラグインと組み合わせることで、他のファイル形式もリントできるルールも一部存在します。ほとんどのチームにとって、JS/TSのルールがインストールの主な理由となるでしょう。

フラット設定とESMによるeslint-plugin-unicornのインストール

プラグインと互換性のあるESLintをインストールし、フラット設定のeslint.config.mjs内でrecommendedプリセットを継承します。現在の要件はESLint 10.xです。ESLint 10はレガシーのeslintrcシステムを完全に削除し、フラット設定が唯一サポートされる形式となりました。そのため、extends: 'plugin:unicorn/recommended'を使った.eslintrc.jsonを示すチュートリアルは時代遅れです。ESLintは最新のNode.jsランタイム(ESLint npmページによるとNode 20.19+、22.13+、または24+)を必要とします。

npm install --save-dev eslint eslint-plugin-unicorn

JavaScriptとTypeScriptファイルを対象とした最小限の動作設定:

// eslint.config.mjs
import unicorn from 'eslint-plugin-unicorn';
import { defineConfig } from 'eslint/config';

export default defineConfig([
  {
    files: ['**/*.{js,mjs,cjs,ts,tsx}'],
    extends: [unicorn.configs.recommended],
    rules: {
      // ここにオーバーライドを記述
    },
  },
]);

これは公式の使用方法を反映しています。eslint-plugin-unicornからunicornをインポートし、eslint/configからdefineConfigをインポートした後、defineConfig内でfilesextends: [unicorn.configs.recommended]を設定します。filesによるスコープ設定は、同じ設定で非JavaScript言語もリントする場合に重要です。UnicornのJSルールは専用の設定オブジェクトに分離してください。

開始点として選択できるプリセットは3つあります:

  • recommended — 厳選されたデフォルト。
  • unopinionated — recommendedから主観的なルールを除いたもの。
  • all — 非推奨ルールを除くすべてのルール。

主観的なルールがノイズになりすぎると感じるチームは、unicorn.configs.recommendedunicorn.configs.unopinionatedに変更することで、以下で説明する手動チューニングの多くを省略できます。

移行に関する注意:eslintrcスタイルの設定のみを提供するプラグインに依存している場合は、@eslint/eslintrcFlatCompatでラップしてください。ESLint 10.xでは最新の@eslint/eslintrcを使用してください。

eslint-plugin-unicornで最も価値の高いルールと変換前後の例

最も価値の高いUnicornルールは、スタイルの好みではなくパフォーマンス、正確性、または可読性の問題を防ぐため、最初に採用する価値があります。以下の例はすべてeslint --fixが生成するものであり、手書きの理想的なコードではありません。

prefer-set-has — 意図せず二乗オーダーになるルックアップを排除

prefer-set-hasは、.includes()によるメンバーシップチェックにのみ使用されている配列にフラグを立て、Setへの変換を促します。存在確認や非存在確認にはArray#includes()よりSet#has()を優先します。これはループ内で特に重要です。Array.prototype.includesは呼び出しごとにO(n)であるため、別のコレクションのイテレーション中に毎回呼び出すとO(n²)になります。一方、Set.prototype.hasはほぼ定数時間です。

// ❌ O(n²): includes()はイテレーションのたびに配列全体を再スキャンする
const allowed = ['read', 'write', 'admin', /* …数百個… */];
const granted = requested.filter((scope) => allowed.includes(scope));

// ✅ O(n): Set.has()はルックアップごとにほぼ定数時間
const allowed = new Set(['read', 'write', 'admin', /* …数百個… */]);
const granted = requested.filter((scope) => allowed.has(scope));

この問題の失敗パターンは無音です。小さな入力では正常に動作し、データが増えるにつれて急激に劣化します。これはまさに、レビュー時ではなく本番環境で後から診断することになる、遅いインタラクションのリグレッションです。

no-array-for-each — TypeScriptの型絞り込みと制御フローの改善

no-array-for-each.forEach()コールバックをfor…ofループに書き換えます。forEachメソッドよりもfor…ofを優先します。この利点はスタイル以上のものです。for…ofはループ本体全体でより優れたTypeScriptの型絞り込みを提供し、コールバックの複雑な記述なしにawaitを直接使用でき、break/continueもサポートします。これらはいずれも.forEach()コールバック内ではきれいに機能しません。

// ❌
users.forEach((user) => {
  sendEmail(user);
});

// ✅ 自動修正後
for (const user of users) {
  sendEmail(user);
}

prefer-node-protocol — Node.js組み込みモジュールの明示化

prefer-node-protocolはベアの組み込みインポートを明示的なnode:形式に書き換えます。Node.jsの組み込みモジュールをインポートする際はnode:プロトコルの使用を優先します。明示的なプレフィックスにより、インポートが紛れもなくNode.js組み込みモジュールであることが明確になり、同名のnpmパッケージによるシャドーイングを防ぎます。

// ❌
import fs from 'fs';
import { join } from 'path';

// ✅ 自動修正後
import fs from 'node:fs';
import { join } from 'node:path';

その他の自動修正可能な改善点

これらはそれぞれレガシーパターンをモダンな同等物にマッピングし、自動的に修正されます:

// throw-new-error — エラーをスローする際にnewを必須とする
throw Error('boom');        // ❌
throw new Error('boom');    // ✅

// prefer-string-replace-all — /gフラグなしのグローバル置換
str.replace(/-/g, ' ');     // ❌
str.replaceAll('-', ' ');   // ✅

// prefer-array-flat-map — map().flat()を統合する
rows.map((r) => r.cells).flat();   // ❌
rows.flatMap((r) => r.cells);      // ✅

// new-for-builtins — Map/Set/Array/Dateなどにnewを必須とする
const cache = Map();        // ❌
const cache = new Map();    // ✅

throw-new-errorルールはエラー作成時にnewを必須とし(const e = Error()throw Error()の両方をカバー)、recommendedの設定に含まれており自動修正可能ですnew-for-builtinsはString、Number、Boolean、Symbol、BigIntを除くすべての組み込みオブジェクトに対してnewの使用を強制します。Array.prototype.flatMapはより明確であるだけでなく、.map().flat()が生成する中間配列のアロケーションも回避できます。

可読性に焦点を当てた2つのルールが高価値セットを完成させます。better-regexは正規表現をより短く一貫性のあるものに改善します(例:[0-9]\dになります)。prefer-ternaryはシンプルなif/elseブロックを折りたたみます。値を返すか代入するシンプルなif文よりも三項式を優先します。if-else文は通常、一行の三項式よりも多くの行を生成し、ブロック内で再代入するためだけにletで変数を宣言することを強いる場合があります。これにより変数が不必要にミュータブルになり、prefer-constがその変数にフラグを立てられなくなります

// ❌
function status(ok) {
  if (ok) {
    return 'pass';
  } else {
    return 'fail';
  }
}

// ✅ 自動修正後
function status(ok) {
  return ok ? 'pass' : 'fail';
}

議論を呼ぶルールとその判断方法

一部のrecommendedルールは正確性ではなく好みを強制するものであり、既存のコードベースで最も多くのノイズを生成します。以下の表は、よく問題になるルールに対するkeep/warn/disableの判断基準を示しています。これらはすべてrecommendedで有効化されています。

ルール判断理由
prevent-abbreviationsWarnまたはスコープ制限eerrorreqrequestpropspropertiesなどにリネームするが、フレームワークやチームがすでに使用している名前と衝突する。
no-nullnullを使用する場合は無効化Unicornはundefinedを優先するが、ORMやAPIがnullを返す場合はnullが正当であり、どちらが正しいという単一の答えはない。
no-array-reduceKeepまたはwarnArray#reduce()とArray#reduceRight()を禁止する。可読性の観点から合理的だが、多くのチームにとってreduceは問題ない。
filename-case例外にignoreを設定フレームワークが指定するファイル名と衝突する。ルールは維持しつつ例外を除外する。

no-nullのケースが最も明確です。Unicornはundefinedを優先しますが、多くのデータベースドライバやHTTP APIはnullを返すため、一方を強制するともう一方への変換ノイズが生じ、正確性の向上もありません。nullがデータ契約の一部である場合は無効化してください。

これらを手動で無効化する必要はありません。unopinionatedプリセットはno-nullprevent-abbreviationsなどの主観的なルールをデフォルトで無効化しているため、recommendedと大幅にカスタマイズした設定の間の組み込みの中間点となります。

維持するが調整したいルールについては、無効化ではなくスコープ制限を行います。filename-caseでフレームワークファイルを除外する一般的なパターン:

{
  files: ['**/*.{js,ts,tsx}'],
  extends: [unicorn.configs.recommended],
  rules: {
    'unicorn/no-null': 'off',
    'unicorn/prevent-abbreviations': 'warn',
    'unicorn/filename-case': [
      'error',
      {
        cases: { pascalCase: true, camelCase: true },
        ignore: ['next-env\\.d\\.ts', 'vite\\.config\\.ts'],
      },
    ],
  },
}

既存コードベースへの段階的な導入

最初の実行でエラーに埋もれないよう、プラグインを段階的に展開します。効果的な手順は以下の通りです:

  1. プリセットから始める。 unicorn/recommendedを継承するか、主観的なルールがチームにとってノイズになりすぎる場合はunicorn/unopinionatedを継承します。
  2. まず自動修正を実行する。 npx eslint . --fixを実行します。ほとんどのルールが自動修正可能であるため、多くの違反がここで解消され、数百のインラインエラーではなく1つの機械的な差分をレビューするだけで済みます。
  3. ノイズの多いルールをwarnにダウングレードする。 大量に発生しているが正確性の問題ではないもの(通常はprevent-abbreviationsが該当)は'warn'に設定し、移行中にCIをブロックしないようにします。
  4. 完全無効化ではなくスコープ制限でignoreを設定する。 ignoreオプション(filename-caseの場合)とfilesグロブを使用して、有用なルールをどこでも無効化するのではなく例外を設定します。
  5. 型を考慮したリンティングを追加する。 Unicornと並行して@typescript-eslint/recommended-type-checkedを追加します。Unicornはイディオムを強制し、typescript-eslintの型チェック設定は型システムだけが検出できるバグを捕捉します。両者は補完的であり、TypeScriptプロジェクトでは両方を有効化することが強力なデフォルトです。

設定を生きたドキュメントとして扱ってください。プラグインをアップグレードするたびに(リリースはほぼ毎月あります)、ルール一覧で新しいルールや名称変更を確認し、warnのルールをerrorに昇格させる準備ができているか再確認してください。

eslint-plugin-unicornでJavaScriptを改善する最速の方法は地味なものです。unicorn/recommendedを継承し、eslint --fixを実行し、チームのコーディングスタイルと本当に衝突する少数の主観的なルールにのみ注意を払います。今日プリセットから始め、自動修正に大部分の作業をさせ、そこからチューニングしていきましょう。公式ルール一覧は各ルールの現在のステータスとオプションのリファレンスです。

よくある質問

eslint-plugin-unicornはtypescript-eslintと並行して機能しますか?それとも競合しますか?

両者は補完的であり、一緒に実行するように設計されています。Unicornはモダンなjavascriptとtypescriptのイディオムを強制し、typescript-eslintは型システムだけが検出できるバグを捕捉する型を考慮したルールを追加します。フラットなeslint.config.mjsで別々の設定オブジェクトとして両方を追加し、unicorn/recommendedとtypescript-eslintのrecommended-type-checked設定を組み合わせてください。両方を有効化することはtypescriptプロジェクトの強力なデフォルトであり、解決すべきルールの重複はありません。

recommendedプリセットとunopinionatedプリセットの違いは何ですか?

recommendedプリセットは正確性ルールと主観的なスタイルルールの両方を有効化する厳選されたデフォルトです。unopinionatedプリセットはrecommendedから主観的なルールを除いたものであり、既存のコードベースと相容れないno-nullやprevent-abbreviationsなどの議論を呼ぶエントリをあらかじめ無効化しています。スタイルルールがチームにとってノイズになりすぎる場合はunopinionatedを選択してください。これはrecommendedと大幅にカスタマイズした設定の間の組み込みの中間点です。3番目のプリセットであるallは非推奨ルールを除くすべてのルールを有効化します。

なぜextends plugin:unicorn/recommendedを使った古い.eslintrc設定が機能しなくなったのですか?

ESLint 10がレガシーのeslintrcシステムを完全に削除し、フラット設定が唯一サポートされる形式となりました。eslint-plugin-unicornはESLint 10.4以上、フラット設定、ESMを必要とします。古い.eslintrcとextends 'plugin:unicorn/recommended'の文字列構文はもはや読み込まれません。eslint.config.mjsに移行し、'eslint-plugin-unicorn'からunicornをインポートして、filesでスコープされた設定オブジェクト内でextendsをunicorn.configs.recommendedに設定してください。

eslint-plugin-unicornを追加すると数百のエラーでビルドが壊れますか?

unicornルールの大部分が自動修正可能であるため、ほとんどの違反は自動的に解消されます。まず--fixフラグを付けてeslintを実行してください。これにより、数百のインラインエラーではなく1つの機械的な差分として多くの問題が解消されます。prevent-abbreviationsなど、大量に発生しているが正確性の問題ではないルールについては、移行中にCIをブロックしないようwarnに設定してください。この段階的な展開により、プラグインを採用しながらビルドをグリーンに保つことができます。

Understand every bug

Uncover frustrations, understand bugs and fix slowdowns like never before with OpenReplay — self-hosted, with full data ownership.

Star on GitHub

We use cookies to improve your experience. By using our site, you accept cookies.