md-fences.ts 1.8 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455
  1. /**
  2. * Shared fenced-code-block extractor for the Markdown doc gates
  3. * (currently `doc-typecheck.ts`; future Markdown gates can share it). One scanner, per-gate
  4. * classification: each gate maps a fence info string (` ```ts `,
  5. * ` ```yaml ignore-check `, …) to its own kind tag and receives every
  6. * classified block with its 1-based opening-fence line.
  7. */
  8. import { readFileSync } from 'node:fs'
  9. /** One extracted fenced block, classified by the caller's `classify`. */
  10. export interface Fence<K> {
  11. /** 1-based line of the opening fence. */
  12. line: number
  13. kind: K
  14. code: string
  15. }
  16. /**
  17. * Extract every fenced block of `absPath` whose info string `classify` maps
  18. * to a kind. Blocks classified `null` are skipped (their bodies are still
  19. * consumed, so an unrelated fence can never leak into a tracked one).
  20. *
  21. * @param absPath — absolute path of the Markdown file.
  22. * @param classify — info string (trimmed, e.g. `ts ignore-check`) → kind, or
  23. * null for fences this gate does not track.
  24. * @returns the classified blocks in document order.
  25. */
  26. export function extractFences<K>(absPath: string, classify: (info: string) => K | null): Fence<K>[] {
  27. const lines = readFileSync(absPath, 'utf8').split('\n')
  28. const blocks: Fence<K>[] = []
  29. let open: { line: number; kind: K; body: string[] } | null = null
  30. let skipping = false
  31. lines.forEach((raw, i) => {
  32. const fence = /^```(\s*)(\S.*)?$/.exec(raw)
  33. if (!fence) {
  34. if (open) open.body.push(raw)
  35. return
  36. }
  37. if (open) {
  38. blocks.push({ line: open.line, kind: open.kind, code: open.body.join('\n') })
  39. open = null
  40. return
  41. }
  42. if (skipping) {
  43. skipping = false
  44. return
  45. }
  46. const kind = classify((fence[2] ?? '').trim())
  47. if (kind !== null) open = { line: i + 1, kind, body: [] }
  48. else skipping = true
  49. })
  50. return blocks
  51. }