// SourceCodeParser.ts import * as fs from "fs"; import * as path from "path"; /** * Interface representing the dependencies of a module. * Key: Absolute path to the dependency module. * Value: The name used in the source code for the link. */ interface Dependencies { [modulePath: string]: string; } /** * Interface representing the information of a module. */ interface ModuleInfo { source: string; dependencies: Dependencies; } /** * Interface representing the adjacency list of the source tree. * Key: Absolute path to the module. * Value: ModuleInfo object containing source and dependencies. */ interface AdjacencyList { [modulePath: string]: ModuleInfo; } /** * SourceCodeParser is responsible for parsing Markdown modules * starting from an entry point and building an adjacency list * representing the source tree. */ export class SourceCodeParser { private entryPoint: string; private adjacencyList: AdjacencyList; private processedModules: Set; constructor(entryPoint: string) { this.entryPoint = path.resolve(entryPoint); this.adjacencyList = {}; this.processedModules = new Set(); } /** * Parses the entry point and builds the adjacency list. * @returns AdjacencyList representing the source tree. */ public async parse(): Promise { await this.processModule(this.entryPoint); return this.adjacencyList; } /** * Processes a single module: reads its content, extracts dependencies, * and recursively processes dependencies. * @param modulePath Absolute path to the module. */ private async processModule(modulePath: string): Promise { if (this.processedModules.has(modulePath)) { return; } if (!fs.existsSync(modulePath)) { throw new Error(`Module not found: ${modulePath}`); } const content = fs.readFileSync(modulePath, "utf-8"); this.adjacencyList[modulePath] = { source: content, dependencies: {}, }; this.processedModules.add(modulePath); const links = this.extractLinks(content); for (const link of links) { const dependencyPath = path.resolve(path.dirname(modulePath), link.href); this.adjacencyList[modulePath].dependencies[dependencyPath] = link.text; await this.processModule(dependencyPath); } } /** * Extracts Markdown links from the source content. * @param content Markdown content. * @returns Array of objects containing link text and href. */ private extractLinks(content: string): { text: string; href: string }[] { const linkRegex = /\[([^\]]+)\]\(([^)]+)\)/g; const links: { text: string; href: string }[] = []; let match: RegExpExecArray | null; while ((match = linkRegex.exec(content)) !== null) { const [_, text, href] = match; // Only consider local .md files as dependencies if ( href.endsWith(".md") && !href.startsWith("http://") && !href.startsWith("https://") ) { links.push({ text, href }); } } return links; } }