Lint Rules

Reference catalogue of every built-in Panache lint rule, the diagnostic codes each rule emits, severity, auto-fix availability, and configuration requirements.

This page documents every built-in lint rule that Panache ships with. Each section lists the rule’s configuration name (used in [lint.rules]), the diagnostic codes the rule may emit at runtime, severity, auto-fix support, and any extension or metadata requirements.

For a user-friendly introduction to the linter, CLI usage, and configuration, see the Linting guide.

Diagnostic format

Diagnostics are displayed in a compiler-style format:

severity[diagnostic-code]: message
  --> file:line:column

Components:

severity
error, warning, or info
diagnostic-code
The specific code emitted (e.g., undefined-reference-label). A single rule may emit several distinct codes.
message
Human-readable description of the issue
location
File path, line number, and column number

Some diagnostics include additional notes pointing at related locations:

warning[duplicate-reference-labels]: Duplicate reference definition 'link1'
  --> document.qmd:4:1
note: First defined here:
  --> document.qmd:3:1

Severity levels

Panache uses three severity levels:

error
Critical issues that prevent correct parsing or rendering
warning
Likely mistakes or best practice violations
info
Informational messages (currently unused, reserved for future use)

Rules

heading-hierarchy

Detects skipped heading levels that violate document structure best practices.

Severity
Warning
Auto-fix
Yes
Diagnostic codes
heading-hierarchy
Description
Headings should increment by at most one level (e.g., H1 → H2 → H3). Skipping levels (H1 → H3) makes the document structure unclear and can break table-of-contents generation.

Example violation:

# Main Title

### Subsection

Diagnostic:

warning[heading-hierarchy]: Heading level skipped from h1 to h3; expected h2
 --> document.qmd:3:1
  |
3 | ### Subsection
  | ^^^^^^^^^^^^^^

Auto-fix: Changes ### Subsection to ## Subsection.

empty-list-item

Detects list items whose content is empty — a bare marker with nothing after it.

Severity
Warning
Auto-fix
No
Diagnostic codes
empty-list-item
Description

An empty list item is almost always a placeholder the author forgot to fill in. The rule fires in two situations:

  1. A LIST_ITEM produced by the parser has no inline content (e.g. a bare - line between two non-empty bullets, or 1. with nothing after the period).
  2. A bare - on the line below a list item gets interpreted as a Setext H2 underline, silently merging the item with the previous line as a heading. (= underlines are not flagged because they don’t share the bullet-marker shape.)

Both cases are valid Markdown, but they tend to surprise readers.

Example violation:

- Item one
-
- Item three

Diagnostic:

warning[empty-list-item]: List item has no content
 --> document.md:2:1
  |
2 | -
  | ^

Resolution: Fill in the missing content, or delete the marker. No auto-fix is provided because the right choice (placeholder vs. removal) is an author-intent decision.

heading-eaten-attrs

Detects HTML comments that cause pandoc to silently drop a heading’s {...} attribute block.

Severity
Warning
Auto-fix
No
Diagnostic codes
heading-eaten-attrs
Requirements
extensions.header-attributes = true (the default for Pandoc, Quarto, and R Markdown flavors).
Description
Pandoc requires the {...} attribute block on a heading to be followed only by whitespace. When any non-whitespace content (including an HTML comment) follows the brace block, pandoc treats the braces as literal text, silently dropping the attributes and producing an auto-generated id like "title-.unnumbered". Cross-references that targeted the intended id then break with no diagnostic from pandoc itself.

Example violation:

# Bibliography {.unnumbered} <!-- TODO -->

Diagnostic:

warning[heading-eaten-attrs]: Comment on a heading line with `{...}` attributes;
pandoc treats the brace block as literal text when anything follows it on the line.
 --> document.qmd:1:30
  |
1 | # Bibliography {.unnumbered} <!-- TODO -->
  |                              ^^^^^^^^^^^^^
help: Move the comment to its own line before or after the heading.

Resolution: Move the comment to its own line. No auto-fix is provided because the right placement (above or below the heading, or deleting the comment entirely) is an author-intent decision.

heading-strip-comments-residue

Detects HTML comments adjacent to a heading’s {...} attribute block that would leave stray whitespace under pandoc --strip-comments.

