Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Linting

badness lint parses each file and reports diagnostics, rendered with source snippets pointing at the offending range. It exits non-zero when there is at least one diagnostic, which makes it usable as a CI gate.

badness lint paper.tex
cat paper.tex | badness lint   # stdin

Parse diagnostics

Alongside the rules, the linter surfaces parse diagnostics: places where the parser recovered from malformed input. Because the parser is error-tolerant, a single problem never aborts the parse—badness anchors recovery on clean LaTeX boundaries (\end{…}, \begin, a blank line, }, $, &, \\) and keeps going, so one file can report several independent diagnostics in one run. Parse diagnostics carry the rule id parse and are never silenced by select/ignore.

Rules

Beyond parse recovery, badness ships a growing set of built-in rules (deprecated-command, dollar-display-math, undefined-ref, and more). Each has a stable id used in diagnostics, config, and suppression comments. See the Linter Rules reference for the full catalogue, or print a single rule’s description and examples from the terminal:

badness lint --explain deprecated-command

Every rule is on by default. Narrow the active set through the [lint] table in badness.toml or the matching --select/--ignore CLI flags; see the Configuration reference.

Suppress a rule at one site with a comment directive:

% badness-lint skip deprecated-command: legacy code
{\bf here}

The verb carries the scope, and there are three:

ScopeDirective
The next construct% badness-lint skip <rule>: <reason>
A region% badness-lint off <rule>% badness-lint on <rule>
The whole file% badness-lint skip-file <rule>: <reason>

Naming the <rule> is optional—leave it out and the directive covers every rule over that same span. An off with no matching on runs to the end of the file. The : <reason> tail is optional everywhere and is never interpreted.

Each has a bare counterpart that turns off the formatter at the same time: % badness skip, % badness off / % badness on, and % badness skip-file. For layout only, use the % badness-format spellings described in Formatting.

In .bib files the same grammar rides an @comment entry, since BibTeX has no line-comment token:

@comment{badness-lint skip missing-required-field: publisher long gone}
@book{oldbook, title = {An Orphaned Book}}

Some rules ship an auto-fix. badness lint --fix applies the meaning-preserving (Safe) ones; --unsafe-fixes also applies fixes that may change output, such as missing-nonbreaking-space (inserting a tie changes line breaking), abbreviation-spacing (inserting \ or \@ changes sentence spacing), or space-before-command (deleting a space before \footnote changes spacing).

Machine-readable output

badness lint --output json emits the findings as a JSON array on stdout (the human-readable pretty and concise modes write to stderr). A clean run emits [], so consumers always receive valid JSON; the exit code still signals whether findings exist. This is the contract external tools consume, e.g. panache when linting latex code blocks in Markdown documents.

[
  {
    "rule": "ellipsis",
    "severity": "warning",
    "path": "paper.tex",
    "start": 5,
    "end": 8,
    "message": "literal `...` ellipsis; use `\\dots`",
    "fix": {
      "edits": [{ "content": "\\dots", "start": 5, "end": 8 }],
      "applicability": "safe",
      "description": "Replace `...` with `\\dots`"
    },
    "related": []
  }
]

Ranges are 0-indexed byte offsets into the named file (no line/column resolution). severity is one of error, warning, info, or hint; applicability is safe or unsafe (the --fix/--unsafe-fixes split). The fix key is omitted when a finding has no auto-fix. An edit carries a path key only when it targets a different file than the diagnostic (a cross-file fix); related lists secondary “see also” locations.

Compared to the sibling tools arity and fatou, the schema differs in two ways: offsets are flat start/end keys rather than a range object, and message is a plain string rather than a structured object.