ESLint:从 flat config 到可审计 JavaScript 门禁
ESLint 的绿色取决于文件是否进入配置
ESLint 在 JavaScript、TypeScript 及语言插件支持的代码上运行规则。一次绿灯只说明被当前配置匹配的文件没有达到失败条件;glob 为空、文件被 ignore、解析器未加载或类型项目缺失,都可能让结果看起来更干净。Prettier 负责确定性排版,Prettier 手册维护格式入口;CSS 规则由 Stylelint 手册负责。
本文实验锁定 ESLint 10.8.1。ESLint 10 已移除旧 eslintrc 配置系统,并要求受支持的现代 Node.js;项目还要核对解析器、插件和共享配置的 peer dependency。发行号只约束实验,升级继续查看官方发行记录与迁移指南。
| 输入 | 决定什么 | 常见假绿 |
|---|---|---|
eslint.config.* | 配置对象顺序、files、ignores 与规则 | 后续对象覆盖前面,目标文件未命中 |
| parser / language plugin | 代码能否解析与建立作用域 | 用默认 parser 扫描 TS 或新语法 |
| TypeScript Project Service | 类型感知规则的类型图 | 文件不属于任何 tsconfig |
| CLI 退出码 | CI 是否阻断 | 包装脚本吞掉非零状态 |
本地依赖和 lockfile 是运行单元
工具安装在项目内并精确锁定,不依赖编辑器全局版本或临时下载:
npm install --save-dev --save-exact eslint@10.8.1 @eslint/js eslint-config-prettier
npm ci
npm exec -- eslint --version
npm ls eslint @eslint/js eslint-config-prettiernpm exec -- 只在依赖已存在时作为本地入口。缺少依赖时临时下载会绕开 lockfile;CI 先 npm ci,再运行项目脚本。回退需要同时恢复 package.json、lockfile、flat config、ignore、缓存和 package scripts,不能只删除 node_modules/eslint。
flat config 的顺序就是行为
一个保守的 eslint.config.mjs:
import { defineConfig } from "eslint/config";
import js from "@eslint/js";
import eslintConfigPrettier from "eslint-config-prettier/flat";
export default defineConfig([
{ ignores: ["dist/**", "coverage/**", "generated/**"] },
js.configs.recommended,
{
files: ["src/**/*.{js,mjs,cjs}"],
rules: {
"no-unused-vars": "error",
"no-undef": "error",
},
},
eslintConfigPrettier,
]);files 决定配置对象适用于谁,ignores 划出排除边界,后面的对象可以覆盖前面的规则。eslint-config-prettier 只关闭与格式化冲突的 Lint 规则,不执行 Prettier。旧 .eslintrc*、.eslintignore 和 eslint-env 注释迁移后要逐项复核,不能只改文件名。
用最终配置解释单个文件:
npm exec -- eslint --print-config src/app.js > artifacts/eslint/app-config.json
npm exec -- eslint --inspect-config--print-config 的输出绑定 ESLint、插件和配置提交,可在升级差异中比较。若文件没有得到预期规则,先看匹配对象与覆盖顺序,不用追加一个全局配置掩盖问题。
TypeScript 类型信息需要明确成本
TypeScript 项目锁定 typescript 与 typescript-eslint,再启用类型感知配置:
import { defineConfig } from "eslint/config";
import tseslint from "typescript-eslint";
export default defineConfig({
files: ["src/**/*.ts"],
extends: [tseslint.configs.recommendedTypeChecked],
languageOptions: {
parserOptions: {
projectService: true,
tsconfigRootDir: import.meta.dirname,
},
},
});Project Service 会寻找文件所属的 tsconfig.json,CPU 和内存成本接近类型检查。大仓库要记录 P50/P95 时间与峰值内存,按 package 切分任务。出现“文件不在项目中”时先修 tsconfig 或 glob,不用宽泛默认项目吞入整个仓库。
正反样例证明规则与退出码
function loadUser() {
const unused = 1;
return missingUser;
}
loadUser();运行:
npm exec -- eslint .quality-smoke/eslint-bad.js预期 no-unused-vars 与 no-undef 命中,退出码非零。删除无用变量并显式传入用户后,同一命令恢复通过。若控制台有违规而 CI 仍绿,检查 shell 管道、|| true、--max-warnings 与包装脚本;若没有违规,检查 --print-config、ignore 与实际工作目录。
项目脚本分开检查与修复:
{
"scripts": {
"lint:js": "eslint . --max-warnings 0",
"lint:js:fix": "eslint . --fix"
}
}CI 只执行检查入口。--fix 会改工作树,放在开发者明确调用的命令或独立自动修复提交,不与业务构建暗中混用。
抑制、历史债务与规则 owner
行内 disable、配置 overrides 和 bulk suppressions 都是门禁状态。ESLint 可报告无用禁用指令;每条例外应含规则、范围、理由、owner 与退出条件。存量引入更严格规则时,先影子运行并记录现有实例,再对新增代码阻断;不能把整目录加入 ignores 当 baseline。
bulk suppression 文件的指纹会受代码和规则变化影响。评审时展示新增、消失和漂移,修复后删除对应记录。未来同类问题必须仍失败,否则 baseline 已退化为永久 allowlist。
插件与配置是构建时代码
eslint.config.mjs 可以执行 JavaScript,parser、processor、语言插件和共享配置都在当前进程权限下运行。依赖只从受控制品源安装,锁版本与完整性;外部 PR 的 Lint Job 不持有发布、云端或生产凭证。编辑器扩展使用工作区 ESLint,不允许全局版本覆盖仓库裁决。
缓存只用于加速。配置、插件、Node.js 或文件边界变化后清理缓存并做无缓存对照。报告和 --print-config 可能暴露内部路径、规则与源码片段,按 CI 制品授权和留存。
升级与回滚
升级 PR 同时记录 Node.js、ESLint、parser、插件、共享配置和最终配置。旧新版本在同一提交上运行,差异按新增违规、消失违规、解析错误、规则重命名、配置匹配和耗时分组;自动修复 diff 与依赖升级分开提交。
回滚单元是 Node.js 基线、依赖清单、lockfile、flat config、插件、suppressions 与缓存清理动作。恢复后从干净安装运行坏样例、修正样例、全量 Lint、构建和测试。只有目标文件确实被配置匹配、规则失败能传到 CI、抑制可审计且编辑器不改变裁决版本,ESLint 门禁才真正闭合。
