Reference Plugin Tour
A guided source-code reading of every plugin that ships with ttsc. By the end you should be able to open packages/<name>/ in your editor and follow what every line does.
Prerequisite: skim Concepts. The tour assumes the vocabulary β descriptor, sidecar, stage, subcommand, shim, auto-discovery β and does not redefine those terms inline.
The shipped plugins are ordered by difficulty:
@ttsc/bannerβ insert a JSDoc preamble at the top of every emitted file. Smallest possible transform; the right place to start.@ttsc/stripβ deleteconsole.log,debugger;, and matching call statements before emit. Your first real AST mutation: filter aNodeList, recurse into block-bodied statements, replace embedded statements with anEmptyStatement.@ttsc/pathsβ rewriteimport "@lib/x"intoimport "./modules/x.js"after consultingcompilerOptions.paths,rootDir,outDir, and the actual Program source files. Introduces tsconfig parsing, AST-leaf-text mutation (the synthesize-flag invariant), and the visitor that finds every module-specifier shape TypeScript supports.@ttsc/lintβ full ESLint-shaped diagnostics engine, plus its public Go API for third-party rule contributors. The advanced tier: rule registry, severity ladder, fix/format dispatch, ESLint runtime delegation, build-time contributor merging.
Pages 1β3 each take a single Go file and walk it line by line. Page 4 is a longer architectural tour because the lint engine is many files, but the reading pattern stays the same: open the file in the repo, then read this page as the commentary track.
Pre-reading: the two halves
Every plugin in this tour follows the two-halves model:
packages/<name>/
βββ package.json β "ttsc": { "plugin": { "transform": "@ttsc/<name>" } }
βββ src/
β βββ index.ts (or .cjs) β JS descriptor β exports { name, source, stage }
βββ go.mod β Go module declaration for the sidecar
βββ plugin/
βββ main.go β Native sidecar entrypoint β the binary ttsc spawns
βββ <name>.go β Package-local helper
βββ driver/
βββ <name>.go β Optional linked transform packageThe JS descriptor lives in node_modules on consumer machines and tells ttsc where the Go source is. The Go sidecar is built once and cached; ttsc then spawns it with subcommands like transform, build, or check.
If anything in that paragraph feels new, read Concepts β Plugin protocol first.
Where the real logic lives
The Go sidecars at packages/banner/plugin/main.go, packages/strip/plugin/main.go, and packages/paths/plugin/main.go are each small dispatchers. When you open strip/plugin/main.go to βlearn the strip plugin,β you are reading the process entrypoint; the statement-removal code lives in packages/strip/driver/strip.go.
Transform packages can also expose linked Go packages instead of executable command packages. A linked package calls driver.RegisterPlugin from init() and implements driver.SourcePreamblePlugin and/or driver.ProgramPlugin; ttsc copies it into the selected native host at build time. This is capability-based, not package-name based: any transform descriptor whose source resolves to a non-main Go package uses the same path.
Practical consequence β flagged again on each walkthrough as it becomes relevant:
- For learning, the shipped plugins are the best worked examples in the repo: production-quality code, small enough to read end-to-end. The walkthroughs point you at the right files (
driver/<name>.gofor linked transform logic;plugin/main.gofor the entrypoint contract). - For your own plugin, use
driver.LoadProgramwhen you own an executable host, or export a linked Go package withdriver.RegisterPluginwhen your transform is a library that should ride inside another host. The canonical executable fixture istests/projects/go-source-plugin-checker.
@ttsc/lint is different: its real engine lives in packages/lint/linthost/, and the sidecar at packages/lint/plugin/main.go is a linthost.Main one-liner. That setup is reusable β the lint deep dive shows how to copy the pattern when your plugin grows past one file.
How to read these pages
Each walkthrough has the same structure:
- What it does for the consumer. A two-line summary plus a copyable
compilerOptions.plugins[]entry, so you can sanity-check what the plugin promises before you read its code. - Directory layout. Where every file lives in the repo, and which file is βthe real oneβ to read.
- JS descriptor. Short β usually 10 lines β but it pins the descriptor shape every plugin must satisfy.
- Go module file. What goes in
go.modand why. Especially for Go beginners. - Native sidecar entrypoint. The
main.godispatcher, line by line. - The real logic. The bulk of the page: a guided read of the actual transform / check code in production.
- Beginner annotations. Sidebars that explain Go-specific constructs (
flag.NewFlagSet, pointer receivers,defer, etc.) the first time they appear. - What to copy, what to ignore. Closing summary of which patterns generalize to plugin authors and which are just package-local implementation details.
You do not need to run anything. Open the cited file in your editor, scroll along with the walkthrough, and the patterns stick faster than building a fresh plugin from scratch.
Once these are familiar, the canonical βbuild a third-party plugin from scratchβ exercises are:
- Authoring β Getting started β the Hello plugin (~80 lines of
main.go). - Authoring β End-to-end transform β
debugger;removal with real AST mutation.
The tour pages assume you have read Concepts but not necessarily Authoring. If you prefer βwrite your own first, then read someone elseβs,β do Authoring first.
Combined fixture
After all four plugins look familiar, this is the smallest tsconfig that uses every one of them in one project:
{
"compilerOptions": {
"paths": {
"@lib/*": ["./src/modules/*"]
},
"rootDir": "src",
"outDir": "dist",
"declaration": true,
"plugins": [
{ "transform": "@ttsc/lint", "rules": { "no-var": "error" } },
{ "transform": "@ttsc/banner", "config": "./banner.config.ts" },
{ "transform": "@ttsc/strip" },
{ "transform": "@ttsc/paths" }
]
},
"include": ["src"]
}banner.config.ts:
import type { TtscBannerConfig } from "@ttsc/banner";
export default {
text: "License MIT",
} satisfies TtscBannerConfig;@ttsc/lint runs first as a check-stage plugin and aborts emit on error-severity findings. The three transform-stage plugins then run inside one compiler emit pass through linked packages: @ttsc/banner overlays the source preamble, @ttsc/strip filters statements, @ttsc/paths rewrites module specifiers. TypeScript-Go owns the final emit of JavaScript, declarations, and source maps.
This exact fixture is locked by tests/test-ttsc/src/features/utility-plugins/test_ttsc_utility_plugins_lint_banner_paths_and_strip_run_together_in_ttsc_build.ts β if anything in the walkthroughs ever drifts from the shipped behavior, that test catches it first.
See also
- Architecture β the build/cache pipeline behind every plugin in this tour.
- Driver API β the curated Go faΓ§ade every third-party plugin should call.
- AST & Checker β the shim surface every plugin in this tour imports.
- Pitfalls β the failure modes the walkthroughs reference but do not redocument.