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