ESLint arrow-body-styleでアロー関数を美しく:エラー解決と実践例

2025-05-27

ESLint の arrow-body-style ルールは、JavaScript のアロー関数 (arrow functions) の本体(body)の書き方を強制するためのルールです。

アロー関数には、その本体の書き方に2つの形式があります。

  1. ブロック本体 (Block Body): 中括弧 {} を使用し、return 文を明示的に記述します。

    const add = (a, b) => {
      return a + b;
    };
    
  2. 式本体 (Expression Body): 中括弧 {} を省略し、式を直接記述します。この場合、式の評価結果が暗黙的に返されます(return は不要です)。

    const add = (a, b) => a + b;
    

arrow-body-style ルールは、これらの2つの形式のどちらを使用するかをプロジェクトのコーディングスタイルに合わせて統一するために使われます。

このルールには、主に以下の3つのオプションがあります。

  • "never":

    • 常に中括弧を省略することを強制します。
    • これは、アロー関数を常に式を返す目的でのみ使用することを前提とした、非常に厳格なスタイルです。複数の文を記述したり、明示的な return を記述したりすることはできません。
    • 例:
      // OK
      const add = (a, b) => a + b;
      
      // OK (オブジェクトリテラルを返す場合も中括弧で囲んで式として扱う)
      const getObject = () => ({ key: 'value' });
      
      // NG (エラーになる)
      const calculateAndLog = (a, b) => {
        const sum = a + b;
        console.log(sum);
        return sum;
      };
      
  • "as-needed" (デフォルト):

    • 中括弧を省略できる場合は省略することを推奨します。
    • つまり、式本体で記述できる場合は式本体を、複数の文や明示的な return が必要な場合はブロック本体を使用することを推奨します。
    • これは最も柔軟なオプションであり、ESLint のデフォルト設定でもあります。
    • 例:
      // OK (中括弧を省略できるため)
      const add = (a, b) => a + b;
      
      // OK (中括弧とreturnが必要なため)
      const calculateAndLog = (a, b) => {
        const sum = a + b;
        console.log(sum);
        return sum;
      };
      
      // NG (中括弧が不要なのに使われているため)
      const multiply = (a, b) => {
        return a * b;
      };
      
    • "as-needed" オプションには、オブジェクトリテラルを暗黙的に返す場合に return を強制するかどうかを設定する requireReturnForObjectLiteral というサブオプションもあります。
      • const foo = () => ({ key: 'value' }); (中括弧なしでオブジェクトを返す)
      • const foo = () => { return { key: 'value' }; }; (中括弧ありでオブジェクトを返す) このどちらを許可するかを制御できます。
  • "always":

    • 常にブロック本体を使用することを強制します。
    • たとえ1行で書ける式でも、中括弧と return 文を記述する必要があります。
    • 例:
      // OK
      const add = (a, b) => {
        return a + b;
      };
      
      // NG (エラーになる)
      const multiply = (a, b) => a * b;
      

このルールは、コードの一貫性と可読性を向上させるために役立ちます。プロジェクト内でアロー関数の本体の書き方が統一されていないと、コードレビューの際に混乱したり、コードベース全体の見た目がばらばらになったりする可能性があります。

例えば、チームで「シンプルなアロー関数は必ず式本体で書く」というスタイルを決めた場合、"as-needed" オプションを使用することで、そのスタイルを自動的に強制できます。

ESLint の設定ファイル(.eslintrc.json など)で、以下のように設定します。

{
  "rules": {
    "arrow-body-style": ["error", "as-needed"]
    // または
    // "arrow-body-style": ["error", "always"]
    // または
    // "arrow-body-style": ["error", "never"]
  }
}


このルールで最もよく目にするエラーメッセージは、設定されたスタイルに反するアロー関数が記述された場合に表示されるものです。

