UNPKG

sicua

Version:

A tool for analyzing project structure and dependencies

178 lines (177 loc) 5.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.createNode = createNode; exports.createEdge = createEdge; exports.generateEdgeId = generateEdgeId; exports.normalizeImportPath = normalizeImportPath; exports.findIsolatedNodes = findIsolatedNodes; exports.getFileTypeFromPath = getFileTypeFromPath; exports.determineRiskLevel = determineRiskLevel; exports.generateGridLayout = generateGridLayout; exports.generateCircularLayout = generateCircularLayout; /** * Creates a node element for React Flow visualization * @param id Node identifier * @param label Display label * @param fullPath Full path to the file * @param directory Directory information * @param position Position coordinates for React Flow * @param parent Optional parent group id * @returns INode compliant with React Flow structure */ function createNode(id, label, fullPath, directory, position, parent, isComponent, fileType) { const nodeData = { label, fullPath, directory, }; if (isComponent !== undefined) { nodeData.isComponent = isComponent; } if (fileType) { nodeData.fileType = fileType; } const node = { id, position, data: nodeData, }; if (parent) { node.parentNode = parent; node.extent = "parent"; } return node; } /** * Creates an edge element for React Flow visualization * @param id Unique edge identifier * @param source Source node id * @param target Target node id * @param type Optional connection type * @param label Optional label * @returns IEdge compliant with React Flow structure */ function createEdge(id, source, target, type, label) { const edgeData = {}; if (type) { edgeData.type = type; } if (label) { edgeData.label = label; } const edge = { id, source, target, }; if (Object.keys(edgeData).length > 0) { edge.data = edgeData; } return edge; } /** * Generates a unique edge ID from source and target * @param source Source node id * @param target Target node id * @returns Unique edge identifier */ function generateEdgeId(source, target) { return `${source}-${target}`; } /** * Normalizes an import path to match component names * @param importPath The raw import path * @param components The list of components to match against * @returns The normalized component name or the original import path */ function normalizeImportPath(importPath, components) { const cleanedImport = importPath .replace(/\.(js|jsx|ts|tsx)$/, "") .replace(/^\.\//, ""); const match = components.find((c) => c.name === cleanedImport || c.fullPath.endsWith(cleanedImport)); return match ? match.name : importPath; } /** * Finds isolated nodes in a graph (nodes with no incoming or outgoing edges) * @param graph The dependency graph * @returns Set of isolated node ids */ function findIsolatedNodes(graph) { const allNodes = new Set(Object.keys(graph)); const connectedNodes = new Set(); // Add nodes with outgoing edges Object.keys(graph).forEach((node) => { if (graph[node]?.length > 0) { connectedNodes.add(node); } }); // Add nodes with incoming edges Object.entries(graph).forEach(([source, targets]) => { targets.forEach((target) => { connectedNodes.add(target); }); }); // Find isolated nodes (in allNodes but not in connectedNodes) return new Set([...allNodes].filter((node) => !connectedNodes.has(node))); } /** * Gets the file type from a full path * @param fullPath The full path to the file * @returns The file extension or undefined */ function getFileTypeFromPath(fullPath) { return fullPath ? fullPath.split(".").pop() : undefined; } /** * Determines risk level based on cluster size * @param size The size of the cluster * @returns The risk level as 'high', 'medium', or 'low' */ function determineRiskLevel(size) { if (size > 5) return "high"; if (size > 2) return "medium"; return "low"; } /** * Generates a simple grid layout for nodes * @param nodeCount Number of nodes to position * @param startX Starting X coordinate * @param startY Starting Y coordinate * @param spacing Spacing between nodes * @returns Array of position coordinates */ function generateGridLayout(nodeCount, startX = 0, startY = 0, spacing = 150) { const positions = []; const cols = Math.ceil(Math.sqrt(nodeCount)); for (let i = 0; i < nodeCount; i++) { const row = Math.floor(i / cols); const col = i % cols; positions.push({ x: startX + col * spacing, y: startY + row * spacing, }); } return positions; } /** * Generates a circular layout for nodes (useful for circular dependencies) * @param nodeCount Number of nodes to position * @param centerX Center X coordinate * @param centerY Center Y coordinate * @param radius Radius of the circle * @returns Array of position coordinates */ function generateCircularLayout(nodeCount, centerX = 0, centerY = 0, radius = 200) { const positions = []; const angleStep = (2 * Math.PI) / nodeCount; for (let i = 0; i < nodeCount; i++) { const angle = i * angleStep; positions.push({ x: centerX + radius * Math.cos(angle), y: centerY + radius * Math.sin(angle), }); } return positions; }