Linter Rules
badness lint runs a set of built-in rules over each file’s parse tree and reports a diagnostic for every finding. Each section describes one rule, identified by its stable rule id. The id appears in diagnostics and selects the rule in [lint] settings, --select and --ignore flags, and % badness-lint skip <id> comments.
Most rules are on by default. Each rule’s section states whether it is enabled by default. Use select to enable an opt-in rule, or use select and ignore in the [lint] table to narrow the default set. See the Configuration reference.
Some rules offer an autofix when a rewrite is unambiguous. badness lint --fix applies safe fixes, shown below under “After applying the fix”. An unsafe fix may change output, such as by inserting a tie that prevents a line break. It applies only with --unsafe-fixes or 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 and cataloged in BibTeX Linter Rules.
abbreviation-spacing
Flag incorrect sentence and interword spacing around abbreviations and acronyms (ChkTeX 12/13). Outside \frenchspacing, TeX widens the space after ., ?, or ! unless the punctuation follows an uppercase letter. A lowercase abbreviation (e.g., i.e., etc., et al.) therefore gets too much space, which \ corrects (e.g.\ foo). An uppercase acronym ending a sentence (USA.) gets too little space, which \@ corrects (USA\@.).
The rule flags a lowercase abbreviation only before a lowercase word, where the sentence clearly continues. It flags an acronym only when two or more capitals precede the period and an uppercase word follows it, indicating a new sentence. Initials (J.), dotted forms (U.S.A.), and mid-sentence acronyms are left alone. Both fixes are unsafe because they change typeset spacing, so --fix leaves them alone; --unsafe-fixes and editor code actions apply them. The rule is silent under \frenchspacing and never touches comments, verbatim content, or math.
This rule is enabled by default.
A lowercase abbreviation followed by more text takes an interword space:
We tried several methods, e.g. gradient descent.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 gapAn acronym ending a sentence takes intersentence spacing:
The rover reached the USA. Then it stopped.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 itblank-line-in-keyval
Flag a blank line at the top level of a key=value argument. A blank line produces a \par token, and a keyval processor reads its entries with macros that are not \long, so the call aborts. TeX’s error names the processor rather than the command the author wrote: \hypersetup yields “Paragraph ended before \kv@processor@default was complete”. This rule points to the source of that error.
The scope follows compilation checks: a blank line nested inside a value’s brace group (\tikzset{aa/.style={draw,\n\nthick}}) compiles cleanly and is not flagged. An unclosed { is left to the parser’s diagnostic. The rule consults only hand-curated signatures. The safe autofix removes the blank line and preserves the following indentation. It changes only whitespace, and ContentKind::Keyval establishes that the processor strips spaces around entries.
This rule is enabled by default.
A blank line separating two keys, which aborts the call:
\hypersetup{colorlinks=true,
linkcolor=blue}error: blank-line-in-keyval
--> example.tex:1:29
|
1 | \hypersetup{colorlinks=true,
| _____________________________^
2 | |
3 | | linkcolor=blue}
| |_^ blank line in `\hypersetup`'s key-value argument; the `\par` aborts the callAfter applying the fix:
\hypersetup{colorlinks=true,
linkcolor=blue}duplicate-label
Flag a label key defined more than once in the same label namespace, either within one file or across files that share a document when a project view is available. LaTeX warns but silently keeps the last definition.
Within a file, a warning requires a prior definition in the same conditional branch or an enclosing context. Separate conditional tests are treated as uncertain and do not trigger a warning. The rule recognizes \if...\else...\fi and common macros with complete braced arguments, including \ifthenelse, \iftoggle, and \IfFileExists. It does not evaluate predicates or combine coverage across branches. Cross-file checks use label namespaces. No autofix is offered because the author must choose whether to rename or delete a definition.
This rule is enabled by default.
The same key defined twice in one file:
\section{One}\label{sec:x}
\section{Two}\label{sec:x}warning: duplicate-label
--> example.tex:2:14
|
1 | \section{One}\label{sec:x}
| ----- first definition of `sec:x`
2 | \section{Two}\label{sec:x}
| ^^^^^^^^^^^^^ label `sec:x` is defined more than oncedeprecated-command
Flag the obsolete two-letter font switches (\bf, \it, \rm, \sf, \tt, \sc, \sl) that LaTeX 2e superseded with \...series, \...shape, and \...family declarations. The rule leaves \em alone because it remains the supported emphasis switch. If the file redefines a name (\renewcommand{\sl}{…}, \def\rm{…}), the rule treats it as the user’s macro and does not flag it anywhere.
The autofix replaces only the control word (\bf → \bfseries), leaving following text untouched. It is withheld where the switch is merely referenced (\let\x\rm, \ifx\rm\y).
This rule is enabled by default.
An obsolete two-letter font switch:
{\bf important}warning: deprecated-command
--> example.tex:1:2
|
1 | {\bf important}
| ^^^ `\bf` is deprecated; use `\bfseries`After applying the fix:
{\bfseries important}deprecated-suppression-syntax
Flag the retired % badness-ignore <rule> and % badness-ignore-file [<rule>] suppression spellings, which remain accepted for compatibility but are no longer documented. The Safe autofix rewrites only the family and verb to % badness-lint skip <rule> or % badness-lint skip-file [<rule>]; the selector and reason remain byte-for-byte unchanged, and the edit stays entirely inside a comment. This meta diagnostic is not silenced by the retired directive it reports; use [lint].ignore to disable the rule deliberately.
This rule is enabled by default.
A retired suppression directive:
% badness-ignore deprecated-command: legacy source
{\bf text}warning: deprecated-suppression-syntax
--> example.tex:1:3
|
1 | % badness-ignore deprecated-command: legacy source
| ^^^^^^^^^^^^^^ retired suppression syntax; use `% badness-lint skip` insteadAfter applying the fix:
% badness-lint skip deprecated-command: legacy source
{\bf text}missing-nonbreaking-space
Flag a plain space 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 TeX tie (~) keeps the reference on the same line. Self-describing references (\autoref, \cref) and textual citations (\textcite, \citet) are not flagged because they supply their own noun.
The rule flags both a same-line space and a single source line break before the command. A blank line starts a new paragraph and is not flagged. For a same-line space, the fix is unsafe because inserting a tie changes line breaking. --fix leaves it alone; --unsafe-fixes and editor code actions apply it. A line break is reported without a fix: replacing it with ~ would join the lines, a reflow the formatter owns.
This rule is enabled by default.
A plain space where a tie belongs before a cross-reference:
see Figure \ref{fig:plot}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 lineobsolete-environment
Flag superseded math environments and name the modern replacement in the diagnostic. The canonical case is eqnarray, which amsmath replaced with align decades ago because it spaces relations incorrectly. It is also a recurring l2tabu warning. The autofix renames the \begin and \end pair in place and leaves the body untouched, so it is correct by construction.
This rule is enabled by default.
The superseded eqnarray environment:
\begin{eqnarray}
a &=& b
\end{eqnarray}warning: obsolete-environment
--> example.tex:1:7
|
1 | \begin{eqnarray}
| ^^^^^^^^^^ `eqnarray` is obsolete; use `align`After applying the fix:
\begin{align}
a &=& b
\end{align}primitive-command
Flag raw plain-TeX primitives discouraged in LaTeX source and name the LaTeX construct that supersedes each one (ChkTeX 41, lacheck, l2tabu). The related deprecated-command rule covers obsolete font switches. Most primitives are reported without a fix because their replacements restructure arguments: a \over b becomes \frac{a}{b}, and \centerline{x} becomes a \centering declaration or a center environment. No single textual edit can perform those rewrites correctly by construction.
A few primitives carry a Safe autofix that replaces one control word with a single token of identical meaning (\sb and \sp become _ and ^). The edit replaces only the control word, preserving losslessness and meaning. It is withheld where the primitive is merely referenced (\let\x\sp, \ifx\sp\y). If the file redefines a name (\renewcommand\sp{…}), the rule treats it as the user’s macro and does not flag it anywhere. The rule also leaves \bgroup and \egroup alone because replacing implicit braces with literal braces can change macro argument and definition boundaries.
This rule is enabled by default.
A plain-TeX fraction primitive (report-only; the LaTeX form restructures its operands):
$a \over b$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 _:
$x\sb2$warning: primitive-command
--> example.tex:1:3
|
1 | $x\sb2$
| ^^^ `\sb` is a raw TeX primitive; use `_`After applying the fix:
$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 in place and leaves the body untouched, so it parses and stays lossless; it is withheld when the display math is unclosed.
This rule is enabled by default.
Plain-TeX display math:
$$a + b = c$$warning: dollar-display-math
--> example.tex:1:1
|
1 | $$a + b = c$$
| ^^ `$$…$$` is plain-TeX display math; use `\[…\]`After applying the fix:
\[a + b = c\]ellipsis
Flag a literal run of three or more periods (...) where an ellipsis command belongs. Literal periods set tight full stops, while LaTeX’s ellipsis commands set correctly spaced dots. In text, the fix is a safe replacement with \dots. It adds a space before a following letter so the control word cannot merge with the next word.
In math, \ldots (baseline dots for comma lists) and \cdots (centered dots for operator chains) are not interchangeable. The fix is therefore unsafe: it chooses \cdots beside an operator or relation and \ldots otherwise. It applies only with --unsafe-fixes or an editor code action. The rule never touches comments or verbatim content.
This rule is enabled by default.
Literal dots in text:
See Chapter 2, 3, ... for details.warning: ellipsis
--> example.tex:1:19
|
1 | See Chapter 2, 3, ... for details.
| ^^^ literal `...` ellipsis; use `\dots`After applying the fix:
See Chapter 2, 3, \dots for details.Literal dots in a math sum (an operator neighbor picks \cdots):
$a_1 + ... + a_n$warning: ellipsis
--> example.tex:1:8
|
1 | $a_1 + ... + a_n$
| ^^^ literal `...` ellipsis; use `\cdots` in math (`\ldots` for lists, `\cdots` for operator chains)expl3-invalid-message-parameter
Flag #5 through #9 in either text argument of a literal expl3 msg_new, msg_set, or msg_gset definition. Messages accept only #1 through #4. Escaped hashes and parameters belonging to an enclosing function definition are distinguished from message parameters. Checks cover recognized executable calls and unexpanded function bodies; stored token lists, expanded text arguments, and unresolved calls stay silent. Report-only: the intended message argument is unknown.
This rule is enabled by default.
A message refers to a fifth parameter:
\ExplSyntaxOn
\msg_new:nnn { demo } { bad-value } { Invalid~value:~#5 }
\ExplSyntaxOffwarning: expl3-invalid-message-parameter
--> example.tex:2:54
|
2 | \msg_new:nnn { demo } { bad-value } { Invalid~value:~#5 }
| ^^ invalid expl3 message parameter `#5`; messages accept only `#1` through `#4`expl3-protected-predicate
Flag a protected expl3 conditional definition whose literal condition list requests a p predicate. Predicates must be expandable, which protection prevents. The new, set, and gset families are checked in recognized executable code, including unexpanded function bodies. Computed condition lists and unresolved calls stay silent. Report-only: choosing between protection and the predicate changes the function’s API or meaning.
This rule is enabled by default.
A protected conditional requests a predicate:
\ExplSyntaxOn
\prg_new_protected_conditional:Nnn \demo_ready: { p, TF }
{ \prg_return_true: }
\ExplSyntaxOffwarning: expl3-protected-predicate
--> example.tex:2:51
|
2 | \prg_new_protected_conditional:Nnn \demo_ready: { p, TF }
| ^ a protected expl3 conditional cannot define an expandable `p` predicateexpl3-variant-type
Flag incompatible or deprecated argument-type conversions in literal expl3 variant-generation calls. A shorter variant inherits the original suffix. Unchanged letters are valid, N may become c, and n may become o, V, v, f, e, or x. Conversions between these two families are deprecated; other changes are incompatible. Checks cover recognized executable calls and unexpanded function bodies, not stored token lists or unresolved expansion. Report-only: the intended signature is the author’s decision.
This rule is enabled by default.
A variant cannot add arguments:
\ExplSyntaxOn
\cs_generate_variant:Nn \demo_use:n { nn }
\ExplSyntaxOffwarning: expl3-variant-type
--> example.tex:2:39
|
2 | \cs_generate_variant:Nn \demo_use:n { nn }
| ^^ incompatible expl3 variant conversion from `n` to `nn`Converting a single-token argument to a token-list argument is deprecated:
\ExplSyntaxOn
\cs_generate_variant:Nn \demo_use:Nn { nn }
\ExplSyntaxOffwarning: expl3-variant-type
--> example.tex:2:40
|
2 | \cs_generate_variant:Nn \demo_use:Nn { nn }
| ^^ deprecated expl3 variant conversion from `Nn` to `nn`extra-alignment-tab
Flags a row in a built-in tabular, tabular*, or array environment that consumes more columns than its column preamble declares. LaTeX cannot place the overflowing cell and reports an extra alignment tab. Short rows are valid and are not flagged. Custom column types and dynamic \multicolumn spans are left alone when their width cannot be established statically. No autofix is offered because either the row or the preamble may be wrong.
This rule is enabled by default.
A row that exceeds the declared table width:
\begin{tabular}{ll}
a & b & c \\
\end{tabular}error: extra-alignment-tab
--> example.tex:2:9
|
1 | \begin{tabular}{ll}
| -- table preamble declares 2 columns
2 | a & b & c \\
| ^ row uses at least 3 columns, but the table preamble declares 2extra-math-linebreak
Flag a plain \\ at the end of an align, alignat, flalign, gather, or multline environment, including their starred forms, or immediately after \intertext{...} or \shortintertext{...} in an environment that supports intertext. These breaks add an empty row, increasing vertical space and potentially adding an equation number. Breaks before intertext, starred breaks, explicit spacing arguments, subsidiary environments such as aligned, and locally redefined environments or intertext commands are left alone. The fix deletes only the offending \\, preserving comments and surrounding whitespace. It is unsafe because it changes typeset spacing and potentially numbering; use --fix --unsafe-fixes or an explicit editor action.
This rule is enabled by default.
A final linebreak adds an empty equation row:
\begin{align}
a &= b \\
\end{align}warning: extra-math-linebreak
--> example.tex:2:10
|
2 | a &= b \\
| ^^ final linebreak adds an empty math rowIntertext already separates the surrounding equation rows:
\begin{align*}
a &= b \\
\intertext{and therefore}\\
c &= d
\end{align*}warning: extra-math-linebreak
--> example.tex:3:28
|
3 | \intertext{and therefore}\\
| ^^ linebreak after `\intertext` adds an empty math rowhard-coded-reference
Flag a possible hard-coded cross-reference in prose, such as Figure 3, Table~1, or Section 2 (textidote sh:hcfig/hctab/hcsec). If the reference points into this document, use \ref or \cref to a \label so the number tracks its target and the reference can carry a hyperlink.
The rule is opt-in because prose alone cannot distinguish local references from valid references to external work, such as Bob's Lemma~1 or Lemma~1 in Bob's paper. No autofix is offered because the correct rewrite needs a label that the text does not supply.
The rule flags only a capitalized reference word (Figure, Table, Section, Eq., …) matched as a whole word and followed by an Arabic number across one space or a tie (~). It leaves plurals, lowercase words, Figure~\ref{x}, and Figure three alone. It also skips citation locators (\cite[Section~8.1]{...}, which refers to external work), environment titles (\begin{thm}[Conway's Theorem 0], a proper name), and description-list captions (\item[Part 3.]). It never touches math, comments, or verbatim content.
This rule is disabled by default; enable it with select.
A hard-coded figure number instead of a cross-reference:
See Figure 3 for the results.warning: hard-coded-reference
--> example.tex:1:5
|
1 | See Figure 3 for the results.
| ^^^^^^^^ possible hard-coded reference `Figure 3`; if this refers to this document, use `\ref` or `\cref`Even tied with ~, the number is still hard-coded:
Table~1 lists the parameters.warning: hard-coded-reference
--> example.tex:1:1
|
1 | Table~1 lists the parameters.
| ^^^^^^^ possible hard-coded reference `Table~1`; if this refers to this document, use `\ref` or `\cref`indented-docstrip-guard
Flag a syntactically complete %<…> marker in a .dtx file when it is preceded only by horizontal whitespace on its physical line. Docstrip recognizes guards only at column zero, so an indented near match is an ordinary comment and does not select or delimit generated code. No autofix is offered because activating a guard can change generated files.
This rule is enabled by default.
A docstrip guard indented by one space:
%<*package>
\ProvidesPackage{example}
%</package>warning: indented-docstrip-guard
--> example.dtx:1:2
|
1 | %<*package>
| ^^^^^^^^^^^ docstrip guards are recognized only at column zero
warning: indented-docstrip-guard
--> example.dtx:3:2
|
3 | %</package>
| ^^^^^^^^^^^ docstrip guards are recognized only at column zeroinert-suppression
Flag a suppression directive that cannot take effect: skip with no following construct, on with no matching off, or a directive written on a .dtx documentation-margin line, where % is typeset prose rather than a comment. Also flag an off region left open at EOF; it currently suppresses through the end of the file, but the missing closer is usually accidental. Report-only: moving, deleting, or closing the directive requires knowing the boundary the author intended. Inline suppressions cannot hide this meta diagnostic; use [lint].ignore to disable the rule deliberately.
This rule is enabled by default.
An on directive with no matching open region does nothing:
% badness-lint on deprecated-command
{\bf text}warning: inert-suppression
--> example.tex:1:1
|
1 | % badness-lint on deprecated-command
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `on` has no matching `off`, so this directive closes no regioninvalid-macrocode-frame
Flag a .dtx macrocode or macrocode* closing frame unless exactly four spaces separate its column-one % from \end{…}. The doc package scans for that literal physical delimiter, so a near match does not close the code chunk even though it looks like an ordinary environment to Badness. The safe autofix replaces only the malformed horizontal space with the required four spaces.
This rule is enabled by default.
A macrocode closer with only three spaces after %:
% \begin{macrocode}
\def\example{value}
% \end{macrocode}error: invalid-macrocode-frame
--> example.dtx:3:2
|
3 | % \end{macrocode}
| ^^^ `macrocode` closing frame requires exactly four spaces after `%`After applying the fix:
% \begin{macrocode}
\def\example{value}
% \end{macrocode}straight-quotes
Flag a literal ASCII double quote (") used for quotation. In LaTeX, a straight " sets a closing double quote, so an opening one comes out backward. The correct forms are `` (two backticks) to open and '' (two apostrophes) to close. The rule reports a quotation once, spanning both quotes, and rewrites the pair in one atomic edit so an editor code action can repair it from either end. A quote with no closer before the paragraph ends is reported on its own.
The fix is unsafe because it infers direction from context. A quote opens when preceded by whitespace, a line break, an opening delimiter ((, [, {), a backtick, or the start of the document; otherwise, it closes. This guess can change the typeset glyph, so the fix applies only with --unsafe-fixes or an editor code action. Single straight quotes (') are left alone because they can be apostrophes. The rule never touches comments, verbatim content, math, TeX hex constants ("2D), or \pdfmapline font maps.
This rule is enabled by default.
Straight ASCII double quotes around a phrase:
He said "hello world" to me.warning: straight-quotes
--> example.tex:1:9
|
1 | He said "hello world" to me.
| ^^^^^^^^^^^^^ straight double quotes; use `` `` `` (opening) and `''` (closing)An opening quote after a parenthesis:
("quoted")warning: straight-quotes
--> example.tex:1:2
|
1 | ("quoted")
| ^^^^^^^^ straight double quotes; use `` `` `` (opening) and `''` (closing)swallowed-space
Flag a text-producing control word followed directly by a space that TeX discards, joining the macro’s output to the next word (\LaTeX is renders as “LaTeXis”) (ChkTeX 1). TeX discards spaces after a control word during tokenization, so they never reach the output.
The rule checks only a curated set of TeX-family logos that take no arguments (\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 left alone. The fix inserts {} after the control word (\LaTeX{} is), ending the macro name so the space survives. It is unsafe because it changes typeset output. --fix leaves it alone; --unsafe-fixes and editor code actions apply it.
This rule is enabled by default.
A logo swallows the following space, gluing it to the next word:
We used \LaTeX to typeset this.warning: swallowed-space
--> example.tex:1:15
|
1 | We used \LaTeX to typeset this.
| ^ `\LaTeX` swallows the following space; add `{}` (`\LaTeX{}`) or `\ ` so it printsspace-before-command
Flag a plain space directly before a command that should follow the preceding word without a gap: \footnote, \footnotemark, \index, or \label (ChkTeX 24/42). A space before \footnote produces an unwanted space before the footnote mark (word \footnote{x} → “word ¹”). A space before a zero-width \index or \label leaves an interword gap that can shift the recorded page. The fix deletes the space. It is unsafe because it changes typeset spacing, so --fix leaves it alone; --unsafe-fixes and editor code actions apply it.
The rule flags only the same-line WORD SPACE \cmd shape. It leaves spaces at line starts or after braces alone and skips math, where inter-token spaces are insignificant, including $…$ and environments such as equation and align. For zero-width \index and \label, the fix is withheld unless whitespace, a newline, or paragraph end follows the group. Otherwise, the leading space separates the preceding word from the following content.
This rule is enabled by default.
A space before a footnote sets a spurious space before the mark:
This is important \footnote{See the appendix.}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 commanddash-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 ---. A range between two numbers takes an en dash, so the rule flags 5-10 and 5---10 with an unsafe fix to --. The fix changes the typeset glyph, and a hyphen between numbers is occasionally intentional.
Between two words, an en dash (--) is almost always a mistake, but the choice between a hyphen and an em dash is ambiguous, so no fix is offered. Coordinate proper names (Barzilai--Borwein, Newton--Raphson) are exempt: an uppercase first letter on either side suppresses the finding.
The rule checks only a word containing exactly one dash run with content on both sides. It leaves dates (2020-01-15), ISBNs, spaced dashes, and option flags (--verbose) alone. It also skips column spans (\cline{1-3}, \cmidrule(lr){2-3}) and key arguments (\label{fig:1-3}, \cite{smith2020-1}), which are specifications or opaque identifiers. The same applies to angle-delimited command and environment specifications such as Beamer’s \item<1-2> and \begin{onlyenv}<2-3>. The rule never touches comments, verbatim content, or math.
This rule is disabled by default; enable it with select.
A hyphen where a number range wants an en dash:
See pages 5-10 for the proof.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 rangeAn en dash between words (ambiguous, so reported without a fix):
A well--known result.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 breaktimes-variable
Flag a literal x used as a multiplication sign between two numbers, such as 640x200 or 3x3 (ChkTeX 29). TeX sets it as an italic letter rather than the \times cross. The rule flags only a whole word of the form digits x digits: one lowercase x with ASCII digits on both sides and nothing else. It leaves ordinary words (matrix), spaced products (n x m), hex literals (0xFF, 0x12), and opaque key arguments such as \label{fig:3x3} or \ref{fig:3x3} alone.
The fix is unsafe because a bare x between numbers is usually a cross but occasionally a variable. In math, it replaces x with \times; in text, it uses $\times$ so the result still compiles. --fix leaves it alone; --unsafe-fixes and editor code actions apply it.
This rule is enabled by default.
A literal x as a multiplication sign in text (fixed to $\times$):
A 640x200 pixel image.warning: times-variable
--> example.tex:1:6
|
1 | A 640x200 pixel image.
| ^ literal `x` as a multiplication sign between numbers; use `\times` for a crossThe same inside math mode (fixed to \times):
The grid is $640x200$ cells.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 crossmath-operator-name
Flag a bare function name from the LaTeX and amsmath set (sin, cos, log, lim, and others) written in math mode without its backslash (ChkTeX 35). TeX sets the name as italic variables rather than an upright operator such as \sin with the correct spacing. The rule matches a name at the start of a WORD and ending at a word boundary, catching both $sin x$ and $sin(x)$. It leaves words such as since alone and prefers the longest match (sinh over sin).
The rule checks only math mode and skips subscripts and superscripts, where max in x_{max} is almost always a label. It also skips text-domain and unknown arguments. The fix inserts the backslash (sin → \sin). It is unsafe because it changes the glyph and operator spacing, and a bare sin can be a product. --fix leaves it alone; --unsafe-fixes and editor code actions apply it.
This rule is enabled by default.
A bare function name typesets as italic variables:
$sin x + cos x = 1$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:
The limit $lim(x)$ diverges.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 name containing @ (\foo@bar, \p@, \@ifnextchar) used outside a \makeatletter and \makeatother region. There, @ has its ordinary catcode and cannot be part of a control word. TeX reads \foo@bar as \foo followed by the text @bar, rather than as the internal macro \foo@bar. This usually means the enclosing \makeatletter and \makeatother were forgotten.
The formatter’s lexer tracks \makeatletter state, so the rule distinguishes these cases exactly. A name inside the region lexes as one token and is never flagged. Only the split form outside the region is flagged: a control word abutting an @-word, or \@ abutting a letter-word. No autofix is offered because wrapping the use in \makeatletter and \makeatother requires more than a local edit. The end-of-sentence \@ in NASA\@. is left alone.
This rule is enabled by default.
An internal @ macro used without \makeatletter:
\my@commandwarning: 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:
\@ifstar{\StarredForm}{\PlainForm}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 structural heading that descends more than one level below the preceding structural heading, such as \section followed directly by \subsubsection, skipping \subsection (textidote’s sh:secskip). The heading hierarchy follows the document class: it includes \chapter only for classes known to provide it or when the source uses it. Unknown classes conservatively omit it. The rule treats \paragraph and \subparagraph as transparent because technical papers commonly use them as run-in labels.
Only downward jumps are flagged. Returning to a higher level or repeating a heading at the same level is normal. The comparison is with the previous structural heading rather than an absolute top level. No autofix is offered because repairing a skip requires the author to choose the structure.
This rule is enabled by default.
A heading that drops two levels at once (skipping \subsection):
\section{Introduction}
\subsubsection{Details}warning: sectioning-level-jump
--> example.tex:2:1
|
2 | \subsubsection{Details}
| ^^^^^^^^^^^^^^ `\subsubsection` skips a sectioning level after `\section` (expected `\subsection`)missing-required-argument
Flag a command with fewer {…} groups than its curated built-in signature requires (ChkTeX warning 14, checked through the parse tree and signature database rather than line heuristics). TeX also accepts unbraced single-token arguments (\frac12), so the rule stays silent when a following token could supply the missing argument. It flags the command only at a hard boundary: the end of the enclosing group, math shell, or environment; an alignment &; a \\ line break; a blank line; or the end of the file.
The rule skips contexts where a bare command may be deliberate: macro-definition bodies (\newcommand{\bold}{\textbf}), arguments of unknown commands, standalone {…} scope groups, \let-style aliases, and names redefined in the file. Curated environment-local signatures take precedence over global signatures: inside parts, exam’s \part takes only optional points. No autofix is offered because the author must supply the missing argument’s content.
This rule is enabled by default.
A fraction missing its denominator:
$\frac{1}$warning: missing-required-argument
--> example.tex:1:2
|
1 | $\frac{1}$
| ^^^^^ `\frac` is missing 1 of its 2 required argumentsA command left bare at the end of a group, with nothing to take:
\emph{see \textbf}warning: missing-required-argument
--> example.tex:1:11
|
1 | \emph{see \textbf}
| ^^^^^^^ `\textbf` is missing its required argumentundefined-ref
Flag a \ref-family reference to a label defined nowhere in the document. The check is sound only when the label namespace is complete, so the rule stays silent unless the project view is closed (every include resolves to an analyzed file) and rooted. It is inactive on stdin or wherever cross-file label resolution is unavailable. No autofix is offered.
This rule is enabled by default.
A reference to a label defined nowhere in the document:
\ref{sec:intro}warning: undefined-ref
--> example.tex:1:1
|
1 | \ref{sec:intro}
| ^^^^^^^^^^^^^^^ reference to undefined label `sec:intro`undefined-citation
Flag a \cite-family key that matches no entry in the document’s bibliography, analogous to undefined-ref. The check is sound only over a closed, rooted namespace where every .bib resource resolves to an analyzed file. Resource lookup honors BibTeX’s BIBINPUTS and TEXBIB search paths. A \nocite{*} wildcard, which marks every key as used, suppresses the rule entirely. The rule is inactive without cross-file citation resolution. No autofix is offered.
This rule is enabled by default.
A citation of a key that matches no bibliography entry:
\cite{knuth:1984}warning: undefined-citation
--> example.tex:1:1
|
1 | \cite{knuth:1984}
| ^^^^^^^^^^^^^^^^^ citation of undefined key `knuth:1984`unreferenced-label
Flag a label definition unused by any \ref-family command in the document. A \eqref{A}--\eqref{D} range also uses labels between A and D when they occur in consecutive, singly labeled equation environments or numbered align and gather rows. This includes literal included files with an unambiguous source order. Manual tags, suppressed numbers, and counter changes stop inference. A reference to a subequations group label also uses the labels in its enclosed math environments.
Like undefined-ref, the check is sound only when the label namespace is complete. The rule stays silent unless the project view is closed (every include resolves to an analyzed file) and rooted. It is inactive on stdin or wherever cross-file label resolution is unavailable. No autofix is offered because removing the unused label and adding a reference are both valid choices.
This rule is enabled by default.
A label that no \ref-family command ever targets:
\section{Intro}\label{sec:intro}warning: unreferenced-label
--> example.tex:1:16
|
1 | \section{Intro}\label{sec:intro}
| ^^^^^^^^^^^^^^^^^ label `sec:intro` is never referencedverbatim-trailing-text
Flag non-whitespace text after a verbatim-like environment’s \end{…} on the same line (ChkTeX warning 31). LaTeX scans line by line to \end{verbatim}, then discards the rest of that line, so \end{verbatim} foo silently drops foo.
The rule identifies verbatim-like environments from an opaque VERBATIM_BODY in the parse tree or a curated built-in verbatim name when the body is empty. Ordinary environments do not discard the rest of their closing line and are not checked. A trailing % comment is treated as trivia and is not flagged. No autofix is offered because the author must choose whether to move or delete the text.
This rule is enabled by default.
Text after \end{verbatim} is silently discarded by LaTeX:
\begin{verbatim}
sample
\end{verbatim} and morewarning: verbatim-trailing-text
--> example.tex:3:16
|
3 | \end{verbatim} and more
| ^^^^^^^^ text after `\end{verbatim}` on the same line is silently discardedduplicate-package
Flag a package loaded more than once in the same file with \usepackage or \RequirePackage, which share one package namespace. LaTeX loads a package only once. A second load is redundant and produces an option-clash error when its options disagree.
A warning requires a prior load in the same conditional branch or an enclosing context, including an unconditional prior load. Separate conditional tests are treated as uncertain and do not trigger a warning. The rule recognizes \if...\else...\fi and common macros with complete braced arguments, including \ifthenelse, \iftoggle, and \IfFileExists. It does not evaluate predicates or combine coverage across branches. No autofix is offered because removing a load can discard options the remaining load lacks. Class loads (\documentclass and \LoadClass) are not flagged.
This rule is enabled by default.
The same package loaded twice:
\usepackage{amsmath}
\usepackage{amsmath}warning: duplicate-package
--> example.tex:2:1
|
2 | \usepackage{amsmath}
| ^^^^^^^^^^^^^^^^^^^^ package `amsmath` is loaded more than oncemissing-provides
Flag a package or class source (.sty or .cls) that never identifies itself with the matching \ProvidesPackage or \ProvidesClass. A well-formed package declares its identity so LaTeX can log it and honor date-based compatibility checks. A .sty file containing only \ProvidesClass still lacks the matching declaration.
The rule is inactive for other extensions: a .tex file has nothing to provide, and a .dtx file hides its declaration inside guarded macrocode. No autofix is offered because the author must choose the declaration’s placement, date, and version.
This rule is enabled by default.
A package source with no self-identification (the docs are rendered against a .sty path):
\NeedsTeXFormat{LaTeX2e}
\RequirePackage{xcolor}warning: missing-provides
--> example.sty:1:1
|
1 | \NeedsTeXFormat{LaTeX2e}
| ^^^^^^^^^^^^^^^ package file lacks `\ProvidesPackage`unknown-option
Flag a \usepackage or \RequirePackage option that the loaded package never declares with \DeclareOption. LaTeX reports this as an “Unknown option” error at compile time. The rule checks only analyzed project packages, such as a sibling .sty file; it ships no option data for system packages.
The package’s declared option set must be trustworthy. A \DeclareOption* default handler, a key-value option processor (kvoptions, \ProcessKeyOptions, …), option forwarding, or an \input in the package silences the rule, as does a key=value option. Class loads (\documentclass) are not checked because an unknown class option becomes an unused global option rather than an error. No autofix is offered because the author must choose whether to remove or rename the option.
This rule is enabled by default.
With a sibling mypkg.sty:
\ProvidesPackage{mypkg}[2026/01/01 v1.0 Demo package]
\DeclareOption{draft}{}
\ProcessOptions\relaxLoading the sibling package with an option it never declares:
\usepackage[final]{mypkg}warning: unknown-option
--> example.tex:1:13
|
1 | \usepackage[final]{mypkg}
| ^^^^^ unknown option `final` for package `mypkg`redundant-script-braces
Flag braces around a single-token subscript or superscript argument, which _ and ^ can bind without braces (x^{2} is x^2). The autofix deletes the two braces and leaves the inner token untouched.
The fix is withheld when removing the braces would let a following character merge with the argument and change meaning. For example, x^{2}-3 stays braced because x^2-3 would re-lex 2-3 as one token, and y_{\alpha}b stays braced because \alphab is one control word. Standard named math operators also remain braced because commands such as \max are not valid unbraced script fields.
This rule is enabled by default.
Redundant braces around a single-token script argument:
$x^{2}$ and $y_{\alpha}$help: redundant-script-braces
--> example.tex:1:4
|
1 | $x^{2}$ and $y_{\alpha}$
| ^^^ redundant braces around a single-token script argument
help: redundant-script-braces
--> example.tex:1:16
|
1 | $x^{2}$ and $y_{\alpha}$
| ^^^^^^^^ redundant braces around a single-token script argumentAfter applying the fix:
$x^2$ and $y_\alpha$unclosed-math-delimiter
Flag a math opener the parser demoted to a plain token because no closer was reachable: a $ without a matching $, a \[ or \( without a matching \] or \), or a \left without a matching \right. These shapes can be data in macro code (>{$} array columns, \expandafter\@tempa\[\@nil), so the parser accepts them without a diagnostic. In prose, they almost always indicate a missing closer.
The rule reports only openers in document prose. It stays silent inside brace groups or optional arguments (\newcommand{...}{$}, the >{$} column specification), expl3 regions, and macrocode bodies. No autofix is offered because the author may need to insert a closer or delete a stray opener.
This rule is enabled by default.
An inline-math $ with no matching $:
Let $x = 1 be the base case.warning: unclosed-math-delimiter
--> example.tex:1:5
|
1 | Let $x = 1 be the base case.
| ^ `$` has no matching `$` (unclosed inline math)A display-math \[ with no matching \]:
The bound \[ x + y follows immediately.warning: unclosed-math-delimiter
--> example.tex:1:11
|
1 | The bound \[ x + y follows immediately.
| ^^ `\[` has no matching `\]` (unclosed display math)A \left with no matching \right:
$a + \left( b + c$warning: unclosed-math-delimiter
--> example.tex:1:6
|
1 | $a + \left( b + c$
| ^^^^^ `\left` has no matching `\right`label-before-caption
Flag a \label before the statement that establishes its intended counter: the outer \caption in a curated float (figure, table, and their starred forms), an explicit \captionof in a curated caption container (minipage), or the first \item in a standard numbered enumerate list. In these positions, \label captures the previous \@currentlabel, usually an enclosing section number, so \ref silently prints an unrelated number. LaTeX gives no warning.
The list check covers only statement-level labels before the first item. Labels after an item may belong to it, while itemize and description items do not step a reference counter. Attached custom item labels and complete Beamer overlay markers remain intact. The float check skips labels nested in command arguments and classifies nested counter steps conservatively. The fix moves the label just after the proven caption or item marker. It is Unsafe because it infers the author’s intent and deliberately changes what \ref prints.
This rule is enabled by default.
A \label above its \caption picks up the section counter, not the figure number:
\begin{figure}
\includegraphics{plot}
\label{fig:plot}
\caption{A plot.}
\end{figure}warning: label-before-caption
--> example.tex:3:3
|
3 | \label{fig:plot}
| ^^^^^^^^^^^^^^^^ `\label` before the outer `\caption` in this `figure` does not capture the float numberA \label before the first \item has not seen the item counter step:
\begin{enumerate}
\label{item:first}
\item First
\end{enumerate}warning: label-before-caption
--> example.tex:2:3
|
2 | \label{item:first}
| ^^^^^^^^^^^^^^^^^^ `\label` before the first `\item` in this `enumerate` does not capture the item numberA \label above \captionof in a minipage likewise precedes the explicit counter step:
\begin{minipage}{\textwidth}
\label{fig:plot}
\captionof{figure}{A plot.}
\end{minipage}warning: label-before-caption
--> example.tex:2:3
|
2 | \label{fig:plot}
| ^^^^^^^^^^^^^^^^ `\label` before `\captionof` in this `minipage` does not capture the caption numberlonely-item
Flag \item written directly in the document environment, where LaTeX reports a lonely item because there is no list. The rule leaves items inside other environments, command arguments, low-level \list/\trivlist pairs, and standalone fragments alone because their list context may come from a custom definition or an including file. Report-only: the intended list type and boundaries cannot be inferred from the item.
This rule is enabled by default.
An item directly in the document body has no list:
\begin{document}
\item A
\end{document}error: lonely-item
--> example.tex:2:1
|
2 | \item A
| ^^^^^ `\item` has no enclosing list environmentSuppression
To suppress a rule at a single site, use a comment directive:
% badness-lint skip deprecated-command: legacy code, leave as-is
{\bf here}The verb determines the scope. skip covers the next construct, off and on delimit a region, and skip-file covers the whole file wherever the directive appears:
% badness-lint off deprecated-command: legacy chapter
{\bf here}
{\it and here}
% badness-lint on deprecated-commandThe <id> is optional. Omitting it suppresses every rule over the same span. The : <reason> is optional everywhere.
% badness skip / off / on / skip-file do the same and turn off the formatter at the same time; see Formatting for the layout-only % badness-format spellings.
Parse diagnostics (rule id parse) are never suppressed by select/ignore.