Skip to Content

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:

  1. A JS descriptor in node_modules that tells ttsc what the plugin does β€” its name, stage, and the Go source directory.
  2. A Go sidecar that holds the actual transform or check logic. ttsc builds it on the consumer machine the first time it is used, then caches the binary.
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/ ◄── Go sidecar source (built by ttsc) β”œβ”€β”€ go.mod └── main.go ◄── implements check/transform/build/fix/format ttsc launcher (Node) β”‚ β”‚ spawns β–Ό built Go binary (cached) β”‚ β”‚ stdout: JSON ({ diagnostics, typescript }) β”‚ stderr: human messages β–Ό diagnostics or rewritten emit

One npm package, one contract, one config (tsconfig.json’s compilerOptions.plugins).

Stages and host-driven subcommands

ttsc knows two stages. Set one on the descriptor with stage: "transform" or stage: "check". The host invokes a fixed set of subcommands per stage:

StageSubcommands the host spawnsWhat the plugin does
"transform"build, transformmutates 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.plugin package field): ttsc reads package.json#ttsc.plugin only from packages listed directly in the nearest consumer package.json at 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, optional composes/contributors. Lives in node_modules.
  • Sidecar β€” the Go binary ttsc builds from the plugin’s source directory. Receives subcommands over CLI; communicates over stdout (JSON for transform) and stderr (human messages).
  • Stage β€” the stage field on the descriptor. Either "transform" (mutates AST before emit) or "check" (diagnostics; optionally fix/format).
  • Subcommand β€” the first CLI argument the host passes to the sidecar (check, transform, build, fix, format). Determined by the stage and the user’s ttsc command.
  • 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.work workspace ttsc writes into the scratch build directory so the plugin’s go 1.x module sees ttsc and shim packages locally.
  • Contributor β€” a Go-source package statically linked into a host plugin’s binary at build time. Used by @ttsc/lint to compose third-party rule packages. Contributors ship as packages, not modules β€” see Plugin protocol β†’ Contributors.
  • Host β€” the ttsc JavaScript launcher process that resolves descriptors, builds source plugins, spawns sidecars, and consumes their output. 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>/plugin on 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 ttsc runs when a transform-stage plugin is active: parse β†’ run plugin AST mutations β†’ run tsgo’s printer β†’ emit JS / d.ts / source maps.
  • Factory context β€” the object ttsc passes 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 array ttsc passes 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.

Last updated on