Expand description
TypeScript and JavaScript formatting, type stripping, and parsing to acorn-typescript’s wire JSON — part of tsv.
format() formats one source in one call; a Formatter formats many on one thread,
keeping its arenas warm between calls. The formatter is non-configurable: Prettier’s style
with a print width of 100, tabs, single quotes and no trailing commas. Formatting output is
not frozen — it changes between releases as formatting bugs are fixed.
JSX and TSX are not supported: JSX is never read as JSX, so a source holding it fails to parse or is misread.
§Usage
use tsv_ts::{Formatter, Goal, format, format_with_goal, strip};
// Format in one call: Module, retried as Script only if that fails
assert_eq!(format("const x=42;")?, "const x = 42;\n");
// ...or at exactly one goal, with no retry
assert!(format_with_goal("with (a) b;", Goal::Module).is_err());
// Many sources on one thread: a `Formatter` keeps its arenas warm between calls
let mut formatter = Formatter::new();
assert_eq!(formatter.format("let y=1")?, "let y = 1;\n");
// Strip the types, keeping every surviving character at its line and column
assert_eq!(strip("let x: number = 1;")?.code, "let x = 1;");
// Parse to the wire JSON AST (the `json` feature, on by default)
let json = tsv_ts::parse_to_json("const x: number = 42;")?;
assert!(json.starts_with(r#"{"type":"Program""#));The wire JSON is the AST as JSON in the canonical parser’s shape — acorn-typescript’s
here. Its start / end are UTF-16 offsets, and the default wire is span-only: no loc
(the locations feature adds it). Error::span, unlike the wire, is a UTF-8 byte range.
§Errors
Formatting and parsing return an Error that says what kind of failure it is
(Error::kind), what went wrong on one line (Error::message), where it is in the
input’s UTF-8 bytes (Error::span), and how tsv’s bindings (its npm packages) report it
(Error::to_report: the message and the frame apart, positions in UTF-16 offsets and
1-based lines). {} prints one line; {:#} adds the frame the tsv CLI prints.
use tsv_ts::{ErrorKind, format};
let err = format("const x=").unwrap_err();
assert_eq!(err.kind(), ErrorKind::Syntax);
assert_eq!(err.span(), Some(8..8));
assert_eq!(err.message(), "Expected expression, found end of input");
assert_eq!(err.to_string(), "Expected expression, found end of input (1:9)");
assert_eq!(
format!("{err:#}"),
"Expected expression, found end of input\n1:9 const x=\n ^ here"
);
let report = err.to_report().expect("a located error reports");
assert_eq!((report.start.offset, report.start.line, report.start.column), (8, 1, 8));Every tsv language crate re-exports this same Error (tsv_css::Error and
tsv_svelte::Error are this type), so one match covers them all.
A refusal (ErrorKind::Refusal) is a source that parses but that an operation cannot
handle, with a span over the refused bytes; the type strip reports its refusals as
StripError::Unsupported. A source longer than 4 GiB − 1 bytes is
ErrorKind::TooLarge, with no span.
§Goals
A TypeScript source parses against a Goal — ECMAScript’s Module or Script grammar —
and each entry point reads it in one of two tiers:
- The bare name names no goal.
format()andstrip()parse atModuleand retry atScriptonly if that fails, so a legacy sloppy script (awithstatement, a leading-zero literal,awaitas a name) formats and strips while a module-valid source is never reinterpreted — neither output depends on which grammar read it.parse_to_jsonparses atModulealone, with no retry: the wire’sProgram.sourceTypeis a claim about which grammar produced it, which a retry would make depend on the input. - A named goal —
format_with_goal,strip_with_goal, andJsonOptions::goalforparse_to_json_with— parses at exactly that goal, with no retry: a source the goal’s grammar rejects is an error even when the other goal would accept it.
When both attempts of a fallback fail, the error reported is the attempt’s whose grammar
the file was written against, decided in two steps. A script attempt that died on a
goal gate — a top-level import / export, an import.meta, a top-level for await,
or the operand a module reads after a top-level await, the constructs only a module
holds — has proved the file a module whatever else is in it, so the module attempt’s
error is the file’s own, even one that sits after the real error (definitions first,
export at the bottom). Otherwise the error that reached furthest into the source
is the file’s own, the module attempt’s on a tie: a broken sloppy script’s module attempt
dies early at its first with / legacy literal / await name — the very construct the
retry exists to admit — so reporting that would point a legacy script’s author away
from their typo. A module attempt the depth limit refused (ErrorKind::TooDeep) takes
no retry: the limit is no verdict on the grammar.
§Deep input
The parsers refuse a tree past their depth limits with ErrorKind::TooDeep, so every
walk of an accepted tree fits a 2 MiB thread in an optimized build and a 16 MiB one in a
debug build. A stack overflow aborts the process rather than panicking, so a debug build
handling input it does not control — cargo test runs each test on a 2 MiB thread — wraps
the call in run_with_stack, which runs it on a thread reserving STACK_SIZE.
§Features
json(default) —parse_to_json,parse_to_json_withandJsonOptions. Turn default features off for a format-and-strip build.locations—JsonOptions::locations, adding aloc(line and column) to every node of the wire.
The _internal_* features are tsv’s own diagnostics, not for consumers
(Stability).
§Stability
This release exposes no AST: parsing returns the wire JSON, in acorn-typescript’s shape,
which @fuzdev/tsv-parse-wasm’s tsv_ast.d.ts types.
Items hidden from these docs (__private, ast and every other #[doc(hidden)] item)
and the _internal_* features are tsv’s own plumbing for its sibling crates, which pin
each other exactly; they change in any release, outside semver.
Structs§
- Error
- Why a parse or format produced no output — a syntax error, a depth limit, a refusal of
a source that parses, or a source over the size cap (
ErrorKind): what went wrong (Error::kind,Error::message), where (Error::span, UTF-8 byte offsets), and the pointed report tsv’s bindings (its npm packages) render (Error::to_report). One pointer wide, so aResultcarrying it costs a successful call almost nothing. - Error
Report - A pointed error as tsv’s bindings (its npm packages) report it: the message and the frame
apart, and the span in the document’s wire coordinates (
Error::to_report). The error’s full text (its{:#}form) is exactlymessage, a\n, thenframe. - Formatter
- Formats many TypeScript sources on one thread, reusing its allocations from one source to the next.
- Json
Options - How
parse_to_json_withparses and what its wire carries. - Parse
Goal Error - The error
Goal’sFromStrreturns for a string that names no goal — anything but"module"or"script". - Stripped
- A successful
strip. - Wire
Point - One position in a language’s wire coordinates, the positions its wire JSON (the parse
output: the AST as JSON in the canonical parser’s shape) carries. It holds the offset a
wire node starting at that byte would carry as its
start, and thelocpointtsv parse --locations(and the npm packages’create_locator(…).position_at(start)) gives it. The coordinates are the language’s canonical parser’s: a leading byte-order mark counted for TypeScript (acorn) and elided for Svelte and CSS, and lines ended by ECMAScript’s terminators for TypeScript and by\nalone for Svelte and CSS.
Enums§
- Error
Kind - What kind of failure an
Erroris (Error::kind). - Goal
- The syntactic goal symbol a parse runs against.
- Strip
Error - Why
stripproduced no output. Either way the failure is aError(StripError::error), so it displays, spans and reports as every other tsv error does: one line under{}, theline:colheader, excerpt and caret under{:#}. - Unsupported
Construct - The TypeScript
striprefuses: syntax that type blanking cannot erase, because erasure would have to transform it rather than blank it.
Constants§
- STACK_
SIZE - Stack reserved by
run_with_stack: 32 MiB, twice the debug build’s contract.
Functions§
- format
- Parse and format
sourcein one call, with no source type named. - format_
with_ goal - Parse and format
sourcein one call at exactlygoal, with no Module-to-Script fallback. - parse_
to_ json - Parse
sourceas a module and return its wire JSON AST: compact JSON in the acorn-typescript shape, every node’sstart/enda UTF-16 offset and noloc. - parse_
to_ json_ with parse_to_jsonunderoptions: the goal it parses at (exact, no retry) and whether the wire carriesloc.- run_
with_ stack - Run
fon a thread reservingSTACK_SIZEand return its value. - strip
- Strip the types from TypeScript
source, keeping every surviving character at its line and UTF-16 column. - strip_
with_ goal stripat exactlygoal, with no Module-to-Script fallback: a source the goal’s grammar rejects is aStripError::Parseeven when the other goal would accept it.