Severity
Warning
Default
Off — opt in via [lint.rules] heading-strip-comments-residue = true.
Auto-fix
No
Diagnostic codes
heading-strip-comments-residue
Requirements
extensions.header-attributes = true.
Description
When attributes still parse (the comment sits before the brace block), invoking pandoc with --strip-comments removes the comment text but leaves the surrounding whitespace. The resulting heading source has trailing or interior whitespace adjacent to the attribute block, which can subtly affect downstream tooling. This rule is opt-in because most authors do not use --strip-comments; enable it when your publishing pipeline does.

Example violation:

# Bibliography <!-- TODO --> {.unnumbered}

Resolution: Move the comment to its own line before or after the heading.

duplicate-reference-labels

Detects duplicate reference link and footnote definitions.

Severity
Warning
Auto-fix
No
Diagnostic codes
duplicate-reference-labels
Description
Each reference label and footnote ID must be unique within a document. Duplicate definitions cause ambiguity—only the first definition is used, making the others ineffective.

Example violation:

See [link1] and [link2].

[link1]: https://example.com
[link1]: https://different.com

Diagnostic:

warning[duplicate-reference-labels]: Duplicate reference definition 'link1'
  --> document.qmd:4:1
note: First defined here:
  --> document.qmd:3:1

Resolution: Rename or remove the duplicate definition.

undefined-references

Detects reference links and footnotes that point to missing definitions.

Severity
Warning
Auto-fix
No
Diagnostic codes
undefined-reference-label, undefined-footnote-id
Description
Flags unresolved reference-style links (including shortcut/collapsed forms) and unresolved footnote references. This helps catch broken cross-references early in editing and CI.

Example violation:

See [missing][nope] and note[^missing].

[ok]: https://example.com

Diagnostic:

warning[undefined-reference-label]: Reference label '[nope]' not found
  --> document.qmd:1:15
warning[undefined-footnote-id]: Footnote '[^missing]' not found
  --> document.qmd:1:31

undefined-reference-label

Emitted when a reference-style link points to a label that has no matching definition.

undefined-footnote-id

Emitted when a footnote reference points to an ID that has no matching footnote definition.

undefined-anchor

Detects inline links whose #fragment destination has no matching anchor in the document.

Severity
Warning
Auto-fix
No
Diagnostic codes
undefined-anchor
Description

