用 claude code 创建自定义 ESLint 规则的完整教程

前段时间我所在的团队遇到一个让人抓狂的问题:业务方在调用 /api/admin/ 开头接口时,有同事习惯性地把登录态 token 写死在请求 headers 里带过去。这种事你不可能靠 code review 每次都拦住,因为人总是会困、会急、会把“写完赶紧上线”排到“安全规范”前面。

于是我去翻 ESLint 的内置规则。no-restricted-syntax 能挡一些 AST 节点,但对于“当请求 URL 匹配某个 pattern 时禁止 headers 里出现某个字段”这种带上下文判断的逻辑,它完全帮不上忙。社区插件也找了一圈,要么大而全但不匹配我们的精确需求,要么三年没更新、依赖链已经断在 Node 版本升级的路上。

最后我打开 Claude Code 的终端窗口,用自然语言描述了我想要的规则行为,五分钟之后,一条能跑、能检测、能在 CI 里阻断构建的自定义 ESLint 规则就接进了项目。

这篇文章我打算把整个流程完整拆开来讲,从我为什么认为 Claude Code 比手写规则更适合这个场景,到具体怎么写出一个 AI 不容易“幻觉”的 Prompt,再到怎么在本地把 AI 生成的规则验证、查漏、调优,最后落进工程流水线。这里没有任何“AI 真强啊”的情绪渲染,只有我在真实业务里一步一步踩出来的经验和判断。

一、核心结论先放前面:用 Claude Code 创建自定义 ESLint 规则这件事,关键不在“写代码”,而在“把约束定义清楚”

这一点我是在第三条规则写完才真正意识到的。

第一次我用 Claude Code 生成 ESLint 规则时,Prompt 写得非常随意,就像平时让 LLM 写一段排序算法那样丢过去:“帮我写一个 ESLint 规则,禁止在 JSX 里使用内联 style 对象。”它确实给我生成了,语法正确,AST 节点选得也没毛病,CallExpressionJSXAttribute 对应的 visitor 写法都是对的。

但等我把它放进项目跑完第一遍,报错信息里混进来一堆我完全没预料到的 case:style={condition ? objA : objB} 被误杀了,因为规则没处理三元表达式;{...spreadStyle} 也被标红,因为 visitor 没区分 JSXSpreadAttributeJSXAttribute。也就是说,它能按照我的字面命令工作,但没有理解我真正想保护的场景边界。

这就是我想在这篇文章开头就讲清楚的核心结论,Claude Code 能帮你把 ESLint 规则的“骨架代码”生成得又快又准,但规则能不能在真实工程里稳妥地跑起来,主要取决于你如何描述“什么时候该报错、什么时候不该报错”以及“边界条件你要想清楚”。

手写规则的时代,你花最多时间在查 AST Explorer、翻 ESLint 的 RuleTester 文档、对着 estraverse 的节点类型一个个试。而在 Claude Code 的时代,你的主要工作变成了:理清业务约束的边界、把意图转化为高质量的 Prompt、用测试用例去逼出 AI 生成代码的盲区。工具变了,但工程严谨性一刻也不能让渡。

这条结论会贯穿整篇文章的后续每一个实战步骤。

二、你为什么要自己写 ESLint 规则?先把这个动机讲清楚

如果你目前对 ESLint 的使用还停留在“装 Airbnb 或者 Standard 规则包,然后在 .eslintrc 里调一调 severity”的阶段,这部分内容可能会让你重新理解自定义规则的价值。

1. 标准规则包本质上是一组“最大公约数”

Airbnb、Standard、Google 这类流行 ESLint 配置包,它们的规则选择逻辑是一个很典型的“最大公约数”思路,只收录那些能被绝大多数 JavaScript/TypeScript 项目接受的、争议足够小的检查项。比如“用了 var 要报错”、“未使用的变量要报错”、“useEffect 依赖数组缺失要报错”。

这些规则代表了社区的共识。但工程实践里真正让代码质量拉开差距的,恰恰是那些不具有普适性、却对你所在项目至关重要的约束

举几个我们团队真实遇到过的例子:

  • 安全约定:禁止在 /api/admin/ 路径的 HTTP 请求中直接携带 Authorization: Bearer 拼接 localStorage token(应该走 withCredentials + cookie)。
  • 架构约束:微前端项目中,子应用严禁直接从 @company/shared 引入 React,必须通过主应用的 externals 共享同一份实例。
  • 埋点规范:所有 page_view 事件的 page_name 参数必须来自枚举常量 PageNames,不能用裸字符串。
  • 业务边界:禁止在“订单模块”直接 import “用户模块”的内部 selector(模块间只能通过 facade 暴露的公共 API 通信)。

这些约束你要是靠 code review 去维持,结果就是 reviewer 累死、author 烦死、线上事故照样出。

于是你会发现一个尴尬的局面:团队代码规范最核心的部分,恰恰是那些社区规则不会替你写、你也没精力自己去写的部分。

用 claude code 创建自定义 ESLint 规则的完整教程

2. 为什么不手写?AST 学习成本曾经是最大的拦路虎

我大概是 2018 年前后第一次尝试手写 ESLint 自定义规则。当时的需求比现在还简单:检测团队内部某个过时 API 的调用并给出迁移提示。我去翻了 ESLint 官方文档,开始啃 RuleTestercontext.reportfixer 这些概念,这些还好,因为文档写得清楚。

但真正把我卡住的,是 AST(抽象语法树)。

你得知道 CallExpressionNewExpression 的区别,知道 MemberExpressionobjectproperty 分别指向什么,知道 Literal 节点和 TemplateLiteral 节点在结构上有什么不同。对于没有编译器前端基础的前端工程师来说,这相当于要你写 Python 之前先去学一遍 CPython 的字节码格式,不是学不会,而是精力投入产出比太低。

