UNPKG

sicua

Version:

A tool for analyzing project structure and dependencies

320 lines (319 loc) 11.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.FunctionBodyParser = void 0; const typescript_1 = __importDefault(require("typescript")); /** * Utility class for parsing and normalizing function bodies */ class FunctionBodyParser { constructor() { this.defaultOptions = { normalizeWhitespace: true, removeComments: false, maxLength: null, includeLocation: false, }; } /** * Extracts function body as a string (for backward compatibility) * @param node The function-like declaration * @param options Optional parsing configuration * @returns Cleaned function body string */ extractBody(node, options = {}) { const mergedOptions = { ...this.defaultOptions, ...options }; const parsed = this.extractBodyDetailed(node, mergedOptions); return parsed.body; } /** * Extracts detailed function body information * @param node The function-like declaration * @param options Optional parsing configuration * @returns Detailed function body information */ extractBodyDetailed(node, options = {}) { const mergedOptions = { ...this.defaultOptions, ...options }; try { if (!node.body) { return this.createEmptyBody(); } // Handle arrow functions with expression bodies if (typescript_1.default.isArrowFunction(node) && !typescript_1.default.isBlock(node.body)) { return this.parseExpressionBody(node.body, mergedOptions); } // Handle block statements if (typescript_1.default.isBlock(node.body)) { return this.parseBlockBody(node.body, mergedOptions); } // Fallback for other body types return this.parseGenericBody(node.body, mergedOptions); } catch (error) { return this.createErrorBody(); } } /** * Checks if a function has an empty body */ isEmpty(node) { if (!node.body) { return true; } if (typescript_1.default.isBlock(node.body)) { return node.body.statements.length === 0; } // Expression body is never empty return false; } /** * Checks if function body is just an expression (arrow function) */ isExpressionBody(node) { return (typescript_1.default.isArrowFunction(node) && node.body !== undefined && !typescript_1.default.isBlock(node.body)); } /** * Counts return statements in a function body */ countReturnStatements(node) { if (!node.body) { return 0; } // Expression body has implicit return if (typescript_1.default.isArrowFunction(node) && !typescript_1.default.isBlock(node.body)) { return 1; } if (typescript_1.default.isBlock(node.body)) { return this.countReturnsInBlock(node.body); } return 0; } /** * Gets the complexity score of a function body */ getBodyComplexity(node) { if (!node.body) { return 0; } let complexity = 1; // Base complexity if (typescript_1.default.isArrowFunction(node) && !typescript_1.default.isBlock(node.body)) { // Expression body - add complexity based on expression type complexity += this.getExpressionComplexity(node.body); } else if (typescript_1.default.isBlock(node.body)) { // Block body - traverse and count complexity complexity += this.getBlockComplexity(node.body); } return complexity; } /** * Parses expression body (arrow function without braces) */ parseExpressionBody(expression, options) { const rawText = expression.getText(); const processedBody = this.processBodyText(rawText, options); return { body: processedBody, isEmpty: false, isExpression: true, hasReturnStatements: true, // Expression body has implicit return lineCount: this.countLines(processedBody), characterCount: processedBody.length, location: options.includeLocation ? this.getLocation(expression) : undefined, }; } /** * Parses block body (function with braces) */ parseBlockBody(block, options) { const rawText = block.getText(); const processedBody = this.processBodyText(rawText, options); return { body: processedBody, isEmpty: block.statements.length === 0, isExpression: false, hasReturnStatements: this.countReturnsInBlock(block) > 0, lineCount: this.countLines(processedBody), characterCount: processedBody.length, location: options.includeLocation ? this.getLocation(block) : undefined, }; } /** * Parses generic body types */ parseGenericBody(body, options) { const rawText = body.getText(); const processedBody = this.processBodyText(rawText, options); return { body: processedBody, isEmpty: rawText.trim().length === 0, isExpression: false, hasReturnStatements: false, lineCount: this.countLines(processedBody), characterCount: processedBody.length, location: options.includeLocation ? this.getLocation(body) : undefined, }; } /** * Processes raw body text according to options */ processBodyText(text, options) { let processed = text; // Remove comments if requested if (options.removeComments) { processed = this.removeComments(processed); } // Normalize whitespace if requested if (options.normalizeWhitespace) { processed = this.normalizeWhitespace(processed); } // Truncate if max length specified if (options.maxLength && processed.length > options.maxLength) { processed = processed.substring(0, options.maxLength) + "..."; } return processed.trim(); } /** * Normalizes whitespace in code while preserving indentation */ normalizeWhitespace(text) { return text .replace(/\r\n/g, "\n") // Normalize line endings .replace(/\t/g, " ") // Convert tabs to spaces .replace(/[ \t]+$/gm, "") // Remove trailing whitespace from each line .replace(/\n{3,}/g, "\n\n") // Limit consecutive empty lines to max 2 .trim(); } /** * Removes comments from code (basic implementation) */ removeComments(text) { // Remove single-line comments text = text.replace(/\/\/.*$/gm, ""); // Remove multi-line comments text = text.replace(/\/\*[\s\S]*?\*\//g, ""); return text; } /** * Counts lines in text */ countLines(text) { return text.split("\n").length; } /** * Gets location information for a node */ getLocation(node) { const sourceFile = node.getSourceFile(); const start = node.getStart(); const end = node.getEnd(); const { line, character } = sourceFile.getLineAndCharacterOfPosition(start); return { start, end, line: line + 1, // Convert to 1-based column: character + 1, // Convert to 1-based }; } /** * Counts return statements in a block */ countReturnsInBlock(block) { let count = 0; const visit = (node) => { if (typescript_1.default.isReturnStatement(node)) { count++; } typescript_1.default.forEachChild(node, visit); }; typescript_1.default.forEachChild(block, visit); return count; } /** * Gets complexity score for an expression */ getExpressionComplexity(expression) { let complexity = 0; const visit = (node) => { if (typescript_1.default.isConditionalExpression(node)) { complexity += 1; } else if (typescript_1.default.isBinaryExpression(node)) { if (node.operatorToken.kind === typescript_1.default.SyntaxKind.AmpersandAmpersandToken || node.operatorToken.kind === typescript_1.default.SyntaxKind.BarBarToken) { complexity += 1; } } else if (typescript_1.default.isCallExpression(node)) { complexity += 0.5; } typescript_1.default.forEachChild(node, visit); }; visit(expression); return Math.round(complexity * 10) / 10; } /** * Gets complexity score for a block */ getBlockComplexity(block) { let complexity = 0; const visit = (node) => { if (typescript_1.default.isIfStatement(node) || typescript_1.default.isWhileStatement(node) || typescript_1.default.isForStatement(node) || typescript_1.default.isForInStatement(node) || typescript_1.default.isForOfStatement(node) || typescript_1.default.isSwitchStatement(node) || typescript_1.default.isCaseClause(node) || typescript_1.default.isTryStatement(node) || typescript_1.default.isCatchClause(node)) { complexity += 1; } else if (typescript_1.default.isConditionalExpression(node)) { complexity += 1; } else if (typescript_1.default.isBinaryExpression(node)) { if (node.operatorToken.kind === typescript_1.default.SyntaxKind.AmpersandAmpersandToken || node.operatorToken.kind === typescript_1.default.SyntaxKind.BarBarToken) { complexity += 1; } } typescript_1.default.forEachChild(node, visit); }; typescript_1.default.forEachChild(block, visit); return complexity; } /** * Creates empty body result */ createEmptyBody() { return { body: "", isEmpty: true, isExpression: false, hasReturnStatements: false, lineCount: 0, characterCount: 0, }; } /** * Creates error fallback body result */ createErrorBody() { return { body: "// Error parsing function body", isEmpty: false, isExpression: false, hasReturnStatements: false, lineCount: 1, characterCount: 30, }; } } exports.FunctionBodyParser = FunctionBodyParser;