T-CREATOR

ESLint パーサ比較:espree と @typescript-eslint/parser の互換性と速度

ESLint パーサ比較:espree と @typescript-eslint/parser の互換性と速度

ESLint でコード品質を向上させる際、パーサの選択は重要な判断ポイントとなります。JavaScript プロジェクトではデフォルトの espree で十分ですが、TypeScript を使用する場合は@typescript-eslint/parser が必要になります。しかし、この 2 つのパーサにはどのような違いがあり、それぞれどのような特性を持っているのでしょうか。

本記事では、ESLint の 2 つの主要パーサである「espree」と「@typescript-eslint/parser」の互換性と速度について、実践的な観点から詳しく解説します。

背景

ESLint とパーサの役割

ESLint は、JavaScript コードの静的解析ツールとして広く使用されています。コードの問題を早期に発見し、一貫したコーディングスタイルを維持するために欠かせないツールですね。

ESLint がコードを解析する際、パーサ(Parser)が重要な役割を担います。パーサはソースコードを読み取り、抽象構文木(AST: Abstract Syntax Tree)と呼ばれるデータ構造に変換します。

以下の図は、ESLint がパーサを使ってコードを解析する基本的な流れを示しています。

mermaidflowchart LR
  code["ソースコード"] -->|読み込み| parser["パーサ<br/>(espree または<br/>@typescript-eslint/parser)"]
  parser -->|変換| ast["AST<br/>(抽象構文木)"]
  ast -->|解析| rules["ESLintルール"]
  rules -->|検出| errors["エラー・警告"]

このように、パーサはソースコードと ESLint ルールの橋渡しをする重要なコンポーネントです。

2 つのパーサの位置づけ

ESLint には主に 2 つのパーサが使用されます。

espreeは ESLint のデフォルトパーサで、JavaScript コードの解析に特化しています。ECMAScript 仕様に準拠し、最新の JavaScript 構文をサポートしていますね。

一方、@typescript-eslint/parserは TypeScript コードを解析するために開発されたパーサです。TypeScript の型情報や独自の構文を理解し、AST に変換する能力を持っています。

#パーサ名対応言語デフォルト開発元
1espreeJavaScriptESLint チーム
2@typescript-eslint/parserTypeScript, JavaScript-TypeScript ESLint チーム

課題

パーサ選択の判断基準が不明確

多くの開発者が直面する課題として、プロジェクトでどのパーサを使用すべきか判断に迷うことがあります。特に、JavaScript と TypeScript が混在するプロジェクトでは、この選択がより複雑になりますね。

互換性に関する懸念

espree と@typescript-eslint/parser は異なる AST フォーマットを生成する場合があります。このため、特定の ESLint プラグインやルールが片方のパーサでしか動作しない可能性があります。

パフォーマンスへの影響

大規模なプロジェクトでは、パーサの処理速度が Lint の実行時間に大きく影響します。TypeScript パーサは型情報を解析するため、espree よりも処理時間が長くなる傾向があります。

以下の図は、パーサ選択時に考慮すべき主な課題を示しています。

mermaidflowchart TD
  start["パーサ選択"] --> q1{"プロジェクトに<br/>TypeScriptを<br/>含むか?"}
  q1 -->|Yes| q2{"型情報を<br/>活用したルールが<br/>必要か?"}
  q1 -->|No| espree_choice["espree を選択"]
  q2 -->|Yes| ts_parser["@typescript-eslint/parser<br/>必須"]
  q2 -->|No| concern["互換性と速度の<br/>トレードオフを検討"]
  concern --> decision["パーサ決定"]
  ts_parser --> decision
  espree_choice --> decision

これらの課題を理解することで、適切なパーサ選択が可能になります。

解決策

パーサの特性を理解する

それぞれのパーサの特性を正確に把握することが、適切な選択の第一歩となります。

espree の特性

espree は軽量で高速なパーサです。以下のような特徴を持っています。

javascript// .eslintrc.js でのespree設定(デフォルトのため通常は記載不要)
module.exports = {
  parser: 'espree', // 明示的に指定する場合
  parserOptions: {
    ecmaVersion: 2024, // 最新のECMAScript構文をサポート
    sourceType: 'module', // ESモジュールとして解析
  },
};

espree の主な特性は以下の通りです。

  • 高速な解析: TypeScript 特有の処理がないため、処理速度が速い
  • ECMAScript 準拠: 標準的な JavaScript 構文を完全にサポート
  • 軽量: 依存関係が少なく、インストールサイズが小さい

@typescript-eslint/parser の特性

@typescript-eslint/parser は TypeScript コードを解析するための強力な機能を提供します。

typescript// .eslintrc.js でのTypeScriptパーサ設定
module.exports = {
  parser: '@typescript-eslint/parser',
  parserOptions: {
    ecmaVersion: 2024,
    sourceType: 'module',
    project: './tsconfig.json', // 型情報を活用する場合に必須
  },
};

