T-CREATOR

ESLint の内部構造を覗く:Parser・Scope・Rule・Fixer の連携を図解

ESLint の内部構造を覗く:Parser・Scope・Rule・Fixer の連携を図解

ESLint を使っていると、コードの問題を自動検出し、時には自動修正までしてくれる便利さに驚かされますよね。しかし、その裏側でどのような仕組みが動いているかを知ると、さらに ESLint を使いこなせるようになります。

この記事では、ESLint の内部構造に焦点を当て、Parser・Scope・Rule・Fixer という 4 つの主要コンポーネントがどのように連携しているのかを図解とともに詳しく解説します。ESLint のカスタムルールを作成したい方や、内部の動作原理を理解したい方にとって、実践的な知識が得られる内容になっています。

背景

ESLint のアーキテクチャ全体像

ESLint は、JavaScript や TypeScript のコードを静的解析し、コーディング規約違反や潜在的なバグを検出するツールです。その内部は、複数のコンポーネントが協調して動作する洗練されたアーキテクチャで構成されています。

まず、ESLint の処理フロー全体を図で見てみましょう。

mermaidflowchart TD
  source["ソースコード<br/>(JavaScript/TypeScript)"] -->|入力| parser["Parser<br/>(構文解析)"]
  parser -->|生成| ast["AST<br/>(抽象構文木)"]
  ast -->|解析| scope["Scope Manager<br/>(スコープ分析)"]
  scope -->|スコープ情報| rules["Rules<br/>(ルール検証)"]
  ast -->|AST 情報| rules
  rules -->|違反検出| report["Report<br/>(問題レポート)"]
  report -->|修正必要?| fixer["Fixer<br/>(自動修正)"]
  fixer -->|修正済み| output["修正されたコード"]
  report -->|修正不要| output2["レポート出力"]

この図から分かるように、ESLint は段階的に処理を進めていきます。

各コンポーネントの役割

ESLint の内部構造は、以下の 4 つの主要コンポーネントで構成されています。

#コンポーネント役割出力
1Parserソースコードを AST(抽象構文木)に変換AST オブジェクト
2Scope Manager変数のスコープや参照関係を分析スコープ情報
3RuleAST を走査し、コーディング規約違反を検出違反レポート
4Fixer検出された問題を自動修正修正されたコード

それぞれのコンポーネントは独立した責務を持ちながら、互いに連携して動作します。この設計により、ESLint は拡張性が高く、カスタムルールやカスタムパーサーを簡単に組み込めるようになっているのです。

AST(抽象構文木)の重要性

ESLint の心臓部と言えるのが AST です。AST とは、ソースコードの構造を木構造で表現したデータ構造で、コードの意味を保ちながら解析しやすい形式に変換されています。

例えば、次のような簡単なコードを考えてみましょう。

javascriptconst greeting = 'Hello';

このコードは、AST では次のような構造になります。

javascript{
  "type": "VariableDeclaration",
  "kind": "const",
  "declarations": [
    {
      "type": "VariableDeclarator",
      "id": {
        "type": "Identifier",
        "name": "greeting"
      },
      "init": {
        "type": "Literal",
        "value": "Hello"
      }
    }
  ]
}

ESLint は、この AST を走査することで、コードの構造を理解し、ルールに基づいた検証を行います。

課題

静的解析の複雑さ

JavaScript や TypeScript のような動的言語を静的に解析するのは、想像以上に複雑な課題があります。以下のような問題を解決する必要があるのです。

構文の多様性

JavaScript には、様々な構文や記法が存在します。例えば、関数定義だけでも以下のような書き方があります。

javascript// 関数宣言
function greet() {
  return 'Hello';
}
javascript// 関数式
const greet = function () {
  return 'Hello';
};
javascript// アロー関数
const greet = () => 'Hello';
javascript// メソッド定義
const obj = {
  greet() {
    return 'Hello';
  },
};

これらすべての構文を正しく解析し、同じルールで検証できるようにするには、統一された AST 表現が必要になります。

スコープの追跡

変数のスコープを正確に追跡するのも大きな課題です。次のコードを見てください。

javascriptlet name = 'Global';

function outer() {
  let name = 'Outer';

  function inner() {
    let name = 'Inner';
    console.log(name); // どの name を参照している?
  }

  inner();
}

同じ変数名でも、スコープによって参照先が異なります。ESLint は、どの変数がどのスコープに属しているかを正確に把握する必要があります。

型情報の不足

