ESLint

コード品質リンターJavaScriptTypeScriptDevOps静的解析自動修正

DevOpsツール

ESLint

概要

ESLintは、JavaScriptとTypeScriptのコード品質チェックツールです。コーディング規約の遵守、潜在的なバグの発見、コードスタイルの統一を支援する最も人気のあるリンター。カスタマイズ可能なルールシステムとプラグインエコシステムにより、プロジェクトのニーズに合わせた柔軟な設定が可能。自動修正機能とIDE統合により、開発効率を大幅に向上させ、JavaScript開発の事実上の標準ツールとして広く採用されています。

詳細

ESLint(Eslint)は2013年にNicholas C. Zakasによって開発され、現在はJavaScript・TypeScript開発における事実上の標準リンターとして確立されています。静的解析によってコードの問題を検出し、コーディング規約の遵守、潜在的なバグの早期発見、コードスタイルの統一を実現します。

主要な特徴

  • 高度にカスタマイズ可能なルールシステム: 300以上の組み込みルールと豊富なプラグインエコシステム
  • 自動修正機能: 多くの問題を自動的に修正(--fixオプション)
  • TypeScript完全対応: @typescript-eslint/parserとプラグインによる型安全チェック
  • フレームワーク対応: React、Vue.js、Angular、Astro等の専用プラグイン
  • Modern JavaScript対応: ES2024、JSX、モジュールシステムの完全サポート
  • 柔軟な設定システム: Flat Config(新形式)と従来の.eslintrc形式の両対応
  • IDE・エディター統合: VS Code、IntelliJ IDEA、Vim等への統合プラグイン
  • CI/CD統合: GitHub Actions、GitLab CI等での自動品質チェック

2025年現在、React、Next.js、Vue.js、Angular、Node.js等の主要プロジェクトで標準採用され、コードレビューの負荷軽減と品質向上を実現しています。

メリット・デメリット

メリット

  • JavaScript/TypeScript開発の事実上の標準で豊富な学習リソース
  • カスタマイズ可能なルールシステムでプロジェクト要件に柔軟対応
  • 自動修正機能により手動でのコード修正作業を大幅削減
  • 豊富なプラグインエコシステムでフレームワーク・ライブラリ固有の問題検出
  • IDE統合によるリアルタイムフィードバックで開発効率向上
  • CI/CD統合により品質ゲートとしての自動チェック実現
  • 設定ファイル共有によるチーム全体でのコーディング規約統一
  • TypeScript対応により型安全性とコード品質の両立

デメリット

  • 初期設定の複雑さ(特に大規模プロジェクトやTypeScript環境)
  • ルール数が多く適切な設定選択に学習コストが必要
  • プラグイン依存による設定の複雑化とメンテナンス負荷
  • 大規模プロジェクトでの実行時間が長い場合がある
  • Flat Configと従来形式の移行期における混乱
  • 過度な厳密設定による開発速度の低下リスク
  • プラグイン間の競合や設定衝突の調整が困難な場合

参考ページ

書き方の例

インストールと基本セットアップ

# ESLintのインストール(プロジェクトローカル)
npm install --save-dev eslint

# TypeScript対応ESLintのインストール
npm install --save-dev eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin

# 人気のプリセット込みのインストール
npm install --save-dev eslint eslint-config-prettier prettier

# インタラクティブ設定ウィザード実行
npx eslint --init

# 設定ファイル作成後の動作確認
npx eslint src/**/*.js
npx eslint src/**/*.ts --ext .ts

基本的な設定ファイル(Flat Config形式)

// eslint.config.js - 新しいFlat Config形式(推奨)
import js from "@eslint/js";
import { defineConfig } from "eslint/config";

export default defineConfig([
    // JavaScript基本設定
    js.configs.recommended,
    
    {
        files: ["**/*.js", "**/*.mjs"],
        languageOptions: {
            ecmaVersion: 2024,
            sourceType: "module",
        },
        rules: {
            // エラーレベル設定
            "no-console": "warn",          // console使用は警告
            "no-unused-vars": "error",     // 未使用変数はエラー
            "no-undef": "error",           // 未定義変数はエラー
            
            // スタイル関連
            "semi": ["error", "always"],   // セミコロン必須
            "quotes": ["error", "double"], // ダブルクォート強制
            "indent": ["error", 4],        // インデント4スペース
            
            // ベストプラクティス
            "eqeqeq": "error",            // 厳密等価演算子強制
            "curly": "error",             // 中括弧必須
            "no-eval": "error",           // eval禁止
        }
    }
]);

TypeScript設定

// eslint.config.js - TypeScript対応
import js from "@eslint/js";
import tseslint from "@typescript-eslint/eslint-plugin";
import tsparser from "@typescript-eslint/parser";
import { defineConfig } from "eslint/config";