我当时的真实经历是:写了 30 行规则代码,在 [astexplorer.net](https://astexplorer.net) 上查了不下 20 次节点类型,改了六七遍才通过 RuleTester 的基础测试。整个过程中,真正花在“理解业务约束”上的精力可能只占 20%,另外 80% 全耗在“搞清楚 AST 长什么样”这件事上。

这也是为什么很多团队宁愿忍着一堆个性化问题,也不想碰自定义规则,不是因为不需要,而是因为门槛太高。

用 claude code 创建自定义 ESLint 规则的完整教程

3. Claude Code 的角色定位:它不是在帮你“写得更快”,而是在帮你把“不想做的事”外包出去

这一点很容易被误解。很多人第一次用 Claude Code 生成 ESLint 规则时的反应是“哇写好快”,但如果你止步于这种感觉,你会低估这个工具的价值。

Claude Code 对自定义规则创建这件事的真正意义在于,它把“查 AST 节点类型、记住 ESLint visitor API 签名、处理不同 parser 下的节点差异”这些机械劳动从你的大脑缓存里卸载了。

你不再是“读文档查 API→写代码→调试验证”这种线性流程里的苦力,而是变成了一个约束设计师:你想清楚什么行为必须被禁止,什么边界情况应该豁免,然后把这些问题用结构化 Prompt 描述出来,让 Claude Code 去负责实现细节。你做的最后一步,是用测试用例去验收它有没有理解偏。

这个定位转变,直接影响了你在创建自定义规则时整个思维模型的变化。接下来我会用完整的工作流演示来说明这一点。

三、开始之前的准备工作:一个你大概率会忽略但非常关键的基础认知

在正式动手写规则之前,有几件事如果你没有提前建立认知,后面 Claude Code 生成的规则再多,你也很难判断哪条能直接用、哪条需要大改、哪条看起来对但上线一定炸。

1. ESLint 规则本质上是一个“AST Visitor

ESLint 的工作原理说起来并不复杂:它把源码解析成 AST,然后用每条规则注册的 visitor 函数去遍历这棵树的每一个节点。当某个节点满足规则定义的检查条件时,规则调用 context.report() 报告一个错误或警告。

一条自定义规则的最小结构长这样:

module.exports = {
meta: {

type: "problem", // 或 "suggestion", "layout"

docs: {

description: "规则描述",

},

fixable: "code", // 如果规则提供了自动修复

schema: [], // 配置项 JSON Schema

},

create(context) {

return {

// visitor: 节点类型 -> 处理函数

CallExpression(node) {

// 检查逻辑

if (需要报错) {

context.report({

node,

message: "报错信息",

});

}

},

};

},

};

这个结构是你后面跟 Claude Code“对话”的基础。你不需要把 AST 节点记全,但你至少要知道:你要检查的代码特征,大致对应什么节点类型。

举个例子:

  • 你要检查“调用了某个函数” → 关注 CallExpression
  • 你要检查“import 了某个模块” → 关注 ImportDeclaration
  • 你要检查“JSX 属性写了什么” → 关注 JSXAttribute
  • 你要检查“new 了一个什么” → 关注 NewExpression

不需要背,但你要有这个意识。这样你在向 Claude Code 描述需求时,可以说“检查所有 CallExpression 节点”而不是“检查所有函数调用”,前者的描述会让 AI 一次性生成可用的 visitor 签名,后者可能出现偏差。

2. 你的项目用的是什么 parser?忘掉这一步后面全是坑

ESLint 本身默认使用 Espree parser,它支持到 ES2024 的 JavaScript 语法。但如果你用的是 TypeScript,那你大概率已经在 .eslintrc 里配了 @typescript-eslint/parser。如果你用的是 Vue SFC,那你可能用的是 vue-eslint-parser。如果是 React + JSX,构建工具链不同(Babel / esbuild / SWC)可能导致 AST 节点类型有差异。

这个差异会直接影响 Claude Code 生成的规则能不能在你的项目里跑通。

我踩过一次很典型的坑:我给一个 TypeScript 项目让 Claude Code 生成了一条规则,它在本地跑得完美。但等我把同一条规则挪到一个 Babel 作为 parser 的老项目里,TSTypeAnnotation 之类的节点完全不出现,规则直接静默失效,不报错也不生效,白白摆了半个月。

所以在你开始让 Claude Code 生成任何规则之前,先确认你项目当前的 parser 配置:

// .eslintrc.json 中关键部分
{

"parser": "@typescript-eslint/parser",  // 或者 undefined(默认 Espree)

"parserOptions": {

"ecmaVersion": 2022,

"sourceType": "module",

"ecmaFeatures": {

"jsx": true

}

}

}

把这个信息写进你的 Prompt 里,这一步非常重要,我在后面讲 Prompt 设计时会详细展开。

3. 三个你需要知道的 ESLint API

写自定义规则时需要跟 ESLint 提供的 API 打交道,其中三个最核心的你需要提前知道它们的存在,不然你连 Claude Code 生成的代码都看不懂:

context.report():规则发现问题时调用的上报接口。关键参数:

  • node:哪个 AST 节点出了问题(ESLint 用它来定位源码位置)
  • message:展示给开发者的错误信息
  • fix(fixer):可选,如果规则提供了自动修复能力

context.options:用户在 .eslintrc 里给这条规则传的配置项。比如你写了一条规则叫 no-internal-api-import,允许用户配置“哪些模块算 internal”,那配置值就会从 context.options 里取到。

fixer 对象:当你的规则要提供自动修复时,fixer 包含一系列方法如 replaceText()insertTextAfter()remove() 等,用来生成修复补丁。

这三个 API 你不需要现在就记住用法,但当你看到 Claude Code 生成的规则里出现 context.reportfixer.replaceTextRange 时,你要能认出这是什么、它在做什么。

四、用 Claude Code 创建 ESLint 规则的完整工作流:三步法

下面进入我这篇文章最想分享的核心部分。我会用一个真实的业务需求作为贯穿案例来示范整个流程。

场景设定:一个中大型 React + TypeScript 项目,团队发现部分开发者在组件内直接 import 了 lodash 的单个函数(如 import debounce from 'lodash/debounce'),而没有使用 lodash-es 或项目约定的 tree-shakable 导入方式,导致打包体积膨胀。你希望创建一条 ESLint 规则来约束:packages/web/ 目录下,禁止从 lodash/* 做 deep import,引导开发者使用 import { debounce } from 'lodash-es'

这个需求有一个清晰的业务场景、有边界条件、且社区里没有现成规则。

第一步:结构化 Prompt,这是决定生成质量的分水岭

很多人让 AI 写代码时 Prompt 是“帮我写一个 ESLint 规则禁止 import lodash”,这就是典型的“一句话需求”,生成出来能用是运气,不能用是正常。我用 Claude Code 创建了十来条自定义规则之后,总结出一套对 ESLint 规则生成特别有效的 Prompt 模板,核心结构包含六个要素:

要素一:明确角色和输出格式。 告诉 Claude Code 你现在需要一个什么类型的东西、输出应该是什么语言/框架、应该遵守什么结构。比如:“请帮我编写一条自定义 ESLint 规则,输出为 CommonJS 格式的 JavaScript 文件,遵循 ESLint 的 Rule 结构(包含 metacreate 函数)。”

要素二:提供项目的 parser 配置信息。 这是我前面强调过的:“本项目使用 @typescript-eslint/parser v6,parserOptions 中 ecmaVersion 为 2022,sourceTypemodulejsx 为 true。”这一步让 AI 知道它应该使用 TypeScript AST 节点类型。

要素三:用自然语言精确描述“检查什么”和“在什么条件下报错”。 这是整个 Prompt 的核心。一个有效的描述方式是使用“当……时,如果……,则报错”这个句式:

> 当代码中出现 ImportDeclaration 节点时,如果 source.value 匹配 lodash/* 模式(即从 lodash 的子路径导入),并且当前文件路径属于 packages/web/ 目录,则报告错误。错误信息应提示用户改为 import { XXX } from 'lodash-es'

注意我描述中的措辞精度:“匹配 lodash/* 模式”比“从 lodash 导入”要精确得多,后者能被 AI 理解为多种不同的意思,包括 import _ from 'lodash'(全量导入),而全量导入可能并不是你要拦的。

要素四:明确边界豁免条件。 很多规则在生产中出问题,不是因为核心逻辑写错了,而是因为没考虑到“这种情况不应该报错”。你要在 Prompt 里就把你能想到的豁免条件列出来:

> 以下情况不应报错:

> – import _ from 'lodash'(全量导入,非子路径导入)

> – import type { DebouncedFunc } from 'lodash/debounce'(仅导入类型)

> – 文件位于 packages/legacy/ 目录下(该目录尚未完成迁移,暂不检查)

要素五:提供 2-3 个正反面测试用例。 这可能是整个 Prompt 里最重要的质量杠杆。你给 Claude Code 的测试用例越多越具体,它生成的规则就越贴合你的预期:

> 测试用例:

> – 错误 case:import debounce from 'lodash/debounce'packages/web/src/App.tsx 中 → 应报错

> – 正确 case:import { debounce } from 'lodash-es'packages/web/src/App.tsx 中 → 不报错

> – 豁免 case:import type { DebouncedFunc } from 'lodash/debounce'packages/web/src/types.ts 中 → 不报错

要素六:指定输出需要包含 RuleTester 测试代码。 这是我自己加的一个习惯,让 Claude Code 在生成规则的同时生成配套的测试用例:

> 请在规则代码后附上一段使用 ESLint RuleTester 编写的测试代码,覆盖上述的正反面测试用例和边界豁免用例。

把以上六个要素拼成一个完整 Prompt,喂给 Claude Code。下面是我实际用过的 Prompt 完整版(可复用的模板):

完整 Prompt 示例:

请帮我编写一条自定义 ESLint 规则,输出为 CommonJS 格式的 JavaScript 文件,遵循 ESLint 的 Rule 结构(包含 meta 和 create 函数)。
项目配置信息:

parser: @typescript-eslint/parser v6

parserOptions: ecmaVersion 2022, sourceType "module", jsx true

项目使用 TypeScript + React

规则需求:

当代码中出现 ImportDeclaration 节点时,如果 source.value 匹配 "lodash/*" 模式(即以 "lodash/" 开头的子路径导入),并且当前文件路径包含 "packages/web/"(即属于 web 包目录),则报告错误。

报错信息示例:"禁止从 lodash 子路径直接导入,请使用 'import { debounce } from \"lodash-es\"' 替代。"

豁免条件(以下情况不报错):

import _ from 'lodash'(全量导入)
import type { ... } from 'lodash/xxx'(仅导入类型)
文件路径包含 'packages/legacy/'(遗留目录暂不检查)
import 'lodash/xxx.css' 或其他非 JS/TS 的副作用导入
请提供以下测试用例的预期行为:

错误:import debounce from 'lodash/debounce' 在 packages/web/src/App.tsx → 报错

错误:import throttle from 'lodash/throttle' 在 packages/web/utils/helper.ts → 报错

正确:import { debounce } from 'lodash-es' 在 packages/web/src/App.tsx → 不报错

正确:import type { DebouncedFunc } from 'lodash/debounce' 在 packages/web/src/types.ts → 不报错

正确:import _ from 'lodash' 在 packages/web/src/legacy.ts → 不报错

豁免:import debounce from 'lodash/debounce' 在 packages/legacy/old.ts → 不报错

请在规则代码后附上一段使用 ESLint RuleTester 编写的测试代码,覆盖上述全部测试用例。

用 claude code 创建自定义 ESLint 规则的完整教程

第二步:审查,不要直接信任 AI 生成的代码,用这三个角度去查

Claude Code 生成代码之后,很多人直接 eslint --rulesdir 跑一遍,看到不报错就算过了。这是一个非常危险的习惯。AI 生成的 ESLint 规则可能“语法正确、逻辑错误”,也可能“核心场景处理正确、边界条件静默失效”。

我总结了一套三角度审查法,每次生成完规则之后按这个顺序过一遍:

角度一:节点类型是否匹配你的 parser

这一检查最简单也最容易漏:打开规则文件,找到 visitor 里用的 AST 节点类型,然后拿一个真实的、包含你要检查语法的测试文件,用 AST Explorer(选择和你项目一致的 parser)去看那些代码实际生成的节点类型是什么。

举例:如果你的 parser 是 @typescript-eslint/parser,但你让 Claude Code 生成的规则去检查 FunctionDeclaration 节点,那你需要确认一下你的测试文件里那个“函数”到底是 FunctionDeclaration 还是被 TypeScript parser 转成了别的东西。一般来说差异不大,但遇到过 parser 版本跨大版本升级导致节点命名不同、或者 TS 4.x 到 5.x 之间 ImportDeclaration 结构发生变化的情况,需要验证一下。

角度二:豁免条件是否都落到了代码里

这是 AI 最常偷懒的地方。你在 Prompt 里列了五条豁免条件,它可能只实现了三条半。你需要逐条在生成的规则代码里找到对应的判断分支。

以我上面的 Prompt 为例,你需要在代码里找到:

  • source.value.startsWith('lodash/') → 核心判断
  • importKind === 'type' 或类似判断 → type import 豁免
  • filePath.includes('packages/legacy/') → 遗留目录豁免
  • source.value 的格式判断以排除 .css 后缀 → 非 JS 导入豁免

如果某一条豁免在你代码里找不到对应的判断分支,直接针对那条豁免写一个新 Prompt 让 Claude Code 补充,或者手动补上几行。

角度三:不报错分支是否会让规则“静默什么都不做”

ESLint 的 visitor 可以 return 在任何地方不 report,这在逻辑上意味着“不匹配条件所以放行”。但如果你的判断条件写得有问题,可能出现一种更糟的情况:你以为规则在生效,但因为判断条件写错导致每个节点都静默放行,规则实际上是无操作状态。

验证方法很简单:故意写一个明显违反规则但没用豁免条件的测试用例,看它是否被捕获。如果连这个都放过了,那规则的判断逻辑一定存在缺陷。

用 claude code 创建自定义 ESLint 规则的完整教程

第三步:本地调试验证,用最小闭环跑通全流程

审查完代码之后,我们需要在本地把规则跑起来验证。这个步骤不需要配置一堆东西,一个最简单的四步闭环就够了:

第一步,把规则文件放在项目里:

mkdir -p eslint-rules
将 Claude Code 生成的规则文件复制到这里

cp /path/to/generated-rule.js ./eslint-rules/no-lodash-deep-import.js

第二步,创建一个测试目标文件:

packages/web/src/ 下新建一个临时文件,把你要测的正反面 case 全写进去:

// test-eslint-target.ts
import debounce from "lodash/debounce";           // 应该报错

import type { DebouncedFunc } from "lodash/debounce"; // 不应报错(type import)

import _ from "lodash";                           // 不应报错(全量导入)

import { debounce } from "lodash-es";             // 不应报错(正确的导入方式)

第三步,用 ESLint 命令行直接指定规则运行:

这是我在实践中发现最高效的调试方式,不修改 .eslintrc,直接用 --rule 参数覆盖:

npx eslint --no-eslintrc \
--rulesdir ./eslint-rules \

--rule '{"no-lodash-deep-import": "error"}' \

--parser @typescript-eslint/parser \

packages/web/src/test-eslint-target.ts

这个命令的精妙之处在于:--no-eslintrc 让 ESLint 忽略你项目现有的所有配置,你完全在一个干净的环境中测试这条新规则。输出的结果非常直观,哪一行报错了、报了什么 message、报错级别是什么。

第四步,对比预期行为与 CLI 输出:

拿着第三步的输出结果,和你 Prompt 里列的测试用例一一对照:

源代码行 你预期的行为 CLI 实际输出 是否一致
import debounce from "lodash/debounce" 报错 ✓ 报错
import type { ... } from "lodash/debounce" 不报错 ✓ 不报错
import _ from "lodash" 不报错 ✓ 不报错
import { debounce } from "lodash-es" 不报错 ✗ 报错 ✗ 不一致!

如果第四行出现了不一致,说明规则的匹配模式 lodash/* 误匹配到了 lodash-es 这个合法库名。这是一个典型的边界场景,Claude Code 很有可能在初始生成时没有考虑到。你只需要回到 Claude Code,把这个问题描述清楚:

> “你的规则误把 import { debounce } from 'lodash-es' 当作 lodash/* 模式处理了。请修改匹配条件,确保 source.value 只有在以 lodash/ 开头(不包括 lodash- 这样的库名)时才触发报错。”

然后把修正后的代码再次跑上述四步验证。

这个闭环的精髓在于:你不需要成为 AST 专家也能完成验证和修正,因为你直接通过测试用例的行为来驱动 Claude Code 修复,而不是自己去读 AST 查哪里不对。

五、常见误区和陷阱:我在生产环境中实际遇到的五个坑

这一部分没有任何理论推演,全是我和团队在实际使用 Claude Code 生成 ESLint 规则时撞过的墙。有的坑花了我整整一个下午才找到根因。

误区一:认为 AI 生成的规则“跑起来就是对的”

这是最危险也最普遍的错觉。ESLint 规则的特殊之处在于,它的“正确性”不仅体现在语法合法,更体现在对真实代码的覆盖度上。

Claude Code 生成的规则往往是基于你提供的几个测试用例去拟合的。它能很好地处理你明确写出来的 case,但对于你没有提及的代码形态变体,它可能完全没有任何判断逻辑。

举个例子:你让 AI 检测“禁止使用 document.cookie”,它生成的 visitor 大概率会检查 MemberExpression 节点,看 object.name === 'document' && property.name === 'cookie'。这个逻辑对你给的 case 有效。

但你的项目里如果有这样的写法:

// 解构赋值
const { cookie } = document;

// 间接访问

const d = document;

d.cookie = 'xxx';

// 方括号访问

document['cookie'];

第一条规则就完全失守了。

解决方案:在 Prompt 的测试用例部分,有意识地给出一到两个“形态变体”的 case。同时你也可以在规则生成好之后,主动拿一个你有印象的真实业务文件跑一遍,看有没有漏网之鱼。

误区二:在 Prompt 里对 AST 节点类型描述得过于“自信”

有一些开发者因为本身懂一些 AST,会在 Prompt 里写得很细:“请在 ImportDeclaration 节点的 visitor 中检查 source.value 是否匹配……”,这个表述本身没问题。

但如果你对某种节点类型一知半解,在 Prompt 里错误地指定了节点类型,比如你以为是 FunctionExpression 但实际在箭头函数下是 ArrowFunctionExpression,那 Claude Code 很可能会顺着你的“错误指引”生成一份无法覆盖真实场景的代码。

我的建议是:如果你不确定某个代码特征对应什么 AST 节点,不要在 Prompt 里硬写。你可以这样描述:“当代码中出现从 lodash/* 子路径的 import 语句时”,把具体节点类型的推断交给 Claude Code。AI 对 AST 节点类型的熟悉程度足以自己选出正确的 visitor key。你的任务是用自然语言把“检测什么行为”说清楚,而非替 AI 做技术选型。

误区三:忽视了 schema 字段,这是一条规则“可配置性”的关键

Claude Code 生成的规则默认情况下 schema: [],空数组,意味着这条规则不接受任何来自 .eslintrc 的用户配置。

在快速原型阶段这不是问题。但当你的规则要在团队内推广,不同子项目或不同目录可能需要不同的豁免路径、不同的禁止列表时,没有 schema 的规则就只能通过“复制一份改几行”来适配,这是代码坏味道的开始。

你应该在 Prompt 里加上一句:“请在 meta.schema 中提供一个配置项 excludePaths(数组类型,默认为空),允许用户在 .eslintrc 中指定不需要检查的文件路径 glob。”

然后在 create 函数中从 context.options 取到这个配置值,配合 minimatch 之类的库实现路径匹配豁免。

误区四:测试用例写得不够“刁钻”

Claude Code 生成的规则最终要面对的是整个团队的代码库,不是你那几个精心挑选的测试 case。如果你的测试用例只覆盖“理想条件下的典型错误”,那你会发现一到真实项目里,误报率可能高到让同事关掉这条规则。

我会在 Prompt 测试用例中加入以下几类刁钻 case:

  • 嵌套结构:检查点在深层嵌套对象或三元表达式中时规则还生效吗?
  • 多行/换行情况:检查点跨行时正则匹配和节点定位还准确吗?
  • 模板字符串 vs 普通字符串:你检查的是字符串字面量,但源码里是模板字符串,规则还能匹配吗?
  • 注释中的内容:需要确保规则不会匹配到注释里的内容(有些 AST 遍历会把 Comment 节点也带上)

误区五:跳过 fixer 的安全性验证

如果你的自定义规则提供了自动修复(fixable: 'code' + fix 函数),那你在上线之前必须做一件事:--fix 跑一遍主干代码,然后 git diff 仔细看改了什么。

Claude Code 生成的 fix 函数有一个常见问题:它在修复时可能改变了代码的语义。比如把 import debounce from 'lodash/debounce' 自动 fix 成了 import { debounce } from 'lodash-es',这个 fix 的意图是对的,但如果原代码中 debounce 被当作默认导出使用、而 lodash-esdebounce 是命名导出,代码的行为可能微妙不同(虽然在 lodash 的场景下恰好一致,但在其他库的场景下可能出问题)。

我的经验是:对于自定义规则的 fix 功能,在上线初期先设 --fix-dry-run 观察一两个迭代周期,确认修复行为稳定、无副作用后,再正式启用 --fix 集成到 lint-staged。

用 claude code 创建自定义 ESLint 规则的完整教程

六、进阶用法:让 Claude Code 帮你管理一整套自定义规则体系

当你手上的自定义规则从一两条增长到七八条甚至更多时,你会发现管理成本开始指数级上升。这个阶段,Claude Code 的角色可以从“单条规则的代码生成器”升级为“规则体系的协作维护者”。

下面是我目前在实际项目中使用的三种进阶用法。

用法一:用 Claude Code 审查现有规则,发现误报和漏报

你可以把整条规则代码和一批真实项目的代码样本一起喂给 Claude Code,让它帮你做审查:

> “以下是项目中的一条自定义 ESLint 规则代码规则名称 no-internal-api-import,以及五个真实业务文件的代码样本。请分析这条规则在这五个文件中分别会产生什么行为(报错/不报错),并指出是否存在明显的误报或漏报问题。如果有,给出修复建议。”

Claude Code 能检查出一些你肉眼看遗漏的边界问题,尤其是在复杂正则匹配条件和多层 if-else 嵌套逻辑中。它就像一个不知疲倦的 code reviewer,专门盯着你的规则代码找逻辑漏洞。

用法二:批量生成规则单元测试

你大概率没有时间为每一条自定义规则手动编写完整的测试用例覆盖。但 Claude Code 可以做到这一点:

> “读取当前目录下的所有自定义规则(eslint-rules/*.js),分析每条规则的 meta 和 create 逻辑,为它们各自生成一份完整的 RuleTester 测试代码,覆盖正常 case、边界 case 和豁免 case。输出为 eslint-rules/__tests__/ 下的测试文件。”

我实测过一次,对于我们团队 9 条自定义规则,Claude Code 生成测试用例的速度比我手动写快一个数量级。当然生成之后仍然需要我逐条跑一遍确认,但这已经是数量级的效率提升。

用法三:让 Claude Code 成为团队规则文档的撰写者

自定义规则多了之后,团队其他成员根本不知道有哪些规则、每条规则在防范什么问题、遇到误报怎么处理。文档如果靠人工维护,不出三个月就会过时。

我的做法是把所有自定义规则文件批处理喂给 Claude Code,让它生成一份 Markdown 格式的规则目录文档:

> “分析以下所有自定义 ESLint 规则文件,生成一份中文 Markdown 文档,包含:规则名称、功能概述、触发场景示例、豁免条件、配置项说明。输出为 RULES.md。”

然后我把 RULES.md 放进项目根目录,每次新增或修改规则时让 Claude Code 同步更新这份文档。文档和规则代码同源维护,过时问题大幅减少。

用 claude code 创建自定义 ESLint 规则的完整教程

七、不同场景下的策略选择:不是所有规则都适合让 AI 来写

讲了这么多 Claude Code 的好处,我必须坦诚地补上这部分,在有些情况下,手动编写规则反而是更理性的选择。

适合用 Claude Code 的场景

描述简单、条件明确的禁止性规则。 比如“禁止 import 某个废弃模块”、“禁止调用某个已弃用的函数”、“禁止在特定目录下使用某种 API”,这类规则的核心逻辑是一条清晰的 if-then 判断,Claude Code 生成起来非常稳。

你清楚自己要检查什么、但不想查 AST 文档的场景。 这是 Claude Code 最典型的用武之地。你有明确的业务约束定义,只是不想花时间去研究 SwitchCaseSwitchStatement 的层级关系。这种情况下 Claude Code 是最佳选择。

需要快速验证一个规则想法的场景。 有时候你不确定某个规则能不能写、写出来会有多少误报。你可以用 Claude Code 两分钟生成一条原型规则,然后在主干代码上 dry-run 跑一遍,看报错的数量和分布是否合理。如果误报率太高,放弃这个方向也不心疼。这比手写半小时再放弃的成本低太多。

适合手动编写的场景

逻辑中包含复杂的跨文件分析。 ESLint 本身是基于单文件 AST 检查的,不擅长跨文件推导。如果你的规则需要分析两个文件之间的引用关系,你可能需要借助 TypeScript Compiler API 或构建依赖图,这部分逻辑超出了 Claude Code 在单文件 ESLint 规则语境下能帮你处理好的范围。

对性能有极高要求的规则。 用于大型 monorepo、需要扫几千个文件的大型项目,规则执行效率是关键考量。AI 生成的代码在访问器遍历策略、早期退出优化等方面通常不是最优。这种场景下你最好手动控制 visitor 的粒度,减少不必要的节点遍历。

需要精确理解极其复杂 AST 结构的规则。 比如你要检查“JSX 中某些特定属性组合的出现模式”,涉及多层条件嵌套,且不同的语法糖(高阶组件、render props、Hooks)会生成完全不同的 AST 结构。这种场景下,你需要先在 AST Explorer 上花大量时间理解节点结构,而这个理解过程本身就是规则设计的一部分。跳过这个过程直接用 AI 生成,结果大概率是一份你可能看不懂、也改不动的代码。

一个判断原则:如果一条规则的核心难度在于“搞清楚 AST 结构”,那 Claude Code 能帮大忙;如果核心难度在于“定义逻辑本身的边界”,那无论 AI 还是手写,这个工作量都不会消失,你只是换了实现方式。

用 claude code 创建自定义 ESLint 规则的完整教程

八、如何把自定义规则接入 CI/CD 并让团队接受它

规则写出来只是第一步。让它在真正的工作流中跑起来、被团队接纳而不被视为负担,这才是完整的工程落地。

1. 渐进式接入策略:从 warn 开始,用数据说话

我犯过一个错误:把新规则直接设成 error,第二天 CI 红了,同事在群里问“谁加的这个规则,我明明写的代码没问题”,然后规则被关掉,信任也丢了。

后来的经验告诉我,新规则上线应该走“三步走”:

阶段 severity 目标 持续时间
观察期 warn 收集报错数量与分布,不阻断 CI 1-2 个迭代
过渡期 error + 临时豁免注释 阻断新增违规,存量豁免 1-2 个迭代逐步清理
稳定期 error 全量执行,无豁免 长期

观察期的关键动作是:统计 warn 数量,确定误报率是否在可接受范围之内。Claude Code 生成的规则在这个阶段非常容易暴露边界遗漏,你可以在观察期内把误报 case 收集起来,喂回给 Claude Code 修复规则逻辑。

2. 在 CI 中集成:只 lint 变更文件

全量 lint 在大型项目中动辄几分钟,不适合放在 PR 阶段的 CI 里。我的做法是只 lint 变更文件:

# 在 CI 脚本中
git diff --name-only origin/main...HEAD -- '*.ts' '*.tsx' | xargs npx eslint --quiet

对于新规则来说,这会带来一个微妙的好处:它只对本次 PR 新引入的代码生效,不会因为历史债务导致 CI 大量报错。 这让团队对新规则的抵触情绪大幅下降。

3. 写好规则的使用说明文档

一条没有文档的自定义规则,在团队看来就像黑箱。同事遇到报错不知道怎么修,第一反应是关掉规则而不是改代码。

这是我给每条自定义规则写的文档模板,放在 eslint-rules/README.md 或独立的规则目录下:

### no-lodash-deep-import
规则意图:禁止从 lodash 子路径做 deep import,统一使用 lodash-es 的命名导入以利用 tree-shaking。

触发场景:

// ❌ 错误:会触发此规则

import debounce from 'lodash/debounce';

// ✅ 正确:推荐方式

import { debounce } from 'lodash-es';

// ✅ 允许:类型导入不受限制

import type { DebouncedFunc } from 'lodash/debounce';

如何修复:将 import XXX from 'lodash/yyy' 替换为 import { XXX } from 'lodash-es'。

配置项:

excludePaths (string[]): 不检查的文件路径 glob 数组,默认 []。

这份文档可以由 Claude Code 帮你生成初稿(如我在第六节提到的用法),你只需要校对修正。文档的存在让规则不再是“作者本人才能理解的黑盒”,大大降低了团队的采纳阻力。

九、整篇文章的核心收束

回到一开头我讲的那个真实场景,团队成员在 /api/admin/ 请求里写了不安全的 Authorization header 拼接。在 Claude Code 的辅助下,创建那条自定义规则我只花了不到 15 分钟,从写 Prompt 到测试通过到第一个 MR 提交完毕。

但我想强调的不是“速度”,而是这件事代表的深层变化。

传统的自定义 ESLint 规则创作有一个很高的前置知识门槛(AST、visitor pattern、RuleTester),导致“写规则”这件事被局限在少数对编译器前端感兴趣的开发者手里。大多数工程团队并不是不需要自定义规则,而是“创建一条成本太高、不值得”。

Claude Code 把这个前置门槛打掉了。你现在不需要记住 CallExpressionNewExpression 的区别,不需要手动查 context.report 的参数签名,不需要对着 AST Explorer 一格一格地点开看节点层级。你只需要:

  1. 清楚地定义你要约束什么行为
  2. 把约束条件、豁免边界、测试用例写进结构化 Prompt
  3. 用四步验证法在本地跑通
  4. 用三步渐进策略把规则接入工程流水线

每一步我已经在这篇文章里完整展开,你可以直接复用。

最后再说一个容易被忽略的小事:你每创建一条自定义规则,就是在把一段隐性知识(“这事不能这么写”)固化成可机器执行的显性约束。 这些东西累积起来,就是你团队工程体系最坚固的底层护城河,它不依赖某个人的 review 意愿,不因为项目进度的压力而妥协,也不会在人员流动中丢失。

打开你的 Claude Code,从一个困扰你超过两周的代码规范问题开始,写你的第一条 Prompt。

下一步行动建议:

  • 从你当前项目里挑一个“code review 重复唠叨了三次以上”的规范问题,用它作为你第一条自定义规则的素材。
  • 按照本文第四节的 Prompt 模板生成初版规则,用 npx eslint --rulesdir 四步闭环在本地验证。
  • 观察期以 warn 接入 CI,收集两周数据,再决定是否升级为 error
  • 把每一条规则的意图和用法写进 RULES.md,随规则代码一起版本管理。

如果你在实践过程中遇到 Claude Code 生成的规则出现了某类反复出现的错误模式,可以在评论区交流。我也会把我在十几条规则迭代中积累的 Prompt 变体和修正策略持续补充更新。

常见问题解答(FAQ)

1. 用 Claude Code 创建自定义 ESLint 规则前,需要理解 AST 吗?

我是一名前端开发,想用 Claude Code 帮我写自定义 ESLint 规则,但听说要懂 AST 抽象语法树,觉得门槛挺高。不知道能不能绕开这个坎?

我的经验是:你完全不需要精通 AST,但需要知道 AST 是什么以及怎么给 Claude Code 描述。

第一次大规模尝试时,我花了三天硬啃 AST 文档试图手写规则,后来用 Claude Code 后,发现它只需要你描述目标,比如“我想禁止在 React 组件中直接使用 document.getElementById,除非是 ref 回调”。

它自己会解析 AST 节点 CallExpressionMemberExpression。关键在于:你不需要写 AST 选择器,但要在 prompt 里给够场景和边界案例。

我踩过一个坑:让它写“禁止在循环中创建函数”,它生成了检测 ForStatementFunctionExpression 的规则,但忽略了 whilefor...of。后来我在 prompt 里明确列出所有循环类型,它一次搞定。

所以,理解 AST 的概念就行,剩下的交给 Claude Code 处理语法细节。

2. 如何写出高效的 prompt 让 Claude Code 生成 ESLint 规则?

我试了几次让 Claude Code 写 ESLint 规则,要么遗漏重要情况,要么生成了不能用的代码。到底该怎么问它,才能一次生成可用的规则?

我总结出一套“意图驱动+边界案例”的 prompt 模板。最初我只会说“请写一条规则禁止使用 var”,它返回的代码能用但不够鲁棒,没有处理 var 在全局作用域或 for 循环里的特殊情况。

改进后,我分成三部分写 prompt:第一,规则意图和触发条件(如“检测所有 VariableDeclarationkindvar”);第二,明确的边界场景(如“排除 for 循环的初始化部分,排除 export 语句中的变量”);

第三,要求它生成 meta 和 fixer。一次我要求“禁止在非空数组上使用 Array.from”,它没考虑到 arguments 对象,但我在 prompt 里补充了“如果是类数组对象则允许”,结果规则直接可用。

此外,让 Claude Code 自己输出测试用例,比如“请顺便生成三个通过和三个不通过的示例代码”,这能大幅减少调试时间。

3. Claude Code 生成的 ESLint 规则如何测试和调试?

让 Claude Code 帮我写了条自定义规则,但在项目中一运行就报错,而且错误信息很抽象。有没有系统性的方法可以测试 AI 生成的规则?

我踩过最深的坑是直接丢进项目跑。第一次生成的规则可能没问题,但一旦有依赖冲突或 node 版本差异,ESLint 就崩。

我的流程分四步:第一步,让 Claude Code 生成规则时,额外让它输出一个独立的测试脚本,比如用 ESLint 类 + Linter 对一段测试代码执行 verify

第二步,本地创建一个临时目录,只放规则文件和测试代码,用 eslint --no-eslintrc --rulesdir ./rules --rule 'my-rule: error' test.js 隔离测试。

第三步,给 Claude Code 看错误堆栈,并问它“这个错误可能是哪个 AST 节点没处理?”,它常常能直接指出是 null 节点或 optional chaining 没考虑。第四步,用它的修复建议更新规则后,再跑一遍测试,同时检查 fixer 是否会引起循环修复。

我曾经有一个规则无限修复直到爆栈,就是因为 fixer 没排除已修复的节点。这个闭环流程确保规则上线前 90% 的 bug 被提前发现。

4. 如何将 Claude Code 创建的自定义 ESLint 规则落地到团队工作流?

我单独写了一条自定义规则,想让团队其他人也用起来,但不知道怎么推广,而且最好能自动修复。Claude Code 在这方面有没有现成的帮助?

我的做法是三步走:第一步,利用 Claude Code 生成规则的 docsurl 字段,让它输出一个 Markdown 文档,描述规则的作用、错误示例和正确示例。这样在 eslint-plugin-my-rules 发布后,团队可以直接在文档中看到提示。

第二步,结合 husky + lint-staged 做增量检查,我在 lint-staged 配置中只对修改的文件运行自定义规则,避免全量扫描拖慢提交。

第三步,关键点,让 Claude Code 为你团队已有的代码库做一次“规则预检”:我让它分析项目中所有文件的 AST 分布,找出哪些文件触发新规则,然后批量自动修复(利用它的 fix 功能)。曾经我一次修复了 300+ 文件中的 const 误用,几乎没有报错。

当然,修复前要在 CI 上跑一个 dry run,确保不会改坏核心逻辑。团队接受度从 30% 提升到 80% 以上的转折点就是:我用 Claude Code 帮他们做了一次零痛感的全量修复,然后告诉他们“以后新代码自动检查,再也不用 review 这种低级问题了”。

核心关键词

读者评论

王安宁

看完这篇文章,我对‘约束设计师’这个角色重新理解了。看了你的流程,我决定这周就用 Claude Code 把这条规则写进 CI,比口头强调一万遍都有用。我们团队插件包维护成本有点高,想参考一下你的工程结构选择,以及 Claude Code 在生成插件时的帮助有多大。

何雨

以前总觉得自定义 ESLint 规则就是查 AST 文档硬肝,你点出了关键:Claude Code 帮你卸载的是机械劳动,但边界条件的定义权必须在自己手里。微前端下禁止子应用直接 import React 这条规则,我们当初是手写的,踩 AST 的坑踩到怀疑人生。关于‘规则边界定义’那一段,我觉得可以再加一个角度:Claude Code 生成的规则很容易‘过于精确’,即只覆盖你 Prompt 里给的 case,而漏掉变体。

李卓

我准备拿团队那个‘禁止跨模块 import 内部 selector’的需求去试一下,先用你给的 Prompt 框架走一遍。你那张时间分配对比图我太有共鸣了,手写时代花在查节点类型上的时间确实超过一半。我的经验是,每次生成完规则后,不要直接用,先用自然语言反问它:‘如果换成这种写法,你的规则还能抓到吗?

唐悦

Prompt 质量决定规则质量的这个判断非常落地。现在用 Claude Code 重构规则,那个‘把不想做的事外包出去’的说法深得我心。多来几轮对话能逼出很多盲区。

沈一诺

我之前用 Claude Code 生成过一条规则,也是没处理好三元表达式和 spread 属性的问题,后来补了五六个 test case 才修好。文章结构很硬,几乎没有废话。你的文章让我意识到,团队里真正缺的不是‘会写 ESLint 规则的人’,而是‘能把约束说清楚的人’。

苏禾

你文章里说的‘用测试用例逼出 AI 盲区’,完全是经验之谈,不是那些只讲概念的文章能比的。开头直接抛业务痛点,中间给图表对比,后面一步步拆工作流,这种写作方式在讲 AI 工具的文章里少见。用 Claude Code 之后,这个问题被前置了。

叶宁

那个安全约定的例子太真实了:“/api/admin/ 请求不能带 token 到头里”。不是‘AI 真强’那类情绪文,更像是一份操作手册,对真正想落地的人很友好。我们上周刚因为一个埋点规范不统一导致数据报表对不上,现在准备用你的方法把 PageNames 枚举约束写成规则。

陆景

我们组几乎一模一样的问题,靠 code review 挡了半年还是出了两次事故。想问一个落地细节:你们把自定义规则集成到 CI 后,是放在单独的一个 ESLint 插件包里,还是直接放在项目的 local-rules 目录下?

文章版权归“万象方舟”www.vientianeark.cn所有。发布者:程, 沐沐,转载请注明出处:https://www.vientianeark.cn/p/598944/

温馨提示:文章由AI大模型生成,如有侵权,联系 mumuerchuan@gmail.com 删除。
(0)
claude code 帮助排查 Git 合并冲突的解决方案
上一篇 19秒前
claude code 在 Jupyter Notebook 中的交互式编程体验
下一篇 11秒前

相关推荐

  • claude code 在 Jupyter Notebook 中的交互式编程体验

    上周四凌晨两点,我盯着 Jupyter Notebook 上一个报错已经四十分钟。数据清洗脚本跑到第三个 Cell 就挂了,KeyError: 'purchase_date',但我在 CSV 文件里分明看到这个列名。检查过编码、检查过分隔符、检查过列名空格,一切看起来都对。 按传统流程,我应该继续逐行 print、查 df.columns、写一串防御性代码。但我那天换了种做法:…

    11秒前
    000
  • claude code 帮助排查 Git 合并冲突的解决方案

    那是在一个周四的凌晨两点,我看着终端里刷屏的冲突标记,<<<<<<< HEAD、=======、>>>>>>> feature/payment-refactor,整整237个冲突文件。三天前开始的重构分支合并,此刻像一堵密不透风的墙。Git告诉我冲突了,但它不会告诉我:A分支删掉的这个方法,B分支为什么要新增调用?…

    19秒前
    000
  • claude code 生成 TypeScript 类型定义文件的方法

    半年前,我接手了一个有着6年历史的电商中台项目。仓库里躺着200多个.js文件,没有一个类型定义。每次新同事入职,我都要重复同一句话:“看代码注释,虽然注释也没有。”给接口联调的时候,前端和Java后端的同学天天吵架,传参类型对不上,返回值结构猜不透,联调时间动辄三四个小时。 最让我记忆犹新的是去年双十一前的一次事故。物流模块一个函数把跟踪号从string悄悄改成了string[],十几个下游服务…

    27秒前
    000
  • 用 claude code 将伪代码直接转换为可运行程序

    用 claude code 将伪代码直接转换为可运行程序 上周三凌晨两点,我盯着飞书多维表格里 47 条“本周已完成”的任务记录,手上还有一份明天早会要交的周报。数据都在,但每次都要手工做统计、画图、排版,整个过程大概需要 40 分钟。 那晚我做了一个决定:不做了,不是不做周报,是不再手工做。我打开 Claude Code,把一段用中文写的“伪代码”丢了进去。这段伪代码大概长这样: > 输入…

    37秒前
    000
  • 在 claude code 中通过 prompt 控制代码输出风格

    三周前的一个深夜,我盯着 Claude Code 刚刚生成的 237 行 Go 代码,血压直接拉满。这段代码功能上完全正确,但 user_service.go 里混着三种命名风格,有的函数叫 GetUserByID,有的叫 findUserByEmail,甚至有一个叫 retrieve_user_data。错误处理更是灾难,有 if err != nil { return err },有 if e…

    46秒前
    000
站长微信
站长微信
分享本页
返回顶部