@typescript-eslint/parser の特性を以下にまとめます。

#特性説明メリット
1TypeScript 構文サポートinterface、type、decorator などを解析TypeScript コードを正確に検証
2型情報の活用tsconfig.json を読み込み型情報を利用高度なルールチェックが可能
3JavaScript 互換JavaScript(.js)ファイルも解析可能混在プロジェクトでも使用可能

互換性の確保方法

プラグインとの互換性

ESLint プラグインは特定のパーサに依存する場合があります。以下の原則で互換性を確保できます。

javascript// TypeScriptプロジェクトでの推奨設定
module.exports = {
  parser: '@typescript-eslint/parser',
  plugins: [
    '@typescript-eslint', // TypeScript専用プラグイン
  ],
  extends: [
    'eslint:recommended', // espreeベースのルール(互換性あり)
    'plugin:@typescript-eslint/recommended', // TypeScript推奨ルール
  ],
};

ポイント: eslint:recommendedは espree 向けのルールセットですが、@typescript-eslint/parser でも問題なく動作します。

JavaScript と TypeScript 混在プロジェクト

ファイル拡張子ごとに異なるパーサを適用することも可能です。

javascript// overridesを使った拡張子ごとの設定
module.exports = {
  parser: 'espree', // デフォルトはJavaScript用
  parserOptions: {
    ecmaVersion: 2024,
    sourceType: 'module',
  },
  overrides: [
    {
      // TypeScriptファイルのみ別パーサを使用
      files: ['*.ts', '*.tsx'],
      parser: '@typescript-eslint/parser',
      parserOptions: {
        project: './tsconfig.json',
      },
      plugins: ['@typescript-eslint'],
      extends: ['plugin:@typescript-eslint/recommended'],
    },
  ],
};

このアプローチにより、JavaScript ファイルは高速な espree で、TypeScript ファイルは適切な@typescript-eslint/parser で解析されます。

速度最適化の手法

型情報を使わないルールの活用

@typescript-eslint/parser の最も重い処理は、型情報の解析です。型情報が不要なルールのみを使用する場合は、projectオプションを省略できます。

typescript// 型情報を使わない設定(高速)
module.exports = {
  parser: '@typescript-eslint/parser',
  parserOptions: {
    ecmaVersion: 2024,
    sourceType: 'module',
    // project オプションを省略
  },
  extends: [
    'plugin:@typescript-eslint/recommended', // 型情報不要のルールのみ
  ],
};

この設定により、処理速度が大幅に向上します。

キャッシュの活用

ESLint のキャッシュ機能を有効にすることで、2 回目以降の実行速度が向上します。

bash# キャッシュを有効にしてESLintを実行
yarn eslint --cache --cache-location .eslintcache src/

キャッシュファイルを.gitignoreに追加することをお忘れなく。

text# .gitignore
.eslintcache

以下の図は、パーサ選択とパフォーマンスの関係を示しています。

mermaidflowchart TD
  project["プロジェクト"] --> has_ts{"TypeScriptを<br/>使用?"}
  has_ts -->|No| use_espree["espree<br/>(最速)"]
  has_ts -->|Yes| need_type{"型情報ルールが<br/>必要?"}
  need_type -->|No| ts_fast["@typescript-eslint/parser<br/>(project オプションなし)<br/>(速い)"]
  need_type -->|Yes| ts_full["@typescript-eslint/parser<br/>(project オプションあり)<br/>(やや遅い)"]

  use_espree --> cache["キャッシュ活用で<br/>さらに高速化"]
  ts_fast --> cache
  ts_full --> cache

この図から、プロジェクトの要件に応じた最適なパーサ構成を選択できます。

具体例

ケース 1: 純粋な JavaScript プロジェクト

React(JavaScript)プロジェクトで espree を使用する例です。

プロジェクト構成

textmy-js-project/
├── src/
│   ├── App.js
│   ├── components/
│   └── utils/
├── package.json
└── .eslintrc.js

パッケージのインストール

bash# ESLintとReact用プラグインをインストール
yarn add -D eslint eslint-plugin-react eslint-plugin-react-hooks

ESLint 設定ファイル

javascript// .eslintrc.js
module.exports = {
  env: {
    browser: true,
    es2024: true,
    node: true,
  },
  extends: [
    'eslint:recommended', // espreeのデフォルトルール
    'plugin:react/recommended',
    'plugin:react-hooks/recommended',
  ],
  parserOptions: {
    ecmaVersion: 2024,
    sourceType: 'module',
    ecmaFeatures: {
      jsx: true, // JSX構文を有効化
    },
  },
  plugins: ['react', 'react-hooks'],
  settings: {
    react: {
      version: 'detect',
    },
  },
};

