import type { Heading, List, Root, RootContent } from "mdast"; import { fromMarkdown } from "mdast-util-from-markdown"; import { toString } from "mdast-util-to-string"; export interface ParsedError { code: string; description: string; } export interface ParsedDependency { module: string; entity: string; } export interface ParsedCommandDoc { commandName: string; permissionScope: string; errors: ParsedError[]; externalDependencies: ParsedDependency[]; } export function parseCommandDoc(fileName: string, markdown: string): ParsedCommandDoc { const commandName = fileName.charAt(0).toLowerCase() + fileName.slice(1); const tree = fromMarkdown(markdown); return { commandName, permissionScope: parsePermissionScope(tree), errors: parseErrorScenarios(tree), externalDependencies: parseExternalDependencies(tree), }; } function parsePermissionScope(tree: Root): string { const nodes = getNodesUnderHeading(tree, "Permission Scope"); for (const node of nodes) { if (node.type === "paragraph") { return toString(node).trim(); } } return ""; } export function errorCodeToClassName(code: string): string { const pascal = code .toLowerCase() .split("_") .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) .join(""); return pascal + "Error"; } function isHeading(node: RootContent): node is Heading { return node.type === "heading"; } function isList(node: RootContent): node is List { return node.type === "list"; } function getNodesUnderHeading(tree: Root, headingText: string): RootContent[] { const nodes: RootContent[] = []; let collecting = false; for (const node of tree.children) { if (isHeading(node)) { if (collecting) break; if (node.depth === 2 && toString(node) === headingText) { collecting = true; continue; } } if (collecting) { nodes.push(node); } } return nodes; } const ERROR_PATTERN = /^([A-Z_]+):\s*(.+)$/; function parseErrorScenarios(tree: Root): ParsedError[] { const nodes = getNodesUnderHeading(tree, "Error Scenarios"); const errors: ParsedError[] = []; for (const node of nodes) { if (!isList(node)) continue; for (const item of node.children) { const text = toString(item); const match = ERROR_PATTERN.exec(text); if (match) { errors.push({ code: match[1], description: match[2].trim() }); } } } return errors; } const DEPENDENCY_PATTERN = /^([^:]+)::(.+)$/; function parseExternalDependencies(tree: Root): ParsedDependency[] { const nodes = getNodesUnderHeading(tree, "External Dependencies"); const deps: ParsedDependency[] = []; for (const node of nodes) { if (!isList(node)) continue; for (const item of node.children) { const firstChild = item.children[0]; if (firstChild?.type !== "paragraph") continue; for (const inline of firstChild.children) { if (inline.type === "link" || inline.type === "linkReference") { const linkText = toString(inline); const match = DEPENDENCY_PATTERN.exec(linkText); if (match) { deps.push({ module: match[1], entity: match[2] }); } } } } } return deps; }