开源项目 pingdot/ts-rust 完全由 LLM 实现 TypeScript 编译器、类型检查器和 LSP 的 Rust 移植,HN 获59票关注。
我想看看 LLMs 能否将 TypeScript 编译器、checker 和 lsp 移植到 Rust。结果表明,完全可以。
整个过程消耗了超过 42 万美元的 token 费用,但其实大约 2 万美元就能完成(见下文)。
测试模型能力。
打造一个快速的 TypeScript 类型检查器。
打造一个能在 WASM 中高性能运行的 ts checker。
这是一个早期版本。在我们测试的所有现实项目中,它实现了 100% 的兼容性。对于绝大多数应用来说,它可以作为直接替代品使用。参见已知问题。
值得一提的是:我从未读过一行这些代码。
警告:我完全不知道这玩意儿实际上能不能跑起来。
npm install -D tsc-rs
npx tsc-rs -p tsconfig.json
我动用了大量的 OpenAI 模型来完成这次移植。在 GPT-5.6 Sol 和 GPT 6 Astra 上,总共消耗了超过 40 万美元的 API 定价 token。历时数月的 /goal 循环,它们写出了超过 130 万行 Rust 代码,但兼容性始终没能突破 84% 左右。
看到我的 Claude Code 限额消耗得那么少,我想着用 Opus 5.5 试试应该挺有意思。结果它 10 小时就做出了一个能跑的 v0。
我以为它会沿用 Codex 模型写出的代码。结果我发现我错了。Opus 5.5 从头开始重写,而且在 1/10 的时间内就超越了 Astra 的水平。
我让它继续跑下去,它确实做到了。总 token 消耗约为 24,047 美元,历时两周。我用的是我的 Claude 账号,算下来大约在每周 200 美元限额的 925% 到 983% 之间。
确实不便宜,但考虑到 typescript-go 投入的工作量,这个价格也不算离谱。
下面所有内容都是我的 LLMs 写的,不是我。
这玩意儿到底是什么?
ts-rust 是微软原生 TypeScript 编译器的直接移植版,原版是 Go 编写的(microsoft/TypeScript,前身是 typescript-go)。它保留了 Go 的算法和行为,拥有相同的命令行(tsc)、语言服务器和 API。
npm install -D tsc-rs
npx tsc-rs -p tsconfig.json
tsc-rs 接收与 tsc 相同的选项。npm 包名为 tsc-rs 以避免与 typescript 包冲突。每个版本也提供各平台的独立压缩包:tsc 二进制文件及其旁边的 lib 文件。
支持的平台:Linux x64(静态链接,任意发行版)和 macOS arm64。Windows 和 Linux arm64 暂不可用。
在 VS Code 中使用方法参见 npm 包的 README。
tsc-rs 内置了 Effect language service diagnostics(错误码 377xxx),因此 Effect 项目不需要第二个编译器。它们来自与 TypeScript diagnostics 相同的检查过程,语言服务器也会显示它们。这些诊断只在 tsconfig 配置了插件时才运行,与 @effect/language-service 的行为一致:
{ "compilerOptions": { "plugins": [{ "name": "@effect/language-service" }] } }
这些规则、选项和 @effect-diagnostics 注释移植自 Effect-TS/tsgo 0.46.1。语言服务的编辑器功能(quick fixes、refactors、hover、completions)未做移植。
该移植版固定在某个上游版本:microsoft/TypeScript 673a5f17d713(2026-09-29,TypeScript 7.1.0-dev;UPSTREAM.md),并在此版本上与 Go 进行对比。要做对比,请使用 typescript@7.1.0-dev.20260929.1,而非 7.0.x 或 @typescript/native-preview。此版本显示的一个差异也是上游行为,当移植版更新到更新的锚定版本时该差异会消失。
结果一致。TanStack Query core 和 Hono 的诊断结果与 Go 版本完全相同。全部 181,711 个移植的 Go 测试通过。语言服务器和 API 在 oracle 测试集上的回答与 Go 一致。
更快。在 60 个开源项目上,类型检查耗时约为 Go 版本的一半(几何平均值)。预览包在 CI 中构建时未启用 PGO 和 BOLT,所以实际构建版本会比这个测量结果更快。
真实项目。在 120 个开源仓库上,命令行输出与 Go 的差异仅出现在下面的已知问题中,以及 Go 自身输出在多次运行间有变化的地方。
T3 Code 的完整类型检查,对比 tsc 6、tsc 7 和 Bun 中新的 bun check。T3 Code 使用了 Effect,所以有两个场景:不带 Effect diagnostics 和带 Effect diagnostics。每次耗时为五个 T3 Code 项目的总和。数值越低越快。
不带 Effect diagnostics
带 Effect diagnostics
当你不需要 Effect diagnostics 时,bun check 是最快的。它本身没有这些 diagnostics,所以 Effect 项目需要第二次检查。tsc-rs 在一次检查中就完成了它们。
错误对比。tsc-rs、tsc 7 + @effect/tsgo 和 effect-tsgo diagnostics pass 报告的 Effect diagnostics 数量相同,都是 221 个。tsc 6 使用的是 JavaScript Effect 插件(@effect/language-service 0.87.4),它的规则集不同:在 apps/server 上报告 287 个,而其他只报告 177 个。tsc-rs 和 tsc 6 多报告了一个错误:apps/server/scripts/record-pi-rpc-replay-fixture.ts 中的 TS2322。TypeScript 7.1.0-dev 也报告了这个错误,pingdotgg/t3code#16704 对此做了修复。
测量方法:与下面真实世界应用相同的机器和方法,添加了 --composite false(apps/web 是 composite)。T3 Code 版本为 cd41c4ad,项目包括 apps/server、apps/web、apps/mobile、packages/client-runtime 和 packages/shared。不带 Effect 时,配置中没有 Effect 插件。带 Effect 时,tsc 7 是来自 @effect/tsgo 0.46.1 的 Effect-patched 7.0.2,tsc 6 是打了 @effect/language-service 补丁的 6.0.3。脚本为 scripts/bench-apps/t3code.sh。T3 Code 中的 tsc-rs 切换为 pingdotgg/t3code#16704。
基准测试:真实世界应用
对六个开源应用进行完整类型检查,对比 tsc 6(JavaScript 编译器)、tsc 7(Go 编译器)、tsc-rs 和 bun check。倍数是相对于 tsc 6 的加速比。数值越低越快。
相对于 tsc 7,tsc-rs 快 1.61 倍,bun check 快 2.95 倍(几何平均值)。bun check 在除 tRPC 外的所有应用上都是最快的。
每个配置在 tsc 7.0.2 下检查时均为 0 错误。其他差异如下:
tsc-rs 在 VS Code 上报告 10 个错误,在 Sentry 上报告 2 个。TypeScript 7.1.0-dev(typescript@next)逐行报告了相同的错误。tsc-rs 移植的是 7.1 dev 版本,其中有 7.0.2 没有的检查。
tsc 6 在 VS Code 上报告 9 个错误。
测量方法:Apple M4 Pro(12 核,48 GB),macOS 26.5.1。hyperfine,预热 1 次后取 5 次运行的中位数,使用 --noEmit --incremental false。每个 checker 使用其默认线程数。tsc 7 和 tsc-rs 作为原生二进制文件运行,不通过 npm 启动器。tsc 6 在 Node 24.19 上运行,堆大小 16 GB,因为在 VS Code 和 Sentry 上使用默认堆会内存不足。版本:tsc-rs 0.1.0、TypeScript 7.0.2 和 6.0.3、Bun canary bd599f5af。检查的行数为 tsc 7 --extendedDiagnostics 计数,包含 .d.ts 文件。上面的 T3 Code 基准测试使用相同的机器和方法。
四个应用需要修改才能在 tsc 7 下零错误检查。没有其他变更:
Excalidraw:移除 baseUrl,因为 TS 7 移除了它。
TypeORM:moduleResolution 从 node 改为 nodenext,因为 TS 7 移除了 node。
VS Code:其 postinstall 添加的 electron 类型定义。
Playwright:其构建生成源文件。
两个应用不在表中:
rxjs main 需要先构建工作区包。
date-fns 使用了项目引用。在那里,tsc -p 和 bun check 做的工作不同。
脚本位于 scripts/bench-apps:setup.sh <dir>,然后 run.sh <dir> 和 summary.py <dir>,T3 Code 用 t3code.sh <dir>。
在某些 monorepo 中,工作区包的源文件既可以通过 node_modules 访问,也可以通过直接导入访问。在这些情况下,tsc-rs 可以为比 tsc 更多的这些文件写入输出,并为它们报告 TS6059(file is not under rootDir)。tsc 通过计时来决定这个问题,所以它自己的结果在多次运行之间会发生变化。tsc-rs 在每次运行中给出相同的结果(TypeScript 6 的结果)。
在 tsc -b 中,当一个项目导入另一个项目的输出而没有项目引用时,tsc-rs 可能会读取旧的或缺失的输出(TS2305 或 TS2307),而 tsc 读取新的输出。这发生在项目只需要写入输出的时候。添加引用即可修复。
在编辑器中,长时间编辑会话期间内存缓慢增长(约每 1,000 次编辑增加 20 MiB)。在我们测量的会话中,它始终低于 tsc 的内存使用。
tsc-rs --version 打印的是它移植的 TypeScript 版本(7.1.0-dev),而非 npm 版本。编译器根据它来做 typesVersions 匹配。
crates/ts_goport 是编译器。它有两个 parts crate:goport_util 和 goport_lsproto,位于 crates/ts_goport/parts,并使用 crates/ts_goport/libs 中的 lib 文件。tools/ts_ast_codegen 生成 crates/ts_goport/src/astdata,tools/ts_diagnostics_codegen 生成 crates/ts_goport/src/diagnostics/catalog.rs 和 crates/ts_goport/src/diag.rs。crates/ts_wasm 是 WebAssembly 构建(npm/wasm)。
./scripts/run-cargo-capped.sh build --release -p ts_goport --bins
./scripts/verify.sh
二进制文件包括 goport(类型检查)和 tsgo(Go tsgo 命令行)。Go 基线测试通过 TS_GO_REPO=/path/to/typescript-go ./scripts/run-cargo-capped.sh test -p ts_goport --test go_baselines 运行。
移植规则:crates/ts_goport/PORTING.md
测量和门控脚本:scripts/goport
npm 包和发布:npm/README.md
Typechecker 工作规则:AGENTS.md,问责规则和保存的状态
项目如何启动:docs/history.md
推送一个 tag v<version>(例如 v0.1.0)。发布工作流构建、打包和测试包,将它们发布到 npm 并创建 GitHub release。稳定版本发布到 dist-tag latest,预发布版本(v0.2.0-beta.1)发布到 next 和 GitHub prerelease。参见 npm/README.md。
MIT 许可证。移植版保留其移植代码的许可证和通知:TypeScript(Apache-2.0)和 Go 标准库的部分代码(BSD-3-Clause)。参见 NOTICE.md。