装完 ESLint 它一声不吭?我还以为代码写得多好

说实话,我之前一直觉得 ESLint 就是个装完就能用的工具,脚手架里自带、公司项目里配好,我只管写代码就行。直到这次自己从零搭一个 demo,一行 npm run lint 跑下去,控制台给我甩了个大红叉,我才意识到这玩意儿我根本没真正搞懂过。

跑了一下直接炸了

事情是这样的,我新开了个空目录,想正经给自己的小工具项目加上代码检查。按记忆里的套路:

bash 复制代码
npm i -D eslint
npx eslint --init

init 过程中选了"检查语法+找问题+统一风格",用的是 JavaScript 模块配置,没有框架,选了 Node 环境。然后在 index.js 里随手写了几行测试代码:

javascript 复制代码
var name = 'xjl_ai';
// let age = 18;
function hello() {
    console.log('hello');
}

hello();

接着在 package.json 里加了脚本:

json 复制代码
{
  "scripts": {
    "lint": "eslint .",
    "lint:fix": "eslint . --fix"
  }
}

敲下 npm run lint,然后我就看到了这个:

满屏红。var 不能用、name 声明了没引用、缩进是 4 个空格它要 2 个、console.log 不允许、字符串必须双引号。5 个问题,4 errors 1 warning。

当时我第一反应是:这不都正常代码吗?var 怎么就不能用了?我写 4 个空格缩进关你什么事?console.log 我调试用的也管?

我以为装完就能用,结果什么都不检查

说实话,看到这一屏报错之前,我还踩了个更蠢的坑。

最开始我只跑了 npm i -D eslint,然后直接 npx eslint .,结果控制台干干净净,什么输出都没有。我当时还觉得"挺不错啊,代码没毛病"。后来越想越不对------我故意写了个 var a = 1; 这种明显"不规范"的代码,它居然一个字都没说。

翻了下文档我才懵了:ESLint 默认所有规则都是关闭的。你光装个 eslint,它就是个空壳子,一条规则都不检查,跟个没装任何插件的 IDE 一样。它不像 Prettier 那样开箱即用,你必须明确告诉它"我要检查哪些东西"。

这就好比你请了个质检员,但你没给他质检标准,他就站那啥也不干。

那怎么加规则?我知道可以在配置文件里一条条写 rules,但总不能把几百条规则全手写吧?这时候就要靠 extends------直接继承别人配好的规则集。@eslint/js 这个包里带了个 recommended 配置,就是官方推荐的那套规则,init 的时候自动帮我加上了。

你看我配置里这行:

javascript 复制代码
extends: ["js/recommended"],

这行的意思就是"把官方推荐的规则全给我打开"。没有这行,ESLint 就是个哑巴。

照着旧教程写配置,结果它根本不认

搞定了"为什么不报错"的问题,我想加点自定义规则。比如强制双引号、缩进 2 空格、语句末尾加分号。

这时候我又踩了第二个坑,而且这个坑卡了我快两天。

我上网搜"ESLint 自定义规则",搜出来的教程清一色教你写 .eslintrc.js

javascript 复制代码
// 网上教程里的旧写法
module.exports = {
  rules: {
    quotes: ["error", "double"],
    indent: ["error", 2]
  }
};

我照着在项目根目录建了个 .eslintrc.js,把规则写进去,跑 npm run lint------结果它完全无视我的配置,还是按默认的来。我反复确认文件路径、JSON 格式、拼写,甚至把规则名抄了一遍又一遍,就是不生效。

后来才发现,我装的是 ESLint v10 (npm 上最新版),从 v9 开始,ESLint 默认使用扁平配置模式(Flat Config) ,配置文件名变成了 eslint.config.mjs,写法也完全不一样了。旧的 .eslintrc 格式在新版里默认不读取,你得主动开兼容模式才认。

跑一下 npx eslint --init 就明白了,它生成的配置文件长这样:

javascript 复制代码
import js from "@eslint/js";
import globals from "globals";
import { defineConfig } from "eslint/config";

export default defineConfig([
  {
    files: ["**/*.{js,mjs,cjs}"],
    plugins: { js },
    extends: ["js/recommended"],
    languageOptions: { globals: globals.node },
    rules: {
      // 级别 2 = error、1 = warn、0 = off
      "no-var": 2,              // 不能用 var
      "no-console": 1,          // console 警告但不报错
      "quotes": ["error", "double"],  // 引号必须双引号
      "semi": ["error", "always"],    // 语句末尾必须有分号
      "indent": ["error", 2],   // 缩进必须 2 个空格 // 就这行,坑了我半小时
    },
  },
]);

注意看规则的级别,这里用数字表示:2 是 error(直接报错,退出码非零,CI 会卡),1 是 warn(黄字警告但不阻塞),0 是 off(关掉这条规则)。也可以写成字符串 "error" / "warn" / "off",效果一样,看个人习惯。