TypeScript を使用している場合は型情報がありますが、純粋な JavaScript では型情報がありません。次のようなコードでは、実行時エラーが発生する可能性があるのです。

javascriptfunction getLength(value) {
  return value.length; // value が null や undefined だとエラー
}

ESLint は、型情報なしでも潜在的な問題を検出する必要があります。

コンポーネント間の連携の難しさ

各コンポーネントが独立して動作しつつ、適切に情報を共有する必要があります。この図は、コンポーネント間のデータフローの複雑さを示しています。

mermaidflowchart LR
  parser["Parser"] -->|AST| ruleContext["Rule Context"]
  scopeManager["Scope Manager"] -->|スコープ情報| ruleContext
  sourceCode["Source Code"] -->|テキスト情報| ruleContext
  ruleContext -->|違反情報| reporter["Reporter"]
  reporter -->|修正指示| fixer["Fixer"]
  fixer -->|AST 変更| output["出力"]

図で理解できる要点:

  • Rule Context が各コンポーネントからの情報を集約している
  • 情報は一方向に流れ、循環依存を避けている
  • Fixer は Reporter からの指示に基づいて動作する

解決策

Parser:構文解析の心臓部

Parser は、ソースコードを AST に変換する最初のステップを担当します。ESLint では、デフォルトで Espree というパーサーを使用していますが、TypeScript や JSX などをサポートするために、カスタムパーサーに置き換えることもできます。

Parser の処理フロー

mermaidflowchart TD
  input["ソースコード文字列"] -->|字句解析| tokens["トークン列"]
  tokens -->|構文解析| ast["AST"]
  ast -->|検証| validated["検証済み AST"]
  validated -->|位置情報付加| final["最終 AST"]

Parser が生成する AST には、コードの構造だけでなく、各ノードの位置情報(行番号、列番号)も含まれます。この位置情報は、エラーメッセージの表示や自動修正に不可欠なのです。

実際の Parser の使用例

ESLint で使用される Parser の基本的な使い方を見てみましょう。

javascriptconst espree = require('espree');

// Parser のオプション設定
const parserOptions = {
  ecmaVersion: 2022,
  sourceType: 'module',
  ecmaFeatures: {
    jsx: true,
  },
};
javascript// ソースコードを AST に変換
const code = `
  const greeting = "Hello, World!";
  console.log(greeting);
`;

const ast = espree.parse(code, parserOptions);
javascript// AST の構造を確認
console.log(JSON.stringify(ast, null, 2));
/*
{
  "type": "Program",
  "body": [
    {
      "type": "VariableDeclaration",
      "declarations": [...],
      "kind": "const"
    },
    {
      "type": "ExpressionStatement",
      "expression": {...}
    }
  ],
  "sourceType": "module"
}
*/

この例では、Espree を使ってソースコードを AST に変換しています。ecmaVersionsourceType などのオプションを指定することで、対応する JavaScript のバージョンやモジュール形式を制御できます。

TypeScript 対応の Parser

TypeScript のコードを解析する場合は、@typescript-eslint​/​parser を使用します。

javascript// .eslintrc.js の設定例
module.exports = {
  parser: '@typescript-eslint/parser',
  parserOptions: {
    ecmaVersion: 2022,
    sourceType: 'module',
    project: './tsconfig.json',
  },
};

この設定により、TypeScript 固有の構文(型アノテーション、インターフェースなど)も正しく解析できるようになります。

Scope Manager:スコープ分析の要

Scope Manager は、変数のスコープや参照関係を分析するコンポーネントです。これにより、未使用変数の検出や、スコープ外の変数参照などの問題を発見できます。

スコープの種類

JavaScript には、複数のスコープの種類があります。

#スコープ種別説明
1Globalプログラム全体で有効トップレベルの変数
2Moduleモジュール内で有効ES Module の変数
3Function関数内で有効関数のローカル変数
4Blockブロック内で有効let/const で宣言された変数
5Classクラス内で有効クラスのフィールド

Scope Manager の動作例

以下のコードで、Scope Manager がどのようにスコープを分析するか見てみましょう。

javascript// グローバルスコープ
const globalVar = 'global';

function outer() {
  // Function スコープ (outer)
  const outerVar = 'outer';

  if (true) {
    // Block スコープ
    const blockVar = 'block';
    console.log(outerVar); // outer の変数を参照
  }

  function inner() {
    // Function スコープ (inner)
    const innerVar = 'inner';
    console.log(outerVar); // outer の変数を参照
    console.log(globalVar); // グローバル変数を参照
  }
}

