import type { Heading, List, RootContent } from "mdast"; import { fromMarkdown } from "mdast-util-from-markdown"; import { toString } from "mdast-util-to-string"; function isHeading(node: RootContent): node is Heading { return node.type === "heading"; } function isList(node: RootContent): node is List { return node.type === "list"; } /** * Collect the list items under a depth-2 heading with the given title. * Uses mdast to traverse the AST instead of string matching. */ function parseSectionListItems(markdown: string, sectionTitle: string): string[] { const tree = fromMarkdown(markdown); const items: string[] = []; let collecting = false; for (const node of tree.children) { if (isHeading(node)) { if (collecting) break; if (node.depth === 2 && toString(node) === sectionTitle) { collecting = true; continue; } } if (collecting && isList(node)) { for (const item of node.children) { const text = toString(item).replace(/\s+/g, " ").trim(); if (text) { items.push(text); } } } } return items; } /** Parse test case descriptions from the `## Test Cases` section of a Markdown doc. */ export function parseTestCasesFromDoc(markdown: string): string[] { return parseSectionListItems(markdown, "Test Cases"); } /** * Parse `it("...")` descriptions from a test file using regex. * Test files are not Markdown, so regex is appropriate here. * Uses separate patterns per quote type so that e.g. an apostrophe * inside a double-quoted string does not end the match early. */ export function parseItDescriptionsFromTest(content: string): string[] { const descriptions: string[] = []; const regex = /\bit\(\s*"([^"]+)"/gs; let match: RegExpExecArray | null; while ((match = regex.exec(content)) !== null) { descriptions.push(match[1].replace(/\s+/g, " ").trim()); } // Also match single-quoted and backtick-quoted descriptions const singleQuoteRegex = /\bit\(\s*'([^']+)'/gs; while ((match = singleQuoteRegex.exec(content)) !== null) { descriptions.push(match[1].replace(/\s+/g, " ").trim()); } const backtickRegex = /\bit\(\s*`([^`]+)`/gs; while ((match = backtickRegex.exec(content)) !== null) { descriptions.push(match[1].replace(/\s+/g, " ").trim()); } return descriptions; }