first commit
This commit is contained in:
114
docs/devtools/tooling/linter/src/markdown-fixer/rules.md
Normal file
114
docs/devtools/tooling/linter/src/markdown-fixer/rules.md
Normal file
@@ -0,0 +1,114 @@
|
||||
# `markdown-fixer/rules`
|
||||
|
||||
## Purpose
|
||||
|
||||
`rules.ts` houses the individual remark plugins that implement each markdown-lint rule
|
||||
the fixer enforces, plus the shared heuristic table (`PROPER_NAMES`) and the
|
||||
`inferCodeLanguage` heuristic. It is the rule-by-rule contract of the fixer; the
|
||||
`markdown-fixer.ts` entry point composes these plugins into a single `unified()`
|
||||
pipeline so the orchestration code stays short and the rule logic stays in one place,
|
||||
testable and reviewable per rule.
|
||||
|
||||
## Features
|
||||
|
||||
- One exported plugin factory per rule — `fixMultipleH1s`, `fixEmphasisAsHeading`,
|
||||
`fixBlankLines`, `fixProperNames`, `fixOrderedListNumbering`, `fixHorizontalRules`,
|
||||
`fixCodeBlockLanguages`. Each takes a `RuleSet` sink the caller mutates as the
|
||||
plugin walks the AST so the fixer can report exactly which rules altered a file.
|
||||
- `HEADING_HINT_BASIC` and `HEADING_HINT_EXTENDED` regexes — the basic matcher
|
||||
recognises `Step N:`, `A)`, and all-caps `LABEL:` prefixes; the extended matcher
|
||||
adds `NEVER`, `ALWAYS`, `CRITICAL`, `IMPORTANT`, `MANDATORY` keywords used in
|
||||
rules / safety docs.
|
||||
- `inferCodeLanguage(value)` — heuristic mapper from code-block content to a
|
||||
language identifier (`bash`, `javascript`, `typescript`, `python`, `markdown`,
|
||||
`html`, `json`, `text`).
|
||||
- `PROPER_NAMES` — closed allow-list of `(lower, proper)` pairs for MD044.
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import {
|
||||
fixMultipleH1s,
|
||||
fixCodeBlockLanguages,
|
||||
} from "@suaveplan/linter/markdown-fixer";
|
||||
|
||||
const rules = new Set<string>();
|
||||
const sink = { add: (r: string) => rules.add(r) };
|
||||
// then plug each factory into a `unified().use(...)` pipeline
|
||||
unified().use(fixMultipleH1s, sink).use(fixCodeBlockLanguages, sink);
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
Compose the same plugin set in a custom pipeline that strips Markdown to plain text:
|
||||
|
||||
```ts
|
||||
import remarkParse from "remark-parse";
|
||||
import remarkStringify from "remark-stringify";
|
||||
import { unified } from "unified";
|
||||
import {
|
||||
fixCodeBlockLanguages,
|
||||
fixMultipleH1s,
|
||||
HEADING_HINT_EXTENDED,
|
||||
fixEmphasisAsHeading,
|
||||
} from "@suaveplan/linter/markdown-fixer";
|
||||
|
||||
const rules = new Set<string>();
|
||||
const sink = { add: (r: string) => rules.add(r) };
|
||||
const processor = unified()
|
||||
.use(remarkParse)
|
||||
.use(fixMultipleH1s, sink)
|
||||
.use(fixEmphasisAsHeading, sink, HEADING_HINT_EXTENDED)
|
||||
.use(fixCodeBlockLanguages, sink)
|
||||
.use(remarkStringify);
|
||||
const file = await processor.process("# A\n# B\n");
|
||||
const applied = [...rules];
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `interface RuleSet { add(rule: string): void }`
|
||||
|
||||
The mutable sink the plugins write to. The fixer passes a `Set<string>` wrapper.
|
||||
|
||||
### `HEADING_HINT_BASIC: RegExp`
|
||||
|
||||
Matches `^(Step \d+:|[A-Z]\)|[A-Z][A-Z\s]+:)` — used by the first emphasis-as-heading
|
||||
pass.
|
||||
|
||||
### `HEADING_HINT_EXTENDED: RegExp`
|
||||
|
||||
Extended matcher used by the second pass; adds keyword prefixes.
|
||||
|
||||
### `PROPER_NAMES: ReadonlyArray<readonly [string, string]>`
|
||||
|
||||
Closed pair list — every lowercase form on the left rewrites to the canonical form on
|
||||
the right with a case-insensitive word-boundary match.
|
||||
|
||||
### `inferCodeLanguage(value: string): string`
|
||||
|
||||
Heuristic returning one of `"bash" | "javascript" | "typescript" | "python" | "markdown" | "html" | "json" | "text"`.
|
||||
|
||||
### Plugin factories
|
||||
|
||||
Each factory accepts the `RuleSet` (and a matcher where applicable) and returns a
|
||||
`unified` transformer of type `(tree: Root) => void`. Exported names:
|
||||
`fixMultipleH1s`, `fixEmphasisAsHeading`, `fixBlankLines`, `fixProperNames`,
|
||||
`fixOrderedListNumbering`, `fixHorizontalRules`, `fixCodeBlockLanguages`.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
- The blank-line pass walks the tree depth-first, fixing each `Parent`'s children
|
||||
bottom-up so a heading nested inside a `blockquote` gets the same surround-with-blanks
|
||||
treatment as one at the document root. The pass also collapses adjacent empty
|
||||
paragraphs as MD012.
|
||||
- The emphasis-as-heading pass runs TWICE in the pipeline — once with the basic
|
||||
matcher, once with the extended matcher. The basic matcher handles the common
|
||||
`Step 1: …` form; the extended pass picks up safety-doc prefixes
|
||||
(`NEVER`, `MANDATORY`) that the basic pattern is intentionally too narrow to catch.
|
||||
- `fixOrderedListNumbering` resets `list.start = 1` only when it isn't already 1.
|
||||
This avoids treating an already-canonical list as "changed" — necessary so
|
||||
`fixMarkdown` can report `changed: false` correctly.
|
||||
- `inferCodeLanguage` is intentionally conservative — any block that doesn't match a
|
||||
known heuristic falls back to `"text"` rather than guessing. The remark fixer is a
|
||||
formatter, not a syntax analyser.
|
||||
Reference in New Issue
Block a user