Flags [text](#fragment) links where #fragment does not match any anchor that will exist in the rendered output. Anchor sources include explicit {#id} attributes on headings, fenced divs, code blocks, spans, and chunk labels, plus auto-generated heading IDs (when the auto_identifiers extension is enabled). Matching is case-sensitive, mirroring how browsers resolve URL fragments.

Links with a path component (other.qmd#frag), absolute URLs (https://example.com#frag), and bare back-to-top links (#) are not flagged. In bookdown projects, sibling chapters are scanned because bookdown’s gitbook renderer rewrites cross-chapter anchors. Quarto books render each chapter to a separate HTML page and are not scanned cross-chapter.

When the citations extension is enabled, links of the form [text](#ref-citekey) are recognized as overriding a citation’s link text (Pandoc renders bibliography entries with id="ref-<citekey>"), so they resolve as long as @citekey appears somewhere in the document.

Anchors declared via raw HTML <a id="x"> / <a name="x"> are not currently inspected, so links to them may be flagged as undefined. <div id="x"> blocks are recognized.

Example violation:

# Real Heading {#real}

See [the typo](#reel).

Diagnostic:

warning[undefined-anchor]: Anchor '#reel' not found in document
  --> document.qmd:3:16

unused-definitions

Detects reference labels and footnote definitions that are declared but never referenced.

Severity
Warning
Auto-fix
No
Diagnostic codes
unused-definition-label, unused-footnote-id
Description
Flags unused reference definitions ([label]: ...) and unused footnote definitions ([^id]: ...). This helps keep documents tidy and avoids dead references that can accumulate over time. When project metadata is available (for example in Quarto/Bookdown project lint runs), usage is resolved across project documents to reduce cross-file false positives.

Example violation:

Text with one note[^1].

[^1]: Used note.
[^2]: Unused note.

[used]: https://example.com
[unused]: https://unused.example.com

Diagnostic:

warning[unused-footnote-id]: Footnote '[^2]' is never used
  --> document.qmd:4:1
warning[unused-definition-label]: Reference definition '[unused]' is never used
  --> document.qmd:7:1

unused-definition-label

Emitted when a reference definition ([label]: ...) is declared but never referenced anywhere in the document (or project, when project metadata is available).

unused-footnote-id

Emitted when a footnote definition ([^id]: ...) is declared but never referenced.

citation-keys

Validates citation keys against loaded bibliographies and detects conflicts in inline bibliography entries.

Severity
Error for bibliography load/parse failures, Warning for undefined keys and duplicates
Auto-fix
No
Requirements
Requires extensions.citations = true in configuration
Diagnostic codes
bibliography-load-error, bibliography-parse-error, missing-bibliography-key, duplicate-bibliography-key, duplicate-inline-reference-id
Description
Checks that all cited keys ([@key]) exist in the configured bibliography files. Also validates inline bibliography entries for duplicates and conflicts.

Example violation (undefined key):

---
bibliography: refs.bib
---

See @smith2020 and @jones2021.

If jones2021 doesn’t exist in refs.bib:

warning[missing-bibliography-key]: Citation key 'jones2021' not found in bibliography
  --> document.qmd:5:17

Example violation (bibliography load error):

---
bibliography: nonexistent.bib
---
error[bibliography-load-error]: Failed to load bibliography nonexistent.bib: File not found
  --> document.qmd:1:1

When it runs: Only when document metadata includes bibliography configuration and the citation extension is enabled.

bibliography-load-error

Emitted when a configured bibliography file cannot be opened (missing, unreadable, etc.).

bibliography-parse-error

Emitted when a bibliography file is opened successfully but contains entries that fail to parse.

missing-bibliography-key

Emitted when a @cite reference does not match any key in the loaded bibliography.

duplicate-bibliography-key

Emitted when the same key appears more than once across loaded bibliography files.

duplicate-inline-reference-id

Emitted when an inline bibliography entry collides with another inline entry or with a key from a loaded bibliography file.

chunk-label-spaces

Detects executable chunk labels containing whitespace (for example {r several words} or label="several words").

Severity
Warning
Auto-fix
No
Diagnostic codes
chunk-label-spaces
Description
Labels with spaces are accepted by Quarto execution, but cross-references often fail to resolve reliably. Use a stable identifier such as several-words or several_words instead.

missing-chunk-labels

Detects executable chunks that do not define a label (either inline or hashpipe style).

Severity
Warning
Auto-fix
No
Diagnostic codes
missing-chunk-labels
Description
Labels facilitate debugging. Add a label with either #| label: my-chunk or inline label=my-chunk.

figure-crossref-captions

Detects figure cross-references that point to chunk labels without a figure caption option.

Severity
Warning
Auto-fix
No
Diagnostic codes
figure-crossref-captions
Description
Bookdown figure cross-references (\@ref(fig:...)) require a captioned chunk to create a resolvable figure label at render time. When the target chunk has a label but no fig-cap/fig.cap, the crossref will not resolve.

unknown-emoji-alias

Detects :alias: emoji shortcodes that are not recognized.

Severity
Warning
Auto-fix
No
Requirements
Requires extensions.emoji = true in configuration
Diagnostic codes
unknown-emoji-alias
Description
Checks parsed emoji aliases against the emoji shortcode dataset and warns when an alias is unknown.

Example violation:

Looks good :smile:, but this one is wrong :not-a-real-emoji:.

Diagnostic:

warning[unknown-emoji-alias]: Unknown emoji alias ':not-a-real-emoji:'
  --> document.qmd:1:40

html-entities

Detects malformed HTML named entity references in inline prose.

Severity
Warning
Auto-fix
No
Diagnostic codes
html-entities
Description

Pandoc and Quarto pass HTML named entities like &hellip; through to the output unchanged, so a typo (&ellips;) or a missing trailing semicolon (&numero instead of &numero;) silently produces wrong output. This rule flags three conservative cases:

  • &NAME; where NAME is not in the HTML5 named-entity table.
  • &NAME (no semicolon) where adding the semicolon would produce a known entity. This avoids firing on plain prose like “Tom & Jerry” or “AT&T”, since those words are not entity names.
  • &NAME (no semicolon, length ≥ 4) where NAME is one edit away from a known entity (e.g. &hellp&hellip;). Far-from-anything words are left alone to keep prose like “Procter &Gamble” quiet.

The rule deliberately ignores numeric character references (&#123;, &#xABCD;) for now and does not scan code spans, code blocks, raw HTML, inline/display math, link destinations, attributes, YAML metadata, or comments.

Example violations:

This is &ellips; wrong.

Section &numero 5 of the report.

Diagnostics:

warning[html-entities]: Unknown HTML entity '&ellips;'
 --> document.qmd:1:9
  = help: did you mean '&hellip;'?

warning[html-entities]: HTML entity '&numero' is missing a trailing ';'
 --> document.qmd:3:9
  = help: write '&numero;' to encode the character

adjacent-footnote-refs

Detects footnote references placed back-to-back ([^a][^b]) where the rendered superscripts run together (e.g. footnotes 7 and 8 look like footnote 78).

Severity
Warning
Auto-fix
Yes (inserts a space between the references)
Requirements
Requires extensions.footnotes = true in configuration
Diagnostic codes
adjacent-footnote-refs
Description
When two footnote references appear with no intervening character, most renderers emit the superscript markers as a single visually-merged run. Inserting a single space between them keeps the markers distinct without changing the prose.

Example violation:

See the prior reports[^a][^b] for context.

Auto-fix output:

See the prior reports[^a] [^b] for context.

footnote-ref-in-footnote-def

Detects footnote references ([^id]) that appear inside a reference-style footnote definition body, where pandoc silently parses them as literal text instead of resolving the reference.

Severity
Warning
Auto-fix
No (the user must decide whether to inline the prose, restructure to lift the reference out of the definition body, or drop it)
Requirements
Requires extensions.footnotes = true in configuration (default for Pandoc, Quarto, R Markdown, and GFM flavors).
Diagnostic codes
footnote-ref-in-footnote-def
Description

Pandoc footnotes do not nest. Inside a [^x]: ... definition body, any [^id] reference is silently parsed as a literal Str — the would-be link disappears from the output with no warning. The same applies to references nested arbitrarily deep inside that body (inside emphasis, strong, strikeout, links, blockquotes, lists, or inline footnotes).

This rule surfaces the silent drop at lint time so the user notices before the document is rendered. After the parser fix that aligns panache with pandoc on this case, the inner references no longer appear as FOOTNOTE_REFERENCE nodes; the rule scans the definition body’s TEXT tokens directly for [^id] byte patterns, which naturally skips code spans, math, raw HTML, and other CST-distinct constructs.

References at the top level (outside any definition body) and inside a top-level inline footnote ^[...] are not flagged — pandoc resolves those normally.

Example violation:

Outer[^a].

[^a]: Body has [^b] ref and **bold [^c] inside** wrapper.

[^b]: B body.
[^c]: C body.

Diagnostic:

warning[footnote-ref-in-footnote-def]: Footnote reference '[^b]' inside a footnote definition body
                                       is silently dropped by pandoc (rendered as literal text)
 --> document.qmd:3:16
  = help: footnotes do not nest in pandoc; inline the prose, restructure to
          keep the reference outside the definition body, or remove it

stray-fenced-div-markers

Detects runs of three or more colons (:::, ::::, …) that appear inside inline text (paragraphs, tight list items, definition list bodies, table cells) instead of parsing as fenced div markers.

Severity
Warning
Auto-fix
No
Requirements
Requires extensions.fenced-divs = true in configuration (default for Pandoc, Quarto, and R Markdown flavors).
Diagnostic codes
stray-fenced-div-markers
Description

Pandoc fenced divs use ::: (or longer colon runs) for both openers (with an attribute or class) and closers (bare colons). Pandoc only treats ::: as a marker when it starts a line on its own; if the colons end up embedded in paragraph text—a stray closer with no matching opener, a closer accidentally glued to the previous line, or a marker with extra words after it—they silently render as ::: characters in the prose and the div either never opens or never closes.

Quarto’s runtime emits a warning when it sees stray :::, but exits 0, which makes the issue invisible to CI/Makefile workflows. This rule fills that gap by flagging the same condition at lint time.

The rule fires on any run of three or more : characters that survives as plain text inside paragraph-like inline content (paragraphs, tight list items, definition list bodies, table cells). Code spans (`:::`), indented code blocks, and raw HTML blocks are not flagged because the colons there are not inline text. Authentic prose mentions of ::: (writing about Pandoc syntax) should be wrapped in backticks anyway—runs of three or more consecutive colons are otherwise extremely rare in natural text.

Example violations:

::: warning
The fence count on the opener and closer don't match.
::::
::: {lang=en-US}
[contact Ms. Nebbercracker]{lang=en-US}:::
[]{#hmm}
::: {lang=zh-TW}
bla
:::

Diagnostic:

warning[stray-fenced-div-markers]: '::::' appears as text, not as a fenced div marker
 --> document.qmd:3:1
  = help: Pandoc only treats ':::' as a fenced div marker when it starts a line
          on its own (optionally followed by a class or attributes). Add a
          newline before it, or wrap it in backticks if it's intentional text

When a ::: run sits at the start of a line and the rest of the line forms a valid fence shape (opener or closer), but a preceding non-blank line pulls it into a paragraph, the diagnostic is sharpened to name the actual failure mode:

warning[stray-fenced-div-markers]: ':::' looks like a fenced div marker, but
the preceding line pulls it into a paragraph
 --> document.qmd:2:1
  = help: Insert a blank line above this line so Pandoc parses it as a fenced
          div instead of paragraph text

math-syntax

Detects structural problems in the TeX content of inline ($...$) and display ($$...$$, \[...\], \begin{env}...\end{env}) math: unbalanced braces and unclosed or mismatched environments.

Severity
Error
Auto-fix
No
Requirements
Requires a tex-math-* extension (e.g. extensions.tex-math-dollars, default for Pandoc, Quarto, and R Markdown flavors). With no math extension enabled there are no math spans, so the rule never fires.
Diagnostic codes
math-unclosed-group, math-unexpected-close-brace, math-unclosed-environment, math-mismatched-environment, math-unexpected-end
Description

The math parser captures math content losslessly even when it is malformed, so a stray brace or unterminated environment never breaks parsing or formatting — but it is build-breaking downstream: quarto render to PDF hard-fails on an unclosed brace or mismatched environment, and MathJax/KaTeX silently drop the equation. That is why these ride at error severity. This rule surfaces the structural problems the parser already detected, with the diagnostic pointing at the offending byte (the unclosed {, the stray }, or the mismatched \end).

The check is purely syntactic: TeX is a macro language, so the rule does not validate command names, argument arity, or semantics — only brace and environment nesting. Because a macro can expand to braces or an environment the structural parser cannot see, valid TeX can occasionally look unbalanced; in that rare case disable the rule with [lint.rules] math-syntax = false or an ignore directive.

Example violation:

The mass-energy relation is $E = mc^{2$.

Diagnostic:

error[math-unclosed-group]: unclosed `{` group
 --> document.qmd:1:38
  |
1 | The mass-energy relation is $E = mc^{2$.
  |                                      ^

Resolution: Balance the braces or close the environment. No auto-fix is provided because the correct repair (which brace to add, where) is an author-intent decision.

math-unclosed-group

Emitted when a { is never closed before the end of the math content.

math-unexpected-close-brace

Emitted when a } appears with no matching {.

math-unclosed-environment

Emitted when a \begin{env} is never closed by a matching \end{env}.

math-mismatched-environment

Emitted when a \begin{a} is closed by \end{b} with a different name.

math-unexpected-end

Emitted when an \end appears with no open \begin.

YAML diagnostics

Panache emits YAML diagnostics when embedded YAML content is invalid. These apply to both document frontmatter (--- ... ---) and executable chunk hashpipe options (#| ...).

yaml-parse-error

Severity
Warning
Auto-fix
No
Description
The YAML lexer/parser could not interpret the content (malformed flow sequences, unterminated strings, etc.).

Example (hashpipe):

```{r}
#| echo: [
1 + 1
```

Diagnostic:

warning[yaml-parse-error]: YAML parse error: ...
  --> document.qmd:2:10

yaml-structure-error

Severity
Warning
Auto-fix
No
Description
The YAML parsed successfully but its top-level shape is not valid for the context (for example, frontmatter that is not a mapping, or a hashpipe block that does not produce a mapping of options).