插件描述符协议
插件描述符是插件作者写的 JavaScript 模块,告诉 ttsc:哪段 Go 源实现原生行为、它在 TypeScript-Go 管线的哪个阶段参与、声明了哪些宿主能力。本页讲完整的 ITtscPlugin 契约及其相关结构。源码在 packages/ttsc/src/structures/。
两个角色:项目配置 vs 插件描述符
要分清两层:
-
ITtscProjectPluginConfig(structures/ITtscProjectPluginConfig.ts):用户写在tsconfig.json的compilerOptions.plugins[]里、或包package.json#ttsc.plugin里的项目侧配置。ttsc 只解释两个字段:transform:用来加载插件描述符/工厂的 JS 模块说明符。enabled:false时保留条目但不加载(方便一份 tsconfig 跨环境开关插件)。- 其余字段原样保留为插件 config,加载后序列化进原生 manifest,让 Go 代码读到一模一样的选项。ttsc 不校验这些字段。
-
ITtscPlugin(structures/ITtscPlugin.ts):transform指向的 JS 模块返回的描述符。一个 JS 插件入口只是加载点;模块必须直接、或作为default、或作为plugin、或从工厂函数返回一个ITtscPlugin。
ITtscPlugin 字段
source:Go 源,不是二进制
source 必须是 Go 包目录或 go.mod 文件,ttsc 用包内 Go 编译器把它构建进缓存。它不接受预编译二进制路径。规则(ITtscPlugin.ts:31):
- 目录源向上最多搜 3 层父目录找
go.mod;直接的go.mod源把模块根当作.。 package main源 → 可执行旁车;非maintransform 源 → 链接进选定原生宿主,须经driver.RegisterPlugin注册。- 相对路径相对消费者项目根解析。npm 包描述符应返回基于自己描述符目录的绝对路径——从工厂的
context.dirname(__dirname的加载模式无关替代品,因为 ttsc 经 ttsx 或作为 ESM 加载描述符时__dirname是 undefined)派生。
stage:check vs transform
TtscPluginStage = "transform" | "check"(structures/TtscPluginStage.ts)。缺省即 transform。
transform:参与 TypeScript-Go transform 路径,不接收 emit 出的 JS 或文件文本。check:emit 前运行、报诊断;适合应在生成 JS/d.ts 前失败的 lint/校验插件。check 插件还可实现fix与format命令,ttsc fix/ttsc format在禁用 emit 下调它们。
被移除的 "output" 阶段会被显式拒绝并提示升级(loadProjectPlugins.ts::resolvePluginStage)。
composes:水平组合
composes 列出本原生源能在同一编译 pass 里执行的其他 transform 插件名或 transform 说明符。包自动发现可能找到多个必须共享一个 emit 宿主的 transform 包;当一个描述符在这里列出另一个条目时,ttsc 保留原 plugin config 但把被组合条目的源指向本描述符的原生源,于是两者解析到同一二进制。
组合规则(loadProjectPlugins.ts::composePluginSources)严格:
- 只一跳,不传递。
A.composes=[B]把 B 送进 A 的二进制;即使B.composes=[C],C 仍用 B 原始源,不级联到 A。 - 检测环。
A.composes=[B] && B.composes=[A]报 "composes cycle detected"。 - 被组合插件不能有自己的
contributors:它的二进制是聚合者的,自己的 contributors 会链接进不同宿主,报错。 - 一个插件被多个聚合者组合 → 报错(每条目只能重定向到一个聚合宿主)。
- 被组合插件继承聚合者的
capabilities(聚合者没设则保留自己的作回退)。
contributors:垂直组合
contributors(structures/ITtscPluginContributor.ts)是要静态链接进本插件二进制的额外 Go 源包("plugin-within-plugin")。每个贡献者的 Go 源被拷进 scratch 构建树作为本插件模块的子包,由合成的空白 import 触达,其 init() 在宿主 main 前运行,注册宿主期望的状态(如经 github.com/samchon/ttsc/packages/lint/rule 注册 lint 规则)。
与 composes 的区别(ITtscPlugin.ts:104):
约束:贡献者不能带 go.mod(必须活在宿主模块内,让 overlay 规则与宿主 go.sum 覆盖传递依赖,也是供应链特性——贡献者不能拉任意 Go 模块);source 必须绝对路径;name 必须匹配 /^[a-z][a-z0-9_]*$/ 且单次构建内唯一。校验在 loadProjectPlugins.ts::validatePluginContributors。
capabilities:宿主能力声明
ITtscPluginCapabilities(structures/ITtscPluginCapabilities.ts)每个字段默认 false,让插件作者声明旁车理解哪些跨切面行为,而不是 ttsc 硬查插件名:
threadingArgs 的存在有具体历史(ITtscPluginCapabilities.ts:40 与 runBuild.ts:597 注释):issue #113 曾把这两个 flag 转发给每个原生旁车,但 #113 前构建的第三方宿主用裸 flag.FlagSet,遇未知 flag 退出 2,于是 commit ad3443a 全面回退。能力标志让第一方宿主(lint)opt-in,而第三方宿主保持保守默认。
reportsTypeScriptDiagnostics
check 旁车是否在 check 子命令里自报项目的常规 TypeScript 诊断。普通 check 插件不设它,ttsc 会另跑一次 tsgo --noEmit 守卫以免吞掉类型错误;只有当旁车自己建 Program 并 emit 同样的诊断时才设它(ITtscPlugin.ts:92)。runBuild.ts::checkPluginsReportTypeScriptDiagnostics 据此决定跳过守卫。
描述符工厂上下文
工厂收到 ITtscPluginFactoryContext(structures/ITtscPluginFactoryContext.ts),关键字段 dirname/filename 是每条目独立派生的(从解析出的 request),给工厂一个加载模式无关的 __dirname/__filename 替身——因为描述符经 ttsx 或作为 ESM 加载时 CommonJS 全局是 undefined。这是个真实坑:从 __dirname 派生 source 会 mis-resolve,resolveGoPackageDir 专门把这种失败命名出来(loadProjectPlugins.ts:857,引 #248)。
不变量
- JS transform 函数(
transformSource/transformOutput)被rejectJsTransformFunctions拒绝——不是公共契约。 - 只有 transform 阶段插件能是 linked kind。
- 贡献者名唯一、匹配命名正则、源为存在的目录且含至少一个非测试
.go文件(hasBuildableGoSource)。