エラーメッセージの例

  • Expected block statement surrounding arrow body. (arrow-body-style)

    • これは、設定が "always" の場合に、ブロック本体ではなく式本体を使っているアロー関数があるときに発生します。

    • 例("always" の場合のエラー)

      const multiply = (a, b) => a * b; // ここでエラー
      
    • これは、設定が "as-needed" または "never" の場合に、不要なブロック本体({}return)を使っているアロー関数があるときに発生します。

    • 例("as-needed" または "never" の場合のエラー)

      const add = (a, b) => { // ここでエラー
        return a + b;
      };
      

よくあるエラーパターンとトラブルシューティング

パターン1: "as-needed" 設定なのに return{} が不要な箇所で使われている

  • トラブルシューティング

    • 修正方法
      中括弧と return を削除して、式本体の形式にします。
      const getName = (user) => user.name;
      const getZero = () => 0;
      
    • オブジェクトリテラルを返す場合
      オブジェクトリテラルを直接返す場合は、{} がブロックと解釈されないように、全体を括弧 () で囲む必要があります。
      // NG (これはブロックと解釈されてしまう)
      const getPerson = () => { name: 'Alice' };
      
      // OK (全体を括弧で囲むことでオブジェクトリテラルと解釈される)
      const getPerson = () => ({ name: 'Alice' });
      
      もし "as-needed" でオブジェクトリテラルを返す場合に必ず return{} を使いたい場合は、オプションに "requireReturnForObjectLiteral": true を追加します。
      "arrow-body-style": ["error", "as-needed", { "requireReturnForObjectLiteral": true }]
      
      この設定の場合、以下のコードはエラーになりません。
      const getPerson = () => {
        return { name: 'Alice' };
      };
      
  • エラーの原因
    "as-needed" は「必要であればブロックを使う」という意味なので、1つの式で値を返す場合は中括弧と return を省略するべきだと判断されます。

  • 問題のコード例

    // .eslintrc.json に "arrow-body-style": ["error", "as-needed"] がある場合
    const getName = (user) => {
      return user.name; // ここでエラー
    };
    
    const getZero = () => {
      return 0; // ここでエラー
    };
    

パターン2: "always" 設定なのに return{} が使われていない

  • トラブルシューティング

    • 修正方法
      中括弧と return を追加します。
      const subtract = (a, b) => {
        return a - b;
      };
      
  • エラーの原因
    "always" は「常にブロックを使う」という意味なので、たとえ1つの式で済む場合でも、明示的に中括弧と return を記述する必要があります。

  • 問題のコード例

    // .eslintrc.json に "arrow-body-style": ["error", "always"] がある場合
    const subtract = (a, b) => a - b; // ここでエラー
    

パターン3: "never" 設定なのに return{} が使われている

  • トラブルシューティング

    • 修正方法
      この設定を選んでいる場合、アロー関数は単一の式を返す目的のみに限定されます。もし複数の文を記述したり、副作用を伴う処理を行う必要がある場合は、そのアロー関数を通常の function キーワードで定義したり、処理を別の関数に切り出すことを検討します。
      // "never" の場合、以下はOK
      const getLength = (data) => data.length;
      
      // 複数の処理を行う場合は、通常の関数を使うか、アロー関数を別の役割に
      function processData(data) {
        console.log(data);
        return data.length;
      }
      
    • もし、オブジェクトリテラルを返す際に return { ... }; と書いている場合もエラーになります。この場合も () => ({ key: value }) のように括弧で囲む必要があります。
      // NG (エラーになる)
      const getConfig = () => {
        return {
          mode: 'production',
          port: 3000
        };
      };
      
      // OK
      const getConfig = () => ({
        mode: 'production',
        port: 3000
      });
      
  • エラーの原因
    "never" は「常に中括弧を省略する」という意味なので、ブロック本体は許可されません。この設定は、アロー関数を「式を返す」という役割に限定したい場合に非常に厳格なスタイルを強制します。console.log のような副作用を伴う処理をアロー関数で直接記述することはできなくなります。

  • 問題のコード例

    // .eslintrc.json に "arrow-body-style": ["error", "never"] がある場合
    const processData = (data) => {
      console.log(data);
      return data.length; // ここでエラー
    };
    

