---
title: Lint Rules
description: >
  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](../guide/linting.qmd).

## 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` {#heading-hierarchy}

Detects skipped heading levels that violate document structure best practices.

Severity
:   Warning

Auto-fix
:   Yes

Diagnostic codes
:   [`heading-hierarchy`](#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:**

```markdown
# 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`.

### `heading-eaten-attrs` {#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`](#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:**

```markdown
# 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` {#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`](#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:**

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

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

### `duplicate-reference-labels` {#duplicate-reference-labels}

Detects duplicate reference link and footnote definitions.

Severity
:   Warning

Auto-fix
:   No

Diagnostic codes
:   [`duplicate-reference-labels`](#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:**

```markdown
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` {#undefined-references}

Detects reference links and footnotes that point to missing definitions.

Severity
:   Warning

Auto-fix
:   No

Diagnostic codes
:   [`undefined-reference-label`](#undefined-reference-label),
    [`undefined-footnote-id`](#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:**

```markdown
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` {#undefined-reference-label}

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

#### `undefined-footnote-id` {#undefined-footnote-id}

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

### `undefined-anchor` {#undefined-anchor}

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

Severity
:   Warning

Auto-fix
:   No

Diagnostic codes
:   [`undefined-anchor`](#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:**

```markdown
# Real Heading {#real}

See [the typo](#reel).
```

**Diagnostic:**

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

### `unused-definitions` {#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-definition-label),
    [`unused-footnote-id`](#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:**

```markdown
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` {#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` {#unused-footnote-id}

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

### `citation-keys` {#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-load-error),
    [`bibliography-parse-error`](#bibliography-parse-error),
    [`missing-bibliography-key`](#missing-bibliography-key),
    [`duplicate-bibliography-key`](#duplicate-bibliography-key),
    [`duplicate-inline-reference-id`](#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):**

```markdown
---
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):**

```markdown
---
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` {#bibliography-load-error}

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

#### `bibliography-parse-error` {#bibliography-parse-error}

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

#### `missing-bibliography-key` {#missing-bibliography-key}

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

#### `duplicate-bibliography-key` {#duplicate-bibliography-key}

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

#### `duplicate-inline-reference-id` {#duplicate-inline-reference-id}

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

### `crossref-as-link-target` {#crossref-as-link-target}

Detects link destinations that begin with `@`, which is almost always a typo for
`#` (anchor) or a misplaced cross-reference / citation key.

Severity
:   Warning

Auto-fix
:   Yes

Requirements
:   Requires `extensions.citations = true` in configuration (default for the
    Pandoc, Quarto, and R Markdown flavors).

Diagnostic codes
:   [`crossref-as-link-target`](#crossref-as-link-target)

Description
:   In Pandoc/Quarto, `@key` is reserved for citations and cross-references and
    must stand alone, not appear inside a link's `(...)` destination. Writing
    `[Figure 2](@fig-2)` produces a link with the literal URL `@fig-2`; the
    author almost always meant `[Figure 2](#fig-2)`.

**Example violation:**

```markdown
See [Figure 2](@fig-2) for details.
```

**Diagnostic:**

```
warning[crossref-as-link-target]: Link target starts with '@'; cross-references and citation keys must stand alone, not appear as a link destination
 --> document.qmd:1:16
  |
1 | See [Figure 2](@fig-2) for details.
  |                ^
```

**Auto-fix output:**

```markdown
See [Figure 2](#fig-2) for details.
```

**When it runs:** On every inline link (`[text](dest)`) and inline image
(`![alt](dest)`) whose destination's leading non-whitespace character is `@`.
Bare cross-references (`@fig-2`) and citation forms (`[@smith2020]`) outside of
link destinations are not flagged.

### `chunk-label-spaces` {#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`](#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` {#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`](#missing-chunk-labels)

Description
:   Labels facilitate debugging. Add a label with either `#| label: my-chunk` or
    inline `label=my-chunk`.

### `figure-crossref-captions` {#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`](#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` {#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`](#unknown-emoji-alias)

Description
:   Checks parsed emoji aliases against the emoji shortcode dataset and warns
    when an alias is unknown.

**Example violation:**

```markdown
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` {#html-entities}

Detects malformed HTML named entity references in inline prose.

Severity
:   Warning

Auto-fix
:   No

Diagnostic codes
:   [`html-entities`](#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](https://html.spec.whatwg.org/multipage/named-characters.html).
    - `&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:**

```markdown
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` {#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`](#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:**

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

**Auto-fix output:**

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

### `footnote-ref-in-footnote-def` {#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`](#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:**

```markdown
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` {#stray-fenced-div-markers}

Detects stray closing fenced div markers (`:::` or longer runs of colons) that
do not pair with an opening fence and consequently land inside paragraph text.

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`](#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). When a closer-shaped line
    has no matching opener in scope, Pandoc treats it as ordinary paragraph text
    instead of as a div marker. This is almost always a typo---a mismatched
    fence count between opener and closer, an accidentally deleted opener, or a
    copy/paste artifact---and the document silently renders with `:::` showing
    up in the prose.

    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 when, after up to three leading spaces, a line in a paragraph
    consists of three or more colons followed only by trailing whitespace. It does
    **not** flag mid-line `:::`, code spans containing `:::`, or indented code
    blocks---those are not closing-fence shapes.

**Example violation:**

```markdown
::: warning
The fence count on the opener and closer don't match.
::::
```

**Diagnostic:**

```
warning[stray-fenced-div-markers]: Stray fenced div marker '::::' has no matching opener
 --> document.qmd:3:1
  = help: if this is meant to close a div, check the opener's class/attributes;
          otherwise escape the colons or rewrite the line
```

## 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` {#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):**

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

**Diagnostic:**

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

### `yaml-structure-error` {#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).
