Badness
Badness is a formatter, linter, and language server for LaTeX, built on a lossless concrete syntax tree.
It parses LaTeX once and serves three tools from that tree:
- Formatter (
badness format): deterministic, rule-based layout. - Linter (
badness lint): diagnostics with source snippets. - Language server (
badness lsp): both, live in your editor.
The architecture follows rust-analyzer: a generic, error-tolerant, hand-written parser produces a lossless tree, semantics are layered on top as a separate concern, and recomputation is incremental. badness never requires resolving macros or catcodes to succeed—anything it cannot statically recognize degrades to generic nodes rather than a crash. Two properties hold by construction and are enforced as tests: losslessness (the tree reconstructs the input byte-for-byte) and idempotence (formatting an already formatted file changes nothing).
Installation
Badness is written in Rust. Build from a checkout:
VS Code extension
If you use VS Code or a compatible editor (such as Positron or Cursor), install
the Badness
extension
from the VS Code Marketplace or the Open VSX
extension. It bundles the
badness binary and starts the language server automatically when you open a
.tex file.
Usage
# Format a file in place (or stdin → stdout with no path)
# Verify formatting without writing — exits non-zero if anything would change
# Lint, reporting parse diagnostics
# Run the language server over stdio
Formatter style is set through flags: --line-width (default 80),
--indent-width (default 2), and --wrap (reflow by default; also
preserve, with sentence/semantic planned). See the documentation for the
full reference.
Documentation
Full documentation lives at https://jolars.github.io/badness/ (built with
mdBook from docs/).
Contributing
Architecture, tenets, and conventions are documented in
AGENTS.md, written for both human and AI contributors. In short:
keep the syntactic layer free of semantic knowledge, every parser feature needs
corpus and snapshot tests plus a losslessness assertion, and code stays
rustfmt-clean with clippy warnings treated as errors.