パターン4: ESLint が設定を読み込んでいない

  • 問題
    コードがルールに違反しているはずなのに、ESLint がエラーを報告しない。

--fix オプションの活用

arrow-body-style ルールは、ほとんどの場合 --fix オプションで自動修正が可能です。

  • コマンドラインで ESLint を実行する際に、--fix フラグを追加します。
    eslint . --fix
    
    これにより、ESLint が設定に基づいて自動的にコードを修正してくれます。これは特に、既存の大量のコードに対してスタイルを適用したい場合に非常に役立ちます。


ここでは、それぞれのオプションと、それに対応する**良い例(OK)悪い例(NG)**のコードを示し、ESLint がどのようにエラーを検出するかを説明します。

ESLint の設定ファイル(例: .eslintrc.json)で、rules セクションに以下のように記述します。

{
  "rules": {
    "arrow-body-style": ["error", "OPTION_NAME"]
  }
}

OPTION_NAME の部分に、"always", "as-needed", "never" のいずれかを指定します。

オプション: "always"

  • コード例

    • OK (正しい書き方)

      // 複数行の処理を含む場合
      const calculateSum = (a, b) => {
        const sum = a + b;
        return sum;
      };
      
      // 1行の式を返す場合でも、ブロック構文とreturnを使用
      const add = (a, b) => {
        return a + b;
      };
      
      // オブジェクトリテラルを返す場合
      const getUser = () => {
        return { id: 1, name: 'Alice' };
      };
      
    • NG (ESLint エラーが発生する書き方)

      // 1行の式で、ブロック構文とreturnを省略しているためエラー
      const multiply = (a, b) => a * b;
      // エラーメッセージ例: Expected block statement surrounding arrow body.
      
      // オブジェクトリテラルを返す際に、()で囲んで式として返しているためエラー
      const getProduct = () => ({ id: 101, name: 'Laptop' });
      // エラーメッセージ例: Expected block statement surrounding arrow body.
      
  • 設定例

    {
      "rules": {
        "arrow-body-style": ["error", "always"]
      }
    }
    
  • 意味
    アロー関数の本体は常にブロック構文({ ... return ... })であるべきです。たとえ1行で書ける式であっても、明示的に return 文を記述する必要があります。

オプション: "as-needed" (デフォルト)

  • コード例

    • OK (正しい書き方)

      // 1行の式で値を返すため、ブロックを省略
      const subtract = (a, b) => a - b;
      
      // オブジェクトリテラルを返すため、()で囲んで式として返す
      const getConfig = () => ({ host: 'localhost', port: 8080 });
      
      // 複数行の処理やconsole.logなどを含むため、ブロックを使用
      const processData = (data) => {
        console.log('Processing data:', data);
        return data.toUpperCase();
      };
      
      // 明示的なreturnが必要な場合
      const findItem = (items, id) => {
        for (const item of items) {
          if (item.id === id) {
            return item;
          }
        }
        return null;
      };
      
    • NG (ESLint エラーが発生する書き方)

      // 1行の式で済むのに、不要なブロック構文とreturnを使用しているためエラー
      const divide = (a, b) => {
        return a / b;
      };
      // エラーメッセージ例: Unexpected block statement surrounding arrow body.
      
      // オブジェクトリテラルを返すのに、returnと{}を使っているためエラー
      const getDefaults = () => {
        return { count: 0, isValid: false };
      };
      // エラーメッセージ例: Unexpected block statement surrounding arrow body.
      
    • requireReturnForObjectLiteral サブオプション
      "as-needed" オプションには、オブジェクトリテラルを返す際にブロック構文を強制するかどうかを設定する requireReturnForObjectLiteral というサブオプションがあります。

      • 設定例(オブジェクトリテラルを返す際に return {} を強制)

        {
          "rules": {
            "arrow-body-style": ["error", "as-needed", { "requireReturnForObjectLiteral": true }]
          }
        }
        
      • この設定の場合:

        // OK (requireReturnForObjectLiteral: true なので、これでOK)
        const getObj = () => {
          return { key: 'value' };
        };
        
        // NG (requireReturnForObjectLiteral: true なので、エラー)
        const getAnotherObj = () => ({ key: 'another value' });
        // エラーメッセージ例: Expected block statement surrounding arrow body.
        
  • 設定例

    {
      "rules": {
        "arrow-body-style": ["error", "as-needed"]
      }
    }
    

    (これは ESLint のデフォルト設定なので、明示的に記述しなくても適用されます。)

  • 意味
    アロー関数の本体は、必要な場合にのみブロック構文を使用すべきです。つまり、1つの式で値を返せる場合はブロックを省略し、複数の文や明示的な return が必要な場合にブロックを使用します。

