深入解析 Cypress 内部 ESLint 插件 @cypress/eslint-plugin-dev:共享规则、自定义检查与 Git 钩子集成
@cypress/eslint-plugin-dev 是 Cypress 单仓库(monorepo)内部使用的 npm 包,它为仓库中所有面向 Cypress 开发的子包提供一组共享的 ESLint 规则与配置预设,包括自定义的"测试规范"检查(如禁止 .only、.skip 无注释)和配套的 lint-changed、lint-pre-commit CLI 二进制。本文以该包为线索,系统讲解其安装方式、三套配置预设(general/tests/react)、四条自定义规则的底层实现,以及如何通过 Git 钩子让"只校验改动文件"落地,帮助你理解大型前端 monorepo 如何沉淀一套内部代码规范基础设施。
包定位:面向 Cypress 内部开发,而非 Cypress 用户
先厘清一个重要边界:@cypress/eslint-plugin-dev 仅供 Cypress monorepo 与其内部工具链使用,它不是一个面向 Cypress 最终用户的 ESLint 插件。面向用户(测试用例作者)的官方插件是独立的 eslint-plugin-cypress 仓库与包。这一点在该包的 AGENTS.md 与 README.md 中均有明确警示,引用时不应混淆两者。
从包的 package.json 可以看到它的基本事实:
- 包名
@cypress/eslint-plugin-dev,描述为 "Common ESLint rules shared by Cypress development-only packages"(Cypress 仅限开发用途包共享的通用 ESLint 规则); - 主入口
main: "./lib",即整个插件逻辑集中在lib/目录; - 声明了两个 CLI 二进制(
bin字段):lint-changed与lint-pre-commit,分别指向 lib/scripts/lint-changed.js 与同目录下的lint-pre-commit.js; - peerDependencies 覆盖 ESLint
^= 8.0.0(精确锁定 8.x)、@typescript-eslint/*(>= 7.0.0)、@babel/eslint-parser、eslint-plugin-import、eslint-plugin-json-format、eslint-plugin-mocha、eslint-plugin-react等,运行时依赖则包括bluebird、chalk、eslint-rule-composer、lodash、shelljs; - 包内自测脚本:
lint(ESLint 自身)、lint-changed、lint-fix(--fix全量)与基于 Vitest 的test。
版本兼容提示:仓库内 AGENTS.md 明确指出,该插件的消费端仅兼容 ESLint 8.x(
"eslint": "^= 8.0.0"),不兼容 ESLint 9 flat config;README.md 进一步补充:ESLint 8 使用 6.x.x 版本,ESLint 7 及以下使用 5.x.x 版本。
源码架构一览
结合 AGENTS.md 与目录结构,包的内部结构如下:
- lib/index.js — 唯一入口,同时导出
configs(三套预设)与rules(自定义规则); - lib/custom-rules/ — 为 Cypress 内部编写的自定义 ESLint 规则,目录内
index.js会扫描目录中除自身外的所有.js文件并自动注册为规则; - lib/scripts/ — CLI 脚本目录:
lint-changed.js(对外暴露为lint-changed二进制,只校验改动文件)、lint-pre-commit.js(对外暴露为lint-pre-commit二进制,供 pre-commit 钩子使用),此外还包含lint-pre-push.js、lint-staged.js与共享工具utils.js; - test/ — Vitest 规格文件(
*.spec.ts)与配套 fixture,覆盖四条自定义规则的通过/失败场景。
其中入口文件的结构可以直观看到三类预设的构成:general 内含庞大的"基础规则基线"(baseRules,约 70 条 ESLint 核心规则,统一为 'error'),tests 面向测试目录启用 mocha 环境与 skip-comment 规则,react 则引入 @babel/eslint-parser 与 JSX 相关规则。
安装与最小接入配置
该插件的接入分两步。首先安装插件本体及按需的伴随插件(详见 README.md):
@cypress/eslint-plugin-dev
eslint-plugin-json-format
@typescript-eslint/parser
@typescript-eslint/eslint-plugin
eslint-plugin-mocha
eslint-plugin-import
# 若工程含 react/jsx 文件,还需:
eslint-plugin-react
@babel/eslint-parser
然后在包根目录的 .eslintrc.json 中声明插件并继承 general 预设:
{
"plugins": [
"@cypress/dev"
],
"extends": [
"plugin:@cypress/dev/general"
]
}
README 给出了三个关键补充约定:
- 若使用 React,追加
"plugin:@cypress/dev/react"; - 若包内有
test/目录,应在其中单独创建.eslintrc.json并继承plugin:@cypress/dev/tests; - 在
.eslintignore中加入!.*,避免忽略以.开头的隐藏文件(这些文件常是 JSON 配置,需要交给json-format插件做格式化)。
# don't ignore hidden files, useful for formatting json config files
!.*
接入后即可用 ESLint 校验;包自身也提供了便捷脚本,例如在 package.json 中定义的 yarn lint(对包自身执行 ESLint)、yarn lint-fix(自动修复),以及带 --fix 的 lint-changed。
三套预设:general / tests / react
general:包根级别的代码风格基线
general 定位为"通常放在包根目录使用"的预设,集中了绝大多数规则,并通过 eslint-plugin-json-format 自动格式化 JSON 文件并排序 package.json。
从 lib/index.js 的源码看,general 的构成相当具体:
parserOptions:ecmaVersion: 2018、sourceType: 'module';- 环境
env:node: true、es6: true; - 插件:
json-format;settings.json中sort-package-json: 'pro',settings.react.version: 'detect'; - 展开
baseRules约 70 条核心规则,值得注意的取值包括:- 缩进
indent: ['error', 2],并对TemplateLiteral节点豁免(源码注释注明这是遗留待修项)、SwitchCase: 1、MemberExpression: 0; quotes: ['error', 'single', { allowTemplateLiterals: true }](单引号、允许模板字符串);semi: ['error', 'never'](不使用分号)——这与 Cypress 仓库代码风格一致;comma-dangle: ['error', 'always-multiline']、brace-style: ['error', '1tbs', { allowSingleLine: false }]、curly: ['error', 'multi-line', 'consistent'];- 大量
no-*类规则(no-console、no-debugger、no-var、no-unused-vars(args: 'none'、ignoreRestSiblings: true)等); padding-line-between-statements强制return前、变量声明与if/while/export/import前后必须空行,同时允许连续多个const/let/var/import之间不必空行;eqeqeq: ['error', 'allow-null']、object-curly-spacing: ['error', 'always']等。
- 缩进
general 还通过 ESLint 的 overrides 对不同文件类型注入差异化规则(lib/index.js):
- 对
*.jsx/*.tsx:关闭@cypress/dev/arrow-body-multiline-braces(JSX 场景下该写法规则被豁免); - 对
*.ts/*.tsx/*.vue:切换解析器为@typescript-eslint/parser,追加@typescript-eslint与import插件,同时关闭与 TS 冲突或冗余的 JS 规则(no-undef、no-unused-vars、indent、no-useless-constructor、no-duplicate-imports),改为启用import/no-duplicates: 'error'、@typescript-eslint/no-unused-vars(附带argsIgnorePattern: '^_',允许_前缀参数)、@typescript-eslint/type-annotation-spacing、@typescript-eslint/member-delimiter-style(多行类型成员间不加分隔符、单行用逗号)以及@typescript-eslint/indent等。
因此从源码结构可以推断:TS/Vue 文件是在 general 内部通过 overrides 自动获得 TS 解析与规则处理的,无需在 extends 里额外声明 TS 预设。
tests:测试目录专用
tests 预设应放在 test/ 目录内使用。从 lib/index.js 看,它做了这些事:
- 设置环境
env.mocha: true、全局expect: true; - 追加
mocha插件,并启用mocha/handle-done-callback、mocha/no-exclusive-tests、mocha/no-global-tests三条规则,全部为'error'; - 启用自定义规则
@cypress/dev/skip-comment: 'error'(见下文规则详解); - 对
*.spec.tsx覆盖使用 TS 解析器,并关闭no-unused-vars(源码注释说明是为避免接口 import 被误报)。
这套组合的实际效果是:测试文件里不允许出现 describe.only/it.only(no-exclusive-tests),不允许全局(顶层)散落的测试(no-global-tests),不允许遗漏 done 回调处理,并且 .skip 必须附带说明性注释。
react:JSX 专项规则
react 预设面向 React/JSX 文件(lib/index.js):
- 环境切到
browser: true; - 解析器改为
@babel/eslint-parser,开启 JSX 与 legacy decorators; - 启用一组
react/*规则,包括react/react-in-jsx-scope、react/jsx-curly-spacing、react/jsx-equals-spacing、react/jsx-no-undef、react/jsx-pascal-case、react/jsx-no-duplicate-props、react/no-unknown-property、react/require-render-return、react/jsx-wrap-multilines等,全部为'error'。
四条自定义规则深入解读
四条规则集中在 lib/custom-rules/,并在 README.md 的自定义规则表中给出了名称、说明、options 与示例;其注册是自动化的——目录内 index.js 通过 fs.readdirSync 遍历并 require 除自身外所有 .js 文件,再以去掉 .js 后缀的文件名作为规则名聚合导出。
@cypress/dev/arrow-body-multiline-braces
该规则只在多行箭头函数定义中强制使用花括号(选项固定为 'always')。它不是从零书写的规则,而是基于 eslint-rule-composer 对 ESLint 内置 arrow-body-style 的再封装。
看 arrow-body-multiline-braces.js 的实现:通过 new eslint.Linter().getRules().get('arrow-body-style') 取到内置规则对象,再用 ruleComposer.filterReports 过滤掉两类误报——单行(问题起点与终点同行的)箭头函数,以及 report 位置命中的 token 为行注释(type === 'Line')且注释值形如 ---(分隔线注释)的情况。换言之,它只拦截"多行、且非注释分隔行场景"下缺失花括号的箭头函数。
通用 preset(general)把该规则默认设为 ['error', 'always'](见 lib/index.js),同时如上文所述在 *.jsx/*.tsx 中关闭。
@cypress/dev/skip-comment
防止开发者在 it/describe/context 上写 .skip(...) 却不对跳过原因做任何说明。规则实现位于 skip-comment.js:
- 在
CallExpression:exit阶段检查调用是否为X.skip(...)形式且X属于it/describe/context; - 通过
sourceCode.getCommentsBefore(node)获取节点前的注释,仅当注释内容以配置的 commentTokens(默认['NOTE:', 'TODO:', 'FIXME:'])开头时才算"有解释",否则直接报告错误; - 错误消息会现场演示正确写法:在被
.skip的测试上方加一行// NOTE: <跳过原因>; - 通过 options 可自定义提示词集合,例如
['error', { commentTokens: ['TODO:'] }]。
该规则在 tests 预设中被直接启用为 'error',与 mocha/no-exclusive-tests 形成互补:前者防 .skip 无理由,后者防 .only 泄漏。
@cypress/dev/no-return-before
禁止在可配置的若干"标记"标识符(默认 ['it', 'describe', 'context', 'expect'])前出现 return。实现在 no-return-before.js:
- 对每个
CallExpression,若 callee 为 Identifier 且名字命中 tokens 集合,则向前取一个 token; - 若该 token 是值为
return的关键字,即报告 "Found a 'return' after '{{token}}'"; - 与其它规则不同,它自带自动修复:
fixer.replaceTextRange会把return连同其后一个空格移除(fixable: 'code')。
例如 return it(...)、return expect(...) 这类写法会被删除无意义的 return。同样支持通过 { tokens: [...] } 定制,如 ['error', { tokens: ['myfn'] }]。
@cypress/dev/no-only
从函数名与文档可看出,它用于阻止 spec 文件中残留 .only。实现在 no-only.js:对 CallExpression 的 callee 做 MemberExpression 匹配,当属性名为 only 且对象属于 it/describe/context 时报告 "Found only: ..."。注意 lib/index.js 中该规则目前以注释形式保留(// '@cypress/dev/no-only': 'error'),推测 .only 的拦截主要由 tests 预设里 mocha 插件的 mocha/no-exclusive-tests 承担。
小结:
no-only、skip-comment、no-return-before共同守护 spec 文件——禁止独占地运行单个用例、禁止无故跳过用例、禁止在用例/断言前画蛇添足地写return。配套的 Vitest 规格与 fixture 位于 test/(如no-only.spec.ts、skip-comment.spec.ts、no-return-before.spec.ts、arrow-body-multiline-braces.spec.ts及对应 fixture 文件),可作为实现行为的可执行验证。
CLI 与 Git 钩子:只 lint 改动的文件
插件不只是一个规则集合,还暴露两个可在 node_modules/.bin 中找到的二进制:
lint-changed— 仅对自上次提交以来修改过的文件执行 lint;lint-pre-commit— 面向 pre-commit 钩子的 lint 入口。
以 lint-changed.js 为例,其核心逻辑清晰:通过 git diff --name-only --diff-filter=M 与 git diff --name-only --diff-filter=MA --staged 取未暂存修改与已暂存新增/修改的文件名并集(_.union),交给共享工具 utils.lintFilesByName 执行;支持 --fix 参数;若有失败文件则 process.exit(failed) 返回非零退出码,否则用 chalk 打印绿色成功计数。这一设计让 CI 与本地钩子只承担与本次改动相关的校验成本。
集成 husky 的方式(README 中给出):
"husky": {
"hooks": {
"pre-commit": "lint-pre-commit"
}
}
README 特别强调了一个保护机制:lint-pre-commit 只会对已暂存的文件做 --fix 并 git add,前提是该文件没有未暂存的改动——从而避免把"部分暂存"的文件在钩子里被整文件误加入暂存区。若想一次性自动修复所有暂存与未暂存文件,可手动执行 ./node_modules/.bin/lint-changed --fix。
按需覆盖规则与关闭 package.json 排序
general 预设的规则并非不可更改,在 .eslintrc.json 的 rules 中覆盖即可(README.md 的配置示例):
// .eslintrc.json
{
"extends": [
"plugin:@cypress/dev/general"
],
"rules": {
"comma-dangle": "off",
"no-debugger": "warn"
}
}
如果某个包不希望 package.json 被自动排序格式化,可在 settings 中关闭:
{
"settings": {
"json/sort-package-json": false
}
}
编辑器集成与日常使用
按 README.md 的 Editors 章节,接入后可让编辑器在保存时即时报错并自动修复:
- VS Code:安装 ESLint 扩展后,在用户或工作区设置中声明
eslint.validate的语言与autoFix: true,覆盖 javascript、javascriptreact、typescript、typescriptreact 与 json 五种语言; - Atom:安装
linter-eslint并开启 "Fix on save"; - Sublime Text:安装
ESLint-Formatter,设置"format_on_save": true、"debug": true。
在 monorepo 根目录或子包中可直接运行 yarn lint、yarn lint-changed、yarn lint-fix,测试规格则通过 yarn test -- <path-to-spec> 或 yarn test -- "<glob-pattern>" 定向执行(见 AGENTS.md)。
总结:一套 monorepo 代码治理的可复用范式
@cypress/eslint-plugin-dev 展示了大型 TypeScript/JS monorepo(即当前 Cypress 仓库,各开发包位于 packages/ 与 npm/,此外大量 eslint.config.ts 分布在 packages/、npm/ 及各子包内)沉淀代码规范的一种路径:把"统一的风格基线 + 面向测试文件的专属约束 + 只检查改动"的组合封装成可发布的内部 npm 包,并配以 Git 钩子与编辑器自动修复。理解它的预设结构与自定义规则实现,不仅有助于在 Cypress 仓库内贡献代码时遵守约定,也为自建团队 lint 基础设施提供了可参考的设计蓝本。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0627
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00