UNPKG

sicua

Version:

A tool for analyzing project structure and dependencies

221 lines (220 loc) 9.72 kB
"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; }