オプション: "never"

  • コード例

    • OK (正しい書き方)

      // 1行の式で値を返す
      const power = (base, exp) => base ** exp;
      
      // オブジェクトリテラルを返す(()で囲む)
      const getCoords = () => ({ x: 10, y: 20 });
      
    • NG (ESLint エラーが発生する書き方)

      // ブロック構文を使用しているためエラー
      const logAndReturn = (value) => {
        console.log(value);
        return value;
      };
      // エラーメッセージ例: Unexpected block statement surrounding arrow body.
      
      // 明示的なreturnを使用しているためエラー
      const findMax = (arr) => {
        let max = 0;
        for (const num of arr) {
          if (num > max) {
            max = num;
          }
        }
        return max;
      };
      // エラーメッセージ例: Unexpected block statement surrounding arrow body.
      
  • 設定例

    {
      "rules": {
        "arrow-body-style": ["error", "never"]
      }
    }
    
  • 意味
    アロー関数の本体は常に式構文であるべきです。ブロック構文({ ... return ... })は許可されません。これは、アロー関数を副作用のない単一の式を返す目的のみに限定したい場合に非常に厳格なスタイルを強制します。

arrow-body-style ルールは、プロジェクトのコーディングスタイルに合わせてアロー関数の書き方を統一するのに非常に役立ちます。

  • "never": アロー関数を純粋な式(副作用なし、単一の値を返す)に限定したい場合に。
  • "as-needed": 簡潔な関数は式で書き、複雑な関数はブロックで書きたい場合に(最も一般的で柔軟な選択)。
  • "always": 明示的な return とブロック構文を常に強制したい場合に。


Prettierなどのコードフォーマッターの使用

これは最も一般的で効果的な代替手段、あるいは補完的なアプローチです。

  • arrow-body-style との関係
    • Prettier はデフォルトで、アロー関数が1行で収まる場合は式本体に、複数行にわたる場合やブロックが必要な場合はブロック本体に整形します。これは ESLint の "as-needed" オプションに近い挙動です。
    • Prettier には arrowParens オプション(引数が1つの場合に括弧を付けるか付けないか)など、アロー関数に関連するいくつかの設定がありますが、arrow-body-style のようにブロック本体か式本体かを細かく制御する直接的なオプションはありません。
    • 利点
      • 自動整形
        コードを保存するたびに自動的に整形されるため、手動でスタイルを修正する手間が省けます。
      • 一貫性
        チーム全体で同じフォーマッターを使用すれば、コードスタイルが常に一貫します。
      • ESLint との連携
        eslint-config-prettiereslint-plugin-prettier を使用することで、ESLint と Prettier が衝突しないように設定し、Prettier で整形できない、あるいは ESLint がより厳しくチェックすべきロジックに関するルールのみを ESLint に任せることができます。
    • 欠点
      • Prettier のスタイルは固定されており、完全にカスタマイズすることはできません。特定の arrow-body-style の設定(例えば "always""never")を厳密に強制したい場合、Prettier だけでは不十分な場合があります。
  • 説明
    Prettier は、コードを整形する専用のツールです。ESLint の一部のルール(特にスタイルに関するもの)は、Prettier と衝突するか、または重複する場合があります。Prettier は arrow-body-style のような構文のスタイルを自動的に統一する機能を持っています。

TypeScript の noImplicitReturns コンパイラオプション

