UNPKG

sicua

Version:

A tool for analyzing project structure and dependencies

240 lines (239 loc) 11.7 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.calculateCyclomaticComplexity = calculateCyclomaticComplexity; const typescript_1 = __importDefault(require("typescript")); const fileReader_1 = require("../utils/fileReader"); const astUtils_1 = require("../../translation/utils/astUtils"); const reactSpecific_1 = require("../../../utils/ast/reactSpecific"); const analysisUtils_1 = require("../../../utils/common/analysisUtils"); async function calculateCyclomaticComplexity(components) { const cyclomaticComplexity = {}; // Group components by file path for efficient processing const componentsByFile = new Map(); components.forEach((component) => { if (!componentsByFile.has(component.fullPath)) { componentsByFile.set(component.fullPath, []); } componentsByFile.get(component.fullPath).push(component); }); // Process each file once and calculate complexity for each component for (const [filePath, fileComponents] of componentsByFile) { const content = await (0, fileReader_1.readFileContent)(filePath); const sourceFile = (0, astUtils_1.createSourceFile)(filePath, content); // Find all component nodes in the file const componentNodes = findComponentNodes(sourceFile, fileComponents); // Calculate complexity for each component fileComponents.forEach((component) => { const componentId = (0, analysisUtils_1.generateComponentId)(component); const componentNode = componentNodes.get(component.name); if (componentNode) { cyclomaticComplexity[componentId] = calculateNodeCyclomaticComplexity(componentNode); } else { // Fallback: if we can't find the specific component, assign base complexity cyclomaticComplexity[componentId] = 1; } }); } return cyclomaticComplexity; } /** * Find component nodes in the source file */ function findComponentNodes(sourceFile, components) { const componentNodes = new Map(); const componentNames = new Set(components.map((c) => c.name)); function visit(node) { // Check for function declarations if (typescript_1.default.isFunctionDeclaration(node) && node.name) { const functionName = node.name.text; if (componentNames.has(functionName) && (0, reactSpecific_1.isReactComponent)(node)) { componentNodes.set(functionName, node); } } // Check for variable declarations (const ComponentName = ...) if (typescript_1.default.isVariableDeclaration(node) && typescript_1.default.isIdentifier(node.name)) { const varName = node.name.text; if (componentNames.has(varName) && node.initializer) { if ((typescript_1.default.isArrowFunction(node.initializer) || typescript_1.default.isFunctionExpression(node.initializer)) && (0, reactSpecific_1.isReactComponent)(node.initializer)) { componentNodes.set(varName, node.initializer); } } } // Check for exported function declarations if (typescript_1.default.isExportAssignment(node) && typescript_1.default.isFunctionDeclaration(node.expression)) { const func = node.expression; if (func.name && componentNames.has(func.name.text) && (0, reactSpecific_1.isReactComponent)(func)) { componentNodes.set(func.name.text, func); } } typescript_1.default.forEachChild(node, visit); } visit(sourceFile); return componentNodes; } function calculateNodeCyclomaticComplexity(node) { let complexity = 1; // Base complexity for linear flow function incrementComplexity(currentNode) { switch (currentNode.kind) { // Decision points that create branching case typescript_1.default.SyntaxKind.IfStatement: complexity++; break; case typescript_1.default.SyntaxKind.ConditionalExpression: complexity++; break; // Loop constructs case typescript_1.default.SyntaxKind.ForStatement: case typescript_1.default.SyntaxKind.ForInStatement: case typescript_1.default.SyntaxKind.ForOfStatement: case typescript_1.default.SyntaxKind.WhileStatement: case typescript_1.default.SyntaxKind.DoStatement: complexity++; break; // Switch case clauses (each case is a decision point) case typescript_1.default.SyntaxKind.CaseClause: complexity++; break; // Exception handling case typescript_1.default.SyntaxKind.CatchClause: complexity++; break; // Logical operators that create short-circuit evaluation case typescript_1.default.SyntaxKind.BinaryExpression: const binaryExpression = currentNode; if (binaryExpression.operatorToken.kind === typescript_1.default.SyntaxKind.AmpersandAmpersandToken || binaryExpression.operatorToken.kind === typescript_1.default.SyntaxKind.BarBarToken) { complexity++; } break; // Function expressions and arrow functions (separate complexity domains) case typescript_1.default.SyntaxKind.FunctionExpression: case typescript_1.default.SyntaxKind.ArrowFunction: // Only calculate nested function complexity if it's not the root component if (currentNode !== node) { const functionComplexity = calculateFunctionComplexity(currentNode); complexity += functionComplexity; return; // Don't traverse children as they're handled above } break; case typescript_1.default.SyntaxKind.FunctionDeclaration: case typescript_1.default.SyntaxKind.MethodDeclaration: // Only calculate nested function complexity if it's not the root component if (currentNode !== node) { const methodComplexity = calculateFunctionComplexity(currentNode); complexity += methodComplexity; return; // Don't traverse children as they're handled above } break; // JSX conditional rendering patterns case typescript_1.default.SyntaxKind.JsxExpression: const jsxExpression = currentNode; if (jsxExpression.expression) { // Handle JSX conditional patterns: {condition && <Component />} if (typescript_1.default.isBinaryExpression(jsxExpression.expression) && jsxExpression.expression.operatorToken.kind === typescript_1.default.SyntaxKind.AmpersandAmpersandToken) { complexity++; } // Handle JSX ternary patterns: {condition ? <A /> : <B />} else if (typescript_1.default.isConditionalExpression(jsxExpression.expression)) { complexity++; } } break; // Optional chaining can create branching logic case typescript_1.default.SyntaxKind.QuestionDotToken: complexity++; break; // Nullish coalescing creates a decision point case typescript_1.default.SyntaxKind.QuestionQuestionToken: complexity++; break; } // Continue traversing children typescript_1.default.forEachChild(currentNode, incrementComplexity); } typescript_1.default.forEachChild(node, incrementComplexity); return complexity; } function calculateFunctionComplexity(functionNode) { let functionComplexity = 1; // Base complexity for the function function incrementFunctionComplexity(node) { switch (node.kind) { case typescript_1.default.SyntaxKind.IfStatement: functionComplexity++; break; case typescript_1.default.SyntaxKind.ConditionalExpression: functionComplexity++; break; case typescript_1.default.SyntaxKind.ForStatement: case typescript_1.default.SyntaxKind.ForInStatement: case typescript_1.default.SyntaxKind.ForOfStatement: case typescript_1.default.SyntaxKind.WhileStatement: case typescript_1.default.SyntaxKind.DoStatement: functionComplexity++; break; case typescript_1.default.SyntaxKind.CaseClause: functionComplexity++; break; case typescript_1.default.SyntaxKind.CatchClause: functionComplexity++; break; case typescript_1.default.SyntaxKind.BinaryExpression: const binaryExpression = node; if (binaryExpression.operatorToken.kind === typescript_1.default.SyntaxKind.AmpersandAmpersandToken || binaryExpression.operatorToken.kind === typescript_1.default.SyntaxKind.BarBarToken) { functionComplexity++; } break; case typescript_1.default.SyntaxKind.JsxExpression: const jsxExpression = node; if (jsxExpression.expression) { if (typescript_1.default.isBinaryExpression(jsxExpression.expression) && jsxExpression.expression.operatorToken.kind === typescript_1.default.SyntaxKind.AmpersandAmpersandToken) { functionComplexity++; } else if (typescript_1.default.isConditionalExpression(jsxExpression.expression)) { functionComplexity++; } } break; case typescript_1.default.SyntaxKind.QuestionDotToken: case typescript_1.default.SyntaxKind.QuestionQuestionToken: functionComplexity++; break; // Nested functions don't add to parent complexity in cyclomatic complexity case typescript_1.default.SyntaxKind.FunctionExpression: case typescript_1.default.SyntaxKind.ArrowFunction: case typescript_1.default.SyntaxKind.FunctionDeclaration: case typescript_1.default.SyntaxKind.MethodDeclaration: return; // Don't traverse nested functions } typescript_1.default.forEachChild(node, incrementFunctionComplexity); } // Get the function body and traverse it if ("body" in functionNode && functionNode.body) { if (typescript_1.default.isBlock(functionNode.body)) { // Function with block body typescript_1.default.forEachChild(functionNode.body, incrementFunctionComplexity); } else { // Arrow function with expression body incrementFunctionComplexity(functionNode.body); } } return functionComplexity; }