CIで変更されたファイルにのみESLintを適用する

2026-02-25 hit count image

大規模モノレポで新しいESLintルールを負担なく導入するために、CIでPRの変更されたファイルにのみ ESLintを実行する方法について紹介します。

github_actions

背景

ESLintに新しいルールを追加すると、既存の全コードがそのルールに違反していないか検査されます。大規模モノレポでは新しいルールを追加するたびに大量の既存コードを修正する必要があり、影響範囲が大きくなる問題があります。

例えば、新しいルールを1つ追加しただけで、数十〜数百のファイルでエラーが検出されるケースがあります。こうなると新しいルールの導入コストが高くなりすぎて、コード品質の改善が進みにくくなります。

理想的には、修正されたファイルのみを検査して影響範囲を抑えながら、段階的に品質を向上させたいところです。

課題

各アプリのCIワークフローでは、ESLintをプロジェクト全体に対して実行していました。

- name: ESLint
  run: yarn lint:app-a

この方式の問題点は以下の通りです。

  • 新ルール追加時に既存の全ファイルが検査対象になる
  • 既存コードの大量修正が必要になり、PRの影響範囲が大きくなる
  • 結果として新ルールの導入をためらうようになる

検討したアプローチ

案A: 設定ファイルを2つに分離して運用

最初に検討した方法は、ESLintの設定ファイルを既存ルール用新規ルール用の2つに分けて運用することでした。

.eslintrc.cjs          ← 既存ルール(全ファイル検査)
.eslintrc.new-rules.cjs ← 新規ルール(変更ファイルのみ検査)
# 既存ルール: プロジェクト全体に実行
- name: ESLint (existing rules)
  run: yarn lint:app-a

# 新規ルール: 変更されたファイルにのみ実行
- name: ESLint (new rules)
  run: npx eslint -c .eslintrc.new-rules.cjs $CHANGED_FILES

この方式は既存ルールの全体検査を維持しながら、新規ルールのみを段階的に導入できるという利点があります。しかし、実際の運用を考えるといくつかの問題がありました。

  • 設定ファイルの管理が複雑になる: 2つの設定ファイルを常に同期する必要がある
  • ルールの昇格作業が必要になる: 新規ルールが全コードに適用可能になったら.eslintrc.new-rules.cjsから.eslintrc.cjsに移す作業が発生する
  • どのルールがどこにあるか把握しにくい: ルールが2箇所に分散し、管理ポイントが増える

案B: 全ルールを変更ファイルにのみ適用(採用)

次に検討した方法は、設定ファイルは1つのまま検査対象を変更ファイルに限定することでした。

.eslintrc.cjs ← 既存ルール + 新規ルール全て含む(変更ファイルのみ検査)

この方式の懸念は「既存ルールが全ファイルに適用されないと品質が下がるのでは?」という点でした。しかし、以下の理由から実質的な問題がないと判断しました。

  • 既存コードはすでに既存ルールをパスしている: 全体検査をしなくても、既存ファイルが突然違反状態になることはない
  • 変更されていないファイルは検査する必要がない: 違反が発生するのはコードが変更された時だけ
  • 設定ファイルが1つなので管理がシンプル: 新規ルールを追加する時は.eslintrc.cjsに追加するだけ

結果的に設定のシンプルさと運用のしやすさを優先して案Bを採用しました。

解決: PRで変更されたファイルにのみESLintを実行

GitHub APIを使用してPRの変更ファイル一覧を取得し、変更されたファイルにのみESLintを実行するように修正しました。

- name: Get changed files and run ESLint
  uses: actions/github-script@v7
  with:
    script: |
      const files = await github.paginate(github.rest.pulls.listFiles, {
        owner: context.repo.owner,
        repo: context.repo.repo,
        pull_number: context.issue.number,
      });

      const filtered = files
        .filter(f => ['added', 'modified', 'renamed', 'copied'].includes(f.status))
        .filter(f => f.filename.startsWith('apps/app-a/src/'))
        .filter(f => /\.(ts|tsx|js|jsx)$/.test(f.filename))
        .map(f => f.filename);

      if (filtered.length === 0) {
        core.info('No matching files to lint.');
        return;
      }

      await exec.exec('npx', [
        'eslint',
        '-c',
        'apps/app-a/.eslintrc.cjs',
        ...filtered,
      ]);

ポイント解説

1. GitHub APIで変更ファイルを取得

const files = await github.paginate(github.rest.pulls.listFiles, {
  owner: context.repo.owner,
  repo: context.repo.repo,
  pull_number: context.issue.number,
});

github.paginateを使用してPRの変更ファイル一覧を取得します。ページネーションに対応しているため、変更ファイルが多い場合でも全件取得できます。

2. 対象ファイルのフィルタリング

const filtered = files
  .filter((f) => ['added', 'modified', 'renamed', 'copied'].includes(f.status))
  .filter((f) => f.filename.startsWith('apps/app-a/src/'))
  .filter((f) => /\.(ts|tsx|js|jsx)$/.test(f.filename))
  .map((f) => f.filename);

3段階でフィルタリングを行います。

フィルタ目的
statusチェック追加、修正、リネーム、コピーされたファイルのみ対象(削除されたファイルは除外)
startsWithチェック該当アプリのsrc/配下のファイルのみ対象
拡張子チェック.ts.tsx.js.jsxファイルのみ対象

3. 変更ファイルがない場合はスキップ

if (filtered.length === 0) {
  core.info('No matching files to lint.');
  return;
}

対象ファイルがない場合はESLintの実行をスキップします。例えば、設定ファイルやドキュメントのみが変更された場合はESLintを実行しません。

4. 変更ファイルにのみESLintを実行

await exec.exec('npx', [
  'eslint',
  '-c',
  'apps/app-a/.eslintrc.cjs',
  ...filtered,
]);

yarn lint:app-a(プロジェクト全体)の代わりに、フィルタリングされたファイルのみを引数として渡してESLintを実行します。各アプリに適用する際は、src/パスとESLint設定ファイルのパスを変更するだけです。

動作確認

テスト用のPRで実際の動作を確認しました。

新しいESLintルールを追加した場合:

  • 変更したファイル: ESLintエラーが検出された
  • 変更していないが問題のあるファイル: ESLintエラーが検出されなかった

これにより、新ルール追加時に既存コードに影響なくCIが通過することを確認できました。

まとめ

BeforeAfter
検査対象プロジェクト全体PRで変更されたファイルのみ
新ルール追加時の影響全ファイルの修正が必要変更ファイルのみ対応すればOK
既存コード品質改善一括対応が必要修正時に段階的に改善

この方式により、新しいESLintルールを負担なく追加できるようになり、コード品質を段階的に向上させることができるようになりました。

私のブログが役に立ちましたか?下にコメントを残してください。それは私にとって大きな大きな力になります!

アプリ広報

今見てるブログを作成たDekuが開発したアプリを使ってみてください。
Dekuが開発したアプリはFlutterで開発されています。

興味がある方はアプリをダウンロードしてアプリを使ってくれると本当に助かります。



SHARE
Twitter Facebook RSS