Open15

ESLint Flat Config メモ

pirosikickpirosikick

https://eslint.org/docs/latest/use/configure/configuration-files-new

設定ファイル

以下の3通り。

  • eslint.config.js
  • eslint.config.mjs
  • eslint.config.cjs

プロジェクトのルートディレクトリに置く必要あり。

以下のようにexport defaultで配列を返し、その中にオブジェクトで設定を並べていく。

export default [
    {
        rules: {
            semi: "error",
            "prefer-const": "error"
        }
    }
];

package.jsonのtypeフィールドが"module"の場合は、eslint.config.jsは以下のようなCommonJSフォーマットである必要がある。

module.exports = [
    {
        rules: {
            semi: "error",
            "prefer-const": "error"
        }
    }
];
pirosikickpirosikick

Configuration Objects

https://eslint.org/docs/latest/use/configure/configuration-files-new#configuration-objects

export defaultで返す配列に含まれるオブジェクト(以降、「設定オブジェクト」)が持てるプロパティ一覧。

files

globパターンの配列。設定オブジェクトに記述された設定を反映したいファイルを指定。
filesがない場合、その設定オブジェクトに記述された設定は他の設定オブジェクトで指定されたすべてのファイルに対して反映される。

ignores

globパターンの配列。設定オブジェクトに記述された設定を反映したくないファイルを指定。

languageOptions

割愛。ecmaVersion、parserとかがある。

parserには文字列でなく(例えば、"@typescript-eslint/parser")、パーサー本体(parse関数 or parserForESLint関数があるオブジェクト)を渡す。

linterOptions

静的解析のプロセスに関する設定。

  • noInlineConfig: falseにするとインラインの設定を無効にできる
  • reportUnusedDisableDirectives: 不要なenable/disableの指定があった場合に教えてくれる

processor

