资讯动态

如何为 fixmyjs 编写自定义修复规则:基于 AST 遍历架构的完整开发教程

发布时间:2026/10/23 1:44:07 来源:尧图企业网站定制
开发工具【免费下载链接】fixmyjsAutomatically fix silly lint errors.项目地址https://gitcode.com/gh_mirrors/fi/fixmyjs点击查看免费下载fixmyjs 是一款自动修复 JavaScript lint 错误的开源工具它能以非破坏性方式修复 eqeqeq、curly、命名风格等常见 JSHint 规则。本文深入剖析其 AST 遍历架构手把手教你编写自定义修复规则从规则文件结构、注册机制到测试验证完整走通扩展流程让你轻松扩展 fixmyjs 的修复能力。 内置规则一览了解 fixmyjs 能修什么fixmyjs 的选项命名基本沿用 JSHint所有规则实现在 lib/rules/ 目录下选项作用规则文件delete移除变量删除语句delete.jsemptyStatement移除空语句emptyStatement.jscamelcase标识符转 camelCasecamelCase.jscurly为语句补全花括号curly.jses3为parseInt补 radix 参数parseInt.jseqeqeq强制严格相等eqeqeq.jsdebugfalse 时生效移除debuggerdebugger.jssubfalse 时生效方括号转点语法dotNotation.js 每个规则文件都只有十几行代码这正是 fixmyjs 架构的精髓一条规则 一个按 AST 节点类型匹配的转换函数。 理解 AST 流水线修复代码的 5 个步骤核心入口是 lib/index.js 中的fix(code, config)整个修复流程只有 5 步校验选项——validateRules检查互斥配置如camelcase与snakecase不能同时开启见 lib/index.js解析代码—— 用esprima.parse把源码解析为 AST开启range、tokens、comment选项lib/index.js同时先剥离 shebang 行挂载注释——escodegen.attachComments把注释挂到 AST 节点上保证修复后注释不丢失遍历并修复—— 核心步骤递归遍历 AST对每个节点应用所有已启用规则生成代码——escodegen.generate把改过的 AST 打印回源码再把 shebang 还原其中最关键的是第 4 步的traverse函数lib/index.jsfunction traverse(o, f, p) { var k var self function (x) { return traverse(x, f, p) } for (k in o) { if (toString.call(o[k]) [object Object]) { o[k] traverse(o[k], f, o) } else if (Array.isArray(o[k])) { o[k] fu.concatMap(self, o[k]) } } return f(o, p) }它的逻辑非常直白递归进入 AST 的每个对象与数组每访问一个节点就调用f(node, parent)—— 把当前节点和父节点一起交给规则处理规则的返回值直接替换原节点而f是怎么从一堆规则中挑出该处理这个节点类型的那条规则的呢看 lib/index.jsvar modifiedTree traverse(astWithComments, function (node, parent) { return fu.foldl(function (node, f) { return f.hasOwnProperty(node.type) ? fnode.type : node }, rules, node) })这就是自定义规则的契约规则文件导出一个对象键是 AST 节点类型如BinaryExpression、CallExpression值是处理函数。节点类型匹配不上就原样返回——所以写规则时天然安全。 规则文件解剖三个最小示例阅读现有规则是最好的学习材料下面三个示例覆盖了三种典型模式。模式一删除节点—— debugger.jsmodule.exports { DebuggerStatement: rmDebuggerStmt } function rmDebuggerStmt() { return [] }返回空数组[]表示用零个节点替换当前节点即删除。debugger;就这样被干掉了。模式二替换节点带守卫条件—— eqeqeq.jsmodule.exports { BinaryExpression: toStrictEquality } function toStrictEquality(node) { if (node.operator ! node.operator ! !) { return node } return { type: BinaryExpression, operator: node.operator ? : !, left: node.left, right: node.right } }注意守卫条件不是所有BinaryExpression都要改只处理/!。不需要修改时原样返回node这是所有规则的统一写法。模式三结合父节点判断—— emptyStatement.jsfunction rmEmpty(node, parent) { if (parent) { switch (parent.type) { case ForStatement: case IfStatement: case WhileStatement: return node } } return [] }这里用了第二个参数parentfor (;;)和if (x);中的空语句是合法且必须保留的所以要结合父节点类型决定是否删除。返回值速查表返回值效果原node不修改新节点对象原地替换[]删除该节点节点数组一个节点拆成多个见下文 multiVar 技巧️ 实战编写你的第一条自定义规则 noVoidZero我们以一个真实需求为例把void 0重写为undefinedJSHint 的经典W073提示全程 4 步。第 1 步创建规则文件新建lib/rules/noVoidZero.js参考 parseInt.js 的守卫写法module.exports { UnaryExpression: rmVoidZero } function rmVoidZero(node) { if (node.operator ! void || node.argument.type ! Literal || node.argument.value ! 0) { return node } return { type: Identifier, name: undefined } }逻辑只拦截void 0这一种一元表达式其余一律原样返回。第 2 步注册规则打开 lib/rules/index.js规则分三个桶builtin始终启用如delete、isNaNaretrue对应选项为true时启用如curly: truearefalse对应选项为false时启用如debug: false才移除debugger我们的规则属于显式开启型在aretrue中加入一行novoidzero: require(./noVoidZero),注册机制在 lib/index.js 的getRules中它根据配置对象逐桶收集启用的规则拼成最终规则数组。选项的取值逻辑由genHas提供——配置中未显式声明的选项默认不生效这一点测试时容易踩坑。第 3 步编写测试fixmyjs 使用testla作为测试框架测试文件统一放在 test/ 目录、以-test.js结尾。test/dependencies.js 会给每个测试注入两个函数f—— 即fixmyjs.fixAST 路径我们测试的对象l—— legacy 模式入口参考 test/eqeqeq-test.js 的写法新建test/noVoidZero-test.jsmodule.exports function (f, assert) { var options { novoidzero: true, _silent: true } return { void 0 is replaced by undefined: function () { assert.equal(f(x void 0;, options), x undefined;) }, other unary expressions stay: function () { var code x -1; assert.equal(f(code, options), code) } } }⚠️务必同时写正向用例和反向用例不该改的不能改这是本项目测试文件的通行惯例。第 4 步运行测试并验证npm testnpm test会先跑jshint .做 lint再用testla执行全部测试见 package.json。全部通过后还可以用 CLI 做一次干跑验证准备一份配置{novoidzero: true}执行node lib/cli.js -r -c config.json demo.js-r即 dry-run只打印彩色 diff 而不写回文件实现见 lib/cli.js确认输出符合预期后再去掉-r真正落盘。 进阶技巧让规则更聪明1. 善用 parent 避免误伤。除 emptyStatement.js 外newSideEffects.js 只在父节点是ExpressionStatement时才剥离new避免破坏赋值场景camelCase.js 则跳过对象属性与成员表达式只转换普通标识符。2. 一个节点拆成多个节点。multiVar.js 在处理var a 1, b 2时返回一个数组把一条声明拆成多条var语句——处理器返回数组时traverse会将其展开替换原节点。3. 使用 fu 函数式小工具。项目依赖fu库fu.concat、fu.map、fu.filter、fu.intoArray等在各规则中随处可见如 isNaN.js保持风格一致即可。4. 注释与 shebang 已被核心层处理。注释通过attachCommentsescodegen的comment: true自动保留shebang 行在解析前剥离、生成后还原lib/index.js。规则作者无需操心也不要在规则里手动处理它们。 测试规范与选项校验新增测试文件命名test/xxx-test.js导出function (f, assert)见 test/curly-test.js互斥选项的校验有专门测试test/validate-rules-test.js 验证camelcase与snakecase同时开启会抛错。如果你的新规则与其他选项互斥记得在validateRules中补充判断代码风格遵循 CONTRIBUTING.md2 空格缩进、单引号、ASI无分号、行宽 80 字符 延伸Legacy 模式是另一套修复体系lib/legacy.js 实现了旧版修复逻辑它不解析 AST而是依赖 JSHint 报告的错误码如W033、W065用正则与字符串替换逐行修复错误码映射表见 lib/legacy.js。CLI 中通过-l启用。 官方在 README 中说明AST 方案比字符串替换更容易编写、维护和支持更多能力因此新规则建议一律写在 AST 路径即前文的规则文件格式legacy 仅用于兼容旧行为。✅ 交付前自检清单在提交自定义规则前逐项确认处理函数以node.type匹配的守卫开头不相关节点原样返回规则注册到了正确的桶builtin/aretrue/arefalse测试同时覆盖应修改与不应修改两类用例选项未显式开启时规则不生效genHas的 fallback 行为npm testlint testla全部通过用node lib/cli.js -r干跑 diff 人工复核过输出至此你已经完整掌握 fixmyjs 的 AST 遍历架构与规则开发全流程esprima解析 → 按节点类型分发 → 规则函数返回新节点 →escodegen生成。下一条规则可能只需 15 行代码。赞分享开发工具【免费下载链接】fixmyjsAutomatically fix silly lint errors.项目地址https://gitcode.com/gh_mirrors/fi/fixmyjs点击查看免费下载相关推荐编写 PMD Java 自定义规则Java Rules完整指南从 AST 遍历到生命周期管理编写 PMD Java 自定义规则Java Rules完整指南从 AST 遍历到生命周期管理 本文是一份面向 PMD 开发者的实战指南系统讲解如何用 J静态分析代码质量如何快速开发 Codemod 扩展编写自定义重构规则的完整指南如何快速开发 Codemod 扩展编写自定义重构规则的完整指南 Codemod 是 Facebook 开发的开源工具库专为大规模代码库重构设计可部分自动化如何为TYAlertController编写专属转场动画基于TYBaseAnimation的完整自定义教程如何为TYAlertController编写专属转场动画基于TYBaseAnimation的完整自定义教程 TYAlertController 是一款强大的上一篇Tilck 开源项目教程构建你自己的小型 Linux 兼容内核下一篇biliLive-tools云存储同步教程百度网盘、阿里云盘、Alist全攻略创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