UNPKG

code-graph-generator

Version:

Generate Json Object of code that can be used to generate code-graphs for JavaScript/TypeScript/Range projects

191 lines 8.04 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.IncrementalGraphBuilder = void 0; // src/analyzer/graph-builder.ts const path_1 = __importDefault(require("path")); const file_utils_1 = require("../utils/file-utils"); const ast_utils_1 = require("../utils/ast-utils"); /** * IncrementalGraphBuilder builds a code graph incrementally as files are added. * Optimized for handling large codebases efficiently. */ class IncrementalGraphBuilder { constructor(projectName, rootDir) { this.rootDir = null; this.projectName = projectName; this.packageMap = new Map(); this.fileMap = new Map(); if (rootDir) { this.rootDir = (0, ast_utils_1.normalizePath)(rootDir); } } /** * Add a file to the graph, creating the package if it doesn't exist. */ addFile(fileGraph) { // Ensure path is normalized const normalizedPath = (0, ast_utils_1.normalizePath)(fileGraph.path); fileGraph.path = normalizedPath; // Store file in map for quick lookups this.fileMap.set(normalizedPath, fileGraph); // Determine package name from directory const packageName = (0, ast_utils_1.normalizePath)(path_1.default.dirname(normalizedPath)); // Create package if it doesn't exist if (!this.packageMap.has(packageName)) { this.packageMap.set(packageName, { name: packageName, files: [], dependencies: [], exports: [] }); } const pkg = this.packageMap.get(packageName); // Add file to package pkg.files.push(fileGraph); // Update package exports for (const exportName of fileGraph.exports) { if (!pkg.exports.includes(exportName)) { pkg.exports.push(exportName); } } } /** * Get the complete code graph. */ getGraph() { file_utils_1.logger.info('Finalizing code graph...'); // Analyze dependencies between packages this.analyzePackageDependencies(); const packages = Array.from(this.packageMap.values()); // Sort packages for consistent output packages.sort((a, b) => a.name.localeCompare(b.name)); return { name: this.projectName, packages }; } /** * Write the graph to a stream, useful for large graphs. */ async writeToStream(writeStream) { file_utils_1.logger.info('Streaming code graph to output...'); // Analyze dependencies between packages this.analyzePackageDependencies(); writeStream.write('{\n'); writeStream.write(` "name": ${JSON.stringify(this.projectName)},\n`); writeStream.write(' "packages": [\n'); const packageNames = Array.from(this.packageMap.keys()).sort(); for (let i = 0; i < packageNames.length; i++) { const pkg = this.packageMap.get(packageNames[i]); if (i > 0) { writeStream.write(',\n'); } const json = JSON.stringify(pkg, null, 2); const indentedJson = json .split('\n') .map((line, j) => j === 0 ? ` ${line}` : ` ${line}`) .join('\n'); writeStream.write(indentedJson); } writeStream.write('\n ]\n}'); } /** * Find a file in the graph by its path. * Handles various path formats and extensions. */ findFileByPath(filePath) { const normalizedPath = (0, ast_utils_1.normalizePath)(filePath); // Direct lookup if (this.fileMap.has(normalizedPath)) { return this.fileMap.get(normalizedPath); } // Try with different extensions if no extension provided if (!path_1.default.extname(normalizedPath)) { const extensions = ['.js', '.jsx', '.ts', '.tsx']; for (const ext of extensions) { const pathWithExt = `${normalizedPath}${ext}`; if (this.fileMap.has(pathWithExt)) { return this.fileMap.get(pathWithExt); } } // Try for index files for (const ext of extensions) { const indexPath = `${normalizedPath}/index${ext}`; if (this.fileMap.has(indexPath)) { return this.fileMap.get(indexPath); } } } // Try without extension const pathWithoutExt = normalizedPath.replace(/\.[^/.]+$/, ''); for (const filePath of this.fileMap.keys()) { const filePathWithoutExt = filePath.replace(/\.[^/.]+$/, ''); if (filePathWithoutExt === pathWithoutExt) { return this.fileMap.get(filePath); } } return undefined; } /** * Resolve an import path to an absolute path. */ resolveImportPath(importerPath, importPath) { // Handle package imports (node_modules) if (!importPath.startsWith('.') && !importPath.startsWith('/')) { return importPath; // External package } // Get directory of importer const importerDir = path_1.default.dirname(importerPath); // Resolve relative path let resolvedPath = (0, ast_utils_1.normalizePath)(path_1.default.resolve(importerDir, importPath)); return resolvedPath; } /** * Analyze dependencies between packages based on file imports. */ analyzePackageDependencies() { for (const [packageName, pkg] of this.packageMap.entries()) { const dependencies = new Set(); for (const file of pkg.files) { // Process each dependency of the file for (const depPath of file.dependencies) { try { // Skip node_modules or absolute imports for package dependency analysis if (!depPath.startsWith('.') && !depPath.startsWith('/')) { continue; // Skip external packages for package dependencies } // Resolve the import path to a file path const resolvedPath = this.resolveImportPath(file.path, depPath); // Find the actual file this import refers to const depFile = this.findFileByPath(resolvedPath); if (depFile) { // Get the package of the dependency const depPackageName = (0, ast_utils_1.normalizePath)(path_1.default.dirname(depFile.path)); // Add as a dependency if it's a different package if (depPackageName !== packageName) { dependencies.add(depPackageName); } } else { // Try just using the directory const normalizedPath = (0, ast_utils_1.normalizePath)(path_1.default.dirname(resolvedPath)); if (normalizedPath !== packageName && this.packageMap.has(normalizedPath)) { dependencies.add(normalizedPath); } } } catch (error) { file_utils_1.logger.warn(`Error resolving dependency ${depPath} from ${file.path}: ${error}`); } } } // Update package dependencies pkg.dependencies = Array.from(dependencies).sort(); } } } exports.IncrementalGraphBuilder = IncrementalGraphBuilder; //# sourceMappingURL=graph-builder.js.map