Skip to content

Linting ​

badness lint parses each file and reports diagnostics with source snippets that point to the offending range. It exits with a nonzero status when it reports at least one diagnostic, so you can use it to fail a CI check.

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

Pass - to read standard input, or omit paths when piping: cat paper.tex | badness lint works too. At an interactive prompt with no paths, the command reports a usage error.

Parse diagnostics ​

Alongside lint findings, the linter reports parse diagnostics where the parser recovered from malformed input. A single problem never aborts the parse. Badness resumes at LaTeX boundaries such as \end{…}, \begin, a blank line, }, $, &, or \\, so it can report several independent diagnostics for one file 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 catalog, or print a single rule’s description and examples from the terminal:

sh
badness lint --explain deprecated-command

Most rules are on by default; the rule reference marks opt-in rules. Use extend-select in the [lint] table in badness.toml to enable additional rules while keeping the defaults:

toml
[lint]
extend-select = ["dash-length"]

The CLI flag --extend-select adds to the configured list and can be repeated. Set select or use --select to replace the default set, then add rules with extend-select. An empty select = [] leaves only those additional rules. Use ignore or --ignore to disable rules from either list; see the Configuration reference.

Suppress a rule at one site with a comment directive:

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

The directive’s verb determines its scope:

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>

The <rule> is optional. Omitting it suppresses every rule over the 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, write the same directive inside an @comment entry, since BibTeX has no line-comment token:

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

The inert-suppression rule warns when a directive cannot act—for example, a dangling skip, an unmatched on, an unclosed off, a directive written as typeset prose on a .dtx documentation line, or a format-only directive in a .bib file.

Some rules ship an autofix. 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).

Human-readable output ​

The default pretty output renders diagnostics with source snippets on stderr. The snippets are colorized when stderr is a terminal; --color always|never overrides that, and NO_COLOR is honored.

Use --output concise for plain, compact diagnostics on stderr or --output json for structured output on stdout. Both stay plain regardless of --color.

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. External tools such as Panache use this output to lint latex code blocks in Markdown documents.

json
[
  {
    "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 use zero-based byte offsets into the named file rather than line and column numbers. 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 autofix. 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.

The schema differs from those of arity and fatou 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.