---
title: "CommonMark conformance"
---

```{ojs}
//| label: load-data
//| echo: false

data = FileAttachment("commonmark-report.json").json()
```

Panache's parser is shared across all flavors (Pandoc, Quarto, RMarkdown, GFM,
CommonMark, MultiMarkdown). Each flavor selects a baseline set of extensions
through `Extensions::for_flavor()`. Under `Flavor::CommonMark`, almost every
Pandoc-specific extension is disabled, leaving only constructs that appear in
the [CommonMark specification](https://spec.commonmark.org/).

Conformance to the spec is tracked by running every example in `spec.txt`
through the parser and comparing the rendered HTML against the expected HTML
from the spec.

## Harness layout

  | Path                                                            | Purpose                                                         |
  | --------------------------------------------------------------- | --------------------------------------------------------------- |
  | `crates/panache-parser/tests/fixtures/commonmark-spec/spec.txt` | Vendored CommonMark spec.                                       |
  | `crates/panache-parser/tests/commonmark.rs`                     | Test runner (`commonmark_allowlist`, `commonmark_full_report`). |
  | `crates/panache-parser/tests/commonmark/spec_parser.rs`         | Parses spec.txt into example records.                           |
  | `crates/panache-parser/tests/commonmark/html_renderer.rs`       | Test-only CST → HTML renderer (not a public API).               |
  | `crates/panache-parser/tests/commonmark/allowlist.txt`          | Example numbers currently passing. CI fails on regression.      |
  | `crates/panache-parser/tests/commonmark/blocked.txt`            | Examples we deliberately do not target yet, with reasons.       |

The renderer mirrors the byte-equality comparison commonmark-hs uses: the output
is normalized via `<li>\n` → `<li>` and `\n</li>` → `</li>` before comparison.

## Baseline

```{ojs}
//| label: headline
//| echo: false

html`<p>
Panache passes
<strong>${data.passing} / ${data.total_examples} examples (${data.pass_pct.toFixed(1)} %)</strong>
under <code>Flavor::CommonMark</code> against
<code>spec.txt</code> v${data.spec_version}.
${data.failing} failing, ${data.blocked} blocked.
</p>`
```

```{ojs}
//| label: section-table
//| echo: false

Inputs.table(
  data.sections.map(s => ({
    Section: s.name,
    Pass: s.pass,
    Fail: s.fail,
    "Pass %": s.pass + s.fail === 0 ? "" : `${(100 * s.pass / (s.pass + s.fail)).toFixed(0)} %`
  })),
  {
    sort: "Section",
    rows: data.sections.length,
    align: { Pass: "right", Fail: "right", "Pass %": "right" }
  }
)
```

The table and headline above are regenerated from
`docs/development/commonmark-report.json`, which is written by the harness when
you run `commonmark_full_report`. To refresh the page, run that test and
re-render the docs.

## Workflow

### Updating the spec fixture

```bash
task update-commonmark-fixtures
# or specify a different ref
./crates/panache-parser/scripts/update-commonmark-spec-fixtures.sh 0.31.2
```

This clones `commonmark/commonmark-spec` at the given tag, copies `spec.txt`
into `tests/fixtures/commonmark-spec/`, and writes a `.panache-source` record of
the upstream commit.

### Running the harness

```bash
# Regression guard: every allowlisted example must still pass.
cargo test -p panache-parser --test commonmark commonmark_allowlist

# Full report: writes tests/commonmark/report.txt with per-section
# counts and a list of every passing example number. Use this output
# to grow the allowlist after fixing parser/renderer bugs.
cargo test -p panache-parser --test commonmark commonmark_full_report \
    -- --ignored --nocapture
```

### Growing the allowlist

1. Fix a parser or renderer bug (with a focused test reproducing the bug).
2. Run `commonmark_full_report` and inspect `report.txt`.
3. Add the newly-passing example numbers to `allowlist.txt`, ideally grouped
   under their section header for readability.
4. Run `commonmark_allowlist` to confirm the additions pass.

If an example is intentionally not targeted (e.g. a construct we choose not to
support under `Flavor::CommonMark`), add the number to `blocked.txt` with a
comment explaining the reason.

## Why an HTML renderer?

The CommonMark spec defines conformance as markdown-input → HTML-output
byte-equality. Panache's primary output is formatted markdown, not HTML, so the
conformance harness needs a renderer to bridge the gap. The renderer in
`tests/commonmark/html_renderer.rs` is **test code only** --- it is not part of
the public crate surface, and it covers only the constructs spec.txt exercises.
If a public `--to html` mode is wanted later, the renderer can graduate from
test-only to a real module; until then it intentionally stays narrow.
