Skip to content

Validation

Carve validation is exposed as a linter. It is designed for documents that parse successfully but would still render as the wrong thing: broken links, silent fallbacks, ignored definitions, and migration-era syntax that looks valid at a glance.

Command Line

Use the carve lint command from the TypeScript implementation:

sh
carve lint doc.crv
carve lint docs/**/*.crv
carve lint < doc.crv

Output is one finding per line:

txt
doc.crv:12:8 broken-crossref — Cross-reference </#missing> has no matching heading id; it renders as the literal text "</#missing>".

The command exits with 0 when the document is clean, 1 when it reports any finding, and 2 for command or file-read errors. That makes it usable in CI and pre-commit hooks:

sh
carve lint docs/**/*.crv

carve lint has no rule-selection flags yet. It reads files or stdin and reports the full built-in rule set.

Programmatic API

JavaScript and TypeScript callers can use lintCarve directly:

ts
import { lintCarve } from '@markup-carve/carve'

const warnings = lintCarve(source)

Each warning includes:

ts
{
  rule: string
  message: string
  line: number
  column: number
  start: number
  end: number
}

If your renderer resolves headings with ASCII-folded ids, pass the same option to the linter so cross-reference validation uses the same slug policy:

ts
lintCarve(source, { asciiHeadingIds: true })

Editor Diagnostics

The language server surfaces the same lint warnings as editor diagnostics, so the command-line and editor behavior stay aligned.

Rules

RuleCatches
duplicate-heading-idtwo headings producing the same id, either by slug collision or repeated explicit {#id}
broken-crossrefa </#id> cross-reference with no matching heading or numbered caption id
unresolved-reference-linka [text][label] or [text][] reference link with no matching link definition or implicit heading target
unresolved-footnotea [^label] footnote reference with no matching [^label]: ... definition
duplicate-footnote-definitiona repeated [^label]: ... definition; the first definition wins and later ones are ignored
unused-footnote-definitiona footnote definition that is never referenced and is omitted from rendered output
heading-trailing-attributea trailing {#id} or {.class} on a heading line; attributes must go on the line above the heading
raw-block-syntaxa legacy ```raw FORMAT fence; Carve raw blocks use ```=FORMAT
block-marker-as-texta line that opens like a block (:::, {#, {.) but parsed as plain text

The CLI also reports Djot/Markdown delimiter collisions from the migration checker, so carve lint is the broadest single validation command.

Released under the MIT License.