sicua
Version:
A tool for analyzing project structure and dependencies
240 lines (239 loc) • 11.7 kB
JavaScript
;
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;
}