ottijp blog

ESLint v9にアップデートしflat configに移行する

  • 2024-12-17

これはInfocom Advent Calendar 2024 17日目の記事です.

自分用のTypeScriptテンプレートで使っているESLintについて,重い腰を上げてv9にアップデートしflat configに移行しました.

flat config

ESLintはv9で設定ファイルのフォーマットに破壊的変更が入り,今までの.eslintrc.jsなどで定義される設定ファイルから,flat configと呼ばれるeslint.config.jsが使われるようになりました.

cf. Configuration Migration Guide - ESLint - Pluggable JavaScript Linter

cf. ESLint’s new config system, Part 2: Introduction to flat config - ESLint - Pluggable JavaScript Linter

私の.eslintrc.js

こちらに置いてありますが,以下のような設定ファイルを作っていました.

.eslintrc.js
module.exports = {
  root: true,
  parser: '@typescript-eslint/parser',
  plugins: ['@typescript-eslint'],
  env: {
    node: true,
    mocha: true,
  },
  parserOptions: {
    ecmaVersion: 'es6',
    sourceType: 'module',
    project: './tsconfig.eslint.json',
    tsconfigRootDir: __dirname,
  },
  ignorePatterns: ['dist'],
  extends: [
    'eslint:recommended',
    'plugin:@typescript-eslint/recommended-requiring-type-checking',
  ],
  rules: {
    'no-console': 'error',
    semi: ['error', 'never'],
    'brace-style': ['error', 'stroustrup', { allowSingleLine: true }],
    'comma-dangle': ['error', 'always-multiline'],
  },
}

ESlintと関連パッケージのアップデート

まずはESlint本体とプラグイン&パーサのメジャーバージョンをアップデートしました. (元々使っていた@typescript-eslint/eslint-pluginというプラグインと@typescript-eslint/parserというパーサはtypescript-eslintというパッケージに統合されていたようなので,ついでに変更しました.)

$ yarn outdated
yarn outdated v1.22.19
info Color legend :
 "<red>"    : Major Update backward-incompatible updates
 "<yellow>" : Minor Update backward-compatible features
 "<green>"  : Patch Update backward-compatible bug fixes
Package                          Current Wanted   Latest  Package Type    URL
@types/chai                      4.3.5   4.3.20   5.0.1   devDependencies https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/chai
@types/mocha                     10.0.1  10.0.10  10.0.10 devDependencies https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/mocha
@types/node                      20.5.0  20.17.10 22.10.2 devDependencies https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/node
@typescript-eslint/eslint-plugin 6.4.0   6.21.0   8.18.0  devDependencies https://typescript-eslint.io/packages/eslint-plugin
@typescript-eslint/parser        6.4.0   6.21.0   8.18.0  devDependencies https://typescript-eslint.io/packages/parser
chai                             4.3.7   4.5.0    5.1.2   devDependencies http://chaijs.com
eslint                           8.47.0  8.57.1   9.17.0  devDependencies https://eslint.org
mocha                            10.2.0  10.8.2   11.0.1  devDependencies https://mochajs.org/
ts-node                          10.9.1  10.9.2   10.9.2  devDependencies https://typestrong.org/ts-node
typescript                       5.1.6   5.7.2    5.7.2   devDependencies https://www.typescriptlang.org/
✨  Done in 1.69s.

$ yarn remove @typescript-eslint/eslint-plugin @typescript-eslint/parser

$ yarn add -D eslint@^9 typescript-eslint@^8

マイグレーションツールの実行

公式のガイドの通り,マイグレーションツールで既存の.eslintrc.jsからeslint.config.jsを作成しました.

$ npx @eslint/migrate-config .eslintrc.js
Need to install the following packages:
@eslint/migrate-config@1.3.5
Ok to proceed? (y) y

Migrating .eslintrc.js

WARNING: This tool does not yet work great for .eslintrc.(js|cjs|mjs) files.
It will convert the evaluated output of our config file, not the source code.
Please review the output carefully to ensure it is correct.


Wrote new config to ./eslint.config.mjs

You will need to install the following packages to use the new config:
- globals
- @eslint/js
- @eslint/eslintrc

You can install them using the following command:

npm install globals @eslint/js @eslint/eslintrc -D

The following messages were generated during migration:
- The 'node' environment is used, but the sourceType is 'module'. Using sourceType 'module'. If you want to use CommonJS modules, set the sourceType to 'commonjs'.

以下のファイルが出力されました.

eslint.config.mjs
import globals from "globals";
import tsParser from "@typescript-eslint/parser";
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 [{
    ignores: ["**/dist"],
}, ...compat.extends(
    "eslint:recommended",
    "plugin:@typescript-eslint/recommended-requiring-type-checking",
), {
    plugins: {
        "@typescript-eslint": typescriptEslint,
    },

    languageOptions: {
        globals: {
            ...globals.node,
            ...globals.mocha,
        },

        parser: tsParser,
        ecmaVersion: "es6",
        sourceType: "module",

        parserOptions: {
            project: "./tsconfig.eslint.json",
            tsconfigRootDir: "/Users/otti/src/github.com/ottijp/ts-template",
        },
    },

    rules: {
        "no-console": "error",
        semi: ["error", "never"],

        "brace-style": ["error", "stroustrup", {
            allowSingleLine: true,
        }],

        "comma-dangle": ["error", "always-multiline"],
    },
}];

必要な追加パッケージのインストール

