Skip to content
pingdotggPublic

About

An experimental Rust port of the TypeScript 7 compiler (tsc)

Topics

Resources

Stars

1.1k stars

Watchers

3 watching

Forks

Repository files navigation

ts-rust (aka tsc-rs)

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)

Motivations

  • Test model capabilities
  • Make a fast TypeScript type checker
  • Make a ts checker that can work in WASM with high performance
  • Memes

Warnings

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.

Install

Be warned, I have no idea if this will actually work.

npm install -D tsc-rs
npx tsc-rs -p tsconfig.json

How did this go?

I 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.

"The Slop Line"

Everything below this was written by my LLMs, not me.

What actually is this?

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.

Install

npm install -D tsc-rs
npx tsc-rs -p tsconfig.json

tsc-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.

Effect diagnostics

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.

Status

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=1 makes 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 --extendedDiagnostics Symbols 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=1 checks some intersection types in a faster order (mongodb's test project: 8.9 s to 1.6 s). The --extendedDiagnostics counts 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.

Benchmark: T3 Code

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 tsc 7 diagnostics exactly. Both Rust versions and tsc 6 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 tsc 7 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.

Benchmark: real-world apps

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 nodenext module 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.

Why bun check is faster

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.

Known problems

  • In some monorepos, the source files of a workspace package are reachable both through node_modules and through a direct import. There, tsc-rs can write output for more of those files than tsc does, and report TS6059 (file is not under rootDir) for them. tsc decides this by timing, so its own result changes between runs. tsc-rs gives 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. Then tsc reads each output file before or after the other project writes it. This is timing, so the result of tsc changes between runs, file by file. tsc-rs makes 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 where tsc reads 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 below tsc's in the sessions we measured (up to 2,190 edits).
  • tsc-rs --version prints the TypeScript version that it ports (7.1.0-dev), not the npm version. The compiler matches typesVersions against it.

Development

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.sh

The 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.

Develop on Windows

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 .... .gitattributes forces LF for *.rs, *.sh and the bundled libs (the lib snapshot tests hash their bytes), but the test fixtures need LF too.
  • Use cargo directly. scripts/run-cargo-capped.sh needs systemd-run.
  • Run the tests with RUST_TEST_THREADS=1, as CI and goport-tests.sh do. The Go baseline tests need TS_GO_REPO and Node, as on Linux (see the CI job for the setup).
  • Two fswatch_watcher tests (test_subscribe_symlink_create and _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) and scripts/goport/win-go-compare.sh run in Git Bash. The other tools in scripts/ (the revision pipeline, the gate, remote.sh) are for the Linux hosts only.

Releases

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.

License

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.

About

An experimental Rust port of the TypeScript 7 compiler (tsc)

Topics

Resources

Stars

1.1k stars

Watchers

3 watching

Forks

Releases

Packages

Contributors

Languages