craftz

Where each rule is stated

A rule is a list item in the rules on one line that opens with its id as a span, like - [`scripts-verify`]{#scripts-verify}: text. Its part is the second-level heading it's under, like Scripts. The doc is the one place a rule is worded, and the code holds only its check and its fix, under the same id.

Code blocks are skipped, so an example can't state a rule. This file reads only the doc, so craftz rules loads nothing else.

import { readFileSync } from "node:fs";
import { fileURLToPath } from "node:url";

/** A rule as the doc states it, and the command that prints it. */
export type Stated = { id: string; text: string; part: string; see: string };

/** The rules doc, which the package ships beside its code. */
export const RULES_DOC = fileURLToPath(new URL("../docs/rules.md", import.meta.url));

const doc = (): string => readFileSync(RULES_DOC, "utf8");

// Each line outside a code block, with the part it's under.
function* lines(markdown: string): Generator<{ line: string; part: string }> {
  let part = "";
  let fenced = false;
  for (const line of markdown.split("\n")) {
    if (/^(```|~~~)/.test(line)) fenced = !fenced;
    if (fenced) continue;
    part = /^## (.+)$/.exec(line)?.[1] ?? part;
    yield { line, part };
  }
}

/** Every rule the doc states, in order. */
export function stated(markdown = doc()): Stated[] {
  const found: Stated[] = [];
  for (const { line, part } of lines(markdown)) {
    const rule = /^- \[`([a-z]+-[a-z-]+)`\]\{#\1\}: (.+)$/.exec(line);
    if (rule?.[1] && rule[2]) {
      found.push({ id: rule[1], text: rule[2], part, see: `craftz rules ${rule[1]}` });
    }
  }
  return found;
}

/** The doc's parts, its second-level headings, in order. */
export function parts(markdown = doc()): string[] {
  return [...lines(markdown)].flatMap(({ line }) => /^## (.+)$/.exec(line)?.[1] ?? []);
}