|
12 | 12 | */ |
13 | 13 |
|
14 | 14 | import { parseArgs } from "node:util"; |
| 15 | +import { fromMarkdown } from "mdast-util-from-markdown"; |
| 16 | +import { gfmTableFromMarkdown } from "mdast-util-gfm-table"; |
| 17 | +import { gfmTable } from "micromark-extension-gfm-table"; |
15 | 18 | import { canonicalDocument } from "../../src/engine/canonical.js"; |
16 | 19 | import { describeRemoval } from "../../src/engine/layers.js"; |
17 | 20 | import { MAX_RETRIES } from "../../src/github/api.js"; |
@@ -292,51 +295,31 @@ const REQUIRED_CLASSES: Record<WitnessSection, MutationClass[]> = { |
292 | 295 | }; |
293 | 296 |
|
294 | 297 | /** |
295 | | - * A pipe is a delimiter only after an EVEN run of backslashes: the engine escapes cells with |
296 | | - * backslash-then-pipe (markdownCell, src/report/markdown.ts); under HOSTILE_NAMES a broken escape changes a row's separator count. |
| 298 | + * The engine escapes a cell's pipes and backslashes (markdownCell, src/report/markdown.ts), so under HOSTILE_NAMES a |
| 299 | + * broken escape surfaces as a row whose cell count differs from the header's; the GFM grammar reads the table as |
| 300 | + * GitHub renders it, and a malformed delimiter row leaves no table at all. |
297 | 301 | */ |
298 | 302 | function summaryTableProblems(summary: string, expectedRows: number): string[] { |
299 | | - const lines = summary.split("\n").map((line) => line.trim()); |
300 | | - const start = lines.findIndex((line) => line.startsWith("|")); |
301 | | - if (start === -1) { |
302 | | - // A completed run renders the header and separator even with zero data rows, so absence is an error at any expectedRows. |
| 303 | + const tree = fromMarkdown(summary, { |
| 304 | + extensions: [gfmTable()], |
| 305 | + mdastExtensions: [gfmTableFromMarkdown()], |
| 306 | + }); |
| 307 | + const table = tree.children.find((node) => node.type === "table"); |
| 308 | + if (table === undefined) { |
| 309 | + // A completed run renders the header and delimiter row even with zero data rows, so absence is an error at any expectedRows. |
303 | 310 | return ["summary carries no markdown table"]; |
304 | 311 | } |
305 | | - const table: string[] = []; |
306 | | - for (let i = start; i < lines.length && lines[i]?.startsWith("|"); i++) { |
307 | | - table.push(lines[i] as string); |
308 | | - } |
309 | | - const separators = (row: string): number => { |
310 | | - let count = 0; |
311 | | - let backslashes = 0; |
312 | | - for (const ch of row) { |
313 | | - if (ch === "\\") { |
314 | | - backslashes++; |
315 | | - continue; |
316 | | - } |
317 | | - if (ch === "|" && backslashes % 2 === 0) { |
318 | | - count++; |
319 | | - } |
320 | | - backslashes = 0; |
321 | | - } |
322 | | - return count; |
323 | | - }; |
| 312 | + const [width = 0, ...rowWidths] = table.children.map((row) => row.children.length); |
324 | 313 | const problems: string[] = []; |
325 | | - if (table.length < 2 + expectedRows) { |
| 314 | + if (rowWidths.length < expectedRows) { |
326 | 315 | problems.push( |
327 | | - `summary table has ${table.length} row(s), expected a header + separator + at least ${expectedRows} data row(s)`, |
| 316 | + `summary table has ${rowWidths.length} data row(s), expected at least ${expectedRows}`, |
328 | 317 | ); |
329 | 318 | } |
330 | | - // The loose "any of -:| whitespace" form would accept an empty separator cell. |
331 | | - const separatorCells = (table[1] ?? "").split("|").slice(1, -1); |
332 | | - if (separatorCells.length === 0 || !separatorCells.every((cell) => /^\s*:?-+:?\s*$/.test(cell))) { |
333 | | - problems.push(`summary table separator row is malformed: "${table[1] ?? "(absent)"}"`); |
334 | | - } |
335 | | - const headerCount = separators(table[0] ?? ""); |
336 | | - for (const [index, row] of table.entries()) { |
337 | | - if (separators(row) !== headerCount) { |
| 319 | + for (const [index, cells] of rowWidths.entries()) { |
| 320 | + if (cells !== width) { |
338 | 321 | problems.push( |
339 | | - `summary table row ${index} has ${separators(row)} cell separator(s), the header has ${headerCount} - a broken pipe escape?`, |
| 322 | + `summary table data row ${index + 1} has ${cells} cell(s), the header has ${width} - a broken pipe escape?`, |
340 | 323 | ); |
341 | 324 | break; |
342 | 325 | } |
|
0 commit comments