export default defineConfig([
    js.configs.recommended,
    
    {
        files: ["**/*.ts", "**/*.tsx"],
        languageOptions: {
            parser: tsparser,
            parserOptions: {
                ecmaVersion: 2024,
                sourceType: "module",
                project: "./tsconfig.json", // TypeScript設定ファイル参照
            },
        },
        plugins: {
            "@typescript-eslint": tseslint,
        },
        rules: {
            // TypeScript推奨ルール
            "@typescript-eslint/no-unused-vars": "error",
            "@typescript-eslint/no-explicit-any": "warn",
            "@typescript-eslint/prefer-const": "error",
            "@typescript-eslint/no-non-null-assertion": "warn",
            
            // 型安全性強化
            "@typescript-eslint/strict-boolean-expressions": "error",
            "@typescript-eslint/prefer-nullish-coalescing": "error",
            "@typescript-eslint/prefer-optional-chain": "error",
            
            // コードスタイル
            "@typescript-eslint/explicit-function-return-type": "warn",
            "@typescript-eslint/consistent-type-definitions": ["error", "interface"],
            
            // JavaScript基本ルールの無効化(TypeScript版を使用)
            "no-unused-vars": "off",      // TypeScript版を使用
            "no-undef": "off",            // TypeScriptコンパイラが検出
        }
    }
]);

React + TypeScript設定

// eslint.config.js - React + TypeScript
import js from "@eslint/js";
import react from "eslint-plugin-react";
import reactHooks from "eslint-plugin-react-hooks";
import tseslint from "@typescript-eslint/eslint-plugin";
import tsparser from "@typescript-eslint/parser";

export default [
    js.configs.recommended,
    react.configs.flat.recommended,
    
    {
        files: ["**/*.tsx", "**/*.jsx"],
        languageOptions: {
            parser: tsparser,
            parserOptions: {
                ecmaVersion: 2024,
                sourceType: "module",
                ecmaFeatures: {
                    jsx: true,
                },
                project: "./tsconfig.json",
            },
        },
        plugins: {
            "react": react,
            "react-hooks": reactHooks,
            "@typescript-eslint": tseslint,
        },
        settings: {
            react: {
                version: "detect", // 自動検出
            },
        },
        rules: {
            // React基本ルール
            "react/react-in-jsx-scope": "off",     // React 17+では不要
            "react/prop-types": "off",             // TypeScriptで型チェック
            "react/jsx-uses-react": "off",         // React 17+では不要
            "react/jsx-uses-vars": "error",        // JSX変数使用チェック
            
            // React Hooks
            "react-hooks/rules-of-hooks": "error",
            "react-hooks/exhaustive-deps": "warn",
            
            // JSXスタイル
            "react/jsx-quotes": ["error", "prefer-double"],
            "react/jsx-indent": ["error", 4],
            "react/jsx-indent-props": ["error", 4],
            
            // TypeScript + React
            "@typescript-eslint/no-unused-vars": "error",
            "@typescript-eslint/no-explicit-any": "warn",
        }
    }
];

コマンドライン実行パターン

# 基本的な実行
npx eslint src/

# 特定ファイルの実行
npx eslint src/components/Header.tsx

# 自動修正付き実行
npx eslint src/ --fix

# 特定の拡張子のみ
npx eslint src/ --ext .ts,.tsx

# 修正可能な問題のみ表示
npx eslint src/ --fix-dry-run

# カスタム設定ファイル指定
npx eslint src/ --config custom-eslint.config.js

# 詳細な出力形式
npx eslint src/ --format=json > eslint-results.json
npx eslint src/ --format=html > eslint-results.html

# キャッシュ使用(高速化)
npx eslint src/ --cache

# 特定ルールのみ適用
npx eslint src/ --rule 'no-console: error'

# 設定デバッグ
npx eslint --inspect-config
npx eslint --print-config src/index.ts

インライン設定とルール制御

// ファイル全体でルール無効化
/* eslint-disable no-console */
console.log("このファイルではconsole.log警告が無効");

// 特定行のみルール無効化
console.log("Debug information"); // eslint-disable-line no-console

// 次の行のみルール無効化
// eslint-disable-next-line no-unused-vars
const unusedVariable = "これは使われない";

// 複数ルールの無効化
/* eslint-disable no-console, no-alert */
console.log("複数ルール無効");
alert("アラート表示");
/* eslint-enable no-console, no-alert */

// ルールの一時的な変更
/* eslint no-console: "error" */
// ここではconsole使用がエラーレベル

// TypeScript固有の制御
// @ts-ignore の代わりにESLintで制御
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const anyValue: any = "型チェック無効";

