ttsc
Version:
General-purpose TypeScript-Go compiler, runtime, plugin host, and LSP host.
192 lines (191 loc) • 9.25 kB
TypeScript
/**
* Subcommands the ttsc CLI dispatches to. The bare `"ttsc"` entry covers the
* default lane (no explicit subcommand, e.g. `ttsc -p tsconfig.json`).
*/
export type TtscSubcommand = "ttsc" | "build" | "cache" | "check" | "fix" | "format" | "prepare" | "clean";
/**
* Subcommands the ttsx CLI dispatches to. ttsx exposes a single lane today but
* the schema mirrors ttsc's shape so future subcommands plug in without a
* second engine.
*/
export type TtsxSubcommand = "ttsx";
export type AnySubcommand = TtscSubcommand | TtsxSubcommand;
/**
* Layers a flag can be consumed by. The order reflects the runtime pipeline:
*
* Launcher → runBuild → tsgo / native sidecars (host, lint).
*
* A flag must declare at least one consumer. `forwardTo` declares where the
* flag travels when the consuming layer does not absorb it (e.g. ttsc-owned
* flags that the JS launcher consumes and re-emits as different tsgo flags).
*/
export type FlagLayer = "launcher" | "runBuild" | "tsgo" | "host" | "lint";
/**
* Argument shape of a flag.
*
* - `boolean` — `--flag` with an optional spaced `true` / `false`. Launcher flags
* also accept `--flag=true` / `--flag=false`; tsgo-forwarded flags stay
* verbatim because pinned tsgo does not split `=`.
* - `value` — `--flag value`. Launcher flags also accept `--flag=value`.
* - `valueOptional` — `--flag` standalone is allowed; if followed by a non-flag
* token that token is consumed as the value. (Currently unused — declared for
* future flags like `--watch [path]`.)
*/
export type FlagKind = "boolean" | "value" | "valueOptional";
/**
* Validation predicate for `value`-kind flags. `none` is the default (accept
* any string).
*/
export type ValueValidator = "none" | "positiveInt";
/**
* One CLI flag's complete contract. Every layer's behaviour is structural —
* which subcommands accept the flag, where it is consumed, where it is
* forwarded, whether it is terminal (prints and exits) — so the next layer
* never silently drops the flag.
*/
export interface FlagSpec {
/**
* Canonical flag name including leading dashes (`"--singleThreaded"`). The
* generator uses this as the map key in the Go allow-list and as the first
* column of the docs table.
*/
readonly name: string;
/**
* Alternative spellings (`"-p"`, `"--project"` for `--tsconfig`). The parsing
* engine treats every alias as an equivalent of `name`.
*/
readonly aliases?: readonly string[];
/** Argument shape: boolean / required value / optional value. */
readonly kind: FlagKind;
/**
* For `value` flags: optional validator. `positiveInt` mirrors the
* `--checkers minValue:1` constraint tsgo enforces.
*/
readonly validator?: ValueValidator;
/** Subcommands that accept this flag. */
readonly subcommands: readonly AnySubcommand[];
/**
* Layers that read the flag into a typed option. Order matters only for
* documentation; the parsing engine merges across layers.
*/
readonly consumedBy: readonly FlagLayer[];
/**
* Where the flag travels when the consuming layer does not absorb it. Default
* for any flag a layer does not consume is `"tsgo"` — i.e. the launcher
* forwards an unknown flag to tsgo via passthrough. Setting this to
* `undefined` while `consumedBy: ["launcher"]` is set means the flag is
* intentionally consumed-not-forwarded (e.g. ttsc-internal `--binary`).
*/
readonly forwardTo?: FlagLayer;
/**
* Terminal flags ask the underlying tool to print something and exit
* (`--help`, `--version`, `--showConfig`, `--listFilesOnly`, `--all`,
* `--init`). ttsc must not wrap them in a pre-emit pass — that is how the
* `--showConfig prints twice` bug appeared (RC-2).
*/
readonly terminal?: boolean;
/**
* `true` when a `terminal` flag's meaning does not presuppose a resolved
* project, so ttsc must answer it before project resolution runs (`--init`
* writes the starter tsconfig, `--all` and `-?` print tsgo's help). Without
* this split `ttsc --init` failed with "could not find tsconfig.json …" in
* the only directory where it is useful.
*
* `--showConfig` and `--listFilesOnly` are terminal but deliberately NOT
* project-free: both describe a project, so failing without one is correct.
*/
readonly projectFree?: boolean;
/**
* `true` when every occurrence of a repeated `value` flag counts rather than
* the last one winning (`ttsx -r a -r b` preloads both). The engine keeps the
* last value in `ParseResult.values` for callers that want a single answer
* and records the complete ordered list in `ParseResult.repeated`.
*/
readonly repeatable?: boolean;
/**
* `true` when ttsc may add this flag to tsgo internally and post-process the
* output. If the user also forwards the same flag, ttsc keeps the
* user-visible behaviour (no double-print, no swallowed output). The shadow
* check is structural rather than `passthrough.includes("…")`.
*
* Currently true for `--listEmittedFiles`, `--noEmit`, `--pretty`.
*/
readonly internalShadow?: boolean;
/**
* `true` when pinned tsgo only permits the option in tsconfig, except for
* command-line `false` or `null`. The launcher forwards the original argv so
* tsgo remains the diagnostic authority.
*/
readonly tsconfigOnly?: boolean;
/**
* Native sidecar capability that must be declared before ttsc sends this flag
* as a bare CLI argument. Everything else routes through `--tsgo-args` or
* stays in the JS launcher.
*/
readonly nativeCapability?: "diagnosticsTiming" | "threadingArgs";
/** Human description for `--help` and the docs table. */
readonly description: string;
}
/**
* Single source of truth for every flag the ttsc / ttsx CLI accepts. New flags
* are added here and only here; the generator rebuilds the parsers and the Go
* allow-lists from this table.
*
* Constraints enforced by the parser and the generator:
*
* 1. Every flag is uniquely identified by `name`; aliases must not collide with
* other flags' `name` or `aliases`.
* 2. A flag listed in `consumedBy: ["launcher"]` without a `forwardTo`
* consumes-not-forwards. The generator flags this in the docs and the Go
* allow-list so the boundary is explicit.
* 3. A flag with `subcommands` covering `clean` or `prepare` is parsed by the
* project-args lane; the parsing engine accepts the same flag in build /
* check / fix / format without a separate parser.
*/
export declare const FLAG_SCHEMA: readonly FlagSpec[];
/**
* Normalize a CLI token to the identity the compiler ttsc wraps resolves it by:
* one or two leading dashes removed, the remainder lower-cased.
*
* TypeScript's option parser — legacy `tsc` and native tsgo alike — strips a
* `--` or `-` prefix and matches the rest case-insensitively, so `--noEmit`,
* `--noemit`, `--NOEMIT`, and `-noEmit` all name the same option to the tool
* ttsc forwards to. The launcher used to key its index on the exact spelling,
* so a case variant of a ttsc-owned flag fell through the unknown-flag escape
* hatch: tsgo honoured it and every ttsc-side consumer of the same flag never
* fired, with no diagnostic.
*
* This is the single normalization. Everything that resolves a token against
* `FLAG_SCHEMA` — the parsing engine, the terminal / shadow / project-free
* classifications, and the generated Go allow-lists — keys off this function,
* so no two layers can disagree about which flag a spelling names.
*/
export declare function normalizeFlagToken(token: string): string;
/**
* Resolve a raw argv token to the flag it names, or `undefined` when the schema
* claims no such flag.
*
* Accepts every spelling the compiler accepts — any casing, one or two leading
* dashes — plus the inline `--flag=value` form, whose value is not part of the
* identity. A token without a leading dash is never a flag: bare tokens are
* input files and flag values, and resolving them here would let a value like
* the `all` of `--target all` masquerade as `--all`.
*/
export declare function resolveFlagSpec(token: string): FlagSpec | undefined;
/**
* Lookup of every declared spelling → its canonical FlagSpec, keyed by
* {@link normalizeFlagToken}. Built once at module load so the parsing engine
* has O(1) flag resolution. Prefer {@link resolveFlagSpec}, which applies the
* normalization for the caller.
*/
export declare const FLAG_BY_TOKEN: ReadonlyMap<string, FlagSpec>;
/** Tokens (canonical name + aliases) accepted in `subcommand`. */
export declare function flagsForSubcommand(subcommand: AnySubcommand): FlagSpec[];
/**
* Allow-list map for the Go layer named `layer` (`"host"` or `"lint"`):
* flag-name (no leading dashes) → whether the flag takes a value token. The
* generator emits a literal Go map with the same shape, but this function is
* the runtime equivalent — used in tests to verify the generated Go matches the
* schema.
*/
export declare function buildGoAllowList(layer: "host" | "lint"): Map<string, boolean>;