A TypeScript compiler, written in Rust.
Scanner, parser, binder, type checker, emitter and language server in one Cargo workspace. Every change is measured against the upstream TypeScript test suites, with the real tsc baselines as the oracle.
export enum Priority {
Low,
High,
}
interface Job<T> {
id: number;
payload: T;
priority: Priority;
}
export class Queue<T> {
private jobs: Job<T>[] = [];
push(payload: T, priority = Priority.Low): Job<T> {
const job: Job<T> = { id: this.jobs.length + 1, payload, priority };
this.jobs.push(job);
return job;
}
next(): Job<T> | undefined {
return this.jobs.shift();
}
}export var Priority;
(function (Priority) {
Priority[Priority["Low"] = 0] = "Low";
Priority[Priority["High"] = 1] = "High";
})(Priority || (Priority = {}));
export class Queue {
jobs = [];
push(payload, priority = Priority.Low) {
const job = { id: this.jobs.length + 1, payload, priority };
this.jobs.push(job);
return job;
}
next() {
return this.jobs.shift();
}
}interface User {
id: number;
name: string;
}
function greet(user: User): string {
return "Hello, " + user.nmae;
}
const total: number = "42";user.ts(7,27): error TS2339: Property 'nmae' does not exist on type 'User'.
user.ts(10,7): error TS2322: Type 'string' is not assignable to type 'number'.
Found 2 errors in 1 file.interface ButtonProps {
label: string;
tone?: "primary" | "ghost";
onPress(): void;
}
export function Button({ label, tone = "primary", onPress }: ButtonProps) {
return (
<button className={"btn btn-" + tone} onClick={onPress}>
{label}
</button>
);
}import { jsx as _jsx } from "react/jsx-runtime";
export function Button({ label, tone = "primary", onPress }) {
return (_jsx("button", { className: "btn btn-" + tone, onClick: onPress, children: label }));
}{
"id": "1",
"file": "button.tsx",
"source": "export const Save = () => <Button label=\"Save\" />;",
"options": { "module": "esnext", "jsx": "react-jsx" }
}{
"id": "1",
"ok": true,
"output": "import { jsx as _jsx } from \"react/jsx-runtime\";\nexport const Save = () => _jsx(Button, { label: \"Save\" });\n",
"elapsed_ms": 0,
"exports": ["Save"]
}Measured, not claimed.
tsc-rs runs the TypeScript project's own compiler, conformance and fourslash suites. A case passes only when the output matches what tsc produced. Skipped cases are never counted as passes.
Emit and diagnostics as of the wave of 27 September 2026, measured with the cache disabled. Language server and test figures are the last recorded run. Read the full conformance report
The whole toolchain, not just a transpiler.
Most Rust TypeScript tools stop at stripping types. tsc-rs implements the compiler end to end, then adds the modes that tooling needs.
Compiler and emitter
JavaScript, source map and declaration emit for ES3 through ESNext, with CommonJS, ESM, AMD, UMD and SystemJS module output.
Type checker
Inference, narrowing, relations and diagnostics that reproduce tsc's messages, positions and codes. Incomplete, and measured case by case.
Language server
Diagnostics, hover, go-to-definition, references, completions, signature help, rename and formatting over stdio, plus a VS Code extension.
Preserve modes
Keep type annotations, comments or the original layout in the emitted JavaScript for runtime type libraries and refactoring tools.
Pipe and daemon modes
A persistent transpile pipe and a type-check daemon that speak newline-delimited JSON, built for bundlers, editors and agents.
Monorepo analysis
Parser-level import graph audits: find unreachable or type-only externals and the files with the widest dependency fan-out.
Five stages, one crate each.
The compiler is a plain pipeline. Each stage is its own crate with its own tests, and the tooling crates build on the same five.
tsc_rs_scannerLexer
tsc_rs_parserParser with tsc-compatible error recovery
tsc_rs_symbolsBinder and symbol table
tsc_rs_typesType checker: inference, narrowing, relations
tsc_rs_emitterJavaScript, source map and declaration emit
tsc_rs_astAST, spans, diagnostics, compiler optionstsc_rs_resolverModule resolutiontsc_rs_projecttsconfig, project graph, references, .tsbuildinfotsc_rs_incrementalIncremental build metadata and change detectiontsc_rs_queryQuery facade used by toolingtsc_rs_serverLSP servertsc_rs_cliThe tsc-rs binarytsc_rs_harnessBaseline harness and reportstsc_rs_benchScanner, parser and emitter benchmarkstsc_rs_wasmWebAssembly build of the compilertsc_rs_analyzeStatic analysis for TypeScript monorepos
In the same league as the fastest native parsers.
One same-session comparison on typescript.js, a 7.8 MB fixture. Lower is better. The benchmarks live in crates/tsc_rs_bench so you can run them yourself.
Parse
Source text to AST.
Parse and semantic analysis
AST plus binding and symbol resolution.
--fast-emit skips type checking and formatting normalization.oxc parses faster; tsc-rs is ahead once semantic analysis is included. Method and raw timings are in the verified waves log.
Keep the types when you need them.
tsc throws all type information away. tsc-rs can carry some of it into the output, so downstream tools see what the author wrote.
- Type annotations. Keep
as,satisfiesand angle-bracket assertions in the emitted JavaScript. - Comments. Keep comments even when
removeCommentsis set. - Whitespace. Keep the original layout (partial).
const raw = JSON.parse("{}") as { port: number };
const config = { port: raw.port, host: "localhost" } satisfies Record<string, string | number>;
export const port = <number>config.port;const raw = JSON.parse("{}");
const config = { port: raw.port, host: "localhost" };
export const port = config.port;The TypeScript front end of bext.
tsc-rs is not a lab project. It compiles every page and module served by the bext engine, including the one you are reading.
PRISM compiles with it
The bext PRISM renderer uses the tsc-rs parser, AST and emitter crates to turn TSX routes into the JavaScript it runs in V8.
One long-lived process
The persistent pipe transforms modules one JSON line at a time and reports imports, dynamic imports and exports with each result.
Errors fail the build
Parser diagnostics are reported even in transpile-only mode, so a broken source file stops the compile instead of shipping.
What it is not, yet.
The tables on this site exist so you can judge the gaps for yourself. These are the ones that matter most today.
Not a drop-in tsc replacement
Diagnostics, declaration emit and expanded option variants are not at parity, and the CLI surface is a subset.
The type checker is incomplete
Hover types and some diagnostics differ from tsc. Expect missing errors and some false ones on advanced code.
Declarations and symbols are early
.d.ts output and the symbol baselines are the least mature lanes of the project.
Build it, point it at your project, read the numbers.
Install from source in one command, or compile a snippet in the playground without installing anything.