lint 引擎子系统

packages/lint(~102k Go LOC,其中 linthost 约 60k)是 @ttsc/lint:一个自带的原生 lint + format 引擎,720+ 规则横跨 21 个家族,作为 ttsc 的 check 阶段插件运行。它的核心卖点是把 lint 违规当作 error TSxxxxx 与类型错误一起从单次编译 pass 里出来,替代独立的 eslint + prettier 步骤。

本子系统页面:

  • 引擎:AST 遍历、规则分派、并行/串行、per-file Context 复用、panic 隔离。
  • 规则与注册表:Rule 接口、注册、家族组织、类型感知规则。
  • 格式化器:format 规则、宽度感知重排、与 lint 的关系。
  • 贡献者插件:第三方规则如何链接进同一二进制。

它在 ttsc 里的位置

@ttsc/lint 不是普通 transform 插件,而是 check 阶段插件,声明了 capabilities.threadingArgs(接受 --singleThreaded/--checkers)。它先于 transform 跑;违规当作 emit 前失败条件。ttsc fix 调它的 fix 子命令(应用 lint + format autofix),ttsc formatformat 子命令(只应用 format 类规则)。

自举:为什么不 import driver

linthost/host.go:1 的注释解释了一个重要决策:lint 宿主从源插件 import github.com/samchon/ttsc/packages/ttsc/driver,因为那会强制每个 @ttsc/lint 消费者把 in-tree 的 samchon/ttsc/packages/ttsc 模块放进 go.work——一个公共 proxy 满足不了、且与 ttsc 运行时生成的 go.work overlay 冲突的依赖。于是它内联了一个最小的 Program/Checker 自举(loadProgramhost.go:66),与每个源插件参考夹具用的同一模式。

这是 shim 设计的直接后果:源插件只能触碰 shim/*,不能触碰 ttsc 的内部 Go 模块。lint 宿主自己用 shim 建 Program,跟 driver 平行但独立。

规模与组织

linthost 目录是扁平的,但文件名编码了家族:

前缀家族数量级
rules_*.go(无家族前缀)ESLint core大量
rules_ts_*.goTypeScript / @typescript-eslint~70 个文件
rules_unicorn_*.gounicorn~200 个文件
rules_react*.go rules_jsx_a11y.goReact/JSX 家族
rules_format_*.go格式化规则~20 个文件
print_*.goprinter(格式化输出引擎)
engine.go dispatch.go config.go引擎核心

README(packages/lint/README.md)声称 720+ 规则跨 21 家族,从 ESLint core、typescript-eslint、react、unicorn、jsx-a11y、jest、vitest、playwright、cypress、storybook、tanstack-query、promise、regexp、security、jsdoc、functional、boundaries 等上游移植语义。

核心数据流

详见 引擎

关键设计取舍

取舍选择原因
规则分派结构shimast.Kind 索引的切片(非 map)KindCount(~350) 小且有界,去掉 per-node map hash
并行度默认按 CPU 并行 per-file,类型感知规则强制串行单共享 checker 非并发安全
Context 分配per-file 复用一个,非 per-(node,rule)大项目下避免百万级短命堆分配
规则崩溃recover() 隔离,转成 error finding第三方贡献规则可能崩,要 bound 爆炸半径
声明文件只绑定 opt-in 规则值级规则在 .d.ts 永不触发,分派纯属浪费

接下来