Badness is a language server, formatter, and linter 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).
Badness is available from several sources:
- crates.io:
cargo install badness - Homebrew:
brew install jolars/tap/badness - npm:
npm install -g badness(bundles a prebuilt binary) - PyPI:
uv tool install badness/pipx install badness - Aqua:
aqua install jolars/badness - Prebuilt binaries: from the releases page
- VS Code/Open VSX: the Badness extension (also works in Positron and Cursor)
- From source:
cargo install --path .in a checkout
If you prefer a one-liner installer that picks the right release artifact for your platform, you can use the installer scripts below. These scripts are fetched directly from this repository and then download the latest matching Badness release asset for your platform, installing to a user-local directory by default. If you prefer, download and inspect the script before running it.
For macOS and Linux:
curl --proto '=https' --tlsv1.2 -LsSf \
https://github.com/jolars/badness/releases/latest/download/badness-installer.sh | shFor Windows PowerShell:
powershell -NoProfile -ExecutionPolicy Bypass -Command "irm https://github.com/jolars/badness/releases/latest/download/badness-installer.ps1 | iex"The VS Code/Open VSX extension bundles the badness binary and starts the
language server automatically when you open a .tex file.
# Format a file in place (or stdin → stdout with no path)
badness format paper.tex
# Verify formatting without writing, showing diffs
badness format --check paper.tex
# Lint, reporting parse diagnostics
badness lint paper.tex
# Run the language server over stdio
badness lspFormatting is configurable via a TOML file named badness.toml. See the
documentation for the full reference.
The language server runs over stdio (badness lsp); see the editor setup
guide for Neovim and VS Code
wiring.
badness-pre-commit provides pre-commit hooks for linting and formatting. It installs a prebuilt binary wheel from PyPI, so no Rust toolchain or LaTeX distribution is required:
repos:
- repo: https://github.com/jolars/badness-pre-commit
# badness version
rev: v0.11.0
hooks:
# Lint .tex, .sty, .cls, .dtx, .ins, and .bib files
- id: badness-lint
# Format the same files in place
- id: badness-formatbadness-action installs badness and runs format and lint checks in CI:
jobs:
badness:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: jolars/badness-action@v1See https://badness.dev/ for the full documentation, including a user guide, reference, and developer guide.
See CONTRIBUTING.md.