Lint Rules
Reference catalog 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, orinfo 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)
Fix safety
Auto-fixes carry a safety level that controls when panache lint --fix applies them:
safe-
The fix preserves the document’s meaning and only tidies syntax. Safe fixes are applied by a plain
--fix. unsafe-
The fix may change the document’s meaning (for example, deleting a key). Unsafe fixes are skipped by a plain
--fix; pass--unsafe-fixesalongside--fixto apply them too. When a run leaves unsafe fixes unapplied, the CLI prints how many are available.
In the editor (LSP), unsafe fixes are still offered as individual quick fixes (labeled (unsafe)), but they are excluded from the aggregate “Fix all auto-fixable lint issues” action, mirroring the safe-by-default CLI behavior.
The “Auto-fix” field of each rule below notes when its fix is unsafe.
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
### SubsectionDiagnostic:
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:
- A
LIST_ITEMproduced by the parser has no inline content (e.g. a bare-line between two non-empty bullets, or1.with nothing after the period). - 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.
- A
Example violation:
- Item one
-
- Item threeDiagnostic:
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.
empty-values
Flags YAML block-mapping keys whose value is an implicit null: a key with nothing after the colon.
- Severity
- Warning
- Auto-fix
-
Yes (unsafe). Deletes the empty key’s whole line. Marked unsafe because dropping a key changes the document’s data, so it is applied only with
--unsafe-fixes. - Diagnostic codes
-
empty-values - Description
-
Modeled on yamllint’s
empty-values. A key with no value (title:followed by a newline) parses as YAML null. That is occasionally intentional, but far more often a value the author forgot to fill in. The rule walks document frontmatter and hashpipe cell options alike (both embed the same YAML CST).An explicit null is a deliberate value and is never flagged, so write
title: null(ortitle: ~) when the empty value is on purpose. The rule covers block mappings only; flow mappings ({a: }) and block sequences are not flagged.
Example violation:
---
title:
author: Jane Roe
---Diagnostic:
warning[empty-values]: Key `title` has an empty value (implicit null)
--> document.md:2:1
|
2 | title:
| ^^^^^
Resolution: Supply a value, delete the key, or write an explicit null if the empty value is intentional. The auto-fix takes the “delete the key” path, but because that discards data it is unsafe and runs only under --unsafe-fixes.
consumer-divergence
Flags a plain YAML scalar whose resolved type or value differs across the document’s active YAML consumers—a real cross-toolchain ambiguity, not a style opinion.
- Severity
- Warning
- Auto-fix
-
Yes (unsafe). Single-quotes the value, forcing a string under every consumer. Marked unsafe because if the author meant the boolean or integer, quoting changes the value for the YAML 1.1 stage, so it is applied only with
--unsafe-fixes. - Diagnostic codes
-
consumer-divergence - Requirements
-
Quarto flavor (
flavor = "quarto"). Only Quarto frontmatter is read under two different YAML versions; every other context has a single consumer and cannot diverge. - Description
-
A Quarto document’s frontmatter is parsed twice: by Quarto’s js-yaml (YAML 1.2 core) and by pandoc’s libyaml (≈ YAML 1.1). The two versions resolve some plain scalars to different values. This rule flags only those genuine divergences. Unlike yamllint’s
truthy/octal-values, it stays silent when the value is unambiguous for the document’s consumers.Detected divergences:
- Booleans (the “Norway problem”). YAML 1.1 reads
y,n,yes,no,on,off(and case variants) as booleans; YAML 1.2 core reads onlytrue/false. Socountry: nois the booleanfalseto pandoc but the string"no"to Quarto. - Leading-zero integers. YAML 1.1 reads
0755/010as octal; js-yaml reads them as strings (it accepts only0o…octal). So the two stages disagree onmode: 0755(integer493vs string).
Quoted, literal, and folded scalars are never flagged (their string tag is pinned), and neither are unambiguous values such as
true,42,3.14,.inf, or plain words. The rule covers block mappings only; flow mappings and sequence items are not yet inspected. - Booleans (the “Norway problem”). YAML 1.1 reads
Example violation:
---
country: no
mode: 0755
---Diagnostic:
warning[consumer-divergence]: Key `country`: value `no` is the boolean `false` to pandoc (YAML 1.1) but the string "no" to Quarto's js-yaml (1.2)
--> document.qmd:2:10
|
2 | country: no
| ^^
Resolution: Quote the value to force a string under every consumer (country: 'no'), or write the explicit true/false (or canonical integer) you mean. The auto-fix takes the quoting path, but because that changes the value for the YAML 1.1 stage it is unsafe and runs only under --unsafe-fixes.
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-commentsremoves 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.comDiagnostic:
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 and 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.comDiagnostic:
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 or cross-reference points to a label that has no matching definition. Cross-references using an extension prefix listed in crossref-prefixes are not validated, since their targets are defined by a mechanism Panache does not model.
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#fragmentdoes 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 theauto_identifiersextension 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
citationsextension is enabled, links of the form[text](#ref-citekey)are recognized as overriding a citation’s link text (Pandoc renders bibliography entries withid="ref-<citekey>"), so they resolve as long as@citekeyappears somewhere in the document.Anchors declared via raw HTML
<a id="x">or<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.comDiagnostic:
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 or parse failures, Warning for undefined keys and duplicates
- Auto-fix
- No
- Requirements
-
Requires
extensions.citations = truein 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. Built-in Quarto cross-references (@fig-, @tbl-, …) are exempt; for extension-injected prefixes such as pseudocode’s @algo-, list them under crossref-prefixes so they are treated as cross-references rather than citations.
duplicate-bibliography-key
Emitted when the same key appears more than once across loaded bibliography files. The diagnostic anchors to a bibliography declaration in the document’s own YAML frontmatter. A duplicate confined to a project-level bibliography (declared in _quarto.yml/_metadata.yml rather than the document) is a defect of that shared file, not of any one document: instead of being repeated for every inheriting document, it is reported once, anchored to the manifest’s own bibliography: value. This manifest check runs when a project directory is linted or when the manifest is targeted explicitly; a single-document lint stays quiet about the ambient manifest.
duplicate-inline-reference-id
Emitted when an inline bibliography entry collides with another inline entry or with a key from a loaded bibliography file.
citation-nonbreaking-space
Detects a breakable space (or a source line break) between text and a bracketed citation ([@key]), where line wrapping can strand the rendered citation — a lone [1] or (Smith 2020) — at the start of a line.
- Severity
- Warning
- Auto-fix
- Yes (replaces the space with a non-breaking one)
- Requirements
-
Requires
extensions.citations = truein configuration (default for the Pandoc, Quarto, and R Markdown flavors). - Diagnostic codes
-
citation-nonbreaking-space - Description
-
Pandoc renders the space before
[@key]as an ordinary breakable space in every output format, so whether the citation lands at the start of a line is left to the final typesetting — and the author usually cannot know at writing time whether the document will render with a numeric or an author-year citation style. Tying the citation to the preceding word with a non-breaking space is safe under both. The rule flags a plain space, tab, or source line break directly before a bracketed citation (including suppressed-author citations like[-@key]). A citation that starts a paragraph, follows a hard line break, or is already tied with\or a literal U+00A0 is not flagged. In-text citations (@key) are out of scope.The auto-fix rewrites the gap to
\(Pandoc’s non-breaking space escape). Whenall-symbols-escapableis disabled,\would not parse as an escape, so the fix inserts a literal U+00A0 character instead.
Example violation:
Some important fact [@smith2020].Auto-fix output:
Some important fact\ [@smith2020].crossref-as-link-target
Detects link destinations that begin with @, which is almost always a typo for # (anchor) or a misplaced cross-reference or citation key.
- Severity
- Warning
- Auto-fix
- Yes
- Requirements
-
Requires
extensions.citations = truein configuration (default for the Pandoc, Quarto, and R Markdown flavors). - Diagnostic codes
-
crossref-as-link-target - Description
-
In Pandoc/Quarto,
@keyis 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:
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:
See [Figure 2](#fig-2) for details.When it runs: On every inline link ([text](dest)) and inline image () 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
Detects executable chunk labels containing whitespace (for example {r several words} or label="several words").
- Severity
- Warning
- Auto-fix
- No
- Requirements
-
extensions.fenced-code-attributes = true(the default for Quarto and R Markdown flavors). - 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-wordsorseveral_wordsinstead.
missing-chunk-labels
Detects executable chunks that do not define a label (either inline or hashpipe style).
- Severity
- Warning
- Auto-fix
- No
- Requirements
- A flavor with executable chunks (Quarto or R Markdown).
- Diagnostic codes
-
missing-chunk-labels - Description
-
Labels facilitate debugging. Add a label with either a hashpipe option or inline
label=my-chunk. The suggested hashpipe comment prefix matches the chunk language (#|for R or Python,//|for C++ or Rust,--|for SQL, and so on).
figure-crossref-captions
Detects figure cross-references that point to chunk labels without a figure caption option.
- Severity
- Warning
- Auto-fix
- No
- Requirements
- A flavor with executable chunks (Quarto or R Markdown).
- 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 alabelbut nofig-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 = truein 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
…through to the output unchanged, so a typo (&ellips;) or a missing trailing semicolon (&numeroinstead of№) silently produces wrong output. This rule flags three conservative cases:&NAME;whereNAMEis 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) whereNAMEis one edit away from a known entity (e.g.&hellp→…). Far-from-anything words are left alone to keep prose like “Procter &Gamble” quiet.
The rule deliberately ignores numeric character references (
{,ꯍ) for now and does not scan code spans, code blocks, raw HTML, inline/display math, link destinations, attributes, YAML metadata, comments, or the verbatim bodies of MyST code and math directives ({code-cell},{code-block},{code},{math}).
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 '…'?
warning[html-entities]: HTML entity '&numero' is missing a trailing ';'
--> document.qmd:3:9
= help: write '№' to encode the character
link-text-is-url
Detects inline links whose visible text is identical to the destination URL (e.g. [https://example.com/](https://example.com/)), which is typically an artifact of HTML to Markdown conversion, and offers to rewrite them as an autolink.
- Severity
- Warning
- Auto-fix
-
Yes (replaces the bracket form with
<url>) - Diagnostic codes
-
link-text-is-url - Description
-
The rule fires only when all of the following hold:
- the link is inline (
[text](url)), not reference-style; - the link text is plain: no nested emphasis, code, or other inline structure;
- the rendered link text is byte-identical to the destination URL, including any trailing slash;
- the link has no title;
- the URL passes the dialect’s autolink validator (CommonMark §6.4 schemes / email shape; the Pandoc dialect is laxer).
The byte-exact text-vs-URL check is intentional: changing
[A](B)to<A>rewrites where the link points. The rule never silently changes a destination, so cases like[https://example.net/](https://example.net)(text and URL differ by a trailing slash) are skipped even though they look “almost” duplicated. Use the LSP code action to convert those manually if intended.Bare URIs (under the
autolink-bare-urisextension, e.g. a plainhttps://example.comin GFM) are never flagged: they carry no brackets, are already the shortest form, and parse as autolinks rather than[text](url)links. - the link is inline (
Example violation:
See [https://example.com/](https://example.com/) for details.Diagnostic:
warning[link-text-is-url]: Link text is identical to the URL; an autolink is shorter and clearer.
--> document.md:1:5
= help: rewrite as `<https://example.com/>`
Auto-fix output:
See <https://example.com/> for details.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 = truein 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 = truein 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 literalStrand 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_REFERENCEnodes; the rule scans the definition body’sTEXTtokens 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 = truein 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,math-unclosed-delimiter,math-unexpected-right - 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 renderto PDF hard-fails on an unclosed brace or mismatched environment, and MathJax/KaTeX silently drop the equation. That is why these ride aterrorseverity. 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, environment, and
\left/\rightdelimiter 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 = falseor 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.
math-unclosed-delimiter
Emitted when a \left delimiter is never closed by a matching \right (for example \left( x with no \right)).
math-unexpected-right
Emitted when a \right appears with no open \left.
quarto-schema
Validates document YAML frontmatter, code-cell options, and project config files (_quarto.yml, _metadata.yml) against Quarto’s machine-readable schema, flagging type mismatches and invalid enum values. (Unknown or misspelled keys are handled by the separate, opt-in quarto-schema-unknown-key rule.)
- Severity
- Warning
- Auto-fix
- No
- Diagnostic codes
-
quarto-schema-type-mismatch,quarto-schema-invalid-enum - Requirements
-
Quarto flavor (
flavor = "quarto"). Pandoc has no metadata schema, so the rule does not run for other flavors. - Description
-
Unlike pandoc—where metadata is an arbitrary mapping—Quarto ships a schema and validates against it. This rule reproduces the high-value parts of that check using a distilled copy of Quarto’s schema (vendored per quarto-cli release; see
assets/quarto-schema/.panache-source). It covers document frontmatter (--- ... ---), code-cell options (#| ..., validated against the cell’s engine schema—knitr for R cells, jupyter otherwise), and project config files—_quarto.yml(against the project-config schema) and_metadata.yml(directory metadata, against the frontmatter schema):- Type mismatches. A scalar whose resolved YAML 1.2 type does not match the schema (e.g. a boolean field set to a non-boolean) is flagged. String fields accept any scalar, since Quarto coerces.
- Invalid enum values. A scalar outside a field’s allowed set is flagged.
The Quarto schema version the rule validates against can be pinned with
[compat] quarto = "1.9". One version is currently bundled, so this is advisory.
Example violation:
---
toc: maybe
---Diagnostic:
warning[quarto-schema-type-mismatch]: value should be a boolean
--> document.qmd:2:6
|
2 | toc: maybe
| ^^^^^
Project config files are validated wherever the linter encounters them: the CLI checks an explicit panache lint _quarto.yml (or _metadata.yml) target, and the LSP validates the manifests reachable from an open project document, publishing diagnostics on each manifest’s own file.
Opt-out: Like Quarto itself, this rule (and quarto-schema-unknown-key) honors validate-yaml: false. When a document’s frontmatter or a manifest sets it, no schema validation runs for that file—useful for filter-driven patterns the core schema rejects, such as a bibliography: map for multibib. A malformed-YAML parse error is still reported.
Limitations: Format-gated keys are accepted regardless of the active output format, and deeply nested options reached through a permissive anyOf branch may not be checked.
quarto-schema-unknown-key
Flags unknown or misspelled keys in document frontmatter, code-cell options, and project config files, validated against Quarto’s machine-readable schema.
- Severity
- Warning
- Default
-
Off—opt in via
[lint.rules] quarto-schema-unknown-key = true. - Auto-fix
- No
- Diagnostic codes
-
quarto-schema-unknown-key - Requirements
-
Quarto flavor (
flavor = "quarto"). Pandoc has no metadata schema, so the rule does not run for other flavors. - Description
-
This rule is off by default because Quarto itself tolerates unknown keys at render time. Its schema objects are open so that pandoc passthrough and custom template metadata keep working. So flagging them is stricter than
quarto render. It is still useful for catching silent-failure typos (a misspelledformatblock simply does nothing, and Quarto will not warn), so it is offered as an opt-in.When enabled, on closed schema objects (e.g. a specific format’s option block) any undeclared key is flagged. On the open top level (which must allow pandoc passthrough and custom metadata) only a near-miss typo of a known key is flagged, with a “did you mean” suggestion.
Example violation (with the rule enabled):
---
forrmat: html
---Diagnostic:
warning[quarto-schema-unknown-key]: unknown key `forrmat`; did you mean `format`?
--> document.qmd:2:1
|
2 | forrmat: html
| ^^^^^^^
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 or 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).