ESLint v9+ 默认只认 eslint.config.js/eslint.config.mjs,网上大量教程还是 .eslintrc 的旧格式,照着抄大概率不生效。先 npx eslint --version 确认版本,v9 及以上直接用新写法。

ESLint 到底在干什么

配置写对了,报错也出来了,我停下来想了想:ESLint 检查代码到底是个什么流程?

简单说,ESLint 干的事就三步:

  1. 解析(Parse):把你的 JS 代码解析成 AST(抽象语法树),就是把代码拆成一棵对象树,每个节点代表一段代码结构(变量声明、函数调用、字符串字面量等等)
  2. 遍历(Traverse):从树的根节点开始往下走,每经过一个节点,就把这个节点交给所有注册了的规则去检查
  3. 报告(Report):某条规则发现节点不符合要求,就记录一个问题(带位置、级别、修复建议)

举个例子,no-var 这条规则,它监听的是 AST 里的 VariableDeclaration 节点,如果发现 kind 属性是 "var",就报错。quotes 规则监听的是 Literal 节点,发现是字符串且用了单引号而配置要求双引号,就报错。

这么理解的话,ESLint 其实就是个"AST 遍历器 + 一堆检查函数"。每条规则就是一个小函数,输入是 AST 节点,输出是"行还是不行"。这个认知帮我理解了为什么规则名长这样------no-varno-unused-varsno-console,它们都是精准描述了"要禁止什么",因为每条规则就盯着一个具体的 AST 模式。

改完代码终于绿了

搞明白配置怎么写之后,我把 index.js 按规则要求改了:

javascript 复制代码
let name = "xjl_ai";
function hello() {
  console.log("hello", name); // 改成双引号、2空格缩进,name也用上了
}

hello();

再跑 npm run lint,只剩一个 warning------no-console 那条我设的是 warn 级别,黄字提醒但不报错。开发阶段留着 console 没问题,上线前处理掉就行。

说到这提一句,有些规则是可以自动修复的,不用手动改。比如缩进、引号、分号这些格式类规则,跑 npm run lint:fix(也就是 eslint . --fix),ESLint 会直接帮你把代码改对。但 no-var 能不能自动修?能,但它只会机械地把 var 改成 let,不会智能判断该用 const 还是 let(比如变量从没被重新赋值过,其实该用 const),所以自动修复之后最好还是自己过一眼。

那些让我挠头的小细节

踩了几天坑,有几个点我觉得特别容易搞混,集中说说。

extends 和 plugins 到底啥区别?

我一开始以为这俩是一回事,后来才搞清楚:plugins 是"装了一包规则",但这些规则默认全是关的;extends 是"直接用别人配好的一套规则组合",相当于别人帮你写好了一堆 rules 配置。类比一下,plugin 是买了一箱乐高零件,extend 是买了一份拼好的图纸+按图纸拼好的成品。

globals 是干嘛的?

配置里有一行 languageOptions: { globals: globals.node },这个是告诉 ESLint:"我在 Node 环境下跑,process__dirnamerequire 这些全局变量是合法的,别给我报 no-undef。"如果不写这个,你代码里用 process.env,ESLint 会说"process is not defined",因为它不知道这是 Node 的全局变量。同理,浏览器环境就用 globals.browser,这样 windowdocument 就不会被报错。

规则级别别乱设

我最初图省事把 no-console 也设成了 2(error),结果 lint 命令因为一个 console.log 直接返回非零退出码,CI 直接挂了。后来想明白:格式类、潜在 bug 类规则设成 error 没问题;console 这种开发时确实会用的,设成 warn 提醒就好;不认同的规则直接 0 关掉,别跟自己较劲。比如有人觉得分号必须有,有人觉得 ASI 自动分号插入足够安全,这种就看团队约定,没有绝对对错。

错误代码和正确代码对比一下,差别一目了然:

javascript 复制代码
// ❌ 被报错的版本
var name = 'xjl_ai';
function hello() {
    console.log('hello');    // 4空格、单引号、name没被用到
}
hello();
javascript 复制代码
// ✅ 通过检查的版本
const name = "xjl_ai";
function hello() {
  console.log("hello", name); // 2空格、双引号、name用到了
}
hello();

npx eslint index.js 可以只检查单个文件,调试规则的时候比 eslint . 快很多,不用每次扫描整个目录。

我的一点感想

这几天折腾下来,最大的感受就是:ESLint 没有我之前想的那么"魔法"。它就是个 AST 检查器,你给它什么规则它就查什么,不给就啥也不干。配置文件的格式因为版本大改(v8→v9 的 Flat Config)导致网上教程新旧混杂,是最容易卡人的地方------这也是我为什么折腾了这么久,照着旧教程写的东西根本跑不起来。

