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 source plugin that holds the actual transform or check logic. ttsc builds it on the consumer machine the first time it is used, then caches the result. Executable package main sources become sidecars; non-main transform packages are linked into a native host.

Go selects native ownership using the effective tags, target OS/architecture, cgo and environment. Excluded files do not turn a library into an executable. For a nested transform source, ttsc first pins the Go tool selected at its original module directory and observes a prospective source-only package: an owned copy keeps that module’s admitted file layout and relative inputs, while only its root go.mod/go.sum are replaced by the maintained generic host manifests. The normal generated workspace applies. This proposal does not activate an outer module graph or toolchain that a linked source would not use. Module-root sources, direct go.mod sources and check plugins use their own module for this observation.

An executable proposal must also select package main in its complete owning module. A linked proposal must select a non-main package inside every chosen actual transform host, with that host’s tool, dependency graph and generated workspace. Errors, missing production files and disagreement terminate loading; ttsc does not retry with another ownership choice. The cold build confirms ownership again in its materialized context before publishing a cached binary. This deterministic proposal rule does not promise a preference between hypothetical competing release-toolchain interpretations. A relative Go input available in the source module can still be unavailable in a linked host, where Go’s actual error is authoritative.

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 emit

One 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:

StageSubcommands the host spawnsWhat the plugin does
"transform"check, transform, buildmutates 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.
  • Plugin source: the Go source package ttsc builds from the descriptor’s source. Executable package main sources build as sidecars; non-main transform packages link into a native host through driver.RegisterPlugin.
  • Sidecar: an executable Go binary built from a package main plugin source. 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 an executable native host or sidecar (check, transform, build, fix, format; and lsp-* verbs when ttscserver talks to an LSP-capable plugin). Linked packages do not receive argv directly; they run inside the selected host through driver.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.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 executable sidecars or links non-main transform 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. By default it is workspace-local at <workspaceRoot>/node_modules/.cache/ttsc/plugins/<key>/plugin (the find-cache-dir convention), so rm -rf node_modules reclaims it and unrelated workspaces do not share it. $TTSC_CACHE_DIR/plugins/... (or --cache-dir) overrides that root when a persistent or shared cache is wanted; ttsc keeps no machine-global cache. 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