lint 贡献者插件
@ttsc/lint 允许其他 npm 包提供 lint 规则,这些规则编译进同一个 @ttsc/lint 二进制、经同一条诊断流上报。本页讲贡献者机制的两端:JS 侧的 contributors 配置如何把规则源链接进来、Go 侧公共 rule 包如何让规则注册进同一分派表。源码 packages/lint/rule/、packages/lint/linthost/contrib_adapter.go、packages/lint/plugin/main.go。
整体机制
README(packages/lint/README.md)的用法:
ttsc 把每个声明的贡献者 Go 源拷进 @ttsc/lint 模块的子包,结果二进制在 main 前就注册了内建 + 贡献者规则。
两层注册表
lint 有两个注册表,对应内建规则与贡献者规则:
内建规则活在 package main、直接经内部 Register 分派。贡献者活在兄弟包、从 init() 调公共 rule.Register。contrib_adapter.go 把公共 rule.Rule 桥接到引擎内部 Rule 接口,让引擎对两者看到同一表面。
init 顺序问题(一个微妙点)
contrib_adapter.go:8 注释记录了一个 Go init 顺序坑:
每个贡献者包的
init在package main的 init 之前跑(Go spec)。但内建规则也从package main的 init 函数注册,同包内文件相对顺序按字母序——所以不能盲目从文件级init()跑贡献者接线、指望内建注册已完成。
解法:registerContributors(contrib_adapter.go:30)显式从 main.run 调,在所有内建 init 都安顿后,这样碰撞检查才有意义。
碰撞策略:贡献者与已有规则(内建或先 init 的另一贡献者)同名时被丢弃并打 stderr 警告。宿主偏好确定、可调试的结果,而非启动时 panic。
公共 rule API
packages/lint/rule/rule.go 是给第三方规则作者的公共 Go API。它提供:
rule.Rule接口(公共版的 Rule 契约)。rule.Register(贡献者从 init 调)。rule.Contextadapter(公共版的 Context,含ReportFix/ReportRangeFix)。rule.DeclarationFileRule标记接口(VisitsDeclarationFiles() bool)。
rule/astutil 包再导出内建规则用的字节范围 helper:NodeText、KeywordStart、FindKeyword、TokenRange,让贡献者规则发 autofix 的方式与内建一致(README 的 "contributor autofix path" 节)。
贡献者的 autofix 路径
贡献者规则发 autofix 的方式与内建相同:ctx.ReportFix(node, message, edits...) 或 ctx.ReportRangeFix(pos, end, message, edits...)。rule/astutil 提供构造 byte-range edit 的 helper。这让贡献者规则的修复经同一 Finding.Fix 机制流回引擎、被 ttsc fix 应用。
声明文件优化的 opt-out
贡献者规则默认在声明文件(.d.ts)上运行。引擎跳过自己的值级规则(可执行语法不能出现在声明文件),但它推不出第三方规则的形状,所以贡献者保持保守默认。只检查可执行代码的规则可实现可选的 rule.DeclarationFileRule 标记(VisitsDeclarationFiles() bool { return false })拿到同样的跳过,在声明密集项目上省下分派(README 末尾)。
供应链约束
贡献者机制建在 go build 缓存 的 contributors 之上,继承其供应链约束:
- 贡献者 ship Go 源作为包(无
go.mod);宿主插件的模块提供每个传递 Go 依赖。这也是供应链特性——贡献者不能在构建时拉任意 Go 模块。 - 贡献者源路径必须绝对(宿主的 JS 工厂通常经
require.resolve解析)。 - 贡献者名作为子包 import 后缀、单次构建内唯一。
@ttsc/lint 的 JS 工厂(packages/lint/src/index.ts::createTtscPlugin)从 lint.config.ts 的 plugins 发现贡献者包,把它们的 Go 源解析成 ITtscPlugin.contributors。
二进制的多入口
packages/lint/plugin/main.go 是 @ttsc/lint 原生后端的薄包装,宿主用以下子命令 spawn 它(规范列表在 linthost/dispatch.go):
行为住在兄弟 linthost 库包;二进制是薄包装,让进程外消费者(这个原生 CLI)与进程内消费者(ttsc.dev playground 的 wasm)共享同一实现。
不变量
- 贡献者注册显式从
main.run调,在内建 init 之后。 - 同名贡献者被丢弃 + 警告,不 panic。
- 贡献者无
go.mod,活在宿主模块内(供应链)。 - 贡献者默认跑声明文件,除非实现
DeclarationFileRuleopt-out。