eslint匹配规则速通
匹配规则
在 eslint.config.js 里,"当前文件" 到底会被哪一个(或哪几个)配置对象命中,是按下述流程逐条计算的。 只要记住一句话:
ESLint 会把文件路径依次跟每个配置对象的
files/ignores做 glob 匹配,命中且未被忽略的就生效;多个对象都命中就层层合并。
下面把"匹配"这件事拆成 5 个具体问题,一次性讲透。
-
拿谁来匹配? 文件在磁盘里的绝对路径 (Windows 带盘符,Unix 以
/开头)。 例:/home/me/proj/src/utils/is-empty.jsD:\repo\src\utils\is-empty.js -
跟谁匹配? 每个配置对象里可能出现的两组 glob 模式:
files数组 ------ 白名单ignores数组 ------ 黑名单(支持!否定)
-
匹配顺序(级联) 对每一个配置对象:
- 若对象没有
files,视为files:["**/*"](全选)。 - 拿文件路径去跟
files里每一项做 minimatch(类.gitignore语法)。- 只要有一项命中就认为该对象"适用于"此文件。
- 若对象还有
ignores,再跟这些模式比对:- 命中任何
ignores就立即把该对象排除,不再参与后续合并。 - 支持否定写法
"!xxx",可再把文件"捞回来"。
- 命中任何
- 全局忽略规则(对象里只有
ignores,没有files等其他键)会在所有对象之前先算一次,原理相同。
- 若对象没有
-
合并结果 经过上面筛选后,可能 0 ~ N 个对象对当前文件生效。
- 0 个 → ESLint 报错 "No configuration provided for ..."
- ≥1 个 → 按数组顺序浅合并 (靠后的覆盖靠前的):
languageOptions/linterOptions/plugins/settings递归合并;rules直接覆盖同名键。
-
快速验证 命令行看一眼到底哪条配置命中:
bashnpx eslint --inspect-config src/utils/is-empty.js会打印出最终合并后的完整配置以及"来自哪几个对象",调试神器。
js
export default [
{ // 对象 A
ignores: ["**/*.config.js"] // 全局忽略
},
{ // 对象 B
files: ["src/**/*.js"],
ignores: ["**/*.test.js"],
rules: { semi: "warn" }
},
{ // 对象 C
files: ["**/*.test.js"],
rules: { semi: "off" }
}
]
现在 lint 文件 /proj/src/utils/is-empty.js
- 全局忽略 A 先算 ------ 文件名不是
*.config.js,未被忽略,继续。 - 对象 B:
files命中src/**/*.js,且不在ignores里 → 生效。 - 对象 C:
files命中**/*.test.js,但当前文件不是测试 → 跳过。
最终只有 B 生效,semi: "warn"。
再把 /proj/src/utils/is-empty.test.js 走一遍:
- 全局忽略 A 同上,未忽略。
- 对象 B:虽然命中
src/**/*.js,但紧接着被**/*.test.js忽略 → 对象 B 被排除。 - 对象 C:命中
**/*.test.js→ 生效 ,semi: "off"。
合并规则
flat Config 的"合并"并不是深度合并(deep merge),而是按数组顺序、一级一级地"覆盖 + 累加",规则非常干脆:
- 数组顺序 = 优先级 后出现的 config 对象如果和前面的同字段同名 ,就整值覆盖;不同名就累加。
- 命中范围(files / ignores)独立判断 对单个文件来说,ESLint 会把所有路径命中 的 config 对象收集起来,再按顺序合并; 只要某一段 config 的
ignores匹配了,这段及后面同路径的 config 就被整段跳过。 - 合并粒度只到"字段"一级
languageOptions、plugins、rules都是整对象替换,不会递归合并内部子键。- 想"增量"就必须把前面那段再抄一遍,或者利用展开运算符自己拼装。
看一段代码就懂
js
export default [
{ // ①
files: ['**/*.ts'],
languageOptions: { parser: ts.parser, ecmaVersion: 2022 },
rules: { '@typescript-eslint/no-explicit-any': 'warn', 'no-console': 'off' }
},
{ // ②
files: ['**/*.ts'],
rules: { 'no-console': 'error' } // 整对象覆盖,结果只剩 no-console:error
}
];
对任意 .ts 文件:
- 先收集 ① → 再收集 ②
- ② 的
rules是全新对象,直接把 ① 的rules整份替换掉;languageOptions保留 ① 的,因为 ② 没写。
最终生效的只有:
js
languageOptions: { parser: ts.parser, ecmaVersion: 2022 }
rules: { 'no-console': 'error' }
@typescript-eslint/no-explicit-any 这条规则被"覆盖没"了------不会自动保留。