このコードのスコープ構造を図で表現すると、次のようになります。

mermaidflowchart TD
  global["Global Scope<br/>globalVar"] -->|contains| outer["Function Scope: outer<br/>outerVar"]
  outer -->|contains| block["Block Scope<br/>blockVar"]
  outer -->|contains| inner["Function Scope: inner<br/>innerVar"]

  inner -.->|references| outerVar["outerVar (outer)"]
  inner -.->|references| globalVar["globalVar (global)"]
  block -.->|references| outerVar2["outerVar (outer)"]

Scope Manager の実装例

ESLint のルールから Scope Manager を使用する例を見てみましょう。

javascriptmodule.exports = {
  meta: {
    type: "problem",
    docs: {
      description: "未使用変数を検出する"
    }
  },

  create(context) {
    return {
      // プログラム終了時にスコープを分析
      "Program:exit"() {
        const scopeManager = context.sourceCode.scopeManager;
        const globalScope = scopeManager.globalScope;
javascript        // すべてのスコープを走査
        const scopes = [globalScope];
        while (scopes.length > 0) {
          const scope = scopes.pop();

          // 子スコープを追加
          scopes.push(...scope.childScopes);
javascript          // スコープ内の変数を確認
          scope.variables.forEach(variable => {
            // 変数の参照をチェック
            if (variable.references.length === 0) {
              context.report({
                node: variable.identifiers[0],
                message: `変数 '${variable.name}' は未使用です`
              });
            }
          });
        }
      }
    };
  }
};

このルールは、Scope Manager を使って未使用変数を検出しています。scopeManager から取得したスコープ情報を走査し、参照されていない変数を見つけてレポートします。

Rule:検証ロジックの中核

Rule は、ESLint の検証ロジックを実装するコンポーネントです。各ルールは、AST を走査し、特定のパターンや問題を検出します。

Rule の基本構造

ESLint のルールは、次のような構造を持っています。

javascriptmodule.exports = {
  // ルールのメタデータ
  meta: {
    type: "problem", // problem, suggestion, layout
    docs: {
      description: "ルールの説明",
      recommended: true
    },
    fixable: "code", // code, whitespace, null
    schema: [] // ルールのオプション定義
  },
javascript  // ルールの実装
  create(context) {
    // context からは様々な情報にアクセスできる
    const sourceCode = context.sourceCode;
    const options = context.options;

    // AST ノードに対するビジター関数を返す
    return {
      // 特定のノードタイプに対する処理
      VariableDeclaration(node) {
        // ノードを検証し、問題があればレポート
      }
    };
  }
};

Rule の実装例:console.log 禁止ルール

実際にカスタムルールを作成してみましょう。本番環境で console.log を禁止するルールです。

javascriptmodule.exports = {
  meta: {
    type: "problem",
    docs: {
      description: "console.log の使用を禁止する"
    },
    messages: {
      noConsoleLog: "console.log は本番環境で使用できません"
    },
    fixable: "code"
  },
javascript  create(context) {
    return {
      // CallExpression ノードを検査
      CallExpression(node) {
        // node.callee が console.log かチェック
        if (
          node.callee.type === "MemberExpression" &&
          node.callee.object.name === "console" &&
          node.callee.property.name === "log"
        ) {
javascript          // 違反をレポート
          context.report({
            node,
            messageId: "noConsoleLog",
            fix(fixer) {
              // 自動修正:console.log を削除
              return fixer.remove(node);
            }
          });
        }
      }
    };
  }
};

このルールは、AST の CallExpression ノードを検査し、console.log の呼び出しを見つけて報告します。また、fix 関数を提供することで、自動修正にも対応しています。

Rule の動作フロー

Rule がどのように動作するかを、フローチャートで示します。

mermaidflowchart TD
  start["Rule 実行開始"] -->|AST 走査| visitor["Visitor 関数呼び出し"]
  visitor -->|ノード検査| check{"ルール違反?"}
  check -->|Yes| report["context.report() 呼び出し"]
  check -->|No| next["次のノード"]
  report -->|fix あり?| hasFix{"fixable?"}
  hasFix -->|Yes| fix["Fixer に修正指示"]
  hasFix -->|No| reportOnly["レポートのみ"]
  fix --> next
  reportOnly --> next
  next -->|未処理ノードあり| visitor
  next -->|完了| finish["Rule 実行終了"]

