anti-slop开发者教程:如何从零编写第一条基于ESTree的Oxlint自定义Lint规则
【免费下载链接】anti-slopOpinionated Oxlint rules for rejecting low-evidence TypeScript and JavaScript patterns项目地址: https://gitcode.com/gh_mirrors/ant/anti-slop
anti-slop 是一组有主见的 Oxlint 规则集,专门拒绝低证据、低信号的 TypeScript 与 JavaScript 模式。本教程带你从零编写第一条基于 ESTree 的 Oxlint 自定义 Lint 规则:理解 AST 节点、用defineRule定义检查逻辑、写测试验证,最终注册进插件。全文以仓库内真实规则为样例,代码量精简,新手也能跟上。🧭
anti-slop 是什么:为什么需要自定义 Lint 规则
大多数团队遇到as unknown as User、type X = unknown这类"看起来能编译、实则掩盖证据"的写法时,通用 Lint 工具并不会拦截。anti-slop 的做法很直接:把团队标准写成规则,交给 Linter 自动执法。
它内置了 15 条规则(如 no-unknown-type-aliases.ts、no-reflect-get.ts),全部基于 Oxlint 的 ESTree API 实现,不引入额外解析器(见 AGENTS.md 的约定)。
这个项目特意设计为"可搬运"(vendored):把 src/ 整个复制到你的仓库、读懂它、改成团队自己的标准——这也是为什么理解"如何写一条规则"如此重要。
一条自定义 Lint 规则由什么构成
先建立心智模型。Oxlint 的 JS 插件 API 兼容 ESLint 的规则写法,一条规则就两样东西:
meta:规则的"身份说明"——类型(problem)、描述、以及报错文案模板(messages,支持{{占位符}})。createOnce(context):规则的"大脑"——返回一组AST 访问器。ESTree 是 JS/TS 代码的抽象语法树(AST)标准表示,每个语法结构都有节点类型,比如Program(整个文件)、CallExpression(函数调用)、Identifier(标识符)。访问器按节点类型注册,每当解析器走到这类节点就被触发。
用仓库里最简单的规则 no-shape-in-symbol-names.ts 感受一下骨架:
export const noForbiddenTermInSymbolNamesRule = defineRule({ meta: { type: "problem", docs: { description: "Disallow the substring \"shape\" in symbol names." }, messages: { forbiddenSymbolName: 'Rename symbol "{{name}}" for its domain role.', }, }, createOnce(context) { return { Identifier(node) { if (!node.name.toLowerCase().includes("shape")) return; context.report({ node, messageId: "forbiddenSymbolName", data: { name: node.name } }); }, }; }, });逻辑拆开看只有三步:① 监听Identifier节点;② 判断名字是否含违禁词;③ 命中就context.report上报,并填充messages里的占位符。这就是全部核心机制——换一种节点类型、换一个判断条件,就是另一条规则。
三步写出你的第一条 Oxlint 自定义 Lint 规则
第 1 步:选场景,找对 AST 节点
写规则前先问自己:这条模式在语法树里长什么样?
以"禁止把类型别名直接定义为unknown"(type Alias = unknown;)为例。它出现在类型别名声明节点TSTypeAliasDeclaration上,而顶层文件结构由Program节点承载。仓库实现见 no-unknown-type-aliases.ts:它选择监听Program,在文件级一次遍历完成收集与检查,这比在每次类型引用处反复查找更省事。
第 2 步:实现检查逻辑并上报
no-unknown-type-aliases.ts 的Program访问器做了两件事:
- 收集:遍历
node.body(顶层语句),把每个TSTypeAliasDeclaration按别名名存进Map; - 检查:对每个别名递归解引用——遇到
TSUnknownKeyword就命中,递归还带visited集合防止type A = B; type B = A;这类循环引用死循环。
注意它如何区分导出写法:export type X = unknown在 AST 里是ExportNamedDeclaration包着TSTypeAliasDeclaration,所以代码里先剥一层声明再判断(第 52-56 行)。这种"剥包装"的防御性写法是 ESTree 规则里的常见模式。
第 3 步:用 RuleTester 写测试
每条语义规则都要配一份聚焦测试。仓库的测试风格非常轻量,看 no-unknown-type-aliases.test.ts:
import { RuleTester } from "oxlint/plugins-dev"; import { noUnknownTypeAliasesRule } from "./no-unknown-type-aliases.ts"; const tester = new RuleTester({ languageOptions: { parserOptions: { lang: "ts" } } }); tester.run("anti-slop/no-unknown-type-aliases", noUnknownTypeAliasesRule, { valid: ["type User = { readonly id: string };"], invalid: [{ code: "type Alias = unknown;", errors: [{ messageId: "unknownAlias" }] }], });valid是不该报警的合法代码,invalid是必须报警的违规代码(按messageId精确断言)。新规则按src/rules/<规则名>.test.ts命名,再把它加进 package.json 的test脚本即可纳入检查流程。
注册规则:让 Oxlint 加载你的插件
写好的规则本身还不生效,需要两处"登记":
插件入口:src/index.ts 用
@oxlint/plugins的eslintCompatPlugin把全部规则收进一个rules映射表,键名就是规则短名。你的新规则在这里 import 并挂上即可。项目配置:目标仓库在
oxlint.config.ts里声明插件入口并开启规则(完整示例见 README.md):
export default defineConfig({ jsPlugins: [ { name: "anti-slop", specifier: "./tools/oxlint/anti-slop/index.ts" }, ], rules: { "anti-slop/no-unknown-type-aliases": "error" }, });配置细节、Vite+ 环境下的等价写法,都可以参考 skills/install-anti-slop/SKILL.md。
项目约定与避坑清单
src/是唯一权威源:改完生产代码后运行pnpm sync:skill-assets,把 src/ 同步到技能打包目录;scripts/sync-skill-assets.mjs 会排除测试文件,--check模式则用于 CI 校验两边一致。- 公共逻辑下沉到 shared:当规则开始"解类型"时(别名递归、泛型展开、内置工具类型识别),别在规则里复制粘贴——参考 src/shared/dictionary-types.ts 的
createTypeEnvironment与classifyWideningTarget,以及 src/shared/reflect-method.ts 中"识别全局Reflect方法调用"的作用域解析技巧。 - 规则保持通用:不加入应用专属的名字、路径或例外(AGENTS.md 明确要求),保证可跨仓库复用。
- 提交前跑全量检查:
pnpm check会串联 lint、test、typecheck 和技能资产一致性校验(package.json)。 - 别为了过 Lint 而加断言:技能文档特别警告,不要通过
as unknown as、降级规则等级来"洗白"类型(SKILL.md 第 65 行)。
总结:你的 Oxlint 自定义 Lint 规则路线图 🎯
- 定位节点:想清楚目标模式对应哪个 ESTree 节点类型(
Program/CallExpression/Identifier…); defineRule两步走:meta写好描述与报错文案,createOnce里注册访问器并context.report;- RuleTester 兜底:
valid/invalid各覆盖几个典型场景,按messageId断言; - 注册生效:挂进 src/index.ts 的映射表,在配置文件中声明插件并设为
error; - 同步与检查:
pnpm sync:skill-assets+pnpm check,保证src/与打包资产一致。
从一条十几行的no-shape-in-symbol-names起步,到递归解析类型别名的no-unknown-type-aliases进阶,这条路径就是 anti-slop 全部 15 条规则的共同生长方式。把规则搬进你的仓库、按团队口味改造,让"低证据代码"无处遁形。
【免费下载链接】anti-slopOpinionated Oxlint rules for rejecting low-evidence TypeScript and JavaScript patterns项目地址: https://gitcode.com/gh_mirrors/ant/anti-slop
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考