sicua
Version:
A tool for analyzing project structure and dependencies
320 lines (319 loc) • 11.2 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.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;