tyc fmt
tyc fmt [PATHS]... [--check]Parses .ty files, runs the in-process whitespace normaliser, then — when ruff is on PATH and the post-normalised buffer contains no Typhon-only tokens — pipes the result through ruff format for spacing around :, =, ->, line wrapping, and quote normalisation. If ruff is missing or fails, the in-process output is kept.
The composition is deliberate: the in-process pass strips every Typhon-only keyword (let, mut, interface, model, gather, go, lazy, comptime, unsafe, extend, frozen, class!, plain class, ? operator, etc.) before ruff sees the buffer, so ruff never encounters syntax it can’t parse.
Examples
tyc fmt src/ # format every .ty in src/tyc fmt src/main.ty # format one filetyc fmt --check src/ # exit 1 if anything would change (CI mode)What the in-process pass changes
- Interior whitespace — runs of 2+ spaces past the leading indent are collapsed to one (
def main→def main). - Bracket adjacency —
( x→(x,x )→x),x ,→x,. - Comma spacing — a run of whitespace after
,is normalised to a single space (a, b→a, b). - Comment hash spacing —
#foo→# foo. Shebangs (#!…) and section-marker double-hash comments (## …) are left alone. - Leading tab expansion — a tab character at the start of a line is expanded to four spaces.
- Blank-line runs — three or more consecutive blank lines are collapsed to two.
- Trailing whitespace — removed (including from whitespace-only lines, which become empty).
- Line endings —
\n.
These rules all skip text inside '…', "…", and triple-quoted regions, so docstrings and embedded code stay verbatim.
What the ruff format wrap adds
When ruff is on PATH and the post-normalised buffer contains no Typhon-only tokens, the buffer is piped through ruff format --stdin-filename <path>. This brings ruff’s standard formatter rules to Typhon source for free:
- Spacing around
:,=,->with bracket-depth awareness (slice vs annotation distinction). - Long-line wrapping with consistent break points.
- Quote normalisation per
ruff format’s configured style.
If ruff is absent, returns non-zero, or the buffer still contains a Typhon-only token after the in-process pass, the in-process output is kept and a single warning is printed. The wrap is best-effort by design — tyc fmt always produces a valid, parseable result.
What it preserves
- Indentation depth — the leading run of spaces is kept; only the characters change (tab → four spaces).
- Typhon-only syntax —
let/mut/?/class!/plain class/frozen/gather/go/lazy/comptime/unsafe/extend/impl/interface/modelare recognised and round-tripped unchanged. - Comment text — content after the normalised
#prefix is kept verbatim; shebangs and## …lines are passed through unchanged.
Flags
| Flag | Effect |
|---|---|
--check (-c) | Exit 1 if any file would change. No writes. |
--check is the only flag. There is no --diff or --quiet; to see what would change, run tyc fmt --check, or format a copy and diff it against the original.
CI integration
- name: Format check run: tyc fmt --check src/Pre-commit
repos: - repo: local hooks: - id: tyc-fmt name: tyc fmt entry: tyc fmt language: system files: \.ty$Files the formatter cannot parse
A file that does not parse is reported and skipped — the walk carries
on through the rest of the tree, and the command exits non-zero at the end
with a count (N files could not be formatted). One work-in-progress file
therefore never stops tyc fmt src/ from formatting everything else, and
tyc fmt --check in CI surfaces every failure in a single run rather than
one per invocation.
The set of files the formatter rejects is the set tyc check rejects: both
run the same sugar-expansion chain over the original source before parsing.
Guarantees
Three properties hold over the whole .ty corpus and are gated by the test
suite:
- Convergence — a second
tyc fmtover already-formatted output is a no-op.tyc fmt --checkaftertyc fmtalways passes. - Acceptance parity — the formatter accepts exactly what
tyc checkaccepts. - Meaning is preserved —
tyc buildemits byte-identical Python before and after formatting. The one thing that can move is a generated__typhon_guard_Ntemporary, whose number is derived from the source line it came from; source maps are regenerated for the new line numbers.
Symlinks
tyc fmt never rewrites a file that resolves outside the project it was pointed at (the nearest directory above the path with a typhon.toml). A checked-out src -> ../elsewhere link, or a link inside the tree that leaves it, is skipped with a warning instead of being written through; a link that stays inside the project is followed. A file that is itself a symlink is never written through either.
What about tyc fmt on .dty?
Not today — tyc fmt collects .ty files only; a .dty path, or the .dty files under a directory, are skipped untouched. Stubs are short signature lists, so hand-formatting them is cheap, and tyc check --stubs still validates them.
Where next
tyc check— type-check after formatting.- Editor Setup — format-on-save.