JavaScript ではなく TypeScript を使用している場合に考慮できる方法です。

  • 欠点
    • 純粋な JavaScript プロジェクトでは使用できません。
    • スタイルの統一ではなく、セマンティックなチェックが主な目的です。
  • 利点
    • 型安全性とコードの堅牢性の向上。
    • undefined の暗黙的な返却によるバグを防ぐ。
  • arrow-body-style との関係
    直接的に arrow-body-style のようなスタイルの強制はしませんが、アロー関数の「返り値」に関する意図を明確にする点で関連性があります。
    • noImplicitReturns: true の場合、以下のコードはエラーになります(else ブロックで何も返さない可能性があるため)。
      const getValue = (condition: boolean) => {
        if (condition) {
          return 1;
        }
        // else { /* ここで何か返さないとエラー */ }
      };
      
    • これは、開発者がアロー関数(特にブロック本体のアロー関数)で何を返すかをより意識するようになるため、結果的にスタイルに影響を与える可能性があります。
  • 説明
    TypeScript のコンパイラオプションである noImplicitReturnstrue に設定すると、関数がコードパスによっては値を返さない可能性がある場合にエラーを報告します。これはアロー関数の式本体で値を返す場合に、意図しない undefined の返却を防ぐのに役立ちます。

Git Hooks の利用 (Lint-staged と husky)

コードスタイルを強制するためのワークフロー的な代替方法です。

  • 欠点
    • 開発環境の設定が必要。
    • ESLint の設定が複雑な場合、フックの実行時間が長くなることがある。
  • 利点
    • 不適切なコードがリポジトリにコミットされるのを防ぐ。
    • CI/CD パイプラインでチェックする前に問題を早期に発見できる。
    • 開発者がスタイルルールを意識するようになる。
  • arrow-body-style との関係
    これは特定のルールに特化したものではなく、ESLint 全体をワークフローに組み込むための方法です。arrow-body-style の設定が ESLint に含まれていれば、このフックを通じてそのルールも強制されます。
  • 説明
    Git Hooks は、Git の特定のイベント(コミット前、プッシュ前など)でスクリプトを実行できる仕組みです。huskylint-staged を組み合わせることで、コミット対象のファイルに対してのみ ESLint(や Prettier)を実行し、スタイル違反があればコミットをブロックすることができます。

最も基本的な、しかし非常に重要な代替手段です。

  • 欠点
    • 人為的なミスが発生しやすい(指摘漏れ、指摘基準のばらつき)。
    • レビューのオーバーヘッドが増える。
    • 新しいメンバーのオンボーディングに時間がかかる。
  • 利点
    • ツールの設定が不要。
    • 人間の判断で、より柔軟なスタイル調整が可能。
    • チームメンバー間のコミュニケーションを促進。
  • arrow-body-style との関係
    ESLint が導入されていない、または特定の設定をしない場合でも、チーム内で「アロー関数は基本的に as-needed スタイルで書く」といったルールを決め、コードレビューで実施することができます。
  • 説明
    ESLint のようなツールが導入されていない、あるいは導入されていてもツールの設定でカバーしきれないニュアンスのあるスタイルについては、コードレビューを通じてチームメンバー間でスタイルを指摘し、統一していく方法です。公式なコーディングガイドライン(ドキュメント)を設けることも含まれます。

arrow-body-style ルール自体の「代替」というよりは、**「コードスタイルを管理し、強制するための異なるアプローチ」**としてこれらの方法を考えることができます。

  • 最も推奨されるアプローチ
    • Prettier と ESLint を併用する (eslint-config-prettier を使って競合を避ける)。Prettier で自動整形できるスタイルの大部分をカバーし、ESLint でより深いロジックや特定のアンチパターンをチェックします。この場合、arrow-body-style は Prettier のスタイルに合わせて "as-needed" に設定するか、Prettier に任せて ESLint のルールを無効にする(あるいは off にする)ことも検討できます。
    • そして、Git Hooks でコミット前にこれらのツールを実行し、コードレビューで最終的な品質を保証する。