Concepts
The shared vocabulary you need before authoring. Two pages follow this one: Plugin protocol (the wire contract) and AST & Checker (the Go-side surface). This page is the first read.
What is a ttsc plugin?
A ttsc plugin is one npm package with two halves:
- A JS descriptor in
node_modulesthat tellsttscwhat the plugin does, its name, stage, and the Go source directory. - A Go source plugin that holds the actual transform or check logic.
ttscbuilds it on the consumer machine the first time it is used, then caches the result. Executablepackage mainsources become sidecars; non-maintransform packages are linked into a native host.
my-ttsc-plugin/ installed in consumer's node_modules
├── plugin.cjs ◄── JS descriptor: factory or object
├── package.json ◄── carries name, ttsc.plugin marker, files[]
├── plugin/ ◄── executable package main sidecar source
│ ├── go.mod
│ └── main.go ◄── implements check/transform/build/fix/format
└── driver/ ◄── linked non-main transform source
└── plugin.go ◄── registers with driver.RegisterPlugin
ttsc launcher (Node)
│
│ builds and caches source
▼
executable sidecar ── spawns with subcommands
linked source ── runs inside native host
│
▼
diagnostics or rewritten emitOne npm package, one contract, one enablement entry in compilerOptions.plugins[].
Stages and host-driven subcommands
ttsc knows two stages. Set one on the descriptor with stage: "transform" or stage: "check". Executable sidecars receive a fixed set of subcommands per stage; linked transform sources do not receive argv and instead run inside the selected native host:
| Stage | Subcommands the host spawns | What the plugin does |
|---|---|---|
"transform" | check, transform, build | mutates TypeScript AST before emit |
"check" | check, fix (opt-in), format (opt-in) | reports diagnostics; optionally applies edits in-place |
Non-zero exit means failure; the host prints stderr verbatim and aborts. version is a smoke verb the host never invokes. For exit-code rules, the verbatim-passthrough rule for transform, and what is fictional in older docs (e.g. <plugin>: fix not supported), see Plugin protocol.
Auto-discovery vs explicit plugin entries
A consumer can enable a plugin two ways:
-
Explicit (recommended for fixtures and tests): list the plugin in
compilerOptions.plugins[]. Wins over auto-discovery for the same package.// tsconfig.json { "compilerOptions": { "plugins": [{ "transform": "my-ttsc-plugin" }], }, } -
Auto-discovery (
ttsc.pluginpackage field):ttscreadspackage.json#ttsc.pluginonly from packages listed directly in the nearest consumerpackage.jsonat or above the selected project.// your plugin's package.json { "name": "my-ttsc-plugin", "ttsc": { "plugin": { "transform": "my-ttsc-plugin" } }, }
The name field is load-bearing. Auto-discovery matches the consumer’s dependencies/devDependencies entry string against your plugin’s package.json name exactly. If the consumer writes "ttsc-plugin-debugger-strip" but your name is "my-plugin", ttsc loads nothing, emits unchanged, and exits 0, no error, no diagnostic.
Do I need the Checker?
| If your plugin… | Then use… |
|---|---|
| Only inspects syntax (banner stripping, comment walking, statement filtering, JSX-tag rewriting) | shimparser.ParseSourceFile, no Program, no Checker, no tsconfig |
| Inspects types, resolves symbols, follows imports, or checks signatures (validator generation, schema, type-aware lint) | driver.LoadProgram to get a *Program with a leased Checker |
When in doubt, start with driver.LoadProgram. The cost is one tsconfig parse and one checker-pool lease; the gain is you can introduce types later without rewriting. The implementation lives in AST & Checker.
Glossary
- Descriptor: the JS object (or factory) every plugin package exports. Carries
name,source,stage, optionalcomposes/contributors. Lives innode_modules. - Plugin source: the Go source package
ttscbuilds from the descriptor’ssource. Executablepackage mainsources build as sidecars; non-maintransform packages link into a native host throughdriver.RegisterPlugin. - Sidecar: an executable Go binary built from a
package mainplugin source. Receives subcommands over CLI; communicates overstdout(JSON fortransform) andstderr(human messages). - Stage: the
stagefield on the descriptor. Either"transform"(mutates AST before emit) or"check"(diagnostics; optionallyfix/format). - Subcommand: the first CLI argument the host passes to an executable native host or sidecar (
check,transform,build,fix,format; andlsp-*verbs whenttscservertalks to an LSP-capable plugin). Linked packages do not receive argv directly; they run inside the selected host throughdriver.RegisterPlugin. - Shim: the narrow Go API at
github.com/microsoft/typescript-go/shim/...that exposes tsgo’s AST, Checker, parser, scanner, printer, and FS. The plugin boundary into tsgo. - Overlay: the synthesized
go.workworkspacettscwrites into the scratch build directory so the plugin’sgo 1.xmodule seesttscand shim packages locally. - Contributor: a Go-source package statically linked into a host plugin’s binary at build time. Used by
@ttsc/lintto compose third-party rule packages. Contributors ship as packages, not modules. See Plugin protocol → Contributors. - Host: the
ttscJavaScript launcher process that resolves descriptors, builds source plugins, spawns executable sidecars or links non-maintransform sources into a native host, and consumes the result. Not the same as the “compiler host” of tsgo. - Cache: the on-disk store of built plugin binaries at the user-level ttsc cache (
~/.cache/ttsc/plugins/<key>/pluginon Linux, platform equivalents elsewhere) or$TTSC_CACHE_DIR/plugins/...when overridden. Cache key folds in ttsc version, tsgo version, platform, plugin source hash, overlay hash, contributor hashes, Go compiler content, and the Go build environment. See Architecture. - Transform path: the pipeline
ttscruns when atransform-stage plugin is active: parse → run plugin AST mutations → run tsgo’s printer → emit JS / d.ts / source maps. - Factory context: the object
ttscpasses to factory-form descriptors:{ binary, cwd, plugin, projectRoot, tsconfig }. The TypeScript surface for advanced descriptors that need to inspect the active host or the consumer’s tsconfig entry. --plugins-json: the JSON arrayttscpasses to every plugin subcommand (per-plugin{name, stage, config}). Wire shape and ordering: Plugin protocol →--plugins-json.
Next
→ Plugin protocol, the wire contract in detail.
→ Authoring → Getting started. Write your first plugin.