UNPKG

sicua

Version:

A tool for analyzing project structure and dependencies

156 lines (155 loc) 5.65 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.detectCircularDependencies = detectCircularDependencies; const graphUtils_1 = require("../utils/graphUtils"); /** * Detects circular dependencies in the component graph using optimized lookups * @param graph The dependency graph with unique component IDs * @param lookupService Pre-initialized lookup service for O(1) component resolution * @returns Complete circular dependency analysis */ function detectCircularDependencies(graph, lookupService) { const context = { visited: {}, recursionStack: {}, nodesInCycles: new Set(), edges: [], }; const circularGroups = []; // Optimized DFS with early cycle detection const dfs = (nodeId, path) => { context.visited[nodeId] = true; context.recursionStack[nodeId] = true; path.push(nodeId); const neighbors = graph[nodeId]; if (neighbors) { for (const neighborId of neighbors) { if (!context.visited[neighborId]) { dfs(neighborId, path); } else if (context.recursionStack[neighborId]) { // Found cycle - extract it efficiently const cycleStart = path.indexOf(neighborId); const cycleNodes = path.slice(cycleStart); // Mark all nodes in cycle for (const node of cycleNodes) { context.nodesInCycles.add(node); } // Store circular group circularGroups.push([...cycleNodes]); // Create edges for visualization createCircularEdges(cycleNodes, context.edges); } } } context.recursionStack[nodeId] = false; path.pop(); }; // Run DFS on all unvisited nodes const allNodeIds = Object.keys(graph); for (const nodeId of allNodeIds) { if (!context.visited[nodeId]) { dfs(nodeId, []); } } // Generate optimized node layout const nodeIds = Array.from(context.nodesInCycles); const positions = (0, graphUtils_1.generateCircularLayout)(nodeIds.length, 400, // centerX 300, // centerY 250 // radius ); // Create nodes using O(1) lookups const nodes = nodeIds.map((nodeId, index) => { const component = lookupService.getComponentById(nodeId); const nodeData = { label: component?.name || nodeId, fullPath: component?.fullPath || nodeId, directory: component?.directory || "", isComponent: true, fileType: component?.fullPath ? component.fullPath.split(".").pop() : undefined, }; return { id: nodeId, position: positions[index], data: nodeData, type: "circular", }; }); // Create detailed circular group information using O(1) lookups const circularGroupInfos = circularGroups.map((group, index) => { const componentNames = group.map((componentId) => { const component = lookupService.getComponentById(componentId); return component?.name || componentId; }); return { id: `circular-${index}`, components: componentNames, path: componentNames, size: group.length, isCritical: group.length > 3, breakSuggestions: [ { component: componentNames[0], alternativeDesign: "Consider extracting common functionality into a shared utility", }, ], }; }); // Create the complete graph data const circularDependencyGraph = { nodes: nodes, edges: context.edges, version: "1.1.0", }; // Create stats with O(1) lookups const componentsByCircularGroups = circularGroups.reduce((acc, group, i) => { const componentNames = group.map((componentId) => { const component = lookupService.getComponentById(componentId); return component?.name || componentId; }); acc[`circular-${i}`] = componentNames; return acc; }, {}); return { circularDependencyGraph, circularGroups: circularGroupInfos, stats: { totalCircularGroups: circularGroups.length, totalComponentsInCircular: context.nodesInCycles.size, maxCircularPathLength: Math.max(...circularGroups.map((g) => g.length), 0), criticalCircularPaths: circularGroups.filter((g) => g.length > 3).length, componentsByCircularGroups, }, }; } /** * Create circular dependency edges efficiently */ function createCircularEdges(cycleNodes, edges) { for (let i = 0; i < cycleNodes.length; i++) { const currentNode = cycleNodes[i]; const nextNode = cycleNodes[(i + 1) % cycleNodes.length]; const edgeData = { type: "import", label: "circular", }; const edge = { id: (0, graphUtils_1.generateEdgeId)(currentNode, nextNode), source: currentNode, target: nextNode, data: edgeData, animated: true, style: { stroke: "#ff6b6b", strokeWidth: 2, }, markerEnd: { type: "arrowclosed", color: "#ff6b6b", }, }; edges.push(edge); } }