ESLint の extends が効かない問題を斬る:Flat Config の files/ignores 落とし穴

ESLint の設定で extends が突然効かなくなった経験はありませんか?特に Flat Config に移行した際、多くの開発者が「なぜ設定が適用されないのか」と頭を抱えています。
実は、この問題の背景には Flat Config における files と ignores の設定方法に関する「落とし穴」が存在します。従来の設定方式とは根本的に異なるアプローチが必要で、単純に設定を移行しただけでは期待通りに動作しないのです。
この記事では、extends が効かない具体的な原因を特定し、確実に動作する解決方法をお示しします。
背景
ESLint Flat Config とは
ESLint Flat Config は、ESLint v8.21.0 で導入された新しい設定方式です。従来の .eslintrc
ファイルに代わり、eslint.config.js
を使用します。
javascript// 新しい Flat Config の基本構造
export default [
{
files: ['**/*.js'],
rules: {
'no-unused-vars': 'error',
},
},
];
Flat Config では、設定を配列として定義し、各要素が特定のファイルパターンに対するルールセットを表現します。この配列の順序が設定の適用順序を決定する重要な要素となります。
以下の図で、Flat Config の基本構造を理解しましょう。
mermaidflowchart TD
config[eslint.config.js] --> array[設定配列]
array --> obj1[設定オブジェクト1]
array --> obj2[設定オブジェクト2]
array --> obj3[設定オブジェクト3]
obj1 --> files1[files: パターン1]
obj1 --> rules1[rules: ルール1]
obj2 --> files2[files: パターン2]
obj2 --> ignores2[ignores: 除外パターン]
obj2 --> extends2[extends: 拡張設定]
obj3 --> global[グローバル設定]
補足: 配列の順序が設定の優先順位を決定し、後に定義された設定が前の設定を上書きします。
従来の設定方式との違い
従来の .eslintrc
では、extends と overrides を組み合わせて設定を構築していました。
javascript// 従来の .eslintrc.js
module.exports = {
extends: ['eslint:recommended'],
overrides: [
{
files: ['*.ts'],
extends: ['@typescript-eslint/recommended'],
},
],
};
一方、Flat Config では設定オブジェクトを配列として並べ、より直線的な構造を採用しています。これにより設定の適用順序が明確になりましたが、同時に新たな複雑さも生まれました。
項目 | 従来設定 | Flat Config |
---|---|---|
ファイル名 | .eslintrc.js | eslint.config.js |
構造 | オブジェクト | 配列 |
継承方式 | extends + overrides | 配列の順序による上書き |
ファイル指定 | overrides.files | 各設定の files |
除外設定 | .eslintignore | ignores プロパティ |
extends の役割と期待される動作
extends は、他の設定を継承するための仕組みです。Flat Config では、2025 年の更新により extends が再導入され、より使いやすくなりました。
javascript// extends の基本的な使用方法
import { defineConfig } from 'eslint/config';
import js from '@eslint/js';
export default defineConfig([
{
files: ['**/*.js'],
extends: ['js/recommended'],
rules: {
'prefer-const': 'off',
},
},
]);
extends は以下の順序で設定を適用します:
- 基底設定の読み込み
- 継承設定の適用
- 個別ルールによる上書き
しかし、files や ignores の設定が不適切だと、この継承チェーンが正しく機能しません。
課題
extends が効かない具体的な症状
多くの開発者が遭遇する典型的な症状をご紹介します。
症状 1: 部分的な設定適用
javascript// 問題のある設定例
export default [
{
extends: ['eslint:recommended'],
rules: {
'no-console': 'error',
},
},
{
files: ['src/**/*.js'],
rules: {
'prefer-const': 'error',
},
},
];
この設定では、eslint:recommended
が全ファイルに適用されるはずですが、src/**/*.js
にマッチするファイルでのみ prefer-const
が適用されます。
症状 2: TypeScript ファイルでの設定無視
javascript// TypeScript ファイルで設定が適用されない例
export default [
{
files: ['**/*.js'],
extends: ['eslint:recommended'],
},
];
この設定は .js
ファイルのみを対象としているため、.ts
ファイルには extends が適用されません。
files と ignores の設定ミス
files と ignores の設定は、extends の適用範囲を決定する重要な要素です。
以下の図で、files と ignores の関係を理解しましょう。
mermaidflowchart LR
all[全ファイル] --> files{files パターン}
files -->|マッチ| target[対象ファイル]
files -->|非マッチ| exclude1[除外ファイル]
target --> ignores{ignores パターン}
ignores -->|マッチ| exclude2[除外ファイル]
ignores -->|非マッチ| final[最終対象ファイル]
final --> lint[リント実行]
補足: files でフィルタリングされた後、ignores でさらに除外処理が行われます。
よくある間違い 1: グロブパターンの記述ミス
javascript// 間違った files パターン
export default [
{
files: ["src/*.js"], // サブディレクトリが含まれない
extends: ["eslint:recommended"]
}
];
// 正しい files パターン
export default [
{
files: ["src/**/*.js"], // サブディレクトリも含む
extends: ["eslint:recommended"]
}
];
よくある間違い 2: ignores の配置順序
javascript// 間違った ignores の配置
export default [
{
files: ['**/*.js'],
ignores: ['dist/**'], // この位置では期待通りに動作しない
extends: ['eslint:recommended'],
},
];
設定の優先順位問題
Flat Config では、配列の後方に配置された設定が前方の設定を上書きします。この仕組みを理解せずに設定を記述すると、予期しない動作を引き起こします。
javascript// 設定の上書きが発生する例
export default [
{
files: ['**/*.js'],
extends: ['eslint:recommended'],
rules: {
'no-console': 'error',
},
},
{
files: ['**/*.js'],
rules: {
'no-console': 'off', // 前の設定を上書き
},
},
];
この場合、2 番目の設定オブジェクトが 1 番目の no-console: "error"
を上書きしてしまいます。
図で理解できる要点:
- 設定の適用順序は配列の順番で決まる
- files パターンのマッチング精度が重要
- ignores は適切な位置に配置する必要がある
解決策
正しい files パターンの指定方法
files パターンを正確に指定することで、extends の適用範囲を制御できます。
基本的なパターン記述
javascript// 推奨される files パターンの指定方法
export default [
{
// JavaScript ファイル全般
files: ['**/*.{js,mjs,cjs}'],
extends: ['eslint:recommended'],
},
{
// TypeScript ファイル全般
files: ['**/*.{ts,tsx}'],
extends: ['@typescript-eslint/recommended'],
},
{
// React ファイル
files: ['**/*.{jsx,tsx}'],
extends: ['plugin:react/recommended'],
},
];
特定ディレクトリに限定した設定
javascript// ディレクトリ別の設定適用
export default [
{
files: ['src/**/*.js'],
extends: ['eslint:recommended'],
rules: {
'no-console': 'error',
},
},
{
files: ['tests/**/*.js'],
extends: ['eslint:recommended'],
rules: {
'no-console': 'off', // テストファイルでは console.log を許可
},
},
];
複数条件の組み合わせ
javascript// 複雑なファイルパターンの組み合わせ
export default [
{
files: [
'src/**/*.js',
'lib/**/*.js',
'!src/**/*.test.js', // テストファイルは除外
],
extends: ['eslint:recommended'],
},
];
ignores の適切な使い方
ignores の適切な使用方法を理解することで、不要なファイルのリントを回避できます。
グローバル ignores の設定
2025 年の更新により、globalIgnores ヘルパーが導入されました。
javascript// globalIgnores ヘルパーの使用
import { defineConfig, globalIgnores } from 'eslint/config';
export default defineConfig([
// グローバル ignore は配列の最初に配置
globalIgnores([
'dist/**',
'build/**',
'node_modules/**',
'coverage/**',
]),
{
files: ['**/*.js'],
extends: ['eslint:recommended'],
},
]);
条件付き ignores の設定
javascript// 特定の設定でのみ適用される ignores
export default [
{
files: ['src/**/*.js'],
ignores: [
'src/**/*.min.js', // minify されたファイル
'src/vendor/**', // サードパーティコード
],
extends: ['eslint:recommended'],
},
];
ignores の継承を避ける方法
javascript// ignores が他の設定に影響しないようにする
export default [
{
// グローバル設定(ignores なし)
files: ['**/*.js'],
extends: ['eslint:recommended'],
},
{
// 特定ディレクトリの設定
files: ['src/legacy/**/*.js'],
ignores: ['src/legacy/vendor/**'],
rules: {
// レガシーコード用の緩い設定
'no-var': 'off',
},
},
];
extends と併用する際の注意点
extends を他の設定要素と併用する際のベストプラクティスをご紹介します。
defineConfig の活用
javascript// defineConfig による型安全な設定
import { defineConfig } from 'eslint/config';
import js from '@eslint/js';
import typescript from '@typescript-eslint/eslint-plugin';
export default defineConfig([
{
files: ['**/*.js'],
plugins: { js },
extends: ['js/recommended'],
rules: {
'prefer-const': 'error',
},
},
{
files: ['**/*.ts'],
plugins: { typescript },
extends: ['typescript/recommended'],
rules: {
'@typescript-eslint/no-unused-vars': 'error',
},
},
]);
プラグイン設定との組み合わせ
javascript// プラグインと extends の適切な組み合わせ
export default [
{
files: ['**/*.js'],
plugins: {
js: require('@eslint/js'),
},
extends: ['js/recommended'],
},
{
files: ['**/*.jsx'],
plugins: {
react: require('eslint-plugin-react'),
},
extends: ['js/recommended', 'react/recommended'],
settings: {
react: {
version: 'detect',
},
},
},
];
設定の分割と管理
javascript// 設定の分割による管理しやすい構造
const baseConfig = {
files: ['**/*.js'],
extends: ['eslint:recommended'],
rules: {
'no-console': 'warn',
},
};
const reactConfig = {
files: ['**/*.jsx'],
extends: [
'eslint:recommended',
'plugin:react/recommended',
],
rules: {
'react/prop-types': 'error',
},
};
const testConfig = {
files: ['**/*.test.js'],
extends: ['eslint:recommended'],
rules: {
'no-console': 'off',
},
};
export default [baseConfig, reactConfig, testConfig];
具体例
問題のある設定例
実際のプロジェクトでよく見られる問題のある設定例をご紹介します。
問題例 1: files パターンの不備
javascript// ❌ 問題のある設定
export default [
{
// TypeScript ファイルが対象外
files: ['*.js'],
extends: ['eslint:recommended'],
},
{
// サブディレクトリが対象外
files: ['src/*.ts'],
extends: ['@typescript-eslint/recommended'],
},
];
この設定の問題点:
*.js
はルートディレクトリのファイルのみ対象src/*.ts
はサブディレクトリが含まれない- 多くのファイルでリントが実行されない
問題例 2: ignores の配置ミス
javascript// ❌ ignores の配置が不適切
export default [
{
files: ['**/*.js'],
extends: ['eslint:recommended'],
ignores: ['dist/**'], // この位置では期待通りに動作しない
},
{
ignores: ['node_modules/**'], // 設定対象外のため無効
rules: {
'no-console': 'error',
},
},
];
この設定の問題点:
- ignores がファイル指定と同じオブジェクト内にある
- グローバル ignores が適切に設定されていない
問題例 3: 設定の重複と競合
javascript// ❌ 設定の重複による予期しない動作
export default [
{
files: ['**/*.js'],
extends: ['eslint:recommended'],
rules: {
'no-console': 'error',
},
},
{
// 同じファイルパターンで競合
files: ['**/*.js'],
extends: ['plugin:prettier/recommended'],
rules: {
'no-console': 'off', // 前の設定を上書き
},
},
];
修正後の設定例
上記の問題を解決した適切な設定例をご紹介します。
修正例 1: 適切な files パターン
javascript// ✅ 修正後の設定
import { defineConfig, globalIgnores } from 'eslint/config';
export default defineConfig([
// グローバル除外設定
globalIgnores(['dist/**', 'build/**', 'node_modules/**']),
{
// JavaScript ファイル全般
files: ['**/*.{js,mjs,cjs}'],
extends: ['eslint:recommended'],
rules: {
'no-console': 'warn',
},
},
{
// TypeScript ファイル全般
files: ['**/*.{ts,tsx}'],
extends: [
'eslint:recommended',
'@typescript-eslint/recommended',
],
rules: {
'@typescript-eslint/no-unused-vars': 'error',
},
},
]);
修正例 2: 階層化された設定
javascript// ✅ プロジェクト構造に合わせた階層化設定
export default defineConfig([
globalIgnores(['dist/**', 'coverage/**']),
// ベース設定
{
files: ['**/*.{js,ts}'],
extends: ['eslint:recommended'],
rules: {
'no-console': 'warn',
'prefer-const': 'error',
},
},
// TypeScript 専用設定
{
files: ['**/*.ts'],
extends: ['@typescript-eslint/recommended'],
rules: {
'@typescript-eslint/explicit-function-return-type':
'warn',
},
},
// React 専用設定
{
files: ['src/components/**/*.{jsx,tsx}'],
extends: [
'plugin:react/recommended',
'plugin:react-hooks/recommended',
],
rules: {
'react/prop-types': 'error',
},
},
// テストファイル専用設定
{
files: ['**/*.{test,spec}.{js,ts}'],
extends: ['plugin:jest/recommended'],
rules: {
'no-console': 'off',
},
},
]);
修正例 3: モノレポ対応設定
javascript// ✅ モノレポプロジェクト向け設定
export default defineConfig([
globalIgnores([
'**/node_modules/**',
'**/dist/**',
'**/build/**',
]),
// 共通設定
{
files: ['packages/*/src/**/*.{js,ts}'],
extends: ['eslint:recommended'],
rules: {
'no-console': 'error',
},
},
// フロントエンドパッケージ
{
files: ['packages/frontend/src/**/*.{jsx,tsx}'],
extends: [
'eslint:recommended',
'plugin:react/recommended',
],
rules: {
'react/react-in-jsx-scope': 'off',
},
},
// バックエンドパッケージ
{
files: ['packages/backend/src/**/*.ts'],
extends: [
'eslint:recommended',
'@typescript-eslint/recommended',
],
rules: {
'no-console': 'off', // サーバーサイドでは console.log を許可
'@typescript-eslint/no-explicit-any': 'error',
},
},
]);
検証方法とデバッグ手順
設定が正しく適用されているかを確認するための手順をご紹介します。
基本的な検証コマンド
bash# 設定の確認
yarn eslint --print-config src/components/Button.tsx
# 特定ファイルのリント実行
yarn eslint src/components/Button.tsx --debug
# 設定ファイル全体の検証
yarn eslint . --debug 2>&1 | grep "Using config"
VS Code での設定確認
VS Code で Flat Config を有効にする設定:
json// .vscode/settings.json
{
"eslint.experimental.useFlatConfig": true,
"eslint.debug": true
}
トラブルシューティング用の設定
javascript// デバッグ用の詳細設定
export default defineConfig([
{
// デバッグ情報を含む設定
name: 'debug-base-config',
files: ['**/*.js'],
extends: ['eslint:recommended'],
rules: {
'no-console': 'warn',
},
},
{
name: 'debug-typescript-config',
files: ['**/*.ts'],
extends: ['@typescript-eslint/recommended'],
rules: {
'@typescript-eslint/no-unused-vars': 'error',
},
},
]);
設定の動作確認スクリプト
javascript// scripts/verify-eslint-config.js
const { ESLint } = require('eslint');
async function verifyConfig() {
const eslint = new ESLint();
// 設定の確認
const config = await eslint.calculateConfigForFile(
'src/index.ts'
);
console.log(
'Config for src/index.ts:',
JSON.stringify(config, null, 2)
);
// ファイルの対象確認
const isIgnored = await eslint.isPathIgnored(
'src/index.ts'
);
console.log('Is src/index.ts ignored:', isIgnored);
}
verifyConfig().catch(console.error);
まとめ
ESLint Flat Config での extends が効かない問題は、主に files と ignores の設定方法に起因します。この記事で紹介した解決策を実践することで、確実に動作する設定を構築できます。
重要なポイントをまとめますと:
- globalIgnores の活用: グローバルな除外設定は配列の最初に配置
- 適切な files パターン:
**/*.{js,ts}
のような包括的なパターンを使用 - defineConfig の採用: 型安全性と最新機能の活用
- 設定の階層化: プロジェクト構造に合わせた適切な分割
- 検証の徹底: デバッグ機能を活用した動作確認
これらの手法を適用することで、Flat Config への移行をスムーズに進められ、期待通りにリンターが動作するプロジェクト環境を構築できるでしょう。
ESLint の設定は複雑に見えますが、基本原則を理解すれば必ず解決できます。ぜひこの記事を参考に、快適な開発環境を整えてください。
関連リンク
- article
ESLint の extends が効かない問題を斬る:Flat Config の files/ignores 落とし穴
- article
ESLint Flat Config 完全理解:eslint.config.js 時代の設計指針
- article
ESLint と Prettier の競合を完全解決:eslint-config-prettier 徹底解説
- article
ESLint でアクセシビリティを向上させる a11y ルール活用術
- article
Lerna × ESLint:マルチパッケージ環境での効率的な Lint 運用
- article
ESLint ルールの重要度設定:error・warn・off の使い分け戦略
- article
Redis OOM を根絶:maxmemory・eviction・大キー検出の実践トリアージ
- article
Git 内部処理の舞台裏:パックファイル・コミットグラフ・参照の関係を図解で理解
- article
Python 依存地獄からの生還:pip/Poetry/uv の競合を解きほぐす実践手順
- article
FFmpeg 音ズレを根治:VFR→CFR 変換と PTS 補正の実践ガイド
- article
ESLint の extends が効かない問題を斬る:Flat Config の files/ignores 落とし穴
- article
Prisma アーキテクチャ超図解:Engines/Client/Generator の役割を一枚で理解
- blog
iPhone 17シリーズの発表!全モデルiPhone 16から進化したポイントを見やすく整理
- blog
Googleストアから訂正案内!Pixel 10ポイント有効期限「1年」表示は誤りだった
- blog
【2025年8月】Googleストア「ストアポイント」は1年表記はミス?2年ルールとの整合性を検証
- blog
Googleストアの注文キャンセルはなぜ起きる?Pixel 10購入前に知るべき注意点
- blog
Pixcel 10シリーズの発表!全モデル Pixcel 9 から進化したポイントを見やすく整理
- blog
フロントエンドエンジニアの成長戦略:コーチングで最速スキルアップする方法
- review
今の自分に満足していますか?『持たざる者の逆襲 まだ何者でもない君へ』溝口勇児
- review
ついに語られた業界の裏側!『フジテレビの正体』堀江貴文が描くテレビ局の本当の姿
- review
愛する勇気を持てば人生が変わる!『幸せになる勇気』岸見一郎・古賀史健のアドラー実践編で真の幸福を手に入れる
- review
週末を変えれば年収も変わる!『世界の一流は「休日」に何をしているのか』越川慎司の一流週末メソッド
- review
新しい自分に会いに行こう!『自分の変え方』村岡大樹の認知科学コーチングで人生リセット
- review
科学革命から AI 時代へ!『サピエンス全史 下巻』ユヴァル・ノア・ハラリが予見する人類の未来