sicua
Version:
A tool for analyzing project structure and dependencies
342 lines (341 loc) • 14.3 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.FlowTreeBuilder = void 0;
const ComponentFlowScanner_1 = require("../scanners/ComponentFlowScanner");
const RouteCoverageBuilder_1 = require("./RouteCoverageBuilder");
/**
* Builds complete component flow trees using optimized services for enhanced performance
*/
class FlowTreeBuilder {
constructor(appDirectory, lookupService, pathResolver, scanResult, config) {
// Initialize component scanner with optimized services
this.componentScanner = new ComponentFlowScanner_1.ComponentFlowScanner(lookupService, pathResolver, scanResult, 10, // maxDepth
config);
this.coverageBuilder = new RouteCoverageBuilder_1.RouteCoverageBuilder(appDirectory);
}
/**
* Builds complete flow tree for a single route using optimized services
*/
buildRouteFlowTree(routeStructure) {
// Build component flow tree using optimized scanner
const pageComponent = this.componentScanner.scanComponentFlow(routeStructure.pageFilePath);
if (!pageComponent) {
throw new Error(`Failed to analyze page component: ${routeStructure.pageFilePath}`);
}
// Build coverage analysis
const coverageAnalysis = this.coverageBuilder.buildRouteCoverage(routeStructure);
// Calculate component statistics using optimized traversal
const componentStats = this.calculateComponentStats(pageComponent);
// Generate visualization metadata
const visualizationData = this.generateVisualizationMetadata(pageComponent, routeStructure, componentStats);
return {
routePath: routeStructure.routePath,
pageComponent: pageComponent,
specialFiles: coverageAnalysis.specialFilesCoverage,
metadata: routeStructure.metadata,
coverageAnalysis,
componentStats,
visualizationData,
};
}
/**
* Builds flow trees for multiple routes
*/
buildMultipleRouteFlowTrees(routeStructures) {
const trees = [];
for (const routeStructure of routeStructures) {
try {
const tree = this.buildRouteFlowTree(routeStructure);
trees.push(tree);
}
catch (error) {
console.warn(`Failed to build flow tree for route ${routeStructure.routePath}:`, error);
}
}
return trees;
}
/**
* Builds a simplified tree for quick visualization
*/
buildSimplifiedFlowTree(routeStructure, maxDepth = 3) {
const originalConfig = this.componentScanner.getConfig();
this.componentScanner.updateConfig({ maxDepth });
try {
const pageComponent = this.componentScanner.scanComponentFlow(routeStructure.pageFilePath);
if (!pageComponent) {
throw new Error(`Failed to analyze page component: ${routeStructure.pageFilePath}`);
}
const coverageAnalysis = this.coverageBuilder.buildRouteCoverage(routeStructure);
return {
routePath: routeStructure.routePath,
pageComponent,
specialFiles: coverageAnalysis.specialFilesCoverage,
metadata: routeStructure.metadata,
};
}
finally {
this.componentScanner.updateConfig(originalConfig);
}
}
/**
* Builds flow tree focused on conditional rendering patterns
*/
buildConditionalFlowTree(routeStructure) {
const pageComponent = this.componentScanner.scanComponentFlow(routeStructure.pageFilePath);
if (!pageComponent) {
throw new Error(`Failed to analyze page component: ${routeStructure.pageFilePath}`);
}
const conditionalPaths = this.extractConditionalPaths(pageComponent);
return {
routePath: routeStructure.routePath,
conditionalPaths,
totalPaths: conditionalPaths.length,
};
}
/**
* Calculates comprehensive component statistics with optimized traversal
*/
calculateComponentStats(rootComponent) {
const stats = {
totalComponents: 0,
externalComponents: 0,
internalComponents: 0,
maxDepth: 0,
conditionalRenderCount: 0,
uniqueComponents: new Set(),
componentsByDepth: new Map(),
};
// Track visited components to prevent double counting
const visitedComponents = new Set();
this.traverseComponentTree(rootComponent, 0, stats, visitedComponents);
return stats;
}
/**
* Recursively traverses component tree to calculate statistics with optimized deduplication
*/
traverseComponentTree(component, depth, stats, visitedComponents) {
// Create unique key for this component
const componentKey = `${component.componentName}:${component.filePath}`;
// Skip if already processed to prevent double counting
if (visitedComponents.has(componentKey)) {
return;
}
visitedComponents.add(componentKey);
// Count this component
stats.totalComponents++;
stats.uniqueComponents.add(component.componentName);
stats.maxDepth = Math.max(stats.maxDepth, depth);
if (component.isExternal) {
stats.externalComponents++;
}
else {
stats.internalComponents++;
}
// Track components by depth
if (!stats.componentsByDepth.has(depth)) {
stats.componentsByDepth.set(depth, []);
}
stats.componentsByDepth.get(depth).push(component.componentName);
// Count conditional renders for this component only
stats.conditionalRenderCount += component.conditionalRenders.length;
// Collect all children to process (avoiding duplicates)
const childrenToProcess = new Map();
// Process conditional renders - collect children but don't traverse yet
for (const conditionalRender of component.conditionalRenders) {
for (const trueChild of conditionalRender.trueBranch) {
const childKey = `${trueChild.componentName}:${trueChild.filePath}`;
childrenToProcess.set(childKey, trueChild);
}
if (conditionalRender.falseBranch) {
for (const falseChild of conditionalRender.falseBranch) {
const childKey = `${falseChild.componentName}:${falseChild.filePath}`;
childrenToProcess.set(childKey, falseChild);
}
}
}
// Process regular children - collect but don't traverse yet
for (const child of component.children) {
const childKey = `${child.componentName}:${child.filePath}`;
childrenToProcess.set(childKey, child);
}
// Now traverse all unique children
for (const child of childrenToProcess.values()) {
this.traverseComponentTree(child, depth + 1, stats, visitedComponents);
}
}
/**
* Generates visualization metadata for the component tree
*/
generateVisualizationMetadata(rootComponent, routeStructure, stats) {
const clusters = this.generateClusters(rootComponent);
const layoutHints = this.generateLayoutHints(stats, routeStructure);
return {
nodeCount: stats.totalComponents,
edgeCount: this.calculateEdgeCount(rootComponent),
clusterInfo: clusters,
layoutHints,
};
}
/**
* Generates cluster information for visualization grouping with optimized traversal
*/
generateClusters(rootComponent) {
const clusters = [];
let clusterId = 0;
const visitedNodes = new Set(); // Prevent duplicate processing
const processNode = (component, depth) => {
const nodeKey = `${component.componentName}:${component.filePath}`;
if (visitedNodes.has(nodeKey)) {
return; // Skip already processed nodes
}
visitedNodes.add(nodeKey);
// Create cluster for conditional renders
for (const conditionalRender of component.conditionalRenders) {
const trueComponents = conditionalRender.trueBranch.map((c) => c.componentName);
const falseComponents = conditionalRender.falseBranch?.map((c) => c.componentName) || [];
if (trueComponents.length > 0) {
clusters.push({
clusterId: `conditional-true-${clusterId++}`,
clusterType: "conditional",
componentNames: trueComponents,
depth,
});
}
if (falseComponents.length > 0) {
clusters.push({
clusterId: `conditional-false-${clusterId++}`,
clusterType: "conditional",
componentNames: falseComponents,
depth,
});
}
// Recursively process conditional children
[
...conditionalRender.trueBranch,
...(conditionalRender.falseBranch || []),
].forEach((child) => {
processNode(child, depth + 1);
});
}
// Create cluster for external components
const externalChildren = component.children.filter((c) => c.isExternal);
if (externalChildren.length > 0) {
clusters.push({
clusterId: `external-${clusterId++}`,
clusterType: "external",
componentNames: externalChildren.map((c) => c.componentName),
depth,
});
}
// Process remaining children
component.children
.filter((c) => !c.isExternal)
.forEach((child) => {
processNode(child, depth + 1);
});
};
processNode(rootComponent, 0);
return clusters;
}
/**
* Generates layout hints for visualization
*/
generateLayoutHints(stats, routeStructure) {
let suggestedLayout = "hierarchical";
let primaryFlow = "vertical";
// Determine layout based on tree characteristics
if (stats.maxDepth > 5) {
suggestedLayout = "force";
primaryFlow = "horizontal";
}
else if (stats.conditionalRenderCount > 10) {
suggestedLayout = "force";
}
else if (stats.totalComponents < 10) {
suggestedLayout = "circular";
}
// Generate groupings
const groupings = {};
for (const [depth, components] of stats.componentsByDepth) {
groupings[`depth-${depth}`] = components;
}
return {
suggestedLayout,
primaryFlow,
groupings,
};
}
/**
* Calculates the total number of edges in the component tree with optimized traversal
*/
calculateEdgeCount(rootComponent) {
let edgeCount = 0;
const visitedNodes = new Set(); // Prevent double counting
const processNode = (component) => {
const nodeKey = `${component.componentName}:${component.filePath}`;
if (visitedNodes.has(nodeKey)) {
return;
}
visitedNodes.add(nodeKey);
// Count edges to conditional children
for (const conditionalRender of component.conditionalRenders) {
edgeCount += conditionalRender.trueBranch.length;
edgeCount += conditionalRender.falseBranch?.length || 0;
[
...conditionalRender.trueBranch,
...(conditionalRender.falseBranch || []),
].forEach(processNode);
}
// Count edges to regular children
edgeCount += component.children.length;
component.children.forEach(processNode);
};
processNode(rootComponent);
return edgeCount;
}
/**
* Extracts all possible conditional rendering paths with optimized traversal
*/
extractConditionalPaths(rootComponent) {
const paths = [];
const visitedNodes = new Set(); // Prevent infinite loops
const extractPaths = (component, currentPath, conditions) => {
const nodeKey = `${component.componentName}:${component.filePath}`;
if (visitedNodes.has(nodeKey)) {
return;
}
visitedNodes.add(nodeKey);
const newPath = [...currentPath, component.componentName];
// If this component has conditional renders, create paths for each branch
for (const conditionalRender of component.conditionalRenders) {
const newConditions = [...conditions, conditionalRender.condition];
// True branch
for (const trueChild of conditionalRender.trueBranch) {
extractPaths(trueChild, newPath, [...newConditions, "true"]);
}
// False branch
if (conditionalRender.falseBranch) {
for (const falseChild of conditionalRender.falseBranch) {
extractPaths(falseChild, newPath, [...newConditions, "false"]);
}
}
}
// Regular children
for (const child of component.children) {
extractPaths(child, newPath, conditions);
}
// If this is a leaf node, add the path
if (component.children.length === 0 &&
component.conditionalRenders.length === 0) {
paths.push({
components: newPath,
conditions: conditions,
isConditional: conditions.length > 0,
});
}
};
extractPaths(rootComponent, [], []);
return paths;
}
}
exports.FlowTreeBuilder = FlowTreeBuilder;