ESLint 核心规则贡献完全指南:从文件结构、单元测试到性能验证与冻结规则约定
【免费下载链接】eslintFind and fix problems in your JavaScript code.项目地址: https://gitcode.com/GitHub_Trending/es/eslint
本篇技术指南以 ESLint 官方贡献文档 docs/src/contribute/core-rules.md 为核心骨架,完整讲解核心规则(Core Rules)的定义、三文件结构、源码编写格式、RuleTester单元测试、npm run perf性能验证、命名约定以及「冻结规则」(Frozen Rules)机制,并结合本仓库中的真实源码(如 lib/rules/no-extra-semi.js、tests/lib/rules/no-extra-semi.js)逐层印证。读完本文,你将掌握向 ESLint 仓库提交一条合格核心规则所需的全部规范与可落地步骤。
什么是 ESLint 核心规则
ESLint 的核心规则(Core Rules)是指随eslint包一起发布的、内置于 ESLint 中的规则集合,分布在仓库的 lib/rules 目录下(共 300 余条,例如no-extra-semi、semi、no-eval等)。与之相对的是由插件提供的自定义规则(Custom Rules)。
核心规则与自定义规则使用完全相同的 API——两者的规则模块都导出meta元数据对象与create访客函数,底层由Linter统一调度。核心规则与自定义规则的主要区别只有两点:
- 核心规则随
eslint包分发,无需额外安装插件即可使用; - 核心规则必须遵守本文档(
docs/src/contribute/core-rules.md)所记录的仓库级约定。
规则编写的完整参考请见 Custom Rules 文档,其中详细规定了meta(含type、docs、fixable、hasSuggestions、schema、defaultOptions、languages、deprecated等)、create()访问器、context对象、context.report()问题上报、fix自动修复与suggest建议等全部细节,核心规则与自定义规则遵循同一套格式。
核心规则的三文件结构
每个核心规则都对应三个同名文件,以规则标识符(如no-extra-semi)命名:
| 位置 | 用途 | 以no-extra-semi为例 |
|---|---|---|
lib/rules/下的源码文件 | 规则实现 | lib/rules/no-extra-semi.js |
tests/lib/rules/下的测试文件 | 规则单元测试 | tests/lib/rules/no-extra-semi.js |
docs/src/rules/下的文档文件 | 规则使用文档 | docs/src/rules/no-extra-semi.md |
重要:若向 ESLint 仓库提交一条核心规则,必须同时提供上述三个文件并遵守下列全部约定。
规则文档文件的格式要求
docs/src/rules/下的文档使用 Markdown 编写,文件头以 YAML front matter 声明元信息,例如 docs/src/rules/no-extra-semi.md 的开头:
--- title: no-extra-semi rule_type: suggestion related_rules: - semi - semi-spacing ---其中rule_type必须与源码meta.type一致(problem/suggestion/layout),related_rules列出相关规则便于文档交叉引用。正文使用::: incorrect/::: correct等容器分别展示该规则的错误示例与正确示例。
核心规则源码的基本格式
官方文档给出了核心规则源码文件的基本骨架,本节将其与仓库真实实现对照解读。
官方模板
/** * @fileoverview Rule to disallow unnecessary semicolons * @author Nicholas C. Zakas */ "use strict"; //------------------------------------------------------------------------------ // Rule Definition //------------------------------------------------------------------------------ /** @type {import('../types').Rule.RuleModule} */ module.exports = { meta: { type: "suggestion", docs: { description: "disallow unnecessary semicolons", recommended: true, url: "https://eslint.org/docs/rules/no-extra-semi", }, fixable: "code", schema: [], // no options }, create: function (context) { return { // callback functions }; }, };模板要点:
- 文件头使用
@fileoverview描述规则用途、@author标注作者; - 通过
/** @type {import('../types').Rule.RuleModule} */进行 JSDoc 类型标注,该类型定义在 lib/types/index.d.ts 中; meta.docs.description是核心规则的必填项,用于生成 rules index 规则索引;meta.fixable只能取"code"或"whitespace",且可修复规则必须声明,否则 ESLint 会在规则尝试产生修复时抛错;meta.schema用于校验规则配置项,无选项时写schema: [],此时用户传入任何选项都会被判定为非法。
真实实现:no-extra-semi的结构解读
查看仓库中 lib/rules/no-extra-semi.js,可以看到一条成熟核心规则的完整形态。其meta部分(lib/rules/no-extra-semi.js#L22-L58)包含:
meta: { deprecated: { message: "Formatting rules are being moved out of ESLint core.", url: "https://eslint.org/blog/2023/10/deprecating-formatting-rules/", deprecatedSince: "8.53.0", availableUntil: "11.0.0", replacedBy: [ { message: "ESLint Stylistic now maintains deprecated stylistic core rules.", url: "https://eslint.style/guide/migration", plugin: { name: "@stylistic/eslint-plugin", url: "https://eslint.style" }, rule: { name: "no-extra-semi", url: "https://eslint.style/rules/no-extra-semi" }, }, ], }, type: "suggestion", docs: { description: "Disallow unnecessary semicolons", recommended: false, url: "https://eslint.org/docs/latest/rules/no-extra-semi", }, fixable: "code", schema: [], messages: { unexpected: "Unnecessary semicolon.", }, },对比模板可以发现几个核心规则进阶要点:
messages对象集中管理违规消息,通过messageId在context.report()中引用(此例为unexpected),避免消息文本在规则文件与测试文件中重复;deprecated结构声明规则的废弃状态、废弃起始版本、可用截止版本以及替代方案(replacedBy);docs.recommended标识该规则是否被@eslint/js的recommended配置默认启用。
create部分(lib/rules/no-extra-semi.js#L60-L166)通过注册EmptyStatement、ClassBody、MethodDefinition, PropertyDefinition, StaticBlock等 AST 访客来识别多余分号,并使用context.report()配合fix函数上报可自动修复的问题,其中还通过FixTracker.retainSurroundingTokens扩大替换范围以避免与semi规则的修复冲突——这正是「核心规则修复必须小而安全」的典型实践。
核心规则的单元测试
每一条随包发布的核心规则都必须附带单元测试才能被接收。测试文件与源码文件同名,放在tests/lib/rules/目录下:若规则源码为lib/rules/foo.js,则测试文件应为tests/lib/rules/foo.js。
ESLint 提供了RuleTester工具,让规则测试的编写变得非常简单。该工具由 lib/rule-tester/rule-tester.js 实现,内部基于 Mocha/Jest 的describe/it框架封装(见 lib/rule-tester/rule-tester.js#L1-L37)。
以 tests/lib/rules/no-extra-semi.js 为例,真实测试骨架如下:
const rule = require("../../../lib/rules/no-extra-semi"), RuleTester = require("../../../lib/rule-tester/rule-tester"); const ruleTester = new RuleTester({ languageOptions: { ecmaVersion: 5, sourceType: "script", }, }); ruleTester.run("no-extra-semi", rule, { valid: [ "var x = 5;", "for(;;);", { code: "for(a of b);", languageOptions: { ecmaVersion: 6 } }, { code: "class A { }", languageOptions: { ecmaVersion: 6 } }, ], invalid: [ { code: "var x = 5;;", output: "var x = 5;", errors: [{ messageId: "unexpected" }], }, { code: "class A { static { ; } }", output: "class A { static { } }", languageOptions: { ecmaVersion: 2022 }, errors: [{ messageId: "unexpected", column: 20 }], }, // 断言 output: null —— 期望规则报告问题但不提供自动修复 { code: "; 'use strict'", output: null, errors: [{ messageId: "unexpected" }] }, ], });RuleTester的关键使用规则
根据 RuleTester 文档:
ruleTester.run(name, rule, tests)接收三个参数:规则名称、规则对象、以及包含valid与invalid两个数组的测试对象;可选传assertionOptions(如requireMessage: true)对invalid用例的断言一致性做强制约束;valid数组中的字符串表示该代码不应触发任何报告;对象形式可附加options、languageOptions、settings、filename、before/after等属性;invalid数组中的每个用例必须声明errors(可指定message字符串、正则表达式或messageId,并可用line、column精确断言位置),output表示自动修复后的期望代码;当output为null时表示断言「该问题没有自动修复」,这正是no-extra-semi在; 'use strict'场景下的行为——移除分号会使后续字符串语句变成指令(directive),因此不能自动修复;RuleTester构造函数不传参时使用 ESLint 默认值(languageOptions: { ecmaVersion: "latest", sourceType: "module" });也可通过静态方法RuleTester.setDefaultConfig(config)、RuleTester.getDefaultConfig()、RuleTester.resetDefaultConfig()批量管理默认配置。
运行测试可使用仓库根目录 package.json 中定义的脚本npm test(实际执行node Makefile.js test),它会在 CI 与本地对全部核心规则测试文件进行跑批。
性能测试:用npm run perf验证规则开销
为了保持 lint 过程的效率与低侵入性,新规则或对现有规则的改动都应验证其性能影响。如何对单条规则进行剖析,可参考 Profile Rule Performance 章节——通过设置TIMING环境变量,在 lint 完成后展示运行时间最长的十条规则及其占比,例如:
$ TIMING=1 eslint lib Rule | Time (ms) | Relative :-----------------------|----------:|--------: no-multi-spaces | 52.472 | 6.1% camelcase | 48.684 | 5.7%要单独测试某条规则,可组合--no-config-lookup与--rule选项;将TIMING设为更大的数值(如TIMING=50)或TIMING=all可查看更长列表。
而在核心仓库内部开发时,npm run perf命令会给出开启全部核心规则后 ESLint 总运行时间的高层概览(该目标定义在仓库根目录 Makefile.js 中,其实现会用到hyperfine等基准工具做回归对比)。官方文档建议的对比流程如下:
$ git checkout main Switched to branch 'main' $ npm run perf CPU Speed is 2200 with multiplier 7500000 Performance Run #1: 1394.689313ms Performance Run #2: 1423.295351ms Performance Run #3: 1385.09515ms Performance Run #4: 1382.406982ms Performance Run #5: 1409.68566ms Performance budget ok: 1394.689313ms (limit: 3409.090909090909ms) $ git checkout my-rule-branch Switched to branch 'my-rule-branch' $ npm run perf CPU Speed is 2200 with multiplier 7500000 Performance Run #1: 1443.736547ms Performance Run #2: 1419.193291ms Performance Run #3: 1436.018228ms Performance Run #4: 1473.605485ms Performance Run #5: 1457.455283ms Performance budget ok: 1443.736547ms (limit: 3409.090909090909ms)使用要点:
- 先在
main分支跑一次基线,再切换到自己的规则分支跑一次,对比多次运行(官方示例为 5 次)的平均耗时; - 输出中的
Performance budget ok表示耗时未超出基于 CPU 速度折算的预算上限(示例中 limit 为3409.09ms); - 仓库会计算 CPU 速度并乘以 multiplier 折算成预算,因此不同机器上的数字不可直接横向比较,应以同机同环境的基线为准。
核心规则命名约定
ESLint 核心规则命名遵循以下约定:
- 单词之间使用**连字符(dash)**分隔,如
no-extra-semi、no-eval; - 若规则仅用于禁止某类写法,必须以
no-前缀命名,例如用no-eval禁止eval()、用no-debugger禁止debugger; - 若规则用于强制要求某类写法,则使用不带特殊前缀的简短名称,例如
semi、quotes、curly。
这套命名约定同样适用于自定义规则,建议自定义规则也遵循它,便于使用者理解规则意图。
冻结规则(Frozen Rules)
当规则达到功能完备(feature complete)状态时,会被标记为冻结,在文档中用 ❄️ 表情指示(可在规则源码的meta.docs中找到对应标记,例如 lib/rules/arrow-body-style.js 声明了frozen: true)。
冻结的判定标准
规则被视为功能完备的标准是:规则的目标用途已被完整实现,能捕获 80% 及以上预期违规,并覆盖绝大多数常见例外场景。在此之后,若遇到未被覆盖的边缘情况,官方期望用户改用禁用注释(disable comments)来处理,而不是要求规则继续扩张。
冻结意味着什么
当一条规则被冻结后,维护策略为:
- Bug 修复:仍会修复被确认的 bug;
- 新 ECMAScript 特性:保证与新语法兼容,即规则不会在新语法上崩溃;
- TypeScript 支持:保证与 TypeScript 语法兼容,不会在 TS 语法上出错,且对 TS 的违规判定保持恰当;
- 新选项:不再新增任何选项,除非新增选项是修复 bug 或支持新增 ECMAScript 特性的唯一途径。
冻结规则的替代方案
如果你认为某条冻结规则在你的场景下稍作改动会更好用,官方推荐的做法是:复制该规则源码到自己的项目中,按需修改后使用。这正好呼应了 Custom Rules 文档中的警告——eslint包中内置的核心规则不属于公共 API,不适合被直接继承扩展;基于核心规则二次开发非常脆弱,未来很可能因内部变化而彻底失效,因此应优先复制源码再改造。
提交核心规则前 Checklist
综合官方文档与仓库实际,向 ESLint 仓库提交一条核心规则前请逐项核对:
- 三文件齐全:
lib/rules/<rule-id>.js源码、tests/lib/rules/<rule-id>.js测试、docs/src/rules/<rule-id>.md文档,三者命名一致; - 源码格式合规:文件头
@fileoverview/@author、"use strict"、JSDoc 类型标注Rule.RuleModule、meta中type/docs.description/schema齐全;可修复规则声明fixable,可修复的messageId收拢在meta.messages中; - 测试覆盖充分:使用
RuleTester提供valid与invalid两组用例,覆盖常见正确/错误代码、语法版本边界(如 ES6 class、ES2022 静态块)以及「不可自动修复」场景(output: null); - 性能验证:使用
npm run perf在main与规则分支间对比总运行时间,确认未突破性能预算; - 命名与状态:遵循连字符命名与
no-前缀约定;评估规则是否已达功能完备而可标记frozen: true,并遵守冻结规则不新增选项的约定。
按此清单推进,你的规则就能与仓库中现有 300 余条核心规则保持一致的工程质量,顺利进入审查与合并流程。
【免费下载链接】eslintFind and fix problems in your JavaScript code.项目地址: https://gitcode.com/GitHub_Trending/es/eslint
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考