図で理解できる要点:

  • Rule は AST を走査しながら各ノードを検査する
  • 違反を発見すると context.report() を呼び出す
  • 修正可能な場合は Fixer に指示を送る

Fixer:自動修正の実現

Fixer は、検出された問題を自動的に修正するコンポーネントです。--fix オプションを使用すると、Fixer が修正を適用します。

Fixer の API

Fixer には、様々な修正メソッドが用意されています。

#メソッド説明使用例
1insertTextBefore(node, text)ノードの前にテキスト挿入セミコロンの追加
2insertTextAfter(node, text)ノードの後にテキスト挿入カンマの追加
3remove(node)ノードを削除不要なコードの削除
4replaceText(node, text)ノードをテキストで置換構文の書き換え
5replaceTextRange(range, text)範囲をテキストで置換複雑な書き換え

Fixer の実装例:インデント修正

インデントを修正するルールを実装してみましょう。

javascriptmodule.exports = {
  meta: {
    type: "layout",
    docs: {
      description: "インデントを 2 スペースに統一する"
    },
    fixable: "whitespace"
  },
javascript  create(context) {
    const sourceCode = context.sourceCode;
    const expectedIndent = 2;

    return {
      // プログラム全体を検査
      Program(node) {
        // すべての行を確認
        const lines = sourceCode.lines;
javascript        lines.forEach((line, lineIndex) => {
          // 行頭の空白を取得
          const match = /^(\s*)/.exec(line);
          const indent = match[1];

          // タブが含まれている場合
          if (indent.includes("\t")) {
            const lineNumber = lineIndex + 1;
            const range = [
              sourceCode.getIndexFromLoc({ line: lineNumber, column: 0 }),
              sourceCode.getIndexFromLoc({ line: lineNumber, column: indent.length })
            ];
javascript            context.report({
              loc: { line: lineNumber, column: 0 },
              message: "インデントはスペース 2 つに統一してください",
              fix(fixer) {
                // タブをスペースに置換
                const spaceCount = indent.replace(/\t/g, "  ").length;
                const correctIndent = " ".repeat(spaceCount);
                return fixer.replaceTextRange(range, correctIndent);
              }
            });
          }
        });
      }
    };
  }
};

この例では、タブインデントをスペースインデントに変換しています。fixer.replaceTextRange() を使って、指定した範囲のテキストを置き換えています。

Fixer の制約事項

Fixer には、いくつかの重要な制約があります。

修正の安全性

Fixer は、コードの意味を変えない修正のみを行うべきです。例えば、次のような修正は危険です。

javascript// 危険な修正例:意味が変わる可能性がある
// Before
if (value) doSomething();
doSomethingElse();

// After(誤った修正)
if (value) {
  doSomething();
}
doSomethingElse(); // 常に実行されるようになってしまう

修正の順序

複数のルールが同じ箇所を修正しようとする場合、競合が発生する可能性があります。ESLint は、修正を適用する順序を制御し、競合を回避します。

javascript// 修正の競合例
const value = 'hello'; // セミコロンなし、ダブルクォート

// Rule 1: セミコロンを追加
const value = 'hello';

// Rule 2: シングルクォートに変換
const value = 'hello';

// 両方適用すると
const value = 'hello';

ESLint は、複数回の修正パスを実行して、すべての修正を適用します。

具体例

カスタムルールの作成:変数命名規則の強制

実際のプロジェクトで役立つカスタムルールを作成してみましょう。変数名がキャメルケースになっているかをチェックし、違反している場合は修正するルールです。

ルールの仕様

以下の命名規則を強制します。

#変数種別命名規則
1通常の変数camelCaseuserName, isValid
2定数UPPER_SNAKE_CASEMAX_COUNT, API_URL
3プライベート変数_camelCase_privateData
4コンポーネントPascalCaseUserProfile, Button

ルールの実装:メタデータ定義