另外我发现一个边界:ESLint 擅长的是静态检查,也就是不运行代码就能发现的问题(变量没用到、用了 var、缩进不对)。但有些问题它查不出来,比如逻辑错误、异步问题、内存泄漏,这些得靠 TypeScript 类型系统或者运行时测试去兜底。别指望 ESLint 万能,它管的是"代码写得规不规范",不是"代码写得对不对"。

反正我现在自己搭项目,第一步就是 npm i -D eslint + npx eslint --init,然后打开 eslint.config.mjs 把常用规则加上,写代码的时候实时能看到红波浪线,比写完再统一改舒服多了。你们第一次配 ESLint 的时候,也被版本问题坑过吗?


顺手捋的核心知识点

写到这顺手把核心的点捋了一遍,我自己回头复盘方便,你们要是懒得翻长文也可以直接看这部分。

1. ESLint 默认零规则

  • 核心结论:ESLint 安装后不开启任何规则检查,必须通过 extends 继承规则集或手动在 rules 中配置规则才会生效
  • 易错提醒:npx eslint . 没有输出不代表代码没问题,可能是根本没配规则;通过 "extends": ["js/recommended"] 开启官方推荐规则集

2. ESLint v9+ 使用 Flat Config 新格式

  • 核心结论:v9 及以上版本默认配置文件为 eslint.config.mjs(或 .js),使用 defineConfig + ES Module 导出数组,旧的 .eslintrc.js 格式默认不再读取
  • 易错提醒:网上大量教程基于 v8 旧格式,照着写 .eslintrc 不会生效;务必先用 npx eslint --version 确认版本

3. 规则的三个严重级别

  • 核心结论:每条规则的值可以是 0/"off"(关闭)、1/"warn"(警告,不阻塞)、2/"error"(错误,退出码非零,阻塞 CI)
  • 易错提醒:no-console 这类开发常用语句建议设为 warn,设为 error 会导致 lint 命令挂掉 CI;格式类规则设 error 没问题

4. extends 与 plugins 的关系

  • 核心结论:plugins 只是加载规则包(规则默认全关),extends 是继承一套已配置好的规则组合,可以来自插件或第三方
  • 易错提醒:只加 plugins 不在 extends 或 rules 里开启,规则不会生效,不要以为装了插件就自动检查

5. globals 声明环境全局变量

  • 核心结论:通过 languageOptions.globals 配置环境预置的全局变量,Node 用 globals.node,浏览器用 globals.browser,避免 no-undef 误报
  • 易错提醒:不配置 globals 时,processwindowdocument 等环境变量会被 ESLint 认定为未定义变量报错

6. lint:fix 自动修复的能力边界

  • 核心结论:格式类规则(缩进、引号、分号)支持 --fix 自动修复,语义类规则(no-var、no-unused-vars)的自动修复可能不完美
  • 易错提醒:--fix 会直接改文件且无法撤销,建议先跑 npm run lint 看问题清单,确认后再 fix,重要代码提前 commit;no-var 自动修复只会改为 let,不会智能判断 const

7. 手动实现一个简易 ESLint 规则检查器的核心步骤

  • 核心结论:ESLint 的本质是"解析 AST → 遍历节点 → 规则匹配",核心三步:
    1. @babel/parserespree 将源码解析为 AST
    2. 用深度优先遍历访问每个 AST 节点
    3. 对每种节点类型注册检查函数(如检查 VariableDeclaration.kind === 'var'),命中则记录行号和错误信息
  • 易错提醒:自己写规则时要注意 AST 节点类型的准确名称(如字符串是 Literal 不是 String),建议用 AST Explorer 网站先看节点结构再写逻辑
相关推荐
凤山老林2 小时前
精细化流量治理:Spring Boot 动态特性开关与灰度发布体系
java·spring boot·后端
FungLeo2 小时前
Node 后端实战 · 列表查询到底怎么写?一个通用 DSL 封装,过滤分页排序一次搞定
数据库·node.js·serverless·dsl 封装·通用列表查询
vipbic2 小时前
一个前端的 9 天重构:我是怎么用 Codex 重做航栈的
前端·javascript·后端
_codemonster4 小时前
npm run dev 是在开发模式运行,怎么在生产环境运行
前端·npm·node.js
考虑考虑5 小时前
Excel导入时产生特殊字符处理
java·后端·java ee
IT_陈寒6 小时前
Redis的DEL命令竟然没删掉数据?我踩的这个坑你得知道
前端·人工智能·后端
用户938515635076 小时前
Docker + Nginx + Node.js:从“我的电脑能跑,你的电脑跑不了”到一键部署
后端·nginx·docker
陆枫Larry6 小时前
两步验证(2FA )到底是什么?
后端
JavaGuide6 小时前
我用 Claude Code/ZCode+GLM-5.3 从零做了一款 Agent 游戏!
前端·后端