badness 0.6.0

A language server, formatter, and linter for LaTeX
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
<!-- Generated by `cargo run --example docgen`. Do not edit by hand: edit each rule's `description()`/`examples()` in `src/linter/rules/` and regenerate. -->

# Linter Rules

`badness lint` runs a set of built-in rules over each file's parse tree and
reports a diagnostic for every finding. This page is the catalogue: one section
per rule, keyed by its stable **rule id**. That id is what appears in a
diagnostic, what `[lint]` `select`/`ignore` (and `--select`/`--ignore`) target,
and what a `% badness-ignore <id>` comment suppresses.

Every rule is **on by default**; narrowing happens only through `select`/`ignore`
(see [Configuration](#configuration)). Where a rewrite is unambiguous a rule
carries an **auto-fix**: a *safe* fix (shown below as "After applying the fix")
is applied by `badness lint --fix`; an *unsafe* fix, one that may change output
such as inserting a line-breaking tie, is applied only with `--unsafe-fixes` or
as an editor code action, so it has no "after" block here.

Each example below is linted live to produce its diagnostic and fixed output, so
this page never drifts from the rules' actual behavior.

This page covers the **LaTeX** linter. BibTeX files have a parallel set of rules
(a separate `BibRule` registry under `src/bib/linter/`), selectable through the
same `[lint]` config but not yet catalogued here.

## `abbreviation-spacing`

Flag TeX's sentence-vs-interword spacing going wrong around abbreviations and acronyms (ChkTeX 12/13). Outside `\frenchspacing`, TeX widens the space after `.`/`?`/`!` unless the punctuation follows an uppercase letter. Two shapes defeat that: a lowercase abbreviation (`e.g.`, `i.e.`, `etc.`, `et al.`) gets a too-wide space, fixed with `\ ` (`e.g.\ foo`); and an uppercase acronym ending a sentence (`USA.`) gets a too-narrow space, fixed with `\@` (`USA\@.`). To stay conservative the first fires only before a lowercase word (the sentence clearly continues) and the second only for a run of two or more capitals before the period and before an uppercase word (a new sentence), so initials (`J.`), dotted forms (`U.S.A.`), and mid-sentence acronyms are left alone. Both fixes are **unsafe** -- they change the typeset spacing -- so `--fix` leaves them alone while `--unsafe-fixes` and the editor code action apply them. The rule is silent under `\frenchspacing`, and never touches comments, verbatim, or math.

A lowercase abbreviation followed by more text takes an interword space:

```tex
We tried several methods, e.g. gradient descent.
```

```text
warning: abbreviation-spacing
 --> example.tex:1:31
  |
1 | We tried several methods, e.g. gradient descent.
  |                               ^ `e.g.` is an abbreviation, not a sentence end; use an interword space `\ ` (`e.g.\ `) so TeX does not widen the gap
```

An acronym ending a sentence takes intersentence spacing:

```tex
The rover reached the USA. Then it stopped.
```

```text
warning: abbreviation-spacing
 --> example.tex:1:26
  |
1 | The rover reached the USA. Then it stopped.
  |                          ^ capital before sentence-ending punctuation suppresses intersentence spacing; use `\@` (`Word\@.`) to restore it
```

## `duplicate-label`

Flag a `\label{key}` defined more than once in the same label namespace -- within one file, or across files that share a document when a project view is available. LaTeX itself only warns and silently keeps the last definition. No autofix: resolving a collision (rename vs delete) is the author's call.

The same key defined twice in one file:

```tex
\section{One}\label{sec:x}
\section{Two}\label{sec:x}
```

```text
warning: duplicate-label
 --> example.tex:2:14
  |
2 | \section{Two}\label{sec:x}
  |              ^^^^^^^^^^^^^ label `sec:x` is defined more than once
```

## `deprecated-command`

Flag the obsolete two-letter font *switches* (`\bf`, `\it`, `\rm`, `\sf`, `\tt`, `\sc`, `\sl`) that LaTeX 2e superseded with the `\...series`/`\...shape`/`\...family` declarations. `\em` is not flagged; it is still the supported emphasis switch. The autofix swaps just the control word (`\bf` -> `\bfseries`), leaving any following text untouched, so it is correct by construction.

An obsolete two-letter font switch:

```tex
{\bf important}
```

```text
warning: deprecated-command
 --> example.tex:1:2
  |
1 | {\bf important}
  |  ^^^ `\bf` is deprecated; use `\bfseries`
```

After applying the fix:

```tex
{\bfseries important}
```

## `missing-nonbreaking-space`

Flag a plain space where a TeX tie (`~`) belongs, before a command whose output a line break would orphan: a bare-number reference (`Figure \ref{x}`, `\eqref`, `\pageref`) or a bracketed citation (`see \cite{a}`, `\parencite`, `\autocite`). A tie keeps the reference on the same line. Self-describing references (`\autoref`, `\cref`) and textual citations (`\textcite`, `\citet`) are not flagged -- they emit their own noun, so a break orphans nothing. Only the same-line `WORD SPACE \cmd` shape is flagged. The fix is **unsafe** -- inserting a tie changes line breaking -- so `--fix` leaves it alone; `--unsafe-fixes` and the editor code action apply it.

A plain space where a tie belongs before a cross-reference:

```tex
see Figure \ref{fig:plot}
```

```text
warning: missing-nonbreaking-space
 --> example.tex:1:11
  |
1 | see Figure \ref{fig:plot}
  |           ^ missing non-breaking space before `\ref`; use a tie `~` so the reference stays on the same line
```

## `obsolete-environment`

Flag math environments the community has superseded, naming the modern replacement in the message. The canonical case is `eqnarray`, which `amsmath` replaced with `align` decades ago (it mis-spaces relations and is a perennial l2tabu warning). Reports only; the swap is left for a later autofix.

The superseded `eqnarray` environment:

```tex
\begin{eqnarray}
  a &=& b
\end{eqnarray}
```

```text
warning: obsolete-environment
 --> example.tex:1:7
  |
1 | \begin{eqnarray}
  |       ^^^^^^^^^^ `eqnarray` is obsolete; use `align`
```

## `primitive-command`

Flag raw plain-TeX primitives discouraged in LaTeX source, naming the LaTeX construct that supersedes each one (ChkTeX 41, lacheck, l2tabu). A sibling of `deprecated-command`, which covers the obsolete font switches. Most primitives are reported only: their LaTeX replacement restructures arguments (`a \over b` becomes `\frac{a}{b}`, `\centerline{x}` becomes a `\centering` declaration or a `center` environment), so no single textual edit can rewrite them correctly by construction. A few carry a `Safe` autofix — a 1:1 control-word swap for a primitive whose LaTeX form is a single meaning-identical token (`\sb`/`\sp` become `_`/`^`); the swap replaces just the control word, so it stays lossless and meaning-preserving.

A plain-TeX fraction primitive (report-only; the LaTeX form restructures its operands):

```tex
$a \over b$
```

```text
warning: primitive-command
 --> example.tex:1:4
  |
1 | $a \over b$
  |    ^^^^^ `\over` is a raw TeX primitive; use `\frac{...}{...}`
```

The plain-TeX subscript alias, carrying a safe swap to `_`:

```tex
$x\sb2$
```

```text
warning: primitive-command
 --> example.tex:1:3
  |
1 | $x\sb2$
  |   ^^^ `\sb` is a raw TeX primitive; use `_`
```

After applying the fix:

```tex
$x_2$
```

## `dollar-display-math`

Flag plain-TeX `$$...$$` display math. `$$` is a TeX primitive that bypasses `amsmath` spacing hooks and breaks `fleqn`/`\everydisplay`, so LaTeX steers users to `\[...\]`. The autofix swaps the delimiters and copies the body verbatim, so it parses and stays lossless; it is withheld when the display math is unclosed.

Plain-TeX display math:

```tex
$$a + b = c$$
```

```text
warning: dollar-display-math
 --> example.tex:1:1
  |
1 | $$a + b = c$$
  | ^^ `$$…$$` is plain-TeX display math; use `\[…\]`
```

After applying the fix:

```tex
\[a + b = c\]
```

## `ellipsis`

Flag a literal run of three or more periods (`...`) where a real ellipsis command belongs. `...` sets three tight full stops; LaTeX's ellipsis commands set correctly spaced dots. In text the fix is a **safe** swap to `\dots` (a space is added before a following letter so the control word cannot glue onto the next word). In math `\ldots` (baseline, for comma lists) and `\cdots` (centered, for operator chains) are not interchangeable, so the fix is **unsafe**: it guesses from the neighboring atoms -- an operator or relation picks `\cdots`, otherwise `\ldots` -- and applies only under `--unsafe-fixes` or as an editor code action. Comments and verbatim are never touched.

Literal dots in text:

```tex
See Chapter 2, 3, ... for details.
```

```text
warning: ellipsis
 --> example.tex:1:19
  |
1 | See Chapter 2, 3, ... for details.
  |                   ^^^ literal `...` ellipsis; use `\dots`
```

After applying the fix:

```tex
See Chapter 2, 3, \dots for details.
```

Literal dots in a math sum (an operator neighbor picks `\cdots`):

```tex
$a_1 + ... + a_n$
```

```text
warning: ellipsis
 --> example.tex:1:8
  |
1 | $a_1 + ... + a_n$
  |        ^^^ literal `...` ellipsis; use `\cdots` in math (`\ldots` for lists, `\cdots` for operator chains)
```

## `hard-coded-reference`

Flag a literal cross-reference written in prose -- `Figure 3`, `Table~1`, `Section 2` -- instead of `\ref`/`\cref` to a `\label` (textidote sh:hcfig/hctab/hcsec). Hard-coding the number defeats LaTeX's automatic numbering: renumbering a float or reordering sections silently breaks the reference and drops the hyperlink. The rule is **report-only** -- the correct rewrite needs the label the number refers to, which is not in the text, so no autofix is offered. To stay conservative it fires only for a capitalized reference word (`Figure`, `Table`, `Section`, `Eq.`, ...) matched as a whole word and directly followed, across one space or a tie `~`, by an arabic number; plurals, lowercase, `Figure~\ref{x}`, and `Figure three` are left alone. It never touches math, comments, or verbatim.

A hard-coded figure number instead of a cross-reference:

```tex
See Figure 3 for the results.
```

```text
warning: hard-coded-reference
 --> example.tex:1:5
  |
1 | See Figure 3 for the results.
  |     ^^^^^^^^ hard-coded reference `Figure 3`; use `\ref`/`\cref` to a `\label` so the number stays in sync
```

Even tied with `~`, the number is still hard-coded:

```tex
Table~1 lists the parameters.
```

```text
warning: hard-coded-reference
 --> example.tex:1:1
  |
1 | Table~1 lists the parameters.
  | ^^^^^^^ hard-coded reference `Table~1`; use `\ref`/`\cref` to a `\label` so the number stays in sync
```

## `straight-quotes`

Flag a literal ASCII double quote (`"`) used for quotation. In LaTeX a straight `"` always sets a *closing* double quote, so an opening one comes out backwards; the correct forms are `` `` `` (two backticks) to open and `''` (two apostrophes) to close. The fix is **unsafe**: it infers direction from context -- a quote preceded by whitespace, a line break, an opening delimiter (`(`, `[`, `{`), a backtick, or the start of the document opens, anything else closes -- and applies only under `--unsafe-fixes` or as an editor code action, since the guess can flip the typeset glyph. Single straight quotes (`'`) are left alone (they are legitimately apostrophes), and comments, verbatim, and math are never touched.

Straight ASCII double quotes around a phrase:

```tex
He said "hello world" to me.
```

```text
warning: straight-quotes
 --> example.tex:1:9
  |
1 | He said "hello world" to me.
  |         ^ straight double quote; use `` `` `` (opening) or `''` (closing) -- inferred opening here
warning: straight-quotes
 --> example.tex:1:21
  |
1 | He said "hello world" to me.
  |                     ^ straight double quote; use `` `` `` (opening) or `''` (closing) -- inferred closing here
```

An opening quote after a parenthesis:

```tex
("quoted")
```

```text
warning: straight-quotes
 --> example.tex:1:2
  |
1 | ("quoted")
  |  ^ straight double quote; use `` `` `` (opening) or `''` (closing) -- inferred opening here
warning: straight-quotes
 --> example.tex:1:9
  |
1 | ("quoted")
  |         ^ straight double quote; use `` `` `` (opening) or `''` (closing) -- inferred closing here
```

## `swallowed-space`

Flag a text-producing control word directly followed by a space that TeX eats, gluing the macro's output to the next word (`\LaTeX is` renders "LaTeXis") (ChkTeX 1). When TeX tokenizes a control word it discards following spaces, so the space never reaches the output. To stay conservative the rule fires only for a curated set of argument-less TeX-family logos (`\LaTeX`, `\TeX`, `\BibTeX`, ...), only in text mode, and only when the next token is a word beginning with an alphanumeric character -- a following period (`\LaTeX .` -> "LaTeX.") is what the author wanted. The fix inserts `{}` after the control word (`\LaTeX{} is`), ending the macro name so the space survives; it is **unsafe** because it changes the typeset output, so `--fix` leaves it alone while `--unsafe-fixes` and the editor code action apply it.

A logo swallows the following space, gluing it to the next word:

```tex
We used \LaTeX to typeset this.
```

```text
warning: swallowed-space
 --> example.tex:1:15
  |
1 | We used \LaTeX to typeset this.
  |               ^ `\LaTeX` swallows the following space; add `{}` (`\LaTeX{}`) or `\ ` so it prints
```

## `space-before-command`

Flag a plain space directly before a command that should hug the preceding word -- `\footnote`, `\footnotemark`, `\index`, `\label` (ChkTeX 24/42). A space before `\footnote` sets a spurious space before the footnote mark (`word \footnote{x}` -> "word ¹"); a space before a zero-width `\index`/`\label` leaves a stray inter-word gap that can shift the recorded page. The fix deletes the space. It is **unsafe** -- removing the space changes the typeset spacing -- so `--fix` leaves it alone while `--unsafe-fixes` and the editor code action apply it. To stay conservative only the same-line `WORD SPACE \cmd` shape is flagged (a space at line start or after a brace is left alone), and math is skipped (an inter-token space is insignificant there), covering both `$…$` and math environments like `equation`/`align`.

A space before a footnote sets a spurious space before the mark:

```tex
This is important \footnote{See the appendix.}
```

```text
warning: space-before-command
 --> example.tex:1:18
  |
1 | This is important \footnote{See the appendix.}
  |                  ^ spurious space before `\footnote`; delete it so no stray space is typeset before the command
```

## `mismatched-delimiter`

Flag a `\left ... \right` pair whose delimiter glyphs point the wrong way -- a closing glyph opening the pair, or an opening glyph closing it (`\left) ... \right(`). Deliberately conservative: only an *orientation* error is flagged, never a mere opener/closer mismatch, since half-open intervals like `\left( ... \right]` are legitimate. Structural faults (a missing `\right`) are reported by the parser, not this rule. No autofix: the intended glyphs are ambiguous.

A `\left`/`\right` pair whose glyphs point the wrong way:

```tex
$\left) x \right($
```

```text
warning: mismatched-delimiter
 --> example.tex:1:7
  |
1 | $\left) x \right($
  |       ^ `\left)` uses a closing delimiter where an opening one is expected
warning: mismatched-delimiter
 --> example.tex:1:17
  |
1 | $\left) x \right($
  |                 ^ `\right(` uses an opening delimiter where a closing one is expected
```

## `dash-length`

Flag a dash of the wrong length for its context (ChkTeX 8). LaTeX sets a hyphen from `-`, an en dash from `--`, and an em dash from `---`. Between two numbers a range takes an en dash, so `5-10` or `5---10` is flagged with an **unsafe** fix to `--` (unsafe because it changes the typeset glyph and a hyphen between numbers is occasionally intentional). Between two words an en dash (`--`) is almost always a mistake, but whether a hyphen or an em dash was meant is ambiguous, so it is reported **without** a fix -- except when it joins coordinate proper names (`Barzilai--Borwein`, `Newton--Raphson`), detected by an uppercase first letter on either flank, where the en dash is correct and the finding is suppressed. To stay conservative the rule only inspects a dash run that sits inside a single word with content on both sides and is the only dash run in that word, so dates (`2020-01-15`), ISBNs, spaced dashes, and option flags (`--verbose`) are left alone. Comments, verbatim, and math are never touched.

A hyphen where a number range wants an en dash:

```tex
See pages 5-10 for the proof.
```

```text
warning: dash-length
 --> example.tex:1:12
  |
1 | See pages 5-10 for the proof.
  |            ^ hyphen between numbers; use an en dash `--` for a number range
```

An en dash between words (ambiguous, so reported without a fix):

```tex
A well--known result.
```

```text
warning: dash-length
 --> example.tex:1:7
  |
1 | A well--known result.
  |       ^^ en dash `--` between words; use a hyphen `-` for a compound or an em dash `---` for a break
```

## `times-variable`

Flag a literal `x` used as a multiplication sign between two numbers, such as `640x200` or `3x3` (ChkTeX 29). TeX sets that `x` as an italic letter rather than the `\times` cross, so it reads wrong. The rule only fires when the whole word is `digits x digits` -- one lowercase `x` with ASCII digits on both sides and nothing else -- so ordinary words (`matrix`), spaced products (`n x m`), and hex literals (`0xFF`, `0x12`) are left alone. The fix is **unsafe** (a bare `x` between numbers is usually a cross but occasionally a real variable): inside math it rewrites the `x` to `\times`, and in text it wraps it as `$\times$` so the result still compiles. So `--fix` leaves it alone; `--unsafe-fixes` and the editor code action apply it.

A literal `x` as a multiplication sign in text (fixed to `$\times$`):

```tex
A 640x200 pixel image.
```

```text
warning: times-variable
 --> example.tex:1:6
  |
1 | A 640x200 pixel image.
  |      ^ literal `x` as a multiplication sign between numbers; use `\times` for a cross
```

The same inside math mode (fixed to `\times`):

```tex
The grid is $640x200$ cells.
```

```text
warning: times-variable
 --> example.tex:1:17
  |
1 | The grid is $640x200$ cells.
  |                 ^ literal `x` as a multiplication sign between numbers; use `\times` for a cross
```

## `math-operator-name`

Flag a bare log-like function name (`sin`, `cos`, `log`, `lim`, and the rest of the LaTeX/amsmath set) written in math mode without its backslash, so TeX sets it as italic variables instead of the upright `\sin` operator with correct spacing (ChkTeX 35). It fires when the name starts a `WORD` and ends at a word boundary, catching both `$sin x$` and the glued `$sin(x)$`, while leaving words that merely begin with one (`since`) alone and preferring the longest match (`sinh` over `sin`). To stay conservative it only fires inside math mode and never inside a subscript or superscript, where `max` in `x_{max}` is almost always a label rather than the operator. The fix inserts the backslash (`sin` -> `\sin`); it is **unsafe** because it changes the typeset output (upright glyph and operator spacing) and a bare `sin` is occasionally a real product, so `--fix` leaves it alone while `--unsafe-fixes` and the editor code action apply it.

A bare function name typesets as italic variables:

```tex
$sin x + cos x = 1$
```

```text
warning: math-operator-name
 --> example.tex:1:2
  |
1 | $sin x + cos x = 1$
  |  ^^^ bare `sin` in math typesets as italic variables; use `\sin`
warning: math-operator-name
 --> example.tex:1:10
  |
1 | $sin x + cos x = 1$
  |          ^^^ bare `cos` in math typesets as italic variables; use `\cos`
```

It fires through the glued `f(x)` form too:

```tex
The limit $lim(x)$ diverges.
```

```text
warning: math-operator-name
 --> example.tex:1:12
  |
1 | The limit $lim(x)$ diverges.
  |            ^^^ bare `lim` in math typesets as italic variables; use `\lim`
```

## `makeat-macro`

Flag a macro whose name contains `@` (`\foo@bar`, `\p@`, `\@ifnextchar`) used outside a `\makeatletter`/`\makeatother` region. There `@` has its ordinary catcode, so it cannot be part of a control word: `\foo@bar` is read as `\foo` followed by the text `@bar`, not as a call to the internal macro `\foo@bar`. Usually the enclosing `\makeatletter`/`\makeatother` was forgotten. Because the formatter's lexer already tracks `\makeatletter` state, this is decided exactly -- an in-region name lexes as one token and is never flagged; only the split out-of-region form (control word abutting an `@`-word, or `\@` abutting a letter-word) is. Report-only: a correct fix would mean wrapping the use in `\makeatletter`/`\makeatother`, not a tight local edit, so no autofix is offered. The end-of-sentence `\@` (as in `NASA\@.`) is not flagged.

An internal `@` macro used without `\makeatletter`:

```tex
\my@command
```

```text
warning: makeat-macro
 --> example.tex:1:1
  |
1 | \my@command
  | ^^^^^^^^^^^ `\my@command` uses `@` in a macro name outside a `\makeatletter` region; `@` is not a letter here, so this reads as `\my` followed by the text `@command`
```

A leading-`@` macro (a `\@`-prefixed internal) outside a region:

```tex
\@ifstar{\StarredForm}{\PlainForm}
```

```text
warning: makeat-macro
 --> example.tex:1:1
  |
1 | \@ifstar{\StarredForm}{\PlainForm}
  | ^^^^^^^^ `\@ifstar` uses `@` in a macro name outside a `\makeatletter` region; `@` is not a letter here, so this reads as `\@` followed by the text `ifstar`
```

## `sectioning-level-jump`

Flag a heading that descends more than one sectioning level below the preceding heading -- `\section` straight to `\subsubsection`, skipping `\subsection` (textidote's `sh:secskip`). Standard sectioning commands form a fixed ladder (`\part`, `\chapter`, `\section`, `\subsection`, `\subsubsection`, `\paragraph`, `\subparagraph`); descending it a rung at a time keeps the outline sound, and a jump usually signals the wrong command. Only *downward* jumps between consecutive headings are flagged -- climbing back up to close sections is normal, as are repeated headings at one level. The comparison is relative to the previous heading, never an absolute top level, so an `article` opening with `\section` is fine. Report-only: repairing a skip (promote the heading or insert an intermediate one) is a structural choice for the author, not a correct-by-construction edit.

A heading that drops two levels at once (skipping `\subsection`):

```tex
\section{Introduction}
\subsubsection{Details}
```

```text
warning: sectioning-level-jump
 --> example.tex:2:1
  |
2 | \subsubsection{Details}
  | ^^^^^^^^^^^^^^ `\subsubsection` skips a sectioning level after `\section` (expected `\subsection`)
```

## `undefined-ref`

Flag a `\ref`-family reference to a label defined nowhere in the document. Sound only when the label namespace is complete, so it stays silent unless the project view is **closed** (every include resolves to an analyzed file) and **rooted**. Inert on stdin or wherever no cross-file label resolution is available. No autofix.

A reference to a label defined nowhere in the document:

```tex
\ref{sec:intro}
```

```text
warning: undefined-ref
 --> example.tex:1:1
  |
1 | \ref{sec:intro}
  | ^^^^^^^^^^^^^^^ reference to undefined label `sec:intro`
```

## `undefined-citation`

Flag a `\cite`-family key matching no entry in the document's bibliography -- the bibliographic analog of `undefined-ref`. Sound only over a **closed, rooted** namespace where every `.bib` resource resolves to an analyzed file, and suppressed entirely by a `\nocite{*}` wildcard (which marks every key as used). Inert without cross-file citation resolution. No autofix.

A citation of a key that matches no bibliography entry:

```tex
\cite{knuth:1984}
```

```text
warning: undefined-citation
 --> example.tex:1:1
  |
1 | \cite{knuth:1984}
  | ^^^^^^^^^^^^^^^^^ citation of undefined key `knuth:1984`
```

## Configuration

Rules are selected through the `[lint]` table in `badness.toml`, or the matching
CLI flags:

```toml
[lint]
# When present, an allowlist: only these rules run.
select = ["deprecated-command", "dollar-display-math"]
# Applied on top of select (or the default set): these are turned off.
ignore = ["missing-nonbreaking-space"]
```

An unknown rule id is reported at lint time, not rejected at config-parse time.
To suppress a rule at a single site, use a comment directive:

```tex
% badness-ignore deprecated-command: legacy code, leave as-is
{\bf here}
```

`% badness-ignore-file <id>: ...` suppresses one rule file-wide, and
`% badness-ignore-file: ...` suppresses all rules file-wide. Parse diagnostics
(rule id `parse`) are never suppressed by `select`/`ignore`.