// React固有の制御
// eslint-disable-next-line react-hooks/exhaustive-deps
useEffect(() => {
    // 依存配列の警告を無効化
}, []);

CI/CD統合例

# .github/workflows/lint.yml - GitHub Actions
name: ESLint Check

on: [push, pull_request]

jobs:
  lint:
    runs-on: ubuntu-latest
    
    steps:
    - uses: actions/checkout@v4
    
    - name: Setup Node.js
      uses: actions/setup-node@v4
      with:
        node-version: '20'
        cache: 'npm'
    
    - name: Install dependencies
      run: npm ci
    
    - name: Run ESLint
      run: |
        npx eslint src/ --format=json --output-file=eslint-results.json
        npx eslint src/ --format=stylish
    
    - name: Upload ESLint results
      uses: actions/upload-artifact@v4
      if: failure()
      with:
        name: eslint-results
        path: eslint-results.json

# package.json のスクリプト設定例
{
  "scripts": {
    "lint": "eslint src/",
    "lint:fix": "eslint src/ --fix",
    "lint:check": "eslint src/ --max-warnings 0",
    "lint:ci": "eslint src/ --format=json --output-file=reports/eslint.json"
  }
}

高度な設定とカスタマイゼーション

// eslint.config.js - 高度な設定例
import js from "@eslint/js";
import globals from "globals";
import tseslint from "@typescript-eslint/eslint-plugin";
import unicorn from "eslint-plugin-unicorn";
import perfectionist from "eslint-plugin-perfectionist";

export default [
    js.configs.recommended,
    
    // グローバル設定
    {
        languageOptions: {
            globals: {
                ...globals.browser,
                ...globals.node,
                ...globals.es2024,
            },
        },
        linterOptions: {
            reportUnusedDisableDirectives: "error",
        },
    },
    
    // TypeScript設定
    {
        files: ["**/*.ts", "**/*.tsx"],
        plugins: {
            "@typescript-eslint": tseslint,
            "unicorn": unicorn,
            "perfectionist": perfectionist,
        },
        rules: {
            // 高品質コード強制
            "unicorn/prefer-modern-math-apis": "error",
            "unicorn/prefer-node-protocol": "error",
            "unicorn/prefer-top-level-await": "error",
            
            // ソート・整理
            "perfectionist/sort-imports": ["error", {
                "type": "natural",
                "order": "asc",
                "groups": [
                    "type",
                    "react",
                    "nanostores",
                    "internal-type",
                    "internal",
                    "external",
                    "unknown"
                ]
            }],
            
            // パフォーマンス
            "@typescript-eslint/prefer-readonly": "error",
            "@typescript-eslint/prefer-readonly-parameter-types": "warn",
            
            // セキュリティ
            "no-eval": "error",
            "no-implied-eval": "error",
            "no-new-func": "error",
        }
    },
    
    // テストファイル専用設定
    {
        files: ["**/*.test.ts", "**/*.spec.ts", "**/__tests__/**/*"],
        rules: {
            "no-console": "off",               // テストでのconsole使用許可
            "@typescript-eslint/no-explicit-any": "off", // テストでのany許可
            "@typescript-eslint/no-non-null-assertion": "off", // テストでの!演算子許可
        }
    },
    
    // 設定ファイル専用
    {
        files: ["*.config.js", "*.config.ts"],
        rules: {
            "no-console": "off",
            "import/no-default-export": "off",
        }
    },
    
    // 除外設定
    {
        ignores: [
            "dist/",
            "node_modules/",
            "coverage/",
            "*.min.js",
            "public/",
            ".next/",
            ".nuxt/",
        ]
    }
];

カスタムルール作成例

// custom-rules/enforce-foo-bar.js - カスタムルール定義
module.exports = {
    meta: {
        type: "suggestion",
        docs: {
            description: "enforce foo variables to be equal to 'bar'",
            category: "Best Practices",
        },
        fixable: "code",  // 自動修正対応
        schema: [],
    },
    create(context) {
        return {
            VariableDeclarator(node) {
                if (node.id.name === "foo" && 
                    node.init && 
                    node.init.value !== "bar") {
                    
                    context.report({
                        node,
                        message: "Variable 'foo' should be equal to 'bar'",
                        fix(fixer) {
                            return fixer.replaceText(node.init, '"bar"');
                        }
                    });
                }
            }
        };
    }
};

// eslint.config.js - カスタムルール使用
import myRule from "./custom-rules/enforce-foo-bar.js";

export default [
    {
        plugins: {
            "local": {
                rules: {
                    "enforce-foo-bar": myRule
                }
            }
        },
        rules: {
            "local/enforce-foo-bar": "error"
        }
    }
];