sicua
Version:
A tool for analyzing project structure and dependencies
221 lines (220 loc) • 9.72 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.calculateHalsteadMetrics = calculateHalsteadMetrics;
exports.computeMaintainabilityIndex = computeMaintainabilityIndex;
exports.calculateFunctionComplexity = calculateFunctionComplexity;
const typescript_1 = __importDefault(require("typescript"));
function calculateHalsteadMetrics(node) {
const operators = new Set();
const operands = new Set();
let totalOperators = 0;
let totalOperands = 0;
const visitor = (node) => {
switch (node.kind) {
// Binary operators
case typescript_1.default.SyntaxKind.BinaryExpression:
const binaryExpr = node;
operators.add(binaryExpr.operatorToken.getText());
totalOperators++;
break;
// Unary operators
case typescript_1.default.SyntaxKind.PrefixUnaryExpression:
case typescript_1.default.SyntaxKind.PostfixUnaryExpression:
const unaryExpr = node;
operators.add(typescript_1.default.tokenToString(unaryExpr.operator) || unaryExpr.operator.toString());
totalOperators++;
break;
// Assignment operators
case typescript_1.default.SyntaxKind.VariableDeclaration:
if (node.initializer) {
operators.add("=");
totalOperators++;
}
break;
// Function calls (function name as operator)
case typescript_1.default.SyntaxKind.CallExpression:
const callExpr = node;
if (typescript_1.default.isIdentifier(callExpr.expression)) {
operators.add(callExpr.expression.text);
totalOperators++;
}
else if (typescript_1.default.isPropertyAccessExpression(callExpr.expression)) {
operators.add(callExpr.expression.name.text);
totalOperators++;
}
break;
// Property access operators
case typescript_1.default.SyntaxKind.PropertyAccessExpression:
operators.add(".");
totalOperators++;
break;
// Array access operators
case typescript_1.default.SyntaxKind.ElementAccessExpression:
operators.add("[]");
totalOperators++;
break;
// Control flow operators
case typescript_1.default.SyntaxKind.IfStatement:
operators.add("if");
totalOperators++;
break;
case typescript_1.default.SyntaxKind.ForStatement:
case typescript_1.default.SyntaxKind.ForInStatement:
case typescript_1.default.SyntaxKind.ForOfStatement:
operators.add("for");
totalOperators++;
break;
case typescript_1.default.SyntaxKind.WhileStatement:
operators.add("while");
totalOperators++;
break;
case typescript_1.default.SyntaxKind.DoStatement:
operators.add("do");
totalOperators++;
break;
case typescript_1.default.SyntaxKind.SwitchStatement:
operators.add("switch");
totalOperators++;
break;
case typescript_1.default.SyntaxKind.CaseClause:
operators.add("case");
totalOperators++;
break;
case typescript_1.default.SyntaxKind.TryStatement:
operators.add("try");
totalOperators++;
break;
case typescript_1.default.SyntaxKind.CatchClause:
operators.add("catch");
totalOperators++;
break;
case typescript_1.default.SyntaxKind.ReturnStatement:
operators.add("return");
totalOperators++;
break;
// Modern JavaScript operators
case typescript_1.default.SyntaxKind.ConditionalExpression:
operators.add("?:");
totalOperators++;
break;
case typescript_1.default.SyntaxKind.TemplateExpression:
operators.add("`");
totalOperators++;
break;
case typescript_1.default.SyntaxKind.SpreadElement:
operators.add("...");
totalOperators++;
break;
// Identifiers (variables, parameters, etc.)
case typescript_1.default.SyntaxKind.Identifier:
const identifier = node;
// Only count as operand if it's not part of a declaration
const parent = identifier.parent;
if (!typescript_1.default.isVariableDeclaration(parent) || parent.name !== identifier) {
operands.add(identifier.text);
totalOperands++;
}
break;
// Literals
case typescript_1.default.SyntaxKind.StringLiteral:
case typescript_1.default.SyntaxKind.NumericLiteral:
case typescript_1.default.SyntaxKind.TrueKeyword:
case typescript_1.default.SyntaxKind.FalseKeyword:
case typescript_1.default.SyntaxKind.NullKeyword:
case typescript_1.default.SyntaxKind.UndefinedKeyword:
operands.add(node.getText());
totalOperands++;
break;
// Template literals
case typescript_1.default.SyntaxKind.TemplateHead:
case typescript_1.default.SyntaxKind.TemplateMiddle:
case typescript_1.default.SyntaxKind.TemplateTail:
case typescript_1.default.SyntaxKind.NoSubstitutionTemplateLiteral:
operands.add(node.getText());
totalOperands++;
break;
// JSX specific
case typescript_1.default.SyntaxKind.JsxElement:
case typescript_1.default.SyntaxKind.JsxSelfClosingElement:
operators.add("jsx");
totalOperators++;
break;
}
typescript_1.default.forEachChild(node, visitor);
};
visitor(node);
return {
n1: operators.size,
n2: operands.size,
N1: totalOperators,
N2: totalOperands,
};
}
function computeMaintainabilityIndex(halsteadVolume, cyclomaticComplexity, linesOfCode) {
// Prevent division by zero and invalid calculations
if (linesOfCode === 0)
return 100;
if (halsteadVolume <= 0)
halsteadVolume = 1;
if (cyclomaticComplexity <= 0)
cyclomaticComplexity = 1;
// Standard maintainability index formula
let maintainabilityIndex = 171 -
5.2 * Math.log(halsteadVolume) -
0.23 * cyclomaticComplexity -
16.2 * Math.log(linesOfCode);
// Normalize to 0-100 scale and ensure reasonable bounds
maintainabilityIndex = (maintainabilityIndex * 100) / 171;
return Math.max(0, Math.min(100, Math.round(maintainabilityIndex * 100) / 100));
}
function calculateFunctionComplexity(node) {
let complexity = 1; // Base complexity
const incrementComplexity = (node) => {
switch (node.kind) {
case typescript_1.default.SyntaxKind.IfStatement:
case typescript_1.default.SyntaxKind.ConditionalExpression:
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:
case typescript_1.default.SyntaxKind.CaseClause:
case typescript_1.default.SyntaxKind.CatchClause:
complexity++;
break;
case typescript_1.default.SyntaxKind.BinaryExpression:
const binaryExpr = node;
if (binaryExpr.operatorToken.kind ===
typescript_1.default.SyntaxKind.AmpersandAmpersandToken ||
binaryExpr.operatorToken.kind === typescript_1.default.SyntaxKind.BarBarToken) {
complexity++;
}
break;
// Additional complexity for modern patterns
case typescript_1.default.SyntaxKind.QuestionDotToken:
case typescript_1.default.SyntaxKind.QuestionQuestionToken:
complexity++;
break;
// JSX conditional patterns
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) {
complexity++;
}
else if (typescript_1.default.isConditionalExpression(jsxExpression.expression)) {
complexity++;
}
}
break;
}
typescript_1.default.forEachChild(node, incrementComplexity);
};
incrementComplexity(node);
return complexity;
}