panache 2.52.0

An LSP, formatter, and linter for Markdown, Quarto, and R Markdown
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
---
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`.

### `empty-list-item` {#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`](#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:**

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

### `link-text-is-url` {#link-text-is-url}

Detects inline links whose visible text is identical to the destination URL
(e.g. `[https://example.com/](https://example.com/)`) --- typically an artifact
of HTML→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`](#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.

**Example violation:**

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

```markdown
See <https://example.com/> for details.
```

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

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

```markdown
::: {lang=en-US}
[contact Ms. Nebbercracker]{lang=en-US}:::
```

```markdown
[]{#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` {#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-unclosed-group),
    [`math-unexpected-close-brace`](#math-unexpected-close-brace),
    [`math-unclosed-environment`](#math-unclosed-environment),
    [`math-mismatched-environment`](#math-mismatched-environment),
    [`math-unexpected-end`](#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:**

```markdown
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` {#math-unclosed-group}

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

#### `math-unexpected-close-brace` {#math-unexpected-close-brace}

Emitted when a `}` appears with no matching `{`.

#### `math-unclosed-environment` {#math-unclosed-environment}

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

#### `math-mismatched-environment` {#math-mismatched-environment}

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

#### `math-unexpected-end` {#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` {#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).