javascriptmodule.exports = {
  meta: {
    type: "suggestion",
    docs: {
      description: "変数名の命名規則を強制する",
      category: "Stylistic Issues",
      recommended: false
    },
    fixable: "code",
    messages: {
      invalidName: "変数名 '{{name}}' は {{expected}} で命名してください",
      fixedName: "変数名を '{{newName}}' に修正しました"
    },
javascript    schema: [
      {
        type: "object",
        properties: {
          allowLeadingUnderscore: {
            type: "boolean",
            default: true
          },
          allowTrailingUnderscore: {
            type: "boolean",
            default: false
          }
        },
        additionalProperties: false
      }
    ]
  },

ルールの実装:ヘルパー関数

javascript  create(context) {
    const options = context.options[0] || {};
    const allowLeadingUnderscore = options.allowLeadingUnderscore !== false;
    const allowTrailingUnderscore = options.allowTrailingUnderscore === true;

    // 命名パターンの判定関数
    function isCamelCase(name) {
      const pattern = /^[a-z][a-zA-Z0-9]*$/;
      return pattern.test(name);
    }

    function isUpperSnakeCase(name) {
      const pattern = /^[A-Z][A-Z0-9_]*$/;
      return pattern.test(name);
    }

    function isPascalCase(name) {
      const pattern = /^[A-Z][a-zA-Z0-9]*$/;
      return pattern.test(name);
    }
javascript// 変数名を camelCase に変換
function toCamelCase(name) {
  return name
    .split('_')
    .map((part, index) => {
      if (index === 0) {
        return part.toLowerCase();
      }
      return (
        part.charAt(0).toUpperCase() +
        part.slice(1).toLowerCase()
      );
    })
    .join('');
}

ルールの実装:検証ロジック

javascript    // 変数が定数かどうかを判定
    function isConstant(node) {
      return node.parent.kind === "const" &&
             node.init &&
             node.init.type === "Literal";
    }

    // 変数名を検証
    function checkVariableName(node, name) {
      // アンダースコアの処理
      let nameToCheck = name;
      let prefix = "";
      let suffix = "";

      if (allowLeadingUnderscore && name.startsWith("_")) {
        prefix = "_";
        nameToCheck = name.slice(1);
      }

      if (allowTrailingUnderscore && name.endsWith("_")) {
        suffix = "_";
        nameToCheck = nameToCheck.slice(0, -1);
      }
javascript      // 定数の場合
      if (isConstant(node)) {
        if (!isUpperSnakeCase(nameToCheck)) {
          const newName = nameToCheck.toUpperCase().replace(/([a-z])([A-Z])/g, "$1_$2");
          reportInvalidName(node, name, "UPPER_SNAKE_CASE", prefix + newName + suffix);
        }
        return;
      }

      // 通常の変数の場合
      if (!isCamelCase(nameToCheck)) {
        const newName = toCamelCase(nameToCheck);
        reportInvalidName(node, name, "camelCase", prefix + newName + suffix);
      }
    }

ルールの実装:レポートと修正

javascriptfunction reportInvalidName(
  node,
  oldName,
  expected,
  newName
) {
  context.report({
    node,
    messageId: 'invalidName',
    data: {
      name: oldName,
      expected,
    },
    fix(fixer) {
      // 変数宣言の識別子を置換
      return fixer.replaceText(node, newName);
    },
  });
}
javascript    return {
      // 変数宣言をチェック
      VariableDeclarator(node) {
        if (node.id.type === "Identifier") {
          checkVariableName(node, node.id.name);
        }
      },

      // 関数パラメータをチェック
      FunctionDeclaration(node) {
        node.params.forEach(param => {
          if (param.type === "Identifier") {
            checkVariableName(param, param.name);
          }
        });
      }
    };
  }
};

この実装では、変数宣言と関数パラメータの命名規則をチェックし、違反している場合は自動修正を提供しています。

ルールのテスト

作成したルールが正しく動作するか、テストを書いて確認しましょう。ESLint では、RuleTester を使ってルールをテストできます。

テストの準備

javascriptconst { RuleTester } = require('eslint');
const rule = require('./variable-naming-rule');

const ruleTester = new RuleTester({
  parserOptions: {
    ecmaVersion: 2022,
    sourceType: 'module',
  },
});

有効なコードのテスト

javascriptruleTester.run("variable-naming", rule, {
  valid: [
    // camelCase の変数
    "const userName = 'John';",
    "let isValid = true;",
    "var itemCount = 0;",

    // 定数(UPPER_SNAKE_CASE)
    "const MAX_SIZE = 100;",
    "const API_URL = 'https://api.example.com';",

    // プライベート変数
    {
      code: "const _privateData = {};",
      options: [{ allowLeadingUnderscore: true }]
    }
  ],

無効なコードのテスト

javascript  invalid: [
    // スネークケースの変数
    {
      code: "const user_name = 'John';",
      errors: [{ messageId: "invalidName" }],
      output: "const userName = 'John';"
    },

    // 大文字始まりの変数(定数でない)
    {
      code: "let UserName = 'John';",
      errors: [{ messageId: "invalidName" }],
      output: "let userName = 'John';"
    },
javascript    // camelCase の定数(UPPER_SNAKE_CASE であるべき)
    {
      code: "const maxSize = 100;",
      errors: [{ messageId: "invalidName" }],
      output: "const MAX_SIZE = 100;"
    },

    // 複数のアンダースコア
    {
      code: "const user__name = 'John';",
      errors: [{ messageId: "invalidName" }],
      output: "const userName = 'John';"
    }
  ]
});