この設定では、espree が暗黙的に使用され、最新の JavaScript 構文と React の JSX を解析します。

サンプルコード検証

javascript// src/App.js
import React, { useState } from 'react';

// ESLintが正しく解析できるJavaScriptコード
function App() {
  const [count, setCount] = useState(0);

  // espreeは最新のアロー関数、分割代入などを完全にサポート
  const handleClick = () => {
    setCount((prevCount) => prevCount + 1);
  };

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={handleClick}>Increment</button>
    </div>
  );
}

export default App;

実行結果

bash# ESLintを実行
yarn eslint src/

# 出力例(問題がない場合)
✨ Done in 0.52s

速度: JavaScript のみのプロジェクトでは、espree が最も高速です。1000 ファイル規模でも数秒で解析が完了します。

ケース 2: TypeScript プロジェクト(型情報なし)

Next.js(TypeScript)プロジェクトで、型情報を使わない軽量な設定例です。

パッケージのインストール

bash# TypeScript ESLint関連パッケージをインストール
yarn add -D @typescript-eslint/parser @typescript-eslint/eslint-plugin
yarn add -D eslint-config-next

ESLint 設定(高速版)

javascript// .eslintrc.js
module.exports = {
  extends: [
    'next/core-web-vitals',
    'plugin:@typescript-eslint/recommended', // 型情報不要のルール
  ],
  parser: '@typescript-eslint/parser',
  parserOptions: {
    ecmaVersion: 2024,
    sourceType: 'module',
    // project オプションを省略することで高速化
  },
  plugins: ['@typescript-eslint'],
  rules: {
    '@typescript-eslint/no-unused-vars': 'error',
    '@typescript-eslint/no-explicit-any': 'warn',
  },
};

この設定では、型情報を使用しないため、@typescript-eslint/parser でも比較的高速に動作します。

サンプルコード検証

typescript// src/components/Counter.tsx
import React, { useState } from 'react';

// TypeScript特有の型注釈をパーサが正しく解析
interface CounterProps {
  initialValue?: number;
}

export const Counter: React.FC<CounterProps> = ({
  initialValue = 0,
}) => {
  const [count, setCount] = useState<number>(initialValue);

  // @typescript-eslint/parserがTypeScript構文を理解
  const increment = (): void => {
    setCount((prev) => prev + 1);
  };

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={increment}>+1</button>
    </div>
  );
};

実行とパフォーマンス

bash# ESLintを実行(キャッシュ有効)
yarn eslint --cache src/

# 初回実行
✨ Done in 2.15s

# 2回目以降(キャッシュ使用)
✨ Done in 0.38s

速度: 型情報を使わない場合、espree と比較して約 2〜3 倍の処理時間ですが、十分に実用的な速度です。

ケース 3: TypeScript プロジェクト(型情報あり)

型情報を活用した高度なルールチェックを行う例です。

tsconfig.json の準備

json{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "lib": ["ES2022", "DOM"],
    "jsx": "preserve",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules"]
}

ESLint 設定(型情報活用版)

javascript// .eslintrc.js
module.exports = {
  extends: [
    'plugin:@typescript-eslint/recommended',
    'plugin:@typescript-eslint/recommended-requiring-type-checking', // 型情報必須
  ],
  parser: '@typescript-eslint/parser',
  parserOptions: {
    ecmaVersion: 2024,
    sourceType: 'module',
    project: './tsconfig.json', // 型情報を読み込む
    tsconfigRootDir: __dirname,
  },
  plugins: ['@typescript-eslint'],
};

recommended-requiring-type-checkingを使うことで、型情報を活用した高度なルールが有効になります。

型情報を活用するルール例

javascript// .eslintrc.jsにルールを追加
module.exports = {
  // ... 前述の設定
  rules: {
    // Promiseの誤用を検出(型情報が必要)
    '@typescript-eslint/no-floating-promises': 'error',

    // 不要なnullチェックを検出(型情報が必要)
    '@typescript-eslint/no-unnecessary-condition': 'warn',

    // any型の明示的な使用を検出
    '@typescript-eslint/no-explicit-any': 'error',
  },
};

サンプルコード検証

typescript// src/services/api.ts
// 型情報を活用したルールが動作するコード例

interface User {
  id: number;
  name: string;
  email: string;
}

class UserService {
  // Promiseを返す関数
  async fetchUser(id: number): Promise<User> {
    const response = await fetch(`/api/users/${id}`);
    return response.json();
  }

  // 型情報を活用したルールでチェックされる
  async processUser(id: number): Promise<void> {
    // ❌ エラー: Promiseが適切に処理されていない
    // @typescript-eslint/no-floating-promises が検出
    // this.fetchUser(id);

    // ✅ 正しい: awaitで処理
    const user = await this.fetchUser(id);
    console.log(user.name);
  }