マイグレーションツール実行時に案内されたパッケージをインストールしました.

yarn add -D globals@^15 @eslint/js@^9 @eslint/eslintrc@^3

エラーの解決

この状態でyarn lintをしてもエラーが発生したので,いくつか修正をしました.

ecmaVersionの値

$ yarn lint
yarn run v1.22.19
$ eslint .

Oops! Something went wrong! :(

ESLint: 9.17.0

TypeError: Key "languageOptions": Key "ecmaVersion": Expected a number or "latest".
    at new Config (/Users/otti/src/github.com/ottijp/ts-template/node_modules/eslint/lib/config/config.js:195:19)
    at [finalizeConfig] (/Users/otti/src/github.com/ottijp/ts-template/node_modules/eslint/lib/config/flat-config-array.js:216:16)
    at FlatConfigArray.getConfigWithStatus (/Users/otti/src/github.com/ottijp/ts-template/node_modules/@eslint/config-array/dist/cjs/index.cjs:1178:55)
    at FlatConfigArray.getConfig (/Users/otti/src/github.com/ottijp/ts-template/node_modules/@eslint/config-array/dist/cjs/index.cjs:1196:15)
    at entryFilter (/Users/otti/src/github.com/ottijp/ts-template/node_modules/eslint/lib/eslint/eslint-helpers.js:282:40)
    at async NodeHfs.<anonymous> (file:///Users/otti/src/github.com/ottijp/ts-template/node_modules/@humanfs/core/src/hfs.js:574:24)
    at async NodeHfs.walk (file:///Users/otti/src/github.com/ottijp/ts-template/node_modules/@humanfs/core/src/hfs.js:614:3)
    at async globSearch (/Users/otti/src/github.com/ottijp/ts-template/node_modules/eslint/lib/eslint/eslint-helpers.js:323:26)
    at async Promise.allSettled (index 0)
    at async globMultiSearch (/Users/otti/src/github.com/ottijp/ts-template/node_modules/eslint/lib/eslint/eslint-helpers.js:408:21)
error Command failed with exit code 2.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.

元々は"es2015"としていましたが,公式のドキュメントを読むと,languageOptions.ecmaVersionには2015という数値を指定するようになったらしいので,そのように変更しました.

tsconfigの設定ファイルにeslint.config.mjsが含まれていない?

$ yarn lint
yarn run v1.22.19
$ eslint .

/Users/otti/src/github.com/ottijp/ts-template/eslint.config.mjs
  0:0  error  Parsing error: "parserOptions.project" has been provided for @typescript-eslint/parser.
The file was not found in any of the provided project(s): eslint.config.mjs

✖ 1 problem (1 error, 0 warnings)

error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.

イマイチちゃんと理解できていないのですが,languageOptions.parserOptions.projectでTypeScriptパーサに渡しているtsconfigファイルのincludeにeslint.config.mjsが入っていない(?)ようだったので,tsconfigファイルのincludeeslint.config.mjsを追加しました.

eslint.config.jsのリファクタリング

これでyarn lintは正常に実行できるようになりました.

$ yarn lint
yarn run v1.22.19
$ eslint .

/Users/otti/src/github.com/ottijp/ts-template/eslint.config.mjs
   1:41  error  Extra semicolon         semi
   2:30  error  Extra semicolon         semi
   3:49  error  Extra semicolon         semi
   4:29  error  Extra semicolon         semi
   5:41  error  Extra semicolon         semi
   6:28  error  Extra semicolon         semi
   7:46  error  Extra semicolon         semi
   9:50  error  Extra semicolon         semi
  10:43  error  Extra semicolon         semi
  14:30  error  Missing trailing comma  comma-dangle
  15:3   error  Extra semicolon         semi
  49:3   error  Extra semicolon         semi

✖ 12 problems (12 errors, 0 warnings)
  12 errors and 0 warnings potentially fixable with the `--fix` option.

error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.

ただし,上記のようにeslint.config.js自体が私のESLintルールに違反しているのと,いくつか冗長(不要に複雑)な定義になっていたので,リファクタリングしました(FlatCompatを使う必要がないのに使っているなど). このリファクタリングにはTypeScript公式のeslint.config.jsを参考にしました.

この際,@eslint/eslintrcは不要になったので削除しました.

yarn remove @eslint/eslintrc

完成

リファクタリングしたeslint.config.jsがこちらです.

eslint.config.js
import path from 'node:path'
import { fileURLToPath } from 'node:url'
import eslint from '@eslint/js'
import globals from 'globals'
import tseslint from 'typescript-eslint'
import tsParser from '@typescript-eslint/parser'

const __filename = fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename)

export default tseslint.config(
  {
    ignores: ['**/dist'],
  },
  eslint.configs.recommended,
  ...tseslint.configs.recommended,
  {
    languageOptions: {
        globals: {
            ...globals.node,
            ...globals.mocha,
        },

        parser: tsParser,
        ecmaVersion: 2015,
        sourceType: 'module',

        parserOptions: {
            project: './tsconfig.eslint.json',
            tsconfigRootDir: __dirname,
        },
    },

    rules: {
        'no-console': 'error',
        semi: ['error', 'never'],

        'brace-style': ['error', 'stroustrup', {
            allowSingleLine: true,
        }],

        'comma-dangle': ['error', 'always-multiline'],
    },
  }
)

ソースはこちらに置いてあります.

refs


ottijp
都内でアプリケーションエンジニアをしています
© 2024, ottijp