shim 设计
本页深入 shim 的设计:模块边界、三种再导出机制的选择规则、生成器与手写文件的协作、版本固定策略、以及插件构建时 shim 如何被 wire 进去。配合 .codex/skills/typescript-go-sync/SKILL.md 阅读。
设计目标与约束
shim 的设计被三件事框定:
- Go 模块规则:禁止跨模块 import
internal/。这是 shim 存在的根本原因。 - 插件作者的稳定表面:插件只能 import
github.com/microsoft/typescript-go/shim/<name>,所以 shim 必须暴露插件需要的全部 AST/transform/printer/emit 表面。 - 可机械验证的完整性:缺失再导出反复出现过(如
SignatureKindConstruct#230),所以需要一个 CI 闸门把"完整"变成可强制的不变量。
每个 shim 子模块的文件分工
以 shim/ast/ 为例(最大的 shim):
生成器靠 shim.go 首行的 // gen_shims:hand-maintained 标记跳过手写文件。这让"生成"与"手写"在同一目录共存而不互相覆盖。
三种机制的选择规则
机制一:类型别名
surface.go 里 type Node = innerast.Node。这是最干净的形式,生成器自动产出。ast/surface.go 开头一大批 NodeFactoryCoercible、MutableNode、NodeBase……都是这类,注释说明"让插件用与 tsgo 内部相同的节点工厂与 printer 面向的 AST 形状"。
机制二:手写包装函数
当导出函数的签名命名了未导出类型,生成器会跳过它(无法用别名表达),需要在 shim.go 手写包装:
extra-shim.json 的 IgnoreFunctions 告诉生成器"这个导出函数有手写变体,别再生成"。
机制三://go:linkname
未导出符号无法别名也无法直接调用,用 //go:linkname 链接:
ast/shim.go 里的 GetSourceFileOfNode / GetNodeAtPosition 就是这种形式。extra-shim.json 的 ExtraFunctions 列出要 linkname 的未导出函数。
一个真实的添加例子
.codex/skills/typescript-go-sync/SKILL.md 记录的工作例子:ast.SetParentInChildren 在 shim/ast/parent.go 作为薄包装暴露,让一个 transform 能在 emit 前重新设置合成节点的父链(emit resolver 会解引用 Parent,否则 nil panic)。这正是 Emit 与重写 里 SetParentInChildrenUnset + restoreOriginalDeclarationSymbols 依赖的能力。
添加流程:
- 在
go env GOMODCACHE/github.com/microsoft/typescript-go@<version>/internal/<pkg>/里找符号,确认名字、签名、是否导出。 - 加到对应
shim/<pkg>/:导出函数干净签名 → 重跑go run ./tools/gen_shims(或生成器跳过则手写包装);导出类型 → 生成器加别名;未导出 →//go:linkname。 - 构建 shim 模块与
packages/ttsc验证链接。
版本固定策略
tsgo 版本逐 shim 模块固定:每个 shim/*/go.mod 里 require github.com/microsoft/typescript-go v0.0.0-<时间戳>-<hash>,全部保持一致,并作为间接 require 出现在 packages/ttsc/go.mod。同级 go.work 把 shim 子模块 wire 起来;将来 tagged 上游版本可替换这些本地 wire。
bump 流程(细节见 审计与同步):改所有 shim/*/go.mod 与 packages/ttsc/go.mod 的 require 行(保持一致)、刷新各 go.sum、重跑生成器、复查手写 shim.go 与 extra-shim.json(上游 rename/签名变更/导出翻转会破坏包装或 linkname)。
插件构建时的 wire-in
插件在用户项目构建,怎么解析到 shim/*?答案在 buildSourcePlugin.ts:
validateSourceReplacements(buildSourcePlugin.ts:579)拒绝插件自己 replace ttsc 管理的模块——"ttsc supplies its own compiler and shim modules"——防止插件偷换 shim。
不变量
surface.go/enums_gen.go是生成物,手改会被覆盖。shim.go首行必须是// gen_shims:hand-maintained,否则生成器会覆盖它。- 所有
shim/*/go.mod的 tsgo 版本必须一致。 - shim 只做再导出与极薄包装,不加业务逻辑。
接下来
- 审计与同步
- driver: Emit 与重写(shim API 的主要消费者)