  // 不要なnullチェックの例
  validateEmail(email: string): boolean {
    // ❌ 警告: emailは既にstring型なのでnullチェック不要
    // @typescript-eslint/no-unnecessary-condition が検出
    // if (email !== null && email.includes('@')) {

    // ✅ 正しい
    if (email.includes('@')) {
      return true;
    }
    return false;
  }
}

これらのルールは、型情報がなければ検出できません。

実行とパフォーマンス比較

bash# 型情報ありで実行
yarn eslint src/

# 処理時間
✨ Done in 5.82s

以下の表は、3 つのケースでの速度比較です。

#ケースパーサ型情報1000 ファイルの処理時間(目安)
1JavaScriptespree-0.5 秒
2TypeScript@typescript-eslint/parserなし1.5 秒
3TypeScript@typescript-eslint/parserあり5.5 秒

速度の考察: 型情報を活用する場合、処理時間は増加しますが、高度なバグ検出が可能になります。プロジェクトの規模と要件に応じて選択しましょう。

ケース 4: モノレポでの混在プロジェクト

複数のパッケージを持つモノレポで、JavaScript と TypeScript が混在する例です。

プロジェクト構成

textmonorepo/
├── packages/
│   ├── shared-utils/      # TypeScriptライブラリ
│   │   ├── src/
│   │   ├── tsconfig.json
│   │   └── .eslintrc.js
│   └── web-app/           # JavaScriptアプリ
│       ├── src/
│       └── .eslintrc.js
├── .eslintrc.js           # ルート設定
└── package.json

ルート ESLint 設定

javascript// ルート .eslintrc.js
module.exports = {
  root: true,
  env: {
    node: true,
    es2024: true,
  },
  parserOptions: {
    ecmaVersion: 2024,
  },
  extends: ['eslint:recommended'],
};

TypeScript パッケージの設定

javascript// packages/shared-utils/.eslintrc.js
module.exports = {
  extends: [
    '../../.eslintrc.js', // ルート設定を継承
    'plugin:@typescript-eslint/recommended',
  ],
  parser: '@typescript-eslint/parser',
  parserOptions: {
    project: './tsconfig.json',
    tsconfigRootDir: __dirname,
  },
  plugins: ['@typescript-eslint'],
};

JavaScript パッケージの設定

javascript// packages/web-app/.eslintrc.js
module.exports = {
  extends: [
    '../../.eslintrc.js', // ルート設定を継承
    'plugin:react/recommended',
  ],
  // parserは明示しない(espreeがデフォルト)
  parserOptions: {
    ecmaFeatures: {
      jsx: true,
    },
  },
  plugins: ['react'],
};

モノレポ全体での Lint 実行

bash# ルートディレクトリからすべてのパッケージをLint
yarn eslint packages/*/src/

# または各パッケージで個別に実行
cd packages/shared-utils && yarn eslint src/
cd packages/web-app && yarn eslint src/

この構成により、各パッケージの特性に応じた最適なパーサが自動的に選択されます。

以下の図は、モノレポにおけるパーサの使い分けを示しています。

mermaidflowchart TD
  root["モノレポルート<br/>(基本設定)"] --> pkg1["packages/shared-utils<br/>(TypeScript)"]
  root --> pkg2["packages/web-app<br/>(JavaScript)"]

  pkg1 --> parser1["@typescript-eslint/parser<br/>+ 型情報活用"]
  pkg2 --> parser2["espree<br/>(デフォルト)"]

  parser1 --> lint1["TypeScript特有の<br/>ルールチェック"]
  parser2 --> lint2["JavaScript<br/>ルールチェック"]

まとめ

ESLint の 2 つの主要パーサ、espree と@typescript-eslint/parser について、互換性と速度の観点から解説しました。

パーサ選択の基本原則は以下の通りです。

  • JavaScript のみのプロジェクト: espree を使用(最速、デフォルト)
  • TypeScript プロジェクト(基本的なチェック): @typescript-eslint/parser を型情報なしで使用(速度と機能のバランス)
  • TypeScript プロジェクト(高度なチェック): @typescript-eslint/parser を型情報ありで使用(最も強力、やや低速)

互換性については、@typescript-eslint/parser は JavaScript ファイルも解析できるため、混在プロジェクトでも使用可能です。ただし、ファイル拡張子ごとにoverridesを使って適切なパーサを割り当てることで、最適なパフォーマンスが得られます。

速度最適化のためには、以下の手法が有効です。

  1. 型情報が不要な場合はprojectオプションを省略する
  2. ESLint のキャッシュ機能(--cache)を活用する
  3. 必要最小限のルールセットを選択する
  4. モノレポでは各パッケージに適したパーサを使い分ける

プロジェクトの規模、言語構成、求められる品質レベルに応じて、適切なパーサ設定を選択することで、開発効率とコード品質の両立が可能になります。

関連リンク