プロセッサーの指定。プロセッサー本体か文字列(pluginName/processorName

plugins

Key-Value形式で有効にするプラグインを記述。
filesが指定されている場合は、そのファイルのみに有効。逆に言うとfilesが無い場合は、すべてのファイルに反映。

rules

ルールの指定。
files、ignoresが指定されている場合は、そのファイルのみに有効。逆に言うとfilesが無い場合は、すべてのファイルに反映。

settings

すべてのルールから参照できる設定。eslint-plugin-importとかでよく使うやつ。

モヤモヤポイント

  • 評価順やマージされるのか否かが気になった。その辺はこの後に出てくるのだろう。
  • プロセッサーってなんやねん
pirosikickpirosikick

Specifying files and ignores

要点のみ、抜き出し。

  • files, ignoresはminimatchのsyntaxで、eslint.config.jsからの相対パスで評価される。
  • ESLintはデフォルトで**/*.js**/*.cjs**/*.mjsにマッチする
  • 前述の通りだが、filesがない場合は他の設定オブジェクトで指定されたファイル、他の設定オブジェクトにもfilesがない場合はデフォルトのファイルに対して設定が適応される
  • Non-global ignores = filesと併用して指定されるignores
  • Non-global ignoresは、ファイル名にしかマッチしない。"dir-to-exclude/"みたいなディレクトリ指定してもマッチしないので注意。代わりに、"dir-to-exclude/**"みたいな風に指定すべし

Globalなignores

filesなしでignoresを指定する場合の話。

  • 追加した設定はデフォルトの["**/node_modules/", ".git/"]の後に追加される。
  • 特定のディレクトリを無視する場合は、"dir/*"
  • 除外するには先頭に!を付ける。例えば、"!node_moduels/mylibrary/"みたいな感じ
  • Globalなignoresは、ディレクトリにもマッチできる。
pirosikickpirosikick

Cascading configuration objects

ファイルが1つ以上の設定オブジェクトにマッチする場合、設定はマージされる。
以下の設定では、"tests/**/*.js"は1つ目の設定オブジェクトにもマッチするので、MY_CUSTOM_GLOBAL、it、describeの3つのグローバル変数を許可することになる。

export default [
    {
        files: ["**/*.js"],
        languageOptions: {
            globals: {
                MY_CUSTOM_GLOBAL: "readonly"
            }
        }
    },
    {
        files: ["tests/**/*.js"],
        languageOptions: {
            globals: {
                it: "readonly",
                describe: "readonly"
            }
        }
    }
];
pirosikickpirosikick

Configuring linter options

linterOptionsの設定項目に関する詳細。特に気になる点はないので、割愛。
ただ、この中にある2つの設定は結構便利そうなので、覚えていて損はなさそう。

pirosikickpirosikick

Configuring language options

ESLintが対象のJSファイルをどう評価するかの設定。

Configuring the JavaScript version

ecmaVersionで、ECMAScriptのバージョンを指定。有効なグローバル変数と文法を決定するのに利用。
指定できるのは、6などのバージョン番号、2022などの西暦かlatest(ESLintがサポートする最も新しいバージョンを指定)。

export default [
    {
        files: ["**/*.js"],
        languageOptions: {
            ecmaVersion: 5
        }
    }
];

Configuring the JavaScript source type

JSファイルが、以下の3つのどれか指定する。

  • ES Module = "module"
  • CommonJS = "commonjs"
  • Script = "script"

デフォルトでは*.js, *.mjs"module"*.cjs"commonjs"として評価する。

export default [
    {
        files: ["**/*.js"],
        languageOptions: {
            sourceType: "script"
        }
    }
];

Configuring a custom parser and its options

パーサーとしてbabelやtypescript-eslintを使いたい場合に設定。

import babelParser from "@babel/eslint-parser";

export default [
    {
        files: ["**/*.js", "**/*.mjs"],
        languageOptions: {
            parser: babelParser
        }
    }
];

parserOptionsを使えば、パーサーにダイレクトにオプションを渡せる。

import babelParser from "@babel/eslint-parser";

export default [
    {
        files: ["**/*.js", "**/*.mjs"],
        languageOptions: {
            parser: babelParser,
            parserOptions: {
                requireConfigFile: false,
                babelOptions: {
                    babelrc: false,
                    configFile: false,
                    // your babel options
                    presets: ["@babel/preset-env"],
                }
            }
        }
    }
];

Configuring global variables

グローバル変数に関する設定。書き込み可か読み取り専用か指定できる。

export default [
    {
        files: ["**/*.js"],
        languageOptions: {
            globals: {
                var1: "writable",
                var2: "readonly"
            }
        }
    }
];

"off"を指定すると、グローバル変数を無効にできる。

export default [
    {
        languageOptions: {
            globals: {
                Promise: "off"
            }
        }
    }
];

Jestで使うグローバル変数を一括で定義するenv.jest = trueみたいなやつは、Flat Configで廃止。globalsを使って指定してね、とのこと。

import globals from "globals";
export default [
    {
        languageOptions: {
            globals: {
                ...globals.browser
            }
        }
    }
];
pirosikickpirosikick

Using plugins in your configuration

Using plugin rules

以下、eslint-plugin-jsdocを設定する例。旧設定と違うのは

  • プラグイン名/ルール名プラグイン名の部分に、任意の文字列を指定できるようになったこと。
  • プラグイン本体を渡すようになったこと
import jsdoc from "eslint-plugin-jsdoc";

export default [
    {
        files: ["**/*.js"],
        plugins: {
            jsd: jsdoc
        },
        rules: {
            "jsd/require-description": "error",
            "jsd/check-values": "error"
        }
    }
];

Using configurations included in plugins

プラグインが提供している設定を使用する場合は、設定オブジェクトの配列の中に入れればよい。

import jsdoc from "eslint-plugin-jsdoc";

export default [
    // configuration included in plugin
    jsdoc.configs["flat/recommended"],
    // other configuration objects...
    {
        files: ["**/*.js"],
        plugins: {
            jsdoc: jsdoc
        },
        rules: {
            "jsdoc/require-description": "warn",
        }
    }
];

Using processors

プロセッサーは、例えばmarkdown中のコードブロックなど、テキストをコード群に変換するために使う。
以下、eslint-plugin-markdownの例。

import markdown from "eslint-plugin-markdown";

export default [
    {
        files: ["**/*.md"],
        plugins: {
            markdown
        },
        processor: "markdown/markdown",
        settings: {
            sharedData: "Hello"
        }
    }
];

プロセッサーは、各コードに0.js1.jsみたいな名前を付けるので、プロセッサーが処理したテキストファイル内のJSのコードに対して設定オブジェクトを反映したい場合、files: ["**/*.md/*.js"]みたいに記述するとよい。

import markdown from "eslint-plugin-markdown";

export default [
    {
        files: ["**/*.md"],
        plugins: {
            markdown
        },
        processor: "markdown/markdown",
        settings: {
            sharedData: "Hello"
        }
    },

    // applies only to code blocks
    {
        files: ["**/*.md/*.js"],
        rules: {
            strict: "off"
        }
    }
];

疑問点

  • processorやpluginって新旧で互換性あるんだっけ?なんかあるっぽい気がするけど。
pirosikickpirosikick

Configuring rules

rulesについて。設定できる値(errorとかwarnとか)については今までと大体同じっぽいので割愛。

Rule configuration cascade

同じルールが複数の設定オブジェクトで指定されている場合は、後勝ち。

export default [
    {
        rules: {
            semi: ["error", "never"]
        }
    },
    {
        rules: {
            semi: ["warn", "always"]. // こっちが適応される
        }
    }
];

ルールのseverty(warnとかerrorとか)のみ上書きして変更できる。以下の場合、semiは["warn", "never"]を指定した状態になる。

export default [
    {
        rules: {
            semi: ["error", "never"]
        }
    },
    {
        rules: {
            semi: "warn"
        }
    }
];
pirosikickpirosikick

Configuring shared settings

settingsについて。今で通りなので割愛。

pirosikickpirosikick

Using predefined configurations

ESLintが提供しているルールセットは以下の2つがある。@eslint/jsパッケージにあるので使ってね。

  • js.configs.recommended
  • js.configs.all
import js from "@eslint/js";

export default [
    js.configs.recommended,
    {
        rules: {
            semi: ["warn", "always"]
        }
    }
];

特定のファイルに効かせたい場合は、以下のように設定オブジェクトに展開すればいい。

import js from "@eslint/js";

export default [
    {
        files: ["**/src/safe/*.js"],
        ...js.configs.recommended
    }
];
pirosikickpirosikick

Configuration File Resolution

ESLintは、cwdをeslint.config.js -> eslint.config.mjs -> eslint.configの順で探す。探しても無ければ親ディレクトリで同じように探す。

この挙動を変えたい場合は、ESLINT_USE_FLAT_CONFIG=true と --config で指定する。

ESLINT_USE_FLAT_CONFIG=true npx eslint --config some-other-file.js **/*.js
pirosikickpirosikick

typescript-eslintのconfigって何やってるの?

https://typescript-eslint.io/getting-started

// @ts-check

import eslint from '@eslint/js';
import tseslint from 'typescript-eslint';

export default tseslint.config(
  eslint.configs.recommended,
  ...tseslint.configs.recommended,
);

上記コードがおすすめされているが、tseslint.configは何をやっているのか気になって仕方ないので調べる。
また、...tseslint.configs.recommendedも気になるので調べる。この書き方をしているということはrecommendedは設定オブジェクトの配列なわけで、何が入るのか気になる。

tseslint.config

ソースコードはこちら。
https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/typescript-eslint/src/config-helper.ts#L65-L107

Utility function to make it easy to strictly type your "Flat" config file

なるほど、config関数を通してFlat Configを型チェックするのが目的なのか〜。// @ts-checkを付けることで、VS Code上で簡単にチェックできる、と。

加えて、本来設定オブジェクトにはないextendsを使えるように拡張されているみたい。
以下のような設定が、

export default [
   {
     ...eslint.configs.recommended,
     files: ['** /*.ts'],
   },
   ...tseslint.configs.recommended.map(conf => ({
     ...conf,
     files: ['** /*.ts'],
   })),
   {
     files: ['** /*.ts'],
     rules: {
       '@typescript-eslint/array-type': 'error',
       '@typescript-eslint/consistent-type-imports': 'error',
     },
   },
 ]

extendsがあることによって以下のようにシンプルに記述できる。

export default tseslint.config({
   files: ['** /*.ts'],
   extends: [
     eslint.configs.recommended,
     tseslint.configs.recommended,
   ],
   rules: {
     '@typescript-eslint/array-type': 'error',
     '@typescript-eslint/consistent-type-imports': 'error',
   },
})

でも、これはtypescript-eslintの役割として正しいのか?というと疑問。

pirosikickpirosikick

tseslint.configs.recommendedの中身を見る。

https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/typescript-eslint/src/index.ts#L32-L46

ソースはこちら。
https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/typescript-eslint/src/configs/recommended.ts#L1-L43

  • 関数として定義しparser, pluginを引数で渡すようになっており、parserとpluginを疎結合にしている。
  • 返り値の配列には、このファイルで定義されているrules以外に、baseConfig, eslintRecommendedConfigがある。

baseConfigは以下のような感じで、parserとpluginの設定を共通化するためのものと思われる。
https://github.com/typescript-eslint/typescript-eslint/blob/9f6f39151211fa63b127a0ce3e611c8f7d0fc784/packages/typescript-eslint/src/configs/base.ts#L1-L14

eslintRecommendedのソースはこちら。
https://github.com/typescript-eslint/typescript-eslint/blob/9f6f39151211fa63b127a0ce3e611c8f7d0fc784/packages/typescript-eslint/src/configs/base.ts#L1-L14

以下がだいぶ怪しい。。。てめえの責任で使ってね?を読み込むやつをGetting Startedに載せるんかーい。

import config from '@typescript-eslint/eslint-plugin/use-at-your-own-risk/eslint-recommended-raw';

eslintのrecommendedの中で役割が被っているものを無効にするのが目的っぽい。