首页
/ 深入解析 Cypress 内部 ESLint 插件 @cypress/eslint-plugin-dev:共享规则、自定义检查与 Git 钩子集成

深入解析 Cypress 内部 ESLint 插件 @cypress/eslint-plugin-dev:共享规则、自定义检查与 Git 钩子集成

2026-09-07 12:48:00作者:秋泉律Samson

@cypress/eslint-plugin-dev 是 Cypress 单仓库(monorepo)内部使用的 npm 包,它为仓库中所有面向 Cypress 开发的子包提供一组共享的 ESLint 规则与配置预设,包括自定义的"测试规范"检查(如禁止 .only.skip 无注释)和配套的 lint-changedlint-pre-commit CLI 二进制。本文以该包为线索,系统讲解其安装方式、三套配置预设(general/tests/react)、四条自定义规则的底层实现,以及如何通过 Git 钩子让"只校验改动文件"落地,帮助你理解大型前端 monorepo 如何沉淀一套内部代码规范基础设施。

包定位:面向 Cypress 内部开发,而非 Cypress 用户

先厘清一个重要边界:@cypress/eslint-plugin-dev 仅供 Cypress monorepo 与其内部工具链使用,它不是一个面向 Cypress 最终用户的 ESLint 插件。面向用户(测试用例作者)的官方插件是独立的 eslint-plugin-cypress 仓库与包。这一点在该包的 AGENTS.mdREADME.md 中均有明确警示,引用时不应混淆两者。

从包的 package.json 可以看到它的基本事实:

  • 包名 @cypress/eslint-plugin-dev,描述为 "Common ESLint rules shared by Cypress development-only packages"(Cypress 仅限开发用途包共享的通用 ESLint 规则);
  • 主入口 main: "./lib",即整个插件逻辑集中在 lib/ 目录;
  • 声明了两个 CLI 二进制(bin 字段):lint-changedlint-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-parsereslint-plugin-importeslint-plugin-json-formateslint-plugin-mochaeslint-plugin-react 等,运行时依赖则包括 bluebirdchalkeslint-rule-composerlodashshelljs
  • 包内自测脚本:lint(ESLint 自身)、lint-changedlint-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.jslint-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 给出了三个关键补充约定:

  1. 若使用 React,追加 "plugin:@cypress/dev/react"
  2. 若包内有 test/ 目录,应在其中单独创建 .eslintrc.json 并继承 plugin:@cypress/dev/tests
  3. .eslintignore 中加入 !.*,避免忽略以 . 开头的隐藏文件(这些文件常是 JSON 配置,需要交给 json-format 插件做格式化)。
# don't ignore hidden files, useful for formatting json config files
!.*

接入后即可用 ESLint 校验;包自身也提供了便捷脚本,例如在 package.json 中定义的 yarn lint(对包自身执行 ESLint)、yarn lint-fix(自动修复),以及带 --fixlint-changed

三套预设:general / tests / react

general:包根级别的代码风格基线

general 定位为"通常放在包根目录使用"的预设,集中了绝大多数规则,并通过 eslint-plugin-json-format 自动格式化 JSON 文件并排序 package.json

lib/index.js 的源码看,general 的构成相当具体:

  • parserOptionsecmaVersion: 2018sourceType: 'module'
  • 环境 envnode: truees6: true
  • 插件:json-formatsettings.jsonsort-package-json: 'pro'settings.react.version: 'detect'
  • 展开 baseRules 约 70 条核心规则,值得注意的取值包括:
    • 缩进 indent: ['error', 2],并对 TemplateLiteral 节点豁免(源码注释注明这是遗留待修项)、SwitchCase: 1MemberExpression: 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-consoleno-debuggerno-varno-unused-varsargs: '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-eslintimport 插件,同时关闭与 TS 冲突或冗余的 JS 规则(no-undefno-unused-varsindentno-useless-constructorno-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-callbackmocha/no-exclusive-testsmocha/no-global-tests 三条规则,全部为 'error'
  • 启用自定义规则 @cypress/dev/skip-comment: 'error'(见下文规则详解);
  • *.spec.tsx 覆盖使用 TS 解析器,并关闭 no-unused-vars(源码注释说明是为避免接口 import 被误报)。

这套组合的实际效果是:测试文件里不允许出现 describe.only/it.onlyno-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-scopereact/jsx-curly-spacingreact/jsx-equals-spacingreact/jsx-no-undefreact/jsx-pascal-casereact/jsx-no-duplicate-propsreact/no-unknown-propertyreact/require-render-returnreact/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-onlyskip-commentno-return-before 共同守护 spec 文件——禁止独占地运行单个用例、禁止无故跳过用例、禁止在用例/断言前画蛇添足地写 return。配套的 Vitest 规格与 fixture 位于 test/(如 no-only.spec.tsskip-comment.spec.tsno-return-before.spec.tsarrow-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=Mgit 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 只会对已暂存的文件做 --fixgit add,前提是该文件没有未暂存的改动——从而避免把"部分暂存"的文件在钩子里被整文件误加入暂存区。若想一次性自动修复所有暂存与未暂存文件,可手动执行 ./node_modules/.bin/lint-changed --fix

按需覆盖规则与关闭 package.json 排序

general 预设的规则并非不可更改,在 .eslintrc.jsonrules 中覆盖即可(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 lintyarn lint-changedyarn 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 基础设施提供了可参考的设计蓝本。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.13 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
897
5.8 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
529
593
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
915
1.83 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.58 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.35 K
1.46 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.01 K
515
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
547
388