precise language tools for TypeScript/JS, CSS, and Svelte in Rust - tsv.fuz.dev
tsv is a Rust toolchain for TypeScript/JS, CSS, and Svelte (and planned HTML/JSON). It ships a formatter that closely follows Prettier + prettier-plugin-svelte, and a parser whose AST is compatible with Svelte's parser + acorn + acorn-typescript.
tsv aims to simplify its covered domains and stay lean, and so it makes opinionated choices. The formatter has a single non-configurable style, using Svelte's Prettier config. Among other benefits, this means tsv doesn't depend on a JS runtime, which it would need in order to resolve configs the way Prettier does, avoiding complexity and performance costs.
Compared to Oxc, Biome, and swc, tsv is a set of focused tools, not an extensible language platform, so it targets Web standards + TS + Svelte and there's no support for JSX/SCSS/etc. tsv's extensibility story is currently limited to using its Rust crates as libraries (or forking); bridging to JS or wasm plugins is an open question (leaning against).
Compared to rsvelte, tsv has its own TS/JS/CSS parsers instead of using Oxc. rsvelte also ships a Svelte compiler and linter/typechecker integration; tsv has experimental work in that direction that may never ship.
tsv prioritizes, in order:
- correctness (spec conformance for HTML/CSS/JS, fidelity to Svelte and TypeScript)
- speed
- binary size and memory usage
- extensibility, modularity, reusability
Staying simple is an overarching goal. Feedback is welcome to help navigate these tradeoffs.
See the benchmarks for measurements. Compared to Oxc/Oxfmt and Biome, tsv is faster and smaller and uses less memory, but lacks their features, extensibility, and broad language support. One reason for tsv to exist is to help find the performance left on the table in the Web's implementations.
tsv's formatters are near production-ready, with a long tail of rare bugs. Reports and opinions are appreciated. See the issues and discussions.
The only first-party unsafe is in tsv_ffi, where the C boundary requires it;
tsv_napi relaxes the lint only to deny so napi-derive's generated code compiles.
Otherwise unsafe_code = "forbid".
AI disclosure: this codebase is LLM-generated using Claude Code. It's a high-effort project that prioritizes quality.
status: the formatters are near production-ready - a long tail of rare bugs remains, and APIs may still change
tsv implements aspects of:
- HTML/CSS/JS and other Web specs
- TypeScript
- Svelte
- Prettier and prettier-plugin-svelte
tsv features:
- parsers for TypeScript/JS, CSS, and Svelte, with ASTs compatible with Svelte+acorn+acorn-typescript
- formatters following Prettier + prettier-plugin-svelte + Svelte's prettier config (with intentional divergences and no supported style config)
- VS Code formatter extension -
fuzdev.tsv-formaton the Marketplace and Open VSX (WASM-only, works in desktop VS Code and the browser host) - type stripping: TypeScript to JS by blanking types to whitespace, preserving every position —
tsv strip,strip_typescriptin@fuzdev/tsv-wasmand@fuzdev/tsv(docs/strip.md) - import records: every module request a TypeScript, JS or Svelte source makes — static, re-export, dynamic,
import x = require()— with its span, decoded specifier and type-only verdict —parse_imports_typescript/parse_imports_sveltein@fuzdev/tsv-wasmand@fuzdev/tsv(docs/imports.md)
Future features (unknown order):
- minifier
- JSON support
- HTML support (formatting as Svelte isn't correct e.g. with whitespace and
{, but the lift to support it is small) - JS parsing diagnostics (test262 negative cases)
- CSS error recovery (recover past invalid CSS per the spec)
- maybe later or never:
- TypeScript 7 integration (the Go impl), unlocking
what rsvelte already provides:
- linter - type aware, initially focused on serializable data-only plugins for extensibility
- svelte-check replacement
- LSP
- includes an experimental first-party typechecker
tsv_check(which may never ship)
- Svelte compiler (experimental partial implementation, may never ship - see rsvelte)
- bundling is out of scope
- discussion welcome
- TypeScript 7 integration (the Go impl), unlocking
what rsvelte already provides:
tsv ships to npm as a native package for Node and Bun, plus three WASM packages for browsers, Deno, and platforms without a prebuilt binary:
@fuzdev/tsv- the full tool (formatter + parser + type stripper + import records) as a prebuilt native N-API addon, shipping tsv's real native CLI binary as thetsvcommand@fuzdev/tsv-wasm- the full tool with the sametsvCLI, as WASM@fuzdev/tsv-format-wasm- formatter only (smaller)@fuzdev/tsv-parse-wasm- parser + JSON AST only (smallest)
npm i -D @fuzdev/tsv
npx tsv format src # if installed locally
npx @fuzdev/tsv format src # or without installing firstimport {format_svelte} from '@fuzdev/tsv';
const result = format_svelte('<script>\nconst x=1\n</script>');
if (result.ok) console.log(result.code); // else result.error: {kind, message, frame, start, end, loc}import {parse_svelte, type Root} from '@fuzdev/tsv';
const result = parse_svelte('<script>const x = 1;</script>');
if (result.ok) {
const ast: Root = result.ast;
}@fuzdev/tsv and @fuzdev/tsv-wasm are drop-in swaps.
The native package is prebuilt
for Linux (x64 gnu and musl, arm64 gnu), macOS (arm64 and x64), and Windows x64.
As with other wasm packages, browsers must call await init().
The same native tsv CLI binaries are attached to each
GitHub Release (one per platform above,
with a SHA512SUMS for sha512sum -c) for use without npm; every asset carries
a build provenance attestation (gh attestation verify tsv-linux-x64-gnu -R fuzdev/tsv).
The checksum file and the attestation are this channel's whole integrity story;
it has no signed release log of its own.
With cargo-binstall,
cargo binstall tsv_cli installs that same prebuilt binary from the GitHub Release
matching the crates.io version (Linux x64 and arm64 gnu, glibc 2.28 or newer; macOS arm64
and x64; Windows x64); any other target, musl included, falls back to a source build.
From crates.io, cargo install --locked tsv_cli builds the same tsv binary from source
(the crate is tsv_cli; the tsv crate on crates.io is an unrelated project).
That build doesn't get the release profile the prebuilt binaries ship with
(LTO and panic = "abort" - a workspace's profiles don't travel with a published crate):
it runs slower, and an internal panic while formatting files is reported as that file's
error rather than aborting the run.
See the website docs and package READMEs for the full API and CLI flags:
- crates/tsv_napi/npm/README.md
- crates/tsv_wasm/README_all.md
- crates/tsv_wasm/README_format.md
- crates/tsv_wasm/README_parse.md
The library crates are on crates.io, one per language -
tsv_ts (TypeScript/JS), tsv_css,
and tsv_svelte - each self-contained, with the same functions:
// format one source; a `Formatter` formats many on one thread, keeping its arenas warm
let code = tsv_ts::format("const x=42;")?; // "const x = 42;\n"
let code = tsv_css::format("a{color:red}")?; // "a {\n\tcolor: red;\n}\n"
let code = tsv_svelte::format("<p class=\"a\">x</p>")?; // "<p class=\"a\">x</p>\n"
let code = tsv_ts::Formatter::new().format("let y=1")?; // "let y = 1;\n"
// TypeScript to JS by blanking the types, every surviving character at its line and column
let js = tsv_ts::strip("let x: number = 1;")?.code; // "let x = 1;"
// the modules a source requests: one record each, in source order
let imports = tsv_ts::parse_imports("import type {T} from './t';")?; // 1 record, type-only
// parse to the wire JSON AST, the same JSON the npm packages return
let json = tsv_ts::parse_to_json("const x = 1;")?;Errors carry a kind() (Syntax, Refusal, TooDeep, TooLarge), a one-line message(),
a span() in the input's UTF-8 bytes, and the report the npm packages return (to_report());
{} prints one line and {:#} the CLI's frame. TypeScript takes an exact parse goal
(Goal::Script / Goal::Module) through format_with_goal, strip_with_goal and
JsonOptions::goal; with none named, format, strip and parse_imports try the module grammar,
then the script one, and parsing reads a module (parse_imports_with_goal names one).
This release exposes no AST: parsing returns the wire JSON — the AST as JSON in the
canonical parser's shape (acorn-typescript's, Svelte's, parseCss's), its start / end
UTF-16 offsets, span-only by default (no loc), unlike span()'s UTF-8 byte range — whose
shape the npm packages type in
tsv_ast.d.ts. parse_to_json_with(source, JsonOptions) adds loc
(the locations cargo feature); the default json feature can be turned off for a build
without parse_to_json*.
npm (@fuzdev/tsv, @fuzdev/tsv-wasm) |
Rust |
|---|---|
format_typescript(src) |
tsv_ts::format(src) |
format_typescript(src, {sourceType: 'script'}) |
tsv_ts::format_with_goal(src, Goal::Script) |
format_css / format_svelte |
tsv_css::format / tsv_svelte::format |
| - | Formatter::new() then .format(src), for many sources on one thread |
strip_typescript(src, {sourceType}) |
tsv_ts::strip / strip_with_goal |
parse_imports_typescript(src, {sourceType}) |
tsv_ts::parse_imports / parse_imports_with_goal |
parse_imports_svelte(src) |
tsv_svelte::parse_imports |
| - | tsv_ts::strip_with_imports: the strip and the records from one parse |
parse_typescript_json(src, {sourceType}) |
tsv_ts::parse_to_json / parse_to_json_with with JsonOptions::goal |
parse_typescript(src) (an object) |
- (parse_to_json and a JSON reader) |
parse_*(src, {locations: true}), reconstruct_locations |
JsonOptions::new().locations(true) (locations feature) |
{ok: false, error} |
Err(Error) (strip: Err(StripError)) |
error.kind 'parse' / 'refused' / 'unsupported' |
ErrorKind::Syntax or TooDeep / ErrorKind::Refusal / StripError::Unsupported |
error.message, error.frame |
message() / to_report()'s message, frame ({e:#} is both) |
error.start / end, error.loc |
to_report()'s start / end (WirePoint: offset, line, column) |
| a thrown error for a source over 4 GiB | ErrorKind::TooLarge |
IgnoreStack (discovery) |
tsv_ignore::IgnoreStack (the discovery policy has no public Rust API) |
The same 1-based lines and UTF-16 offsets appear on both sides; span() is the Rust-only
byte range.
Also published: tsv_ignore, the gitignore-faithful matcher
behind discovery, and tsv_html, the WHATWG named character
references; tsv_lang and tsv_discover are support crates.
tsv's goal is to be an optimal, focused toolchain for TypeScript/JS, CSS, and Svelte (with planned JSON/HTML).
- reduce complexity within the defined scope
- supports Web+TS+Svelte - but no JSX/SCSS/etc
- formatter is non-configurable: formatting style is hardcoded to
Prettier's defaults with Svelte's official repo config
(
printWidth: 100,useTabs: true,singleQuote: true, andtrailingComma: 'none'), and there are no config files or CLI options for formatting style; (opinionated likegofmt,zig fmt, and Python's Black)
- parsers compatible with Svelte/acorn/acorn-typescript
- can generate a public JSON AST that should exactly match Svelte 5's modern AST with acorn and acorn-typescript (excluding bug fixes, see docs/conformance_svelte.md), and tsv has its own internal optimal AST
- the AST is span-only by default (
start/endoffsets, like acorn'slocations: false);{locations: true}addsloc— acorn's exactly for TypeScript, and for Svelte the true line/column of every node rather than a copy of Svelte's ownlocquirks - svelte-tsv is an experimental fork of Svelte that parses with tsv (by the author of tsv), running Svelte's own compiler and test suite on tsv's ASTs for conformance and benchmarks
- formatters following Prettier
- formatting is similar to Prettier and prettier-plugin-svelte for the common case, and diverges more often for Svelte than TypeScript; tsv closely follows Prettier's TypeScript choices (see docs/conformance_prettier.md)
tsv formatdiscovery honors.gitignore,.prettierignore,.formatignore(the latter is original to tsv, all 3 use gitignore syntax)
- Rust-only
- minimal dependencies
- no C compiler needed to build tsv
- crates are published to crates.io for reusability (Rust)
- tsv currently has no support for JS plugins or JS/WASM runtime integration - JS bridging and WASM plugins will be evaluated to see if the tradeoffs work for tsv's goals, but the current lean is against, mainly for performance and simplicity; JS reaches tsv through the WASM and native N-API/FFI bindings
- optimal
- prioritizes speed, then binary size and memory usage
- ships optimal binary artifacts: so if all you need is a formatter or parser, a minimal build is available (currently just for WASM where it matters more, and with lang-specific artifacts likely coming), and heavier future layers (incremental parsing, CST for LSP) will be feature-gated so they don't regress the focused artifacts
- modern and Web-conformant, roughly aiming to be up-to-date with late-stage TC39 proposals
Intentional non-features:
- no formatter style config settings
- no markup for frameworks besides Svelte - no JSX/TSX, Vue, Astro, etc (unlike Biome+Oxc+SWC+friends) - but note that you can use tsv's crates and patterns to vibe your own thing
- no SCSS, CSS Modules, LESS, etc - standard CSS with Svelte extensions only
- no strict Prettier conformance - see the conformance doc and discussion #1
Each language is a self-contained Rust crate exposing the same
parse_to_json/format functions over its own concrete types - no
central Language trait, registry, or dynamic dispatch ("closed scope, open convention").
Each artifact includes only what it uses: the parse build excludes the printers,
the format build excludes the JSON-AST conversion layer, and a future TypeScript/JS-only
build would exclude Svelte and CSS (lang-specific builds aren't published yet) -
see docs/architecture.md.
- CLAUDE.md - development guide (commands, structure, conventions)
- docs/architecture.md - the major design decisions
- docs/directives.md -
format-ignore/prettier-ignoreformatting directives - docs/cli.md - commands and design
- docs/strip.md - the type strip: TypeScript to JS by blanking, every position kept
- docs/imports.md - import records: the modules a source requests, where, and whether type-only
- docs/audits.md - the standing audit gates: what each proves, its blind spots, and where it gates
- docs/benchmarks.md - benchmark fairness caveats, the implementation catalog, and binary size reporting
- docs/conformance_prettier.md - where formatting diverges from Prettier (and why); its §Catalogs table indexes the per-language catalogs
- docs/conformance_svelte.md - where the parser diverges from Svelte (and why)
- docs/conformance_test262.md - ECMAScript parser conformance
- docs/conformance_tsc.md - TypeScript parser conformance against the compiler's own corpus
- docs/fixture_overview.md - fixture system design
- docs/fixture_workflow.md - step-by-step fixture creation
- docs/fixture_naming.md - fixture naming conventions and patterns
Dev dependencies:
- Rust - rustc, cargo
- Deno - see deno.json for the tasks
- uses
npm:imports fromsvelte,typescript,acorn,@sveltejs/acorn-typescript,prettier,prettier-plugin-svelte
- uses
Rust dependencies are kept fairly minimal. See CLAUDE.md § Rust Crates for the full list.
# Build workspace (recommended - uses deno tasks)
deno task build # dev build
deno task dev # watch mode (requires: cargo install cargo-watch)
deno task check # all checks (typecheck, tests, audits, lint, fmt)
# Or build directly with cargo
cargo build --workspace
cargo check --workspace # fast syntax check (no codegen)
cargo test --workspace # all tests including fixture validation
# Run CLI
cargo run -p tsv_cli parse --content "const x = 1;" --parser typescript
cargo run -p tsv_cli format --content "<div>test</div>" --parser svelteFor the full reference see CLAUDE.md § Commands.
Multi-crate workspace with clean separation of concerns:
tsv/
├── Cargo.toml # workspace root
├── crates/
│ ├── tsv_lang/ # shared foundation (Span, Error, the doc printer, the wire writer)
│ ├── tsv_arena/ # shared binding substrate: per-thread reusable AST/doc arenas + the goal axis
│ ├── tsv_html/ # HTML spec data: named character references
│ ├── tsv_ignore/ # gitignore-aware discovery matcher (.gitignore/.formatignore/.prettierignore)
│ ├── tsv_discover/ # file-discovery policy (build-output heuristic + safety nets) over tsv_ignore
│ ├── tsv_ts/ # TypeScript parser/formatter (standalone)
│ ├── tsv_css/ # CSS parser/formatter (standalone)
│ ├── tsv_svelte/ # Svelte parser/formatter (uses tsv_ts + tsv_css)
│ ├── tsv_svelte_compile/ # experimental Svelte→JS compiler + JS canonicalizer (may never ship; not in any shipped artifact)
│ ├── tsv_check/ # experimental TypeScript binder/checker (may never ship; not in any shipped artifact)
│ ├── tsv_cli/ # unified CLI (binary: `tsv`)
│ ├── tsv_debug/ # dev utilities (binary: `tsv_debug`, uses Deno)
│ ├── tsv_ffi/ # C FFI bindings
│ ├── tsv_wasm/ # WebAssembly bindings
│ └── tsv_napi/ # N-API bindings (Node/Bun native path)
└── tests/ # workspace-level integration tests
Each language crate exports the same functions - see Rust; the AST-taking primitives the bindings and dev tools use are hidden plumbing, not public API.
For more details see CLAUDE.md.
tsv is an implementation of various software designs and specs:
Software:
- Svelte
- Prettier which was forked from recast's printer, which is based on the algorithms described in "A prettier printer" by Philip Wadler
- TypeScript
- ts-blank-space - the reference oracle for type stripping
Web Standards:
- WHATWG HTML - HTML Living Standard
- WHATWG DOM - DOM Living Standard
- W3C CSS Working Group - CSS specifications
- TC39 ECMAScript - JS language specification
- TC39 test262 - ECMAScript conformance tests
- W3C webref - Machine-readable web specs
- web-platform-tests/wpt - Test suites for Web platform specs
Claude Code was instrumental to this project, and tsv wouldn't exist without LLMs because of the high coding labor requirements. Source code of projects similar to tsv was not used by agents or consulted by the author unless listed above. The author learned Rust in 2015 but wrote only simple learning programs and some abandoned toys before tsv.
The code for the following projects was sometimes read by AI agents while producing tsv, so their license information is included for completeness:
Svelte Copyright (c) 2016-2026 Svelte Contributors MIT - https://github.com/sveltejs/svelte/blob/main/LICENSE.md
Prettier Copyright © James Long and contributors MIT - https://github.com/prettier/prettier/blob/main/LICENSE
ts-blank-space Copyright 2024 Bloomberg Finance L.P. Apache-2.0 - https://github.com/bloomberg/ts-blank-space/blob/main/LICENSE