microsoft/TypeScript
github.com/microsoft/TypeScript ↗ · 3997 files · 9.0 MB · scan capped
TypeScript TypeScript is a language for application-scale JavaScript.
Overview
This repo is the native Go port of the TypeScript compiler and language service (TypeScript 7, internally 'Corsa', shipped as tsgo). A single Go module under tsc/ builds one `tsc` binary that runs in three modes: batch compiler (tsc/tsc -b/--watch), an LSP server (`tsc --lsp`), and an IPC API server (`tsc --api`, MessagePack or JSON-RPC) consumed by the `@typescript/typescript` npm package. The repo also ships the `native-preview` ('TypeScript 7') VS Code extension that spawns the Go binary as a language server, plus extensive baseline-test infrastructure ported from the original TypeScript test suite.
System map
The system is one Go module (tsc/, module github.com/microsoft/TypeScript/tsc) in a go.work workspace with a tools/ module, orchestrated by a Node hereby build (Herebyfile.mjs). The single binary at tsc/cmd/tsc multiplexes three runtimes: batch compile via internal/execute, an LSP server via internal/lsp over stdin/stdout, and a programmatic API server via internal/api over stdio or a named pipe. The compiler core is layered scanner/parser/ast -> binder -> checker (parallel checker pool) -> transformers -> emitter/printer/sourcemap, all reading through a vfs.FS abstraction with lib .d.ts files embedded via internal/bundled. Shipping surfaces are the `tsc`/`tsgo` binary, per-platform npm packages wrapped by packages/typescript, and the packages/vscode-typescript VS Code extension.
- The only product entrypoint is tsc/cmd/tsc/main.go: runMain() applies a debug stack limit, routes `tsc --lsp` to runLSP and `tsc --api` to runAPI, and otherwise calls execute.CommandLine with the real OS system.
- The compiler core lives in tsc/internal packages layered as scanner -> parser -> ast -> binder -> checker, with module resolution in internal/module and emit in internal/transformers, internal/printer and internal/sourcemap.
- compiler.NewProgram drives program construction: it calls processAllProgramFiles (the file loader/parse-task machinery in filesparser.go/fileloader.go), then initCheckerPool and verifyCompilerOptions.
- All file IO goes through the vfs.FS interface (FileExists, ReadFile, WriteFile, GetAccessibleEntries, Realpath, ...); the CLI wraps the real OS FS with bundled.WrapFS so embedded lib.d.ts files are served from the binary.
- Build/test/lint/format/generate tasks are defined in Herebyfile.mjs; `npx hereby build` runs `go build ./cmd/tsc` into built/local/tsc and copies tsc/internal/bundled/libs into built/local.
- The repo is a Go workspace (go.work) containing modules tsc/ and tools/, requiring Go 1.27; the tsc module pins a small dependency set (go-winio, klauspost/compress, mackerelio/go-osstat, zeebo/xxh3, golang.org/x/*, gotest.tools).
- The LSP server is internal/lsp.Server; its request dispatch is a lazily-built handlerMap (handlers()) mapping lsproto.Method values to registered handlers, with lsproto/lsp_generated.go generated from the upstream LSP meta-model.
- The programmatic API server (internal/api.StdioServer) accepts a connection over an ipc.Transport (stdio or named pipe/unix socket) and speaks either MessagePack (sync) or JSON-RPC (async) against a project.Session.
- The npm package @typescript/typescript exposes a `tsc` bin that is a Node shim (bin/tsc -> lib/tsc.js) which resolves the platform-specific Go binary via lib/getExePath.js and execve/execFileSync's it.
- The VS Code extension 'native-preview' (display name 'TypeScript 7') activates on JS/TS languages and starts the Go server by spawning the resolved exe with args ['--lsp', ...pprofArgs] through vscode-languageclient.
- internal/fswatch is a vendored, pure-Go (no cgo) filesystem watcher ported from @parcel/watcher, with per-OS backends including fanotify/inotify on Linux, FSEvents via hand-written arm64/amd64 assembly FFI on Darwin, and kqueue.
- Release packaging is driven by hereby tasks (typescript:release, vscode-typescript:release) invoked from CI's release-build job and by Azure pipelines under tools/pipelines (typescript-build.yml, typescript-publish.yml, vscode-typescript-publish.yml).
Feature catalog
User-facing surface area splits into four groups. (1) The tsc CLI: single-project and build-mode compilation, watch mode, incremental/tsbuildinfo, --init/--showConfig/--listFilesOnly, tracing, pprof, diagnostics statistics. (2) A broad LSP feature set: pull diagnostics, hover, completions with auto-imports, definitions/references/implementations, rename, formatting, code actions, code lenses, inlay hints, semantic tokens, call hierarchy, workspace symbols, folding/selection/linked-editing, willRenameFiles, plus custom debugging and content-mapper methods. (3) A ~300-method programmatic API over IPC (parse, transpile, symbol/type queries, diagnostics, emit). (4) The VS Code extension's commands and settings. Notably, CHANGES.md documents deliberate feature removals versus the Strada compiler (Closure-style JSDoc features, constructor expandos, several removed options).
- `tsc` batch mode resolves a tsconfig.json via -p/--project or an ancestor-directory search (findConfigFile), supports --watch, --init (WriteConfigFile), --version, --help/--all, --showConfig, and rejects incompatible flag combos like --watch with --listFilesOnly.
- `tsc -b/--build` build mode parses a build command line (tsoptions.ParseBuildCommandLine) and runs build.NewOrchestrator over the project-reference graph.
- Watch mode is created by createWatcher in internal/execute/watcher.go, layered on internal/execute/watchmanager and the vendored internal/fswatch backends.
- Incremental compilation reads a prior .tsbuildinfo via incremental.ReadBuildInfoProgram, then wraps the new program with incremental.NewProgram to compute changes and reuse results; emit bookkeeping lives in emitfileshandler.go/snapshottobuildinfo.go.
- Diagnostics options --diagnostics/--extendedDiagnostics report phase timings and memory statistics (statistics.go), --generateTrace writes a trace file via internal/tracing, and --pprofDir starts CPU/heap profiling via internal/pprof.
- The LSP server implements pull diagnostics (textDocument/diagnostic), hover, completion (+completionItem/resolve with auto-imports), definition/typeDefinition/sourceDefinition, references (incl. custom VS references), rename/prepareRename, workspace/willRenameFiles, formatting (document/range/on-type), codeAction, codeLens(+resolve), inlayHint, semanticTokens full/range, documentHighlight and a custom multiDocumentHighlight, foldingRange, selectionRange, linkedEditingRange, callHierarchy in/out, workspace/symbol, and VS autoInsert.
- Custom (non-standard) LSP methods are registered for logging ($/setLogVerbosity), developer profiling (runGC, saveHeapProfile, saveAllocProfile, startCPUProfile, stopCPUProfile), initializeAPISession, projectInfo, and setContentMapperContributions.
- Automatic type acquisition (ATA) lives in internal/project/ata: it discovers typings (discovertypings.go, typesmap.go), validates package names, and installs npm packages through an injected ata.NpmExecutor implemented by the LSP server via exec `npm`.
- Content mappers are a plugin mechanism: a tsconfig `contentMappers` entry names an npm package whose package.json declares Exec argv; the compiler spawns it as a child process speaking JSON-RPC (internal/ipc) to transform unsupported file types (e.g. .vue) into virtual TypeScript.
- The IPC API exposes ~300 methods declared in internal/api/proto.go, including createSnapshot/updateSnapshot, createSourceFile, transpileModule/transpileDeclaration, parseConfigFile, getSymbolAtPosition, getTypeAtLocation, getSignaturesOfType, getSemanticDiagnostics/getSyntacticDiagnostics/etc., emit/emitToString, getJavaScriptEmit/getDeclarationEmit, intrinsic-type getters, and profiling controls.
- The npm package exposes the API under unstable exports: ./unstable/sync and ./unstable/async clients, ./unstable/fs (FS callbacks into the Go server), ./unstable/proto, and an AST toolkit (./unstable/ast with is/factory/scanner/visitor/clone subpaths).
- The VS Code extension contributes commands typescript.native-preview.{enable,disable,restart,output.focus,reportIssue,selectVersion,goToSourceDefinition,sortImports,removeUnusedImports,codeLens.showLocations} plus dev.* profiling commands, and settings like js/ts.trace.server, js/ts.server.pprofDir, js/ts.server.goMemLimit, js/ts.contentMappers.enabled.
Data flow
For the CLI, input is argv -> tsoptions parsing (with ancestor tsconfig discovery and extended-config caching) -> a file loader that walks imports/type directives as parse tasks -> parser.ParseSourceFile producing ast.SourceFile trees -> binder-produced symbols -> a parallel checker pool producing diagnostics -> transformer chain -> printer/sourcemap emit through vfs.FS.WriteFile. For the LSP/API servers the same core is wrapped by internal/project: an immutable Snapshot owns a SnapshotFS overlay plus a ProjectCollection, and every edit/request produces a new snapshot; language-service operations read a snapshot's program. The API server adds a registry layer assigning stable SymbolID/TypeID/NodeHandle values that are encoded to MessagePack or JSON for the Node client.
- CLI input boundary: execute.CommandLine lower-cases argv[0] to detect -b/--build, then tsoptions.ParseCommandLine/ParseBuildCommandLine produce ParsedCommandLine values; tscCompilation locates tsconfig.json by walking ancestor directories via tspath.ForEachAncestorDirectory.
- tsconfig parsing (GetParsedCommandLineOfConfigFile) resolves `extends` chains through an ExtendedConfigCache, merges command-line compilerOptions wrapped under a 'compilerOptions' key, and reports config parse errors as diagnostics before any compilation.
- File loading is a worklist of parseTask structs: each task loads a file via the compiler host, records module/type-reference resolutions in ModeAwareCache fields, and enqueues sub-tasks for discovered dependencies (filesparser.go/filesParser, fileloader.go).
- Module resolution is centralized in internal/module.Resolver with per-file mode-aware caches; resolved modules feed both the loader (which files to parse) and emit metadata (importHelpersImportSpecifier, jsxRuntimeImportSpecifier).
- Parse/bind/check/emit diagnostics flow through compiler.GetDiagnosticsOfAnyProgram; EmitFilesAndReportErrors calls GetBindDiagnostics and GetSemanticDiagnostics per file (timed against CompileTimes), then ProgramLike.Emit writes outputs via compiler.EmitOptions.WriteFile.
- Emit runs a transformer chain (internal/transformers: tstransforms type erasure/legacy decorators/metadata, moduletransforms commonjs/esmodule, estransforms for downlevel ES features, declarations for .d.ts) feeding internal/printer and internal/sourcemap.
- Type checking is parallelized by a CheckerPool on the Program (compilerCheckerPool enables grouped parallel iteration and non-exclusive access for emit); ProgramOptions.CreateCheckerPool is injectable so the project system can supply its own pool.
- In the language server, state ownership is an immutable project.Snapshot: it holds a SnapshotFS (overlay of open-file contents over the base FS), a ProjectCollection, ConfigFileRegistry, AutoImports registry, and user preferences; each update clones forward the unchanged state.
- LSP request path: stdin bytes -> lsproto.BaseReader -> json.Unmarshal into lsproto.Message -> dynamic_queue requestQueue -> handleRequestOrNotification -> handlers() lookup -> for document requests, session.GetLanguageService(ctx, uri) resolves the project/snapshot, then ls.LanguageService methods compute results converted by internal/ls/lsconv.
- The API session assigns cross-process handles: symbols get IDs from a global atomic (ast.GetSymbolId) stored in a snapshot-wide symbolRegistry, while types and signatures get per-project registries keyed by project.ID to avoid ID collisions between checker pools.
- API transport: StdioServer.Run picks ipc.NewPipeTransport (PipePath) or NewStdioTransport, then wraps a session in either NewMessagePackProtocol+NewSyncConn or NewJSONRPCProtocol+NewAsyncConn depending on the --async flag.
- Node-side, the sync API client (packages/typescript/src/api/sync/client.ts) spawns the binary with getAPIProcessArgs, optionally enabling FS callbacks (--callbacks=...) so the Go server can call back into JS for readFile/fileExists/writeFile etc. via SyncRpcChannel.
Behavior traces
Three flows were traced end to end. Batch compile: main -> runMain -> execute.CommandLine -> tscCompilation -> tsoptions parse/config discovery -> NewCachedFSCompilerHost -> compiler.NewProgram (processAllProgramFiles -> checker pool) -> EmitAndReportStatistics -> EmitFilesAndReportErrors (bind+check diagnostics, Emit) -> report + exit status. LSP: runLSP -> flag parse -> bundled.WrapFS(osvfs.FS()) -> lsp.NewServer -> Server.Run -> read/dispatch loop -> handlerMap -> project.Session snapshots -> ls.LanguageService. API: runAPI -> StdioServer.Run -> ipc transport accept -> StandaloneSession -> proto method table dispatch. A fourth flow covers watch/incremental rebuilds and a fifth covers VS Code extension activation.
- Startup (compile): main() -> runMain() -> core.ApplyDebugStackLimit() -> osutil.Args() -> execute.CommandLine(ctx, newSystem(), args, nil) -> os.Exit(result.Status); newSystem builds osSys with cwd, bundled.WrapFS(osvfs.FS()), bundled.LibPath().
- Compile flow order: CommandLine -> tscCompilation -> report early errors -> handle --init/--version/--help -> resolve configFileName (-p or findConfigFile ancestor walk) -> tsoptions.GetParsedCommandLineOfConfigFile -> CreateReportErrorSummary -> branch to watcher.start / performIncrementalCompilation / performCompilation.
- performCompilation: NewContentMapperHost (gated on RunExternalCode) -> compiler.NewCachedFSCompilerHost -> startTracingIfNeeded -> compiler.NewProgram -> tsc.EmitAndReportStatistics -> stopTracing -> CommandLineResult.
- Inside NewProgram: processAllProgramFiles parses and resolves all root+dependency files (parse tasks -> fileLoader -> module.Resolver), then initCheckerPool, verifyCompilerOptions, collectContentMapperOptionDiagnostics.
- Emit path: EmitAndReportStatistics -> EmitFilesAndReportErrors -> GetDiagnosticsOfAnyProgram(ctx, programLike, ...) with per-file GetBindDiagnostics then GetSemanticDiagnostics -> programLike.Emit(EmitOptions{WriteFile}) -> SortAndDeduplicateDiagnostics -> ReportDiagnostic each -> listFiles -> ReportErrorSummary -> exit-status mapping (EmitSkipped+diagnostics => DiagnosticsPresent_OutputsSkipped).
- Incremental compile: performIncrementalCompilation reads oldProgram via incremental.ReadBuildInfoProgram(host) before parsing, then incremental.NewProgram(program, oldProgram, CreateHost(host), sys.Now, testing) computes the change set and EmitAndReportStatistics runs against the incremental program.
- LSP startup: `tsc --lsp --stdio` -> runLSP -> flag.Parse (--pprofDir, --clientProcessId; --pipe/--socket parsed but rejected, only stdio supported) -> bundled.WrapFS(osvfs.FS()), osvfs.GetGlobalTypingsCacheLocation() -> lsp.NewServer(ServerOptions{In,Out,Err,Cwd,FS,NpmInstall,Spawn,...}) -> s.Run(ctx).
- LSP request handling: Server.Run reads messages into requestQueue; handleRequestOrNotification consults handlers()[req.Method]; document requests are wrapped by registerLanguageServiceDocumentRequestHandler which resolves session.GetLanguageService(ctx, params.TextDocumentURI()) and returns async work that runs the handler, then EnqueuePublishGlobalDiagnostics.
- LSP lifecycle: initialize -> handleInitialize creates the project.Session (s.session set; requests before initialize get ErrorCodeServerNotInitialized via the register wrappers); shutdown -> handleShutdown; exit -> handleExit; a parent-process watchdog goroutine polls isProcessAlive(parentPID) every 5s and cancels the server context if the editor dies.
- API session startup: `tsc --api` -> runAPI -> parseAPIFlags (--cwd, --pipe, --callbacks, --async, --timing, --runExternalCode) -> api.NewStdioServer -> Run: create pipe or stdio transport -> bundled.WrapFS(osvfs.FS()) optionally wrapped by callbackFS -> project.SessionInit -> NewStandaloneSession -> transport.Accept -> protocol (msgpack sync or JSON-RPC async) -> conn serve loop.
- Watch flow: tscCompilation sees Watch.IsTrue() -> createWatcher(sys, config, commandLineOptions, raw, reporters, testing) -> watcher.start(ctx) -> returns CommandLineResult{Watcher: watcher}; watching is implemented over watchmanager which multiplexes fswatch backends.
- VS Code flow: extension activate() -> SessionManager.start -> getExe(context) resolves the tsgo binary -> client.start(exe) builds ServerOptions running `[exe, --lsp, ...pprofArgs]` with stdio transport -> NativePreviewLanguageClient (vscode-languageclient) connects; setContext 'typescript.native-preview.serverRunning' toggles command enablement.
Testing signals
Testing is Go-native (`go test ./...`, gotest.tools asserts) plus Node tests for the npm package and a bundled esbuild+node:test suite for the extension. The dominant signal is the baseline harness: internal/testrunner runs the ported Strada corpus under tsc/testdata/tests/cases (compiler/conformance/transpile, ~12.4k case files) and diffs outputs against tsc/testdata/baselines/reference, with `hereby baseline-accept` to promote. ~4,360 generated fourslash _test.go files exercise language-service features through an in-process LSP client; tsctests simulate the CLI including watch mode; the API and project/session layers have dedicated session tests. CI runs a large matrix: OS/arch (incl. Windows/Linux/macOS, arm64), race mode, noembed build tag, concurrent test programs, plus lint/format/generate/tidy/smoke jobs.
- internal/testrunner.CompilerBaselineRunner enumerates tsc/testdata/tests/cases/{compiler,conformance} for .ts/.tsx files and runs each as a baseline test; a TranspileRunner covers tests/cases/transpile; ~12,407 case files exist.
- Baselines compare generated output in tsc/testdata/baselines/local against tsc/testdata/baselines/reference and fail with 'Run `hereby baseline-accept` if the new baseline is correct'.
- A skippedTests list in compiler_runner.go excludes tests that depended on the JS codebase (APISample_* using typescript.d.ts) or removed options (verbatimModuleSyntax*, importsNotUsedAsValues*, noImplicitUseStrict*, etc.).
- Known divergences from the Strada baselines are tracked explicitly in tsc/testdata/submoduleAccepted.txt and submoduleTriaged.txt (per-file .diff lists), and promotedTestCollisions.txt.
- Language-service behavior is tested by ~4,360 generated Go tests under tsc/internal/fourslash/tests driving a FourslashTest harness that runs an in-process LSPClient against a virtual FS (vfstest/iovfs) with marker/range annotations and state baselines.
- The CLI is tested end-to-end in internal/execute/tsctests (tsc_test.go, tscbuild_test.go, tscwatch_test.go, watcher_race_test.go) using a fake FS (fs.go) and mock watch backend (mock_watch_backend.go), with CommandLineTesting hooks (OnProgram, OnEmittedFiles) injected into execute.
- Server-side coverage includes lsp server tests (server_test.go, server_completion_test.go, server_semantictokens_test.go, replay_test.go, stack_sanitizer_test.go), project/session tests (session_test.go, project_test.go, configfilechanges_test.go), api session tests (session_*_test.go), and unit tests scattered beside code (~4,548 _test.go files total in tsc/).
- CI 'test' job runs `npx hereby test:tsc` and `test:benchmarks` on a matrix (ubuntu, windows, macos, linux-arm64, windows-arm64, race, noembed, concurrent-test-programs) with coverage uploaded to Codecov; baseline diffs are printed on failure and uploaded as artifacts.
- CI 'go-test' job proves the tsc module builds and tests without Node.js (asserts `! command -v node`), and runs benchmarks once (`go test -run=- -bench=. -benchtime=1x ./...`).
- Linting is `npx hereby lint` running golangci-lint with the tools/customlint plugin analyzers (e.g. forbidparentaccess, checkchildren, shadow) enforced in CI's lint job on ubuntu+windows and a noembed variant.
- Generated-code freshness is enforced: CI's 'generate' job runs `npx hereby generate` then fails on `git diff --staged --exit-code`; 'tidy' runs `go mod tidy -diff` on both modules plus `go work sync`.
- A smoke job builds with --race and runs the built binary on tsc/testdata/fixtures/compiler with and without --singleThreaded.
Risk map
The highest-risk surfaces are process spawning and protocol trust boundaries: the binary can exec arbitrary commands for content mappers (gated by --runExternalCode) and npm installs for ATA, and the --api server exposes the host filesystem (optionally piped through client callbacks) to whoever holds the pipe/stdio. Correctness risk concentrates in the baseline corpus: thousands of accepted diffs vs the JS compiler and an explicit skip list mean behavior parity is approximate, not exact. Platform-specific code (fswatch assembly FFI, per-OS process-alive checks) and panic-prone paths (NewServer panics on missing Cwd; per-request recover in handlers) are fragile spots. Generated files and the dual go.mod workspace create mechanical traps rather than runtime risk.
- osSys.Spawn/exec.Command is a raw process-execution boundary: spawnProcess launches command[0] with args, inheriting stderr; childProcess.Close kills and reaps the child. Used for content mappers and API-side spawning.
- Content mappers execute arbitrary npm-package code declared in a project's tsconfig/package.json; execution is gated by the RunExternalCode option (--runExternalCode flag / js/ts.contentMappers.enabled setting) and NewContentMapperHost returns nil when disabled.
- ATA can shell out to `npm` (ServerOptions.NpmInstall runs exec.Command("npm", args...)); the LSP server implements ata.NpmExecutor, so a language server session can trigger package installation into the typings cache.
- The --api server binds the host filesystem directly: StdioServer wraps bundled.WrapFS(osvfs.FS()) and optionally a callbackFS delegating readFile/fileExists/directoryExists/getAccessibleEntries/realpath to the client; a named-pipe/Unix-socket transport (PipePath) is an additional local attack surface.
- Thousands of behavior diffs vs the Strada compiler are accepted wholesale in submoduleAccepted.txt, and CHANGES.md documents deliberate feature removals (Closure JSDoc, constructor expandos, several options) — parity-sensitive consumers can hit silent behavioral differences.
- The compiler-test skip list encodes known uncovered areas (all APISample_* tests and every test using removed options like verbatimModuleSyntax/importsNotUsedAsValues are skipped rather than ported).
- Platform-specific low-level code carries portability risk: fsevents_darwin_ffi_{amd64,arm64}.s hand-written assembly for FSEvents, isprocessalive_{unix,windows,other}.go for the LSP parent watchdog, and transport_{unix,windows}.go for pipes.
- Error-containment: NewServer panics when ServerOptions.Cwd is empty, and document-request handlers run under s.recover(req) so a checker crash is contained to the request; userFacingRequestFailedError distinguishes expected failures from logged errors.
- contentMapperFallbackResponse silently returns empty/null LSP responses for unknown file types during the mapper-unregistration window — a deliberate correctness-for-availability trade that can mask real errors. (inferred)
- ReuseProgram's in-place file replacement has many bail-out conditions (redirect files, importHelpers/JSX-runtime imports, supplemental file shape); subtle misses here would produce stale-program bugs that only appear in long-running sessions and are hard to baseline-test. (inferred)
- The symbol/type handle layer in the API assumes binder symbol identity is stable snapshot-wide (global atomic IDs) but assigns types per-project; cross-project symbol queries depend on a first-writer-wins canonical project that could return subtly different results depending on call order. (inferred)
- Very large generated artifacts (lsproto/lsp_generated.go from the LSP meta-model, diagnostics_generated.go from diagnosticMessages.json, ast/factory/encoder generated files in the npm package) must be regenerated via hereby generate; hand edits are overwritten and CI fails on diffs.
Extension points
Extension happens at well-defined registries. New LSP methods plug into the handlers() map via registerRequestHandler/registerNotificationHandler/registerLanguageService*RequestHandler helpers, with protocol types generated from the LSP meta-model. New API methods add a Method constant plus an unmarshallerFor entry in proto.go and a handler on the session. The compiler is host-driven: vfs.FS, CompilerHost, CheckerPool, and contentmapper.Spawner are all injectable. Diagnostics, enums, AST kinds and localized strings are codegen'd from JSON/Go sources via hereby generate:* tasks. On the product side, tsconfig `contentMappers` is a user-facing plugin model, and the VS Code extension exposes registerContentMappers to other extensions.
- Adding an LSP method: implement a handleX on *Server (or a *ls.LanguageService method) and register it in handlers() inside server.go; the register* wrappers handle params unmarshalling, initialization gating, result sending, and async work.
- LSP protocol types are generated from the upstream meta-model: lsproto/_generate/fetchModel.mts downloads metaModel.json and generateLSP produces lsp_generated.go; custom methods are declared in lsproto/lsp.go (e.g. CustomSetLogVerbosityInfo, CustomTextDocumentSourceDefinitionInfo).
- Adding an API method: declare a Method constant in proto.go, add its params unmarshaller to the method table (unmarshallerFor[T]), and implement the handler on the API session; clients call it through the sync/async Node clients.
- File-system behavior is pluggable via the vfs.FS interface with existing implementations osvfs (real FS), wrapvfs, cachedvfs, iovfs, trackingvfs, vfsmock (moq-generated), and vfstest for tests.
- Compilation is host-injected: compiler.ProgramOptions takes a CompilerHost (NewCachedFSCompilerHost for CLI, compilerhost.go/projectreferencedtsfakinghost.go variants in the compiler and build packages), an optional CreateCheckerPool factory, TypingsLocation, and Tracing.
- New diagnostics are added by editing diagnosticMessages.json/extraDiagnosticMessages.json and running `npx hereby generate:diagnostics`, which regenerates diagnostics_generated.go, loc stubs (loc_generated.go) and localized lcl payloads.
- Enum/stringer codegen is declarative: //go:generate npx hereby generate:* directives in source files map to Herebyfile stringerGenerator/moqGenerator specs (e.g. generate:ast-stringer, generate:vfs, generate:projecttestutil).
- Emit transforms plug in through the transformer chain (internal/transformers/chain.go): per-feature transformers live in tstransforms (type erasure, legacy decorators, metadata), estransforms (downlevel ES), moduletransforms (cjs/esm), and declarations for .d.ts emit.
- Content mappers are the user-facing plugin point: tsconfig 'contentMappers' entries name npm packages whose package.json carries mapper metadata (Exec argv, declared compilerOptions, DynamicConfig); resolution lives in tsoptions/contentmappers.go.
- The VS Code extension exposes an ExtensionAPI to other extensions: onLanguageServerInitialized, initializeAPIConnection(pipe) and registerContentMappers(contributorId, contributions) which flow to the server's custom setContentMapperContributions method.
- project.Session is client-abstracted: project.Client and ata.NpmExecutor interfaces let the same session machinery serve the LSP server and the API server; test mocks are generated via moq into testutil/projecttestutil.
- The execute package is test-hooked through the tsc.CommandLineTesting interface (GetTrace, OnProgram, OnEmittedFiles, watch-status callbacks), letting tsctests intercept compilation internals without changing product code.
Contributor handoff
Treat this as a Go repo with a Node-driven build. Read CONTRIBUTING.md first (Go 1.27, Node 24, `npm ci`, hereby commands), then Herebyfile.mjs for task wiring. For compiler changes the spine is cmd/tsc/main.go -> internal/execute/tsc.go -> internal/compiler/program.go; for server work it is internal/lsp/server.go (handlers map) + internal/project/session.go + internal/ls/*. Never edit *_generated.go / *.generated.ts files — change the generator inputs and run `npx hereby generate`. Verify with focused Go tests (`go -C ./tsc test -run 'TestLocal/<name>' ./internal/testrunner`), the baseline-accept workflow, and `npx hereby lint`/`format`/`tidy` before finishing.
- Environment: Go 1.27 (go.work with tsc/ and tools/ modules), Node >=22.18 / npm pinned via packageManager, `npm ci` then `npx hereby build`; built binary lands at built/local/tsc.
- Start by reading CONTRIBUTING.md 'Common tasks' and 'Compiler tests' sections — they document every hereby command and the focused-test invocation `go -C ./tsc test -run='TestLocal/<test name>' ./internal/testrunner`.
- Files to read first for compiler work: tsc/cmd/tsc/main.go, tsc/internal/execute/tsc.go, tsc/internal/compiler/program.go, tsc/internal/compiler/filesparser.go, tsc/internal/checker/checker.go; for server work: tsc/internal/lsp/server.go, tsc/internal/project/session.go, tsc/internal/ls/languageservice.go.
- Generated code is pervasive and must not be hand-edited: *_generated.go files (stringer/moq/lsp/diagnostics outputs), packages/typescript/src/**/*.generated.ts, and bundled libs_generated.go; regenerate via `npx hereby generate` or the matching generate:* subtask; CI's generate job fails on any diff.
- Compiler test cases are single .ts/.tsx files added under tsc/testdata/tests/cases/{compiler,conformance}; run them through the testrunner baseline harness and promote outputs with `npx hereby baseline-accept` into testdata/baselines/reference.
- Language-service features are tested by adding fourslash-style tests; existing tests under tsc/internal/fourslash/tests are generated files that drive an in-process LSP client against a virtual FS — follow their marker/range annotation conventions.
- Before submitting: run hereby generate, build, test, test:all, lint, format, check:format, the npm package build/test, extension build, `go mod tidy -diff` on both modules, `go work sync`, and verify `git diff --exit-code` — per CONTRIBUTING.md's checklist.
- Formatting is dprint (Go via gofumpt plugin, plus TS/JSON/YAML) — `npx hereby format` / `check:format`; Go lint uses golangci-lint plus the repo's customlint analyzers, so patterns like parent access on nodes or unchecked type assertions may fail lint even when they compile.
- The build intentionally passes -tags=noembed for local builds and CGO_ENABLED=0 (unless --race); the noembed build tag swaps bundled lib embedding for disk access — behavior differs between dev and release binaries.
- Editing shared kernel types (ast.Node, checker.Type) has huge blast radius: session handle registries, the node encoder index tables, and baseline baselines all depend on their invariants; the api/session.go comments document the symbol/type identity assumptions. (inferred)
- Known trap: `--pipe` and `--socket` LSP flags are parsed but unsupported ('only stdio is supported'); likewise the sync API client throws for socket connections — don't assume those transports work end-to-end.
- Do not add dependencies casually: tsc/go.mod has a deliberately tiny require list, and CI 'misc' runs checkmodpaths plus tidy checks on both modules.
Languages
| Language | Files | Share of source |
|---|---|---|
| TypeScript | 13158 | 42% |
| JavaScript | 13133 | 42% |
| Go | 5119 | 16% |
| JSON | 185 | docs/config |
| Markdown | 66 | docs/config |
| YAML | 34 | docs/config |
Start reading here
Probable source entrypoint: packages/ — inferred from the directory tree and manifest, not the upstream docs.
Source roots
packages/ (185 files)
Source layout — packages/
| Path | Files |
|---|---|
packages/typescript/ | 148 |
packages/vscode-typescript/ | 33 |
packages/vscode-typescript-nightly/ | 4 |
Repository structure
| Top-level path | Files |
|---|---|
tsc/ | 3697 |
packages/ | 185 |
tools/ | 67 |
.github/ | 31 |
.devcontainer/ | 1 |
Docs found: CONTRIBUTING.mdREADME.md
Package
package.json — @typescript/repo v0.0.0, 13 declared dependencies.
@dprint/json@dprint/typescript@jakebailey/dprint-plugin-gofumpt@types/node@unicode/unicode-15.1.0adm-zipchokidardprintdprint-plugin-yamlherebytartinyexec
CI & config entrypoints
README.md— Repository readme.github/workflows/bump-vscode-typescript.yml— GitHub Actions workflow.github/workflows/ci.yml— GitHub Actions workflow.github/workflows/close-issues.yml— GitHub Actions workflow.github/workflows/codeql.yml— GitHub Actions workflow.github/workflows/copilot-setup-steps.yml— GitHub Actions workflow.github/workflows/create-cherry-pick-pr.yml— GitHub Actions workflow.github/workflows/scorecard.yml— GitHub Actions workflow.github/workflows/sync-wiki.yml— GitHub Actions workflow.github/workflows/tag-vscode-typescript.yml— GitHub Actions workflow
Structural health signals
Average score 7.1/10 across 96 analyzed source files; 28 hotspots surfaced.
packages/typescript/src/api/sync/api.ts— 1830 lines, score 4.7 (Large file, Deep nesting)packages/typescript/src/api/async/api.ts— 2070 lines, score 4.7 (Large file, Deep nesting)packages/typescript/src/ast/factory.generated.ts— 1920 lines, score 5 (Large file, Deep nesting)
These are deterministic review leads — size, churn, and structural signals — not proof of a bug in this project.
Recent history
5f6db9c914862026-09-22 — Remove implicit floating-point FMAs (#64323)5b20f2000e762026-09-22 — Check JSDoc function type predicates (#64303)1ac2d5aed0bf2026-09-21 — Make all the codegen we do incremental (#64331)36482c8dbb892026-09-21 — Fix import statement completion filtering in tsgo (#64357)145b158cf1e72026-09-21 — Remove misplaced parameter description from RegExp#source JSDoc (#64352)