UNPKG

sicua

Version:

A tool for analyzing project structure and dependencies

230 lines (229 loc) 12.2 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.calculateCognitiveComplexity = calculateCognitiveComplexity; 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 calculateCognitiveComplexity(components) { const cognitiveComplexity = {}; // 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) { cognitiveComplexity[componentId] = calculateNodeCognitiveComplexity(componentNode); } else { // Fallback: if we can't find the specific component, assign 0 complexity cognitiveComplexity[componentId] = 0; } }); } return cognitiveComplexity; } /** * 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 calculateNodeCognitiveComplexity(node) { let totalComplexity = 0; function calculateComplexity(currentNode, nestingLevel = 0) { let nodeComplexity = 0; switch (currentNode.kind) { // Control flow structures that increase nesting case typescript_1.default.SyntaxKind.IfStatement: nodeComplexity += 1 + nestingLevel; const ifStatement = currentNode; // Process condition nodeComplexity += calculateComplexity(ifStatement.expression, nestingLevel); // Process then statement with increased nesting nodeComplexity += calculateComplexity(ifStatement.thenStatement, nestingLevel + 1); // Process else statement with same nesting level as if if (ifStatement.elseStatement) { // Don't add complexity for else clause itself, but check if it's an else-if if (typescript_1.default.isIfStatement(ifStatement.elseStatement)) { nodeComplexity += calculateComplexity(ifStatement.elseStatement, nestingLevel); } else { nodeComplexity += calculateComplexity(ifStatement.elseStatement, nestingLevel + 1); } } 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: nodeComplexity += 1 + nestingLevel; // Process children with increased nesting typescript_1.default.forEachChild(currentNode, (child) => { nodeComplexity += calculateComplexity(child, nestingLevel + 1); }); break; // Switch statements add complexity for the switch itself plus each case case typescript_1.default.SyntaxKind.SwitchStatement: nodeComplexity += 1 + nestingLevel; const switchStatement = currentNode; // Process switch expression nodeComplexity += calculateComplexity(switchStatement.expression, nestingLevel); // Process case clauses with increased nesting switchStatement.caseBlock.clauses.forEach((clause) => { if (typescript_1.default.isCaseClause(clause)) { nodeComplexity += 1 + nestingLevel; // Each case adds complexity } // Process statements in the case with increased nesting clause.statements.forEach((statement) => { nodeComplexity += calculateComplexity(statement, nestingLevel + 1); }); }); break; // Try-catch blocks case typescript_1.default.SyntaxKind.TryStatement: const tryStatement = currentNode; // Process try block with same nesting nodeComplexity += calculateComplexity(tryStatement.tryBlock, nestingLevel); // Catch clause adds complexity if (tryStatement.catchClause) { nodeComplexity += 1 + nestingLevel; nodeComplexity += calculateComplexity(tryStatement.catchClause.block, nestingLevel + 1); } // Finally block doesn't add complexity but process its contents if (tryStatement.finallyBlock) { nodeComplexity += calculateComplexity(tryStatement.finallyBlock, nestingLevel); } break; // Ternary expressions case typescript_1.default.SyntaxKind.ConditionalExpression: nodeComplexity += 1 + nestingLevel; const conditionalExpression = currentNode; // Process all parts with increased nesting for true/false expressions nodeComplexity += calculateComplexity(conditionalExpression.condition, nestingLevel); nodeComplexity += calculateComplexity(conditionalExpression.whenTrue, nestingLevel + 1); nodeComplexity += calculateComplexity(conditionalExpression.whenFalse, nestingLevel + 1); break; // Logical expressions (&&, ||) 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) { nodeComplexity += 1 + nestingLevel; } // Process both operands nodeComplexity += calculateComplexity(binaryExpression.left, nestingLevel); nodeComplexity += calculateComplexity(binaryExpression.right, nestingLevel); break; // Function expressions and arrow functions create new scope but don't increase nesting case typescript_1.default.SyntaxKind.FunctionExpression: case typescript_1.default.SyntaxKind.ArrowFunction: // For nested functions within components, reset nesting level typescript_1.default.forEachChild(currentNode, (child) => { nodeComplexity += calculateComplexity(child, 0); }); break; // Don't process other function declarations as separate scopes within components case typescript_1.default.SyntaxKind.FunctionDeclaration: case typescript_1.default.SyntaxKind.MethodDeclaration: // Only process if this is not the root component function if (currentNode !== node) { typescript_1.default.forEachChild(currentNode, (child) => { nodeComplexity += calculateComplexity(child, 0); }); } else { // This is the component function itself, process normally typescript_1.default.forEachChild(currentNode, (child) => { nodeComplexity += calculateComplexity(child, nestingLevel); }); } break; // JSX conditional rendering adds complexity case typescript_1.default.SyntaxKind.JsxExpression: const jsxExpression = currentNode; if (jsxExpression.expression) { // Check for conditional patterns in JSX if (typescript_1.default.isBinaryExpression(jsxExpression.expression) && jsxExpression.expression.operatorToken.kind === typescript_1.default.SyntaxKind.AmpersandAmpersandToken) { nodeComplexity += 1 + nestingLevel; } else if (typescript_1.default.isConditionalExpression(jsxExpression.expression)) { nodeComplexity += 1 + nestingLevel; } nodeComplexity += calculateComplexity(jsxExpression.expression, nestingLevel); } break; // Optional chaining and nullish coalescing add slight complexity case typescript_1.default.SyntaxKind.QuestionQuestionToken: nodeComplexity += 1; typescript_1.default.forEachChild(currentNode, (child) => { nodeComplexity += calculateComplexity(child, nestingLevel); }); break; default: // Process all children with current nesting level typescript_1.default.forEachChild(currentNode, (child) => { nodeComplexity += calculateComplexity(child, nestingLevel); }); } return nodeComplexity; } totalComplexity = calculateComplexity(node); return totalComplexity; }