插件描述符协议

插件描述符是插件作者写的 JavaScript 模块,告诉 ttsc:哪段 Go 源实现原生行为、它在 TypeScript-Go 管线的哪个阶段参与、声明了哪些宿主能力。本页讲完整的 ITtscPlugin 契约及其相关结构。源码在 packages/ttsc/src/structures/

两个角色:项目配置 vs 插件描述符

要分清两层:

  1. ITtscProjectPluginConfigstructures/ITtscProjectPluginConfig.ts):用户写在 tsconfig.jsoncompilerOptions.plugins[] 里、或包 package.json#ttsc.plugin 里的项目侧配置。ttsc 只解释两个字段:

    • transform:用来加载插件描述符/工厂的 JS 模块说明符。
    • enabledfalse 时保留条目但不加载(方便一份 tsconfig 跨环境开关插件)。
    • 其余字段原样保留为插件 config,加载后序列化进原生 manifest,让 Go 代码读到一模一样的选项。ttsc 校验这些字段。
  2. ITtscPluginstructures/ITtscPlugin.ts):transform 指向的 JS 模块返回描述符。一个 JS 插件入口只是加载点;模块必须直接、或作为 default、或作为 plugin、或从工厂函数返回一个 ITtscPlugin

ITtscPlugin 字段

interface ITtscPlugin {
  name?: string;                            // 仅用于诊断/构建消息, 不做路由
  source: string;                           // Go 包目录或 go.mod, ttsc 惰性构建
  composes?: string[];                      // 水平组合: 多条目派发到一个二进制
  stage?: TtscPluginStage;                  // "transform"(默认) | "check"
  capabilities?: ITtscPluginCapabilities;   // 宿主能力声明
  reportsTypeScriptDiagnostics?: boolean;   // check 旁车是否自报 TS 诊断
  contributors?: ITtscPluginContributor[];  // 垂直组合: 静态链接额外 Go 源
}

source:Go 源,不是二进制

source 必须是 Go 包目录或 go.mod 文件,ttsc 用包内 Go 编译器把它构建进缓存。它接受预编译二进制路径。规则(ITtscPlugin.ts:31):

  • 目录源向上最多搜 3 层父目录找 go.mod;直接的 go.mod 源把模块根当作 .
  • package main 源 → 可执行旁车;非 main transform 源 → 链接进选定原生宿主,须经 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 插件还可实现 fixformat 命令,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:垂直组合

contributorsstructures/ITtscPluginContributor.ts)是要静态链接进本插件二进制的额外 Go 源包("plugin-within-plugin")。每个贡献者的 Go 源被拷进 scratch 构建树作为本插件模块的子包,由合成的空白 import 触达,其 init() 在宿主 main 前运行,注册宿主期望的状态(如经 github.com/samchon/ttsc/packages/lint/rule 注册 lint 规则)。

composes 的区别(ITtscPlugin.ts:104):

composes(水平)contributors(垂直)
关系多条目派发到一个二进制一个二进制链接额外源
顶层公民是,各有生命周期槽否,不作顶层插件出现
发现方式tsconfig / 包自动发现宿主插件自己的配置文件(如 lint.config.ts

约束:贡献者不能带 go.mod(必须活在宿主模块内,让 overlay 规则与宿主 go.sum 覆盖传递依赖,也是供应链特性——贡献者不能拉任意 Go 模块);source 必须绝对路径;name 必须匹配 /^[a-z][a-z0-9_]*$/ 且单次构建内唯一。校验在 loadProjectPlugins.ts::validatePluginContributors

capabilities:宿主能力声明

ITtscPluginCapabilitiesstructures/ITtscPluginCapabilities.ts)每个字段默认 false,让插件作者声明旁车理解哪些跨切面行为,而不是 ttsc 硬查插件名:

能力含义
diagnosticsTiming旁车接受 --diagnostics/--extendedDiagnostics 并可打印计时
lsp旁车实现 ttsc 的 LSP 插件协议,可向 ttscserver 贡献诊断/code action/命令
threadingArgs旁车接受 --singleThreaded/--checkers@ttsc/lint 设了它)

threadingArgs 的存在有具体历史(ITtscPluginCapabilities.ts:40runBuild.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 据此决定跳过守卫。

描述符工厂上下文

工厂收到 ITtscPluginFactoryContextstructures/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)。

接下来