このテストを実行することで、ルールが期待通りに動作するか確認できます。

全体の連携フロー

これまで見てきた Parser・Scope・Rule・Fixer がどのように連携して動作するか、実際の処理フローを図で示します。

mermaidsequenceDiagram
  participant Source as ソースコード
  participant Parser as Parser
  participant Scope as Scope Manager
  participant Rule as Rule Engine
  participant Fixer as Fixer
  participant Output as 出力

  Source->>Parser: コード入力
  Parser->>Parser: 字句解析
  Parser->>Parser: 構文解析
  Parser->>Scope: AST 生成
  Scope->>Scope: スコープ分析
  Scope->>Rule: AST + スコープ情報

  loop 各ルールの実行
    Rule->>Rule: AST 走査
    Rule->>Rule: パターン検出
    alt 違反発見
      Rule->>Fixer: 修正指示
      Fixer->>Fixer: 修正適用
    end
  end

  Fixer->>Output: 修正済みコード
  Rule->>Output: レポート

図で理解できる要点:

  • 処理は順番に進み、各コンポーネントが前の結果を利用する
  • Rule Engine は複数のルールをループで実行する
  • Fixer は違反が見つかった場合のみ動作する

プラグインとしてのパッケージング

作成したカスタムルールをプラグインとしてパッケージ化し、複数のプロジェクトで再利用できるようにしましょう。

プラグインの構造

javascript// index.js
module.exports = {
  rules: {
    'variable-naming': require('./rules/variable-naming-rule'),
    'no-console-log': require('./rules/no-console-log-rule'),
  },
  configs: {
    recommended: {
      rules: {
        'my-plugin/variable-naming': 'error',
        'my-plugin/no-console-log': 'warn',
      },
    },
  },
};

package.json の設定

json{
  "name": "eslint-plugin-my-plugin",
  "version": "1.0.0",
  "description": "カスタム ESLint ルール集",
  "main": "index.js",
  "keywords": ["eslint", "eslintplugin", "eslint-plugin"],
  "peerDependencies": {
    "eslint": ">=8.0.0"
  }
}

プラグインの使用方法

javascript// .eslintrc.js
module.exports = {
  plugins: ['my-plugin'],
  extends: ['plugin:my-plugin/recommended'],
  rules: {
    'my-plugin/variable-naming': [
      'error',
      {
        allowLeadingUnderscore: true,
        allowTrailingUnderscore: false,
      },
    ],
  },
};

これで、作成したカスタムルールを他のプロジェクトでも簡単に利用できるようになりました。

まとめ

ESLint の内部構造について、Parser・Scope・Rule・Fixer という 4 つの主要コンポーネントの役割と連携方法を詳しく見てきました。

各コンポーネントの重要なポイントをまとめると、次のようになります。

Parser は、ソースコードを AST に変換する最初のステップを担い、コードの構造を解析可能な形式に変換します。Espree や @typescript-eslint/parser など、カスタムパーサーに置き換えることで、様々な構文に対応できるのです。

Scope Manager は、変数のスコープや参照関係を追跡し、未使用変数の検出やスコープ外参照のチェックを可能にします。グローバル、モジュール、関数、ブロック、クラスといった複数のスコープ種別を正確に管理しています。

Rule は、ESLint の検証ロジックの中核であり、AST を走査してコーディング規約違反を検出します。カスタムルールを作成することで、プロジェクト固有の要件にも対応できますね。

Fixer は、検出された問題を自動的に修正する機能を提供し、開発者の手間を大幅に削減します。ただし、コードの意味を変えない安全な修正のみを行うよう、慎重に実装する必要があります。

これらのコンポーネントは、互いに連携しながら ESLint の強力な静的解析機能を実現しています。カスタムルールを作成する際には、この内部構造を理解することで、より効果的なルールを実装できるでしょう。

ESLint のアーキテクチャは、拡張性と柔軟性を兼ね備えており、プロジェクトの要件に合わせてカスタマイズできる設計になっています。この知識を活かして、チームのコード品質向上に役立つカスタムルールを作成してみてください。

関連リンク