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
JavaScript
"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