😈

ESLintのバージョンアップを、新入社員のDevinに全てやってもらった

に公開

はじめに

こんにちは!イノベーション開発チームのmiyaken85です!
弊社が運営するITトレンドのフロントエンドでは、静的解析ツールとしてESLintを用いているのですが、バージョンアップ、めんどくさいですよね。
今回はそんな面倒で仕方ないESLintのバージョンアップ作業を、最近入社した新入りエンジニアのDevinくんに全てお任せしてやってもらった話をします。

前提

対象読者

  • AIを駆使した開発に興味がある方
  • Devin導入を検討している方
  • ESLintのバージョンアップやらないとと思ってる方

バージョンアップ前環境

  • ESLint:v8.57.1
  • Next.JS:v15系
  • Node.JS:v20.18.0

What's ESLint?

JavaScriptとTypeScriptのコードを静的解析してくれるツールです。
フロントエンドの静的解析ツールは、ESLint一強状態が長らく続いております。
https://eslint.org/

ESLintバージョンアップを検討した背景

以下の理由から、今バージョンアップをしておいた方がいいよねという決断に至りました。

サポート終了に早く対応しておきたかった

以前からESLint公式により、バージョンサポートポリシーが発表されております。
https://eslint.org/blog/2024/09/eslint-v8-eol-version-support/

これによると、

ESLintのメジャーリリースは以下の3段階でサポートされます:
• Current:現行バージョンとして積極的に開発・修正される。
• Maintenance:次のメジャー開発が始まると移行し、重要なバグやセキュリティ修正のみ対応。
• End of Life(EOL):現行版の一般公開から6か月経過後にサポート終了となり、以降は更新なし。

とあります。
実際、v8系はv9がリリースされた 2024/04/06 から6ヶ月後の 2024/10/05 にサポート終了となりました。
EOL後も使用を続けることは可能ですが、今後のバージョンアップ等で、新機能やバグ修正が提供されなくなるリスクが常に付きまとうことになるため、早急なバージョンアップが必要と判断しました。(とはいえ初動が遅い感は否めませんが)

Next.JSがESLintの最新バージョンに対応した

Next.JSのバージョン15系から、ESLintのv9に対応したと発表がありました。
(今までNext.JSの14系は、ESLintのv9に対応していなかったんですよねー。これがあってバージョンアップすぐにできなかった背景もある)
https://nextjs.org/blog/next-15#eslint-9-support

んで先日、ITトレンドでもNext.JSを最新の15系バージョンにアップデートしたことにより、ESLintのバージョンアップも行うタイミングとしてはバッチリでした。

Flat Config設定に対応しておきたかった

ESLint v9から、Flat Config形式がデフォルトとなり、v10でeslintrcは完全に削除されることが発表されています。
https://eslint.org/blog/2023/10/flat-config-rollout-plans/

Flat Configは、従来の.eslintrc形式に代わる新しい設定方式で、単一のeslint.config.jsファイルを使用し、設定の簡素化と柔軟性の向上を目的としているらしいです。

ITトレンドは、まだeslintrc形式の設定ファイルであったため、早急にFlat Config形式に対応する必要がありました。

バージョンアップ作業大変すぎる

そんなESLint、経験した人ならわかると思いますが、v8からv9へのESLintのバージョンアップって、単純にアップデートするだけでなく、関連する無数の外部ライブラリのアップデートも発生します。
その外部ライブラリの中でも、v9に対応しているものとそうでないものがあったりするので、それに応じてライブラリのダウングレードを行う、もしくはアンインストールして代替ライブラリに変えるなどの対応も別途必要になります。
加えて今回は、Flat Config形式に移行しなければならないので、設定ファイルのコードの書き方も変更する必要があり、とてもコストを要する作業になることが予想できました。。

そこで今回このタスクを、最近新しく入社したAIエンジニア、Devinくんにお任せしてみることにしてみました。

別プロジェクトに既にアサインされていて、ブイブイ言わせてるという評判も伺っていたので、お手並み拝見です😏

What's Devin?

公式のイントロダクションをそのまま日本語訳すると、

Devinは、野心的なエンジニアリングチームがより多くのことを達成できるように構築された、コラボレーションAIチームメイトです。

らしいです。かっこよ。
https://docs.devin.ai/get-started/devin-intro

要は、自然言語で指示を出すだけで、実際のソフトウェア開発タスク(コーディング・ビルド・テスト・デプロイなど)を勝手にやってくれる、新入りエンジニアくんです。

実際の作業工程

基本的には通常の開発工程と同じで、要件定義 → 開発 → テストして完了という流れでDevinくんには進めてもらいます。

要件定義

いうてDevinくんは新入りのエンジニアなので、コードを書く開発のフェーズをお任せすることにして、今回はシニアエンジニアの、ChatGPT o3大先生に要件定義を相談することにしました。

質問した内容は以下です。

ESLintのバージョンを、8.57.1から、最新バージョンの9系にあげる作業をdevinにやらせるためのプロンプトを作成してください。
package.jsonファイルの中身を参考に載せておきます。

~~ package.jsonファイルの中身 ~~

先生からの回答はこちら↓

ChatGPT回答
以下のテキストを Devin にそのまま渡せば、ESLint 8 → 9 へのアップグレード作業を自動でこなしてくれるように設計したプロンプトです。――必要な前提・制約・完了条件まで盛り込んであるので、そのままコピー&ペーストで使ってください。

# 🎯 ゴール
Next.js プロジェクト **next-app** の ESLint バージョンを **8.57.1 → 最新 9.x** にアップグレードし、全ての Lint/ビルド/テストがパスする状態を保つこと。

# 🗂️ 参考 package.json(抜粋)
{
  "scripts": {
    "lint": "next lint --ignore-path .gitignore",
    "lint:fix": "next lint --ignore-path .gitignore --fix"
  },
  "dependencies": {
    "eslint": "^8.57.1",
    "eslint-config-next": "15.2.4"
  },
  "devDependencies": {
    "@eslint/compat": "^1.2.4",
    "@eslint/js": "^9.17.0",
    "@eslint/migrate-config": "^1.3.5",
    "eslint-config-prettier": "^8.10.0",
    "eslint-plugin-sort-keys-fix": "^1.1.2",
    "eslint-plugin-storybook": "^0.11.6",
    "eslint-plugin-typescript-sort-keys": "^2.3.0",
    "eslint-plugin-unused-imports": "^3.0.0"
  },
  "engines": { "node": "20.x" }
}

# ✅ 完了の定義
1. **eslint**9 系の最新安定版 (^9) でインストールされている  
2. ESLint 関連パッケージ(公式・プラグイン・共有設定)が 9 系と互換性のあるバージョンに更新されている  
3. 必要に応じて **Flat Config** 形式へ移行し、既存ルールが再現されている  
4. `npm run lint` / `npm run lint:fix` がエラーなく終了する  
5. `npm run build`・`npm test` がローカルで成功する  
6. CI(GitHub Actions)がグリーンになる  
7. 変更点・破壊的変更への対応内容を **PR の概要** に日本語で説明している  

# 🛠️ 想定作業フロー
1. ブランチ cut: `feat/upgrade-eslint-v9`
2. 依存更新  
   - `npm install -D eslint@^9`  
   - `npm install -D eslint-config-next@latest`  
   - 関連プラグイン・共有設定を一括で最新化 (`npm outdated` → compatible versions)  
3. 移行ツール使用  
   - `npx @eslint/migrate-config@latest` で Flat Config へ変換(必要であれば)  
4. ルール衝突の調整  
   - Prettier 連携 (`eslint-config-prettier`) を再確認  
   - TypeScript/React 向けルールの互換性チェック  
5. コード自動修正  
   - `npm run lint:fix` を実行し自動修正  
   - 修正できない箇所はコミットメッセージで理由を明示  
6. 動作確認  
   - `npm run dev` と Storybook (`npm run storybook`) を起動し警告が出ないか確認  
   - E2E/ユニットテスト (`npm test`)  
7. ドキュメント更新  
   - `README.md` の開発環境セットアップ手順(もし ESLint コマンドが変わる場合)  
8. Pull Request 作成  
   - 説明欄に「主な breaking changes / 対応方針 / テスト結果」を記載  
   - Draft→Ready for review の段階で自動 CI を走らせる  

# ⏳ 制約
- Node v20.x、npm v10.x 前提  
- `package-lock.json` を必ずコミット  
- 既存コードの動作・ロジックは変更しない(Lint 修正のみ)  
- 30 min 以上ビルドが走るような重い変更は避ける  

# 📎 参考資料
- ESLint v9 Migration Guide: https://eslint.org/docs/latest/use/migration-guide  
- eslint-config-next Release Notes: https://nextjs.org/docs/app/building-your-application/configuring/eslint  
- Flat Config RFC: https://eslint.org/docs/latest/use/configure/flat-config  

よろしく!

とっても良さげです。流石すぎます。
僕の超絶アバウトな相談に対し、開発完了の定義、作業フロー、制約、更にはバージョンアップの際に参考にして欲しい資料なんかも、僕が何も言ってないのにプロンプトに載せてくれました。
(おそらくpackage.jsonの内容を相談内容に含めたことでより具体的なプロンプトになったものと思われます)

少しだけ、現在のリポジトリ構成に対応するように少しだけプロンプト修正を加え(ここは少しだけだったので手動)Devinくんにお願いしてみることにします。

いざバージョンアップ

先程o3先生に作成してもらったプロンプトをコピペして、Devinくんに依頼しました。
Devinくん、しっかり理解したようで作業を進めてくれそうです。
devin

僕はその間にメジャーリーグの試合を観ることにします⚾️

~~ 1時間後 ~~

終わったようです。
devin

どうやらCIのビルドに失敗してるようですが、ログを見たらprettierによるコード整形を作業フローにいれてなかったことが原因でした。これは僕の指示ミスなので、今回はとりま手動でコマンドを流すこととします。

.eslintrc.jsonは消え、見事にFlat Config形式に書き換わっておりました。すごい。。

eslint.config.mjs
eslint.config.mjs
import importPlugin from 'eslint-plugin-import';
import typescriptSortKeys from "eslint-plugin-typescript-sort-keys";
import unusedImports from "eslint-plugin-unused-imports";
import path from "node:path";
import { fileURLToPath } from "node:url";
import js from "@eslint/js";
import { FlatCompat } from "@eslint/eslintrc";

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const compat = new FlatCompat({
    baseDirectory: __dirname,
    recommendedConfig: js.configs.recommended,
    allConfig: js.configs.all
});


export default [
  ...compat.extends("next", "next/core-web-vitals", "prettier", "plugin:storybook/recommended"),
  {
    plugins: {
        "typescript-sort-keys": typescriptSortKeys,
        "unused-imports": unusedImports,
        "import": importPlugin,
    },

    rules: {
        "import/order": ["error", {
            groups: [
                "builtin",
                "external",
                "internal",
                "parent",
                "sibling",
                "index",
                "object",
                "type",
            ],

            "newlines-between": "always",

            alphabetize: {
                order: "asc",
            },
        }],

        "import/no-duplicates": "error",
        "@next/next/no-img-element": "off",
        "react/jsx-sort-props": "error",
        "unused-imports/no-unused-imports": "error",
    },
  }
];

少しだけ書き方が推奨と違う箇所等ありましたが全然大目に見れる範囲だし、
プルリクのコメントも、中々しっかり書いてるやん。。
devin-pullrequest

テスト

CIで通常時のLintは通ってることが確認できているので、ESLint自体がちゃんと機能しているかをテストしていきます。

ブランチをローカルに落とし、わざとこのESLIntの設定でエラーが起こるようなコードを書きます。
コード自体は、僕が普段使っているAIコードエディターのCursorに書いてもらいました。

ESLintエラーコード
// ──────────── import 関連の違反 ────────────
// ・アルファベット順でもグループ順でもない
// ・同じモジュールを重複インポート
// ・使っていない import あり
import zlib from 'zlib';
import React from 'react';
import React, { useState, useEffect } from 'react'; // ← duplicate
import fs from 'fs';
import '@splidejs/splide/css';                       // external
import MyComponent from '@/components/MyComponent';  // internal
import path from 'node:path';                        // builtin

// ──────────── 未使用の変数・import ────────────
const unusedVariable = 42;
function helper() {}             // 未使用関数
// (`fs`, `zlib`, `path` も未使用)

// ──────────── typescript-sort-keys 違反 ────────────
const unsortedObj = {
  zebra: 1,
  apple: 2,
  monkey: 3,
};

// ──────────── コンポーネント ────────────
export default function ExampleBadComponent() {
  const [count, setCount] = useState(0);
  useEffect(() => {
    setCount(count + 1);
  }, [count]);

  return (
    <div>
      {/* @next/next/no-img-element に違反 */}
      <img src='/dummy.png' />

      {/* jsx-sort-props に違反(アルファベット順でない) */}
      <MyComponent
        zProp='zzz'
        aProp='aaa'
        bProp='bbb'
      />

      <p>{unsortedObj.zebra}</p>
    </div>
  );
}

この状態でnpm run lintを回します。
lint-error

やったぜエラー大量祭り!
ちゃんとESLintが機能していることが確認できました!

(エラーが出て喜ぶとは・・)

まとめ

バージョンアップとFlat Configへのマイグレーション、大変な作業になることを予想していたので、こんなにあっけなく終わると思いませんでした。
複数のAIツールを活用しながらプロンプトさえしっかり作成してしまえば、Devinくんが真価を発揮するのかなという印象ですね。
これからもっと仲良くなっていきたい。

最後まで見ていただきありがとうございました!

株式会社イノベーション Tech Blog

Discussion