// The two pieces of markdown handling both checks in this directory need, in one // place rather than two copies that drift. // // Shared code, not a shared description. Core's own loader and its schema replay // use one splitter for the same reason (MODULE_API.md §2.6): two implementations // of "what counts as a code fence" would disagree eventually, and the check that // disagreed quietly would be the one still reporting green. /** * The text with every fenced code block blanked out, line count preserved. * * Fenced blocks are stripped before either check reads anything, because a fence * can legitimately contain a path or a link that does not exist: a directory * listing of the project the reader has not created yet, a URL in an example. In * a repo made entirely of that document type, flagging them makes the check * useless. * * Done by walking lines and toggling on a fence marker rather than by regexp — a * fence's own content can contain anything, including a line that looks like the * end of one. Lines are replaced by empty strings rather than removed so that * line numbers in a report still point at the right place. */ function stripFences(text) { const out = [] let fence = null for (const line of text.split(/\r?\n/)) { const m = /^\s*(```+|~~~+)/.exec(line) if (fence) { if (m && m[1][0] === fence[0] && m[1].length >= fence.length) fence = null out.push('') continue } if (m) { fence = m[1] out.push('') continue } out.push(line) } return out.join('\n') } /** * Every inline code span outside a fenced block, with the 1-based line it is on. * * `[a](b)` inside backticks is an example rather than a link, and `template/x.js` * inside backticks is a claim about this repo's tree — which is why one check * throws these away and the other reads only these. */ function codeSpans(text) { const found = [] stripFences(text).split(/\r?\n/).forEach((line, i) => { for (const m of line.matchAll(/`([^`]+)`/g)) { found.push({ text: m[1], line: i + 1 }) } }) return found } module.exports = { stripFences, codeSpans }