Raxol.Docs.ProseLint (Raxol v2.6.1)

View Source

House prose rules for Markdown, as pure functions over file contents.

The rules encode style decisions that were previously enforced by hand and kept regressing: three separate sweeps removed em-dashes from docs/ and each time they came back, and the sweep that cleaned docs/ never covered packages/ at all. A rule that lives in a checker cannot have that scope gap.

Three rules run by default, all errors:

  • :unicode_punctuation - em-dash, en-dash, and curly quotes. House style is ASCII (see the repo CLAUDE.md).
  • :prose_dash - -- used as an em-dash substitute. Fenced code, inline code spans, and all-dash table cells are exempt, so CLI flags (mix ... -- --quick), Lua comments, and | -- | placeholders pass.
  • :broken_link - a relative Markdown link whose target file or #anchor does not resolve.

A fourth rule, :heading_case, is opt-in via headings: true and reports warnings. It cannot be a default: distinguishing a Title Case heading from a proper noun needs a dictionary, and on this repo the naive form flagged ## The Elm Architecture (TEA) and ### Stage 3: Output generation. It fires only when every alphabetic word in the heading is capitalized and one of them is in @title_case_words, which keeps it useful for a sweep without making the default run noisy enough to ignore.

check_file/2 returns findings; the caller decides what is fatal.

Summary

Functions

Lint already-read content. Split out so tests do not touch disk.

Lint one Markdown file.

Types

finding()

@type finding() :: %{
  path: String.t(),
  line: pos_integer(),
  rule: atom(),
  severity: :error | :warning,
  message: String.t(),
  text: String.t()
}

Functions

check_content(path, content, opts \\ [])

@spec check_content(String.t(), String.t(), keyword()) :: [finding()]

Lint already-read content. Split out so tests do not touch disk.

check_file(path, opts \\ [])

@spec check_file(
  String.t(),
  keyword()
) :: [finding()]

Lint one Markdown file.

Options: :root (repository root the path is relative to, default ".") and :headings (run the opt-in heading-case rule, default false).