I wanted to see if LLMs could port the TypeScript compiler, checker and lsp to Rust. Turns out they can.
It cost over $420,000 in tokens to do it, but you could probably have done it for ~$20k (see below)
- Test model capabilities
- Make a fast TypeScript type checker
- Make a ts checker that can work in WASM with high performance
- Memes
This is an early release. It has 100% compatibility in every real world project we have tested. It should work as a drop in replacement for the vast majority of apps. See Known problems.
Also worth mentioning: I've never read a line of this code.
Be warned, I have no idea if this will actually work.
npm install -D tsc-rs
npx tsc-rs -p tsconfig.jsonI used a lot of OpenAI models to try and complete this port. In total I did over $400,000 in API priced tokens with GPT-5.6 Sol and GPT 6 Astra. They wrote over 1.3m lines of Rust over multiple months of /goal loops and never got past like 84% compat.
When I saw how little my Claude Code limits were burning, I figured it'd be fun to throw Opus 5.5 at this. It had a working v0 in 10 hours.
I assumed it kept using the code the Codex models wrote. I was wrong. Opus 5.5 started from scratch. It got further than Astra in 1/10th the time.
I let it keep going, and it definitely did. Total token spend was ~$24,047 of API spend over 2 weeks. I was using my Claude accounts, and it worked out to somewhere between 925% and 983% of my $200 plan weekly limits.
Expensive, for sure, but not that bad considering how much work has went into typescript-go.
Everything below this was written by my LLMs, not me.
ts-rust is a direct port of Microsoft's native TypeScript compiler, which is written in Go
(microsoft/TypeScript, formerly
typescript-go). It keeps Go's algorithms and
behavior and has the same command line (tsc), language server and API.
npm install -D tsc-rs
npx tsc-rs -p tsconfig.jsontsc-rs takes the same options as tsc. The npm package is tsc-rs so that it does not clash
with the typescript package. Each release also
has a standalone archive per platform: the tsc binary with the lib files next to it.
Platforms: Linux x64 and arm64 (static, any distribution), macOS arm64, and (from the first release after 0.2.0) macOS x64, Windows x64 and Windows arm64.
To use it in VS Code, see the npm package README.
tsc-rs has the Effect language service diagnostics built in (codes
377xxx), so an Effect project needs no second compiler. They come from the same check as the
TypeScript diagnostics, and the language server shows them too. They run only when the tsconfig has
the plugin, as with @effect/language-service:
{ "compilerOptions": { "plugins": [{ "name": "@effect/language-service" }] } }The rules, options and @effect-diagnostics comments are a port of
Effect-TS/tsgo 0.51.1. The language server also has its editor
features: the Effect hover (with the Layer summary), inlay hints, the auto-import style, quick fixes,
document symbols, completions and refactors. All the editor features of 0.51.1 are ported. The lightbulb
and the quick fix menu show the Effect refactors. The editor's Refactor menu does not show them yet: it
needs refactor.rewrite in the server's code action kinds, which waits for a decision.
The API stability rules of 0.51.1 read the @stability JSDoc tags of Effect 4.
unstableApiUsage (TS377136) and experimentalApiUsage (TS377135) report each use of an
unstable or experimental API, as warnings by default. allowedUnstableApis and
allowedExperimentalApis in the plugin entry allow a module (effect/cli) or one export
(effect/cli/Command#make). apiStabilityLeak (TS377137, off by default) is for library
maintainers. So a project on effect@4 gets TS377136 warnings for its unstable imports, as with
effect-tsgo 0.51.1. See the Effect diagnostics doc.
The port is pinned to one upstream revision, microsoft/TypeScript
673a5f17d713
(2026-09-29, TypeScript 7.1.0-dev; UPSTREAM.md), and compared with Go at that
revision. To compare, use typescript@7.1.0-dev.20260929.1, not 7.0.x or
@typescript/native-preview. A difference that this build also shows is upstream behavior, and it
goes away when the port moves to a newer pin.
- Same results. TanStack Query core and Hono check with diagnostics identical to Go's. All 181,711 ported Go tests pass. The language server and API answers match Go on the oracle test sets.
- Faster. On 60 open-source projects, type checking takes about half of Go's time (geometric mean). The npm packages from 0.2.0 on are built in CI with PGO but without BOLT (0.1.0 has neither), so they are slower than that measured build.
- Real projects. On 120 open-source repos, the command-line output differs from Go's only in the problems below and where Go's own output changes from run to run.
Three switches make some projects faster or use less memory. They are off by default.
--checkers 8(Go's option; the default is 4) runs 8 checkers. On an 8-core host, VS Code checks in 30% less time and mui in 23% less, but T3 Code server gains nothing. CPU time goes up 13% to 51% and peak memory 7% to 34%. The output is the same as Go's with--checkers 8. Go's own output can change from 4 to 8 checkers (on zod, a TS5115 moves).GOPORT_LAZY_MEMBERS=1makes a member of an instantiated class or interface only when the checker needs it, as microsoft/TypeScript#64475 does (open, not in the pin). Peak memory goes down (zod 15%, T3 Code server 6.5%, Effect 4%). The time stays about the same. The--extendedDiagnosticsSymbols count is then lower than Go's. Diagnostics and emit stay the same as Go's, except when the members of a type resolve inside their own resolution (a cycle): there, as with #64475 in Go, a TS2589, TS5114 or TS5115 can move to another node or go away. None of the tested projects has such a case.GOPORT_FAST_NEVER_ORDER=1checks some intersection types in a faster order (mongodb's test project: 8.9 s to 1.6 s). The--extendedDiagnosticscounts are then lower than Go's. Diagnostics and emit stay the same as Go's, except near the TS2589 limit (5,000,000 type instantiations): there, a TS2589 that Go reports can go away.
Full type check of T3 Code at
cd41c4ad,
with and without Effect diagnostics. Each time is the sum of the medians for apps/server,
apps/web, apps/mobile, packages/client-runtime and packages/shared, not the elapsed time
of the parallel benchmark workflow. Lower is faster.
Without Effect diagnostics
| Checker | Time | Speed vs tsc 7 |
|---|---|---|
bun check |
4.57s | 3.51× |
tsc-rs main |
6.41s | 2.51× |
tsc-rs 0.3.0 |
6.87s | 2.34× |
tsc 7 |
16.07s | baseline |
tsc 6 |
79.57s | 0.20× |
With Effect diagnostics
| Checker | Time | Speed vs tsc 7 |
|---|---|---|
tsc-rs main (Effect built in) |
8.55s | 3.06× |
tsc-rs 0.3.0 (Effect built in) |
10.71s | 2.45× |
tsc 7 + @effect/tsgo |
26.20s | baseline |
bun check, then effect-tsgo diagnostics |
45.44s | 0.58× |
tsc 6 + JavaScript Effect plugin |
167.16s | 0.16× |
Compared with 0.3.0, main takes 6.7% less time without Effect and 20.2% less time with Effect. Bun is fastest without Effect. With Effect, main is 5.32× faster than Bun followed by the separate Effect pass.
Diagnostics matter when comparing these times:
- Without Effect, main matches the pinned
tsc7 diagnostics exactly. Both Rust versions andtsc6 report the same TS2322 in the server fixture; 0.3.0 formats its message differently. Bun reports no TypeScript errors. - With Effect, main, 0.3.0 and the native Effect compiler agree on 4,823 Effect diagnostics, including 4,602 unstable-API warnings. The separate Effect pass after Bun reports the same rules and messages, with different path and severity formatting.
- The native Effect compilers also report 26 TS2790 diagnostics. Main and 0.3.0 additionally
report the TS2322 above, which the pinned
tsc7 also reports; the Effect compiler's older TypeScript version does not. The JavaScript Effect plugin uses a different rule set and reports 305 diagnostics in total. These tools do not all perform identical checks.
Measured on 2026-10-11–12 UTC, on Blacksmith blacksmith-16vcpu-ubuntu-2404 runners
(16 AMD EPYC vCPUs, about 62 GiB RAM). Each project/mode has its own runner; all five compilers
for that case run on that same runner. Every cell has 5 runs after 1 warmup, with
--noEmit --incremental false --composite false and default thread counts. TypeScript 6
runs on Node 24.19.0 with a 16 GiB heap.
Without Effect, the configs have no Effect plugin and tsc 7 is 7.1.0-dev.20261008.1.
With Effect, @effect/tsgo 0.51.1 supplies its TypeScript 7.0.2 executable; the JavaScript
comparison uses TypeScript 6.0.3 and @effect/language-service 0.87.4. Bun is
1.4.4-canary.1+7c96a1212. Main is
6c4d9ae33,
an unreleased build with the same compiler source as the app benchmark below. Both Rust
versions use release PGO without BOLT.
Results, samples, hashes and per-case metadata come from this Blacksmith run. The workflow caches the verified compiler and prepared inputs before running the comparisons in parallel. The T3 Code runner is scripts/bench-apps/t3code.sh.
Full type check of six open-source apps on a Blacksmith blacksmith-16vcpu-ubuntu-2404
runner (16 AMD EPYC vCPUs, about 62 GiB RAM). All five compilers ran on the same runner.
Lower times are faster. tsc-rs main is an unreleased build; 0.3.0 is the published package.
| App | tsc 6 |
tsc 7 |
tsc-rs 0.3.0 |
tsc-rs main |
bun check |
|---|---|---|---|---|---|
| VS Code | 69.65s | 7.44s | 4.65s | 4.55s | 1.65s |
| Sentry (frontend) | 71.24s | 9.02s | 4.48s | 5.01s | 2.75s |
| Playwright | 5.85s | 0.79s | 0.39s | 0.41s | 0.23s |
| Excalidraw | 6.50s | 1.30s | 0.76s | 0.69s | 0.24s |
| TypeORM | 4.99s | 0.54s | 0.38s | 0.38s | 0.14s |
| tRPC (server package) | 1.44s | 0.17s | 0.09s | 0.09s | 0.10s |
Geometric mean vs tsc 6 |
1.0× | 7.7× | 13.5× | 13.6× | 27.0× |
tsc-rs main is 1.76× faster than tsc 7 across these apps (geometric mean).
The corresponding figures are 1.75× for 0.3.0 and 3.49× for Bun. Main and 0.3.0 are within
1% overall, with differences by project shown above. Bun is fastest on five apps;
tsc-rs main is fastest on tRPC.
Diagnostics differ between compilers. Main, 0.3.0 and the pinned tsc 7 produce identical
full diagnostics: 10 on VS Code, 2 on Sentry, and none on the other four apps. tsc 6 reports
9 on VS Code and none on the others. Bun reports none on VS Code or Sentry, and 2 on tRPC.
The time comparisons do not imply identical checking behavior for Bun or tsc 6.
Measured on 2026-10-11: median of 5 runs after 1 warmup, --noEmit --incremental false,
default thread counts, and direct native binaries. tsc 6 runs on Node 24.19.0 with a 16 GiB
heap. Versions: TypeScript 6.0.3 and 7.1.0-dev.20261008.1, tsc-rs 0.3.0,
tsc-rs main at 69f0a1df3,
and Bun 1.4.4-canary.1+7c96a1212. Both Rust builds use release PGO without BOLT; main uses the
same static musl build and training process as the release workflow.
Results, samples, hashes and input revisions come from this Blacksmith run. Its app stage completed; the later T3 Code setup failure did not affect these measurements.
The pinned app inputs use the same preparation as the earlier benchmark:
- Excalidraw: remove
baseUrl, which native TypeScript no longer supports. - TypeORM: use
nodenextmodule resolution. - VS Code: install the Electron typings normally supplied by its postinstall.
- Playwright: generate the injected sources normally supplied by its build.
The scripts are in scripts/bench-apps. The benchmark workflow caches the verified compiler and each prepared project, then runs each comparison on a separate runner of the same size. Each comparison keeps all compilers on one runner and saves their full output and exit codes.
The lead of bun check is all in the check step. Its checkers share one type store on all cores.
tsc 7 runs 4 checkers, and each one makes its own copies of the types it needs: on T3 Code
apps/server, 57% of the types. tsc-rs keeps the tsc 7 model, because its goal is output that
is identical to tsc 7. More checkers or one shared store would change that output, so tsc-rs
does not copy them. --checkers 8 uses more cores with output identical to tsc 7 at
--checkers 8 (see Status).
Per thread, tsc-rs is not slower. On T3 Code it was 1.5× to 7× faster per thread than
bun check, and on 28 other open-source repos the two did about the same check work per thread.
tsc-rs also loads and parses files as fast as bun check or faster.
The output is not the same. bun check gave output identical to tsc 7 on 37 of 60 open-source
repos, and tsc-rs on all 60. bun check also reports errors that tsc 7 does not, for example
TS2749 on T3 Code apps/server. These numbers are from our studies of 2026-10-07 and 2026-10-08,
with tsc-rs 0.1.0 and R179 on Linux.
- In some monorepos, the source files of a workspace package are reachable both through
node_modulesand through a direct import. There,tsc-rscan write output for more of those files thantscdoes, and report TS6059 (file is not underrootDir) for them.tscdecides this by timing, so its own result changes between runs.tsc-rsgives the same result in every run (the result of TypeScript 6). - In
tsc -b, when one project imports the output of another project without a project reference, the two projects can build at the same time. Thentscreads each output file before or after the other project writes it. This is timing, so the result oftscchanges between runs, file by file.tsc-rsmakes the programs one at a time and writes the outputs of a project together when that project ends. So it can read old or missing output wheretscreads new output, or the reverse. Often the only difference is the file hashes in the reading project's.tsbuildinfo. Sometimes it is an error, such as TS2305, TS2307 or TS2322. Add the reference to fix it. - In the editor, memory grows slowly during long edit sessions (about 20 MiB per 1,000 edits). It
starts 12 to 24% above
tsc's, and from about edit 20 it stays belowtsc's in the sessions we measured (up to 2,190 edits). tsc-rs --versionprints the TypeScript version that it ports (7.1.0-dev), not the npm version. The compiler matchestypesVersionsagainst it.
crates/ts_goport is the compiler. It has two parts crates, goport_util and goport_lsproto,
in crates/ts_goport/parts, and uses the lib files in crates/ts_goport/libs.
tools/ts_ast_codegen generates crates/ts_goport/src/astdata, and
tools/ts_diagnostics_codegen generates crates/ts_goport/src/diagnostics/catalog.rs and
crates/ts_goport/src/diag.rs. crates/ts_wasm is the WebAssembly build
(npm/wasm).
./scripts/run-cargo-capped.sh build --release -p ts_goport --bins
./scripts/verify.shThe bins are goport (type check) and tsgo (the Go tsgo command line). The Go baseline tests
run with
TS_GO_REPO=/path/to/typescript-go ./scripts/run-cargo-capped.sh test -p ts_goport --test go_baselines.
- Port rules: crates/ts_goport/PORTING.md
- Measurement and gate scripts: scripts/goport
- npm packages and releases: npm/README.md
- Typechecker work rules: AGENTS.md, the accountability rules and the saved state
- How the project started: docs/history.md
CI builds and tests x86_64-pc-windows-msvc (the Windows job of
ci.yml).
- Install Rust with the MSVC toolchain (rustup's default on Windows) and the Visual Studio Build Tools (C++ workload).
- Clone with LF line endings:
git clone -c core.autocrlf=false .....gitattributesforces LF for*.rs,*.shand the bundled libs (the lib snapshot tests hash their bytes), but the test fixtures need LF too. - Use
cargodirectly.scripts/run-cargo-capped.shneedssystemd-run. - Run the tests with
RUST_TEST_THREADS=1, as CI andgoport-tests.shdo. The Go baseline tests needTS_GO_REPOand Node, as on Linux (see the CI job for the setup). - Two
fswatch_watchertests (test_subscribe_symlink_createand_delete) make file symlinks, which need Developer Mode (Settings, System, For developers) or an elevated shell. Without either they fail with OS error 1314. - The scripts are bash. The release scripts (
crates/ts_goport/scripts/build-pgo.sh,copy-libs.sh,scripts/goport/pgo-train.sh) andscripts/goport/win-go-compare.shrun in Git Bash. The other tools inscripts/(the revision pipeline, the gate,remote.sh) are for the Linux hosts only.
Push a tag v<version> (for example v0.1.0). The
release workflow builds, packs and tests the packages, publishes
them to npm and creates a GitHub release. A stable version goes to the dist-tag latest, and a
prerelease version (v0.2.0-beta.1) to next and a GitHub prerelease. See
npm/README.md.
MIT. The port keeps the licenses and notices of the code it ports: TypeScript (Apache-2.0) and parts of the Go standard library (BSD-3-Clause). See NOTICE.md.