refactor(scripts): consolidate gate scripts on mdast fences, parseArgs, and globSync

Implements the gate-consolidation Agent Note from the NIH dependency audit:

- Shared markdownFences helper in scripts/markdown.ts (mdast code-node visit);
  doc-typecheck and verify-type-equiv extract fences through it; md-fences.ts
  and the duplicated extractEquivBlocks regex scanner are deleted;
  markdownProseLines derives fenced lines from parsed code-node positions
  instead of a second fence regex.
- publint-all.ts and verify-built-package-invariants.mjs parse argv with
  node:util parseArgs instead of hand-stepped parseOptions copies.
- Five straggler readdirSync walks become globSync: verify-runtime-closure,
  dev-web discoverPluginDirs, verify-package-paths realPackageNames,
  verify-client-domain-graph listSources, publint-all addPath. The
  dirent-diagnostic walks in check-workspace-constraints.ts and clean.ts stay.

Behavior parity verified: pnpm run doc-sync and every rewritten gate produce
byte-identical output before and after on this tree.

Moves the owning Agent Note proposed -> implemented and re-records its pair.
This commit is contained in:
Tianyi Cui
2026-07-26 23:14:28 +08:00
parent c9dc097749
commit c3873464ba
15 changed files with 163 additions and 268 deletions

View File

@@ -21,6 +21,18 @@ export interface MarkdownHeadingLine extends MarkdownProseLine {
text: string
}
/** One code block from a parsed Markdown source. */
export interface MarkdownFence {
/** 1-based source line of the opening fence. */
line: number
/** Info-string language (its first word), null on a bare or indented block. */
lang: string | null
/** Full info string (e.g. `ts ignore-check`), '' on a bare or indented block. */
info: string
/** Block body without the fence delimiters. */
code: string
}
/** Parse GitHub-flavored Markdown with the repository's standard extensions. */
export function parseMarkdown(source: string): Nodes {
return fromMarkdown(source, { extensions: [gfm()], mdastExtensions: [gfmFromMarkdown()] })
@@ -38,6 +50,23 @@ export function visitMarkdown(node: Nodes, visitor: (node: Nodes) => boolean | v
}
}
/**
* Extract every parsed code block with its info string, in document order.
* @param source - Markdown source to scan.
* @returns each block's opening line, language, info string, and body.
*/
export function markdownFences(source: string): MarkdownFence[] {
const fences: MarkdownFence[] = []
visitMarkdown(parseMarkdown(source), (node) => {
if (node.type !== 'code' || node.position === undefined) return
const lang = node.lang ?? null
const meta = node.meta ?? ''
const info = lang === null ? '' : meta === '' ? lang : `${lang} ${meta}`
fences.push({ line: node.position.start.line, lang, info, code: node.value })
})
return fences
}
/** Text a reader sees from one Markdown node; raw HTML itself contributes none. */
function renderedText(node: Nodes): string {
if (node.type === 'text' || node.type === 'inlineCode') return node.value
@@ -115,27 +144,22 @@ function hasRenderedTextOutsideComments(raw: string, ranges: readonly ColumnRang
}
/**
* Return source lines outside backtick or tilde fences and HTML comments.
* Return source lines outside code blocks and HTML comments.
* @param source - Markdown source whose prose should be retained verbatim.
* @returns unfenced lines with their original 1-based locations.
*/
export function markdownProseLines(source: string): MarkdownProseLine[] {
let fence: { marker: '`' | '~'; length: number } | undefined
const kept: MarkdownProseLine[] = []
const rawLines = source.split('\n')
const comments = htmlCommentRanges(source, rawLines)
const fenced = new Set<number>()
visitMarkdown(parseMarkdown(source), (node) => {
if (node.type !== 'code' || node.position === undefined) return
for (let line = node.position.start.line; line <= node.position.end.line; line += 1) fenced.add(line)
})
const kept: MarkdownProseLine[] = []
rawLines.forEach((raw, i) => {
const token = /^ {0,3}(`{3,}|~{3,})/.exec(raw)?.[1]
if (token !== undefined) {
const marker = token[0] as '`' | '~'
if (fence === undefined) {
fence = { marker, length: token.length }
} else if (marker === fence.marker && token.length >= fence.length) {
fence = undefined
}
return
}
if (fence === undefined && hasRenderedTextOutsideComments(raw, comments.get(i + 1))) {
if (fenced.has(i + 1)) return
if (hasRenderedTextOutsideComments(raw, comments.get(i + 1))) {
kept.push({ index: i + 1, raw })
}
})