sicua
Version:
A tool for analyzing project structure and dependencies
383 lines (382 loc) • 17.3 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.FunctionExtractor = void 0;
const typescript_1 = __importDefault(require("typescript"));
const ASTUtils_1 = require("../../../utils/ast/ASTUtils");
const functionClassifier_1 = require("./functionClassifier");
const analysisUtils_1 = require("../../../utils/common/analysisUtils");
const functionFilter_1 = require("../filters/functionFilter");
const errorHandler_1 = require("./errorHandler");
const typeResolver_1 = require("./typeResolver");
const parameterParser_1 = require("./parameterParser");
const functionBodyParser_1 = require("./functionBodyParser");
const asyncDetector_1 = require("./asyncDetector");
const reactSpecific_1 = require("../../../utils/ast/reactSpecific");
/**
* Enhanced extractor that analyzes functions from a TypeScript source file
*/
class FunctionExtractor {
constructor(typeChecker) {
this.functionFilter = new functionFilter_1.FunctionFilter();
this.errorHandler = new errorHandler_1.ErrorHandler({
logErrors: true,
throwOnCritical: false,
maxErrorsPerFile: 50,
includeStackTrace: false,
enableRecovery: true,
});
// Initialize utility classes
this.typeResolver = new typeResolver_1.TypeResolver(typeChecker);
this.parameterParser = new parameterParser_1.ParameterParser();
this.bodyParser = new functionBodyParser_1.FunctionBodyParser();
this.asyncDetector = new asyncDetector_1.AsyncDetector(typeChecker);
this.classifier = new functionClassifier_1.FunctionClassifier(this.errorHandler, typeChecker);
}
/**
* Extracts relevant functions from a specific component within a source file
* @param context The extraction context with source file and component name
* @returns Array of extracted function data
*/
extractFunctions(context) {
const functions = [];
const filePath = context.sourceFile.fileName;
// Find the specific component node in the source file
const componentNode = this.findComponentNode(context.sourceFile, context.componentName);
if (!componentNode) {
return functions;
}
// Safe AST traversal with error handling, limited to the component node
const traversalResult = this.errorHandler.safeASTTraversal(componentNode, (node) => this.traverseAndExtract(node, context, functions, componentNode), functions, filePath);
// Log extraction summary
if (this.errorHandler.getErrorsForFile(filePath).length > 0) {
console.warn(`Function extraction completed with ${this.errorHandler.getErrorsForFile(filePath).length} errors for component ${context.componentName} in ${filePath}`);
}
return functions;
}
/**
* Find the specific component node in the source file
*/
findComponentNode(sourceFile, componentName) {
let componentNode = null;
const visit = (node) => {
if (componentNode)
return; // Already found
// Check for function declarations
if (typescript_1.default.isFunctionDeclaration(node) && node.name) {
const functionName = node.name.text;
if (functionName === componentName && (0, reactSpecific_1.isReactComponent)(node)) {
componentNode = node;
return;
}
}
// Check for variable declarations (const ComponentName = ...)
if (typescript_1.default.isVariableDeclaration(node) && typescript_1.default.isIdentifier(node.name)) {
const varName = node.name.text;
if (varName === componentName && node.initializer) {
if ((typescript_1.default.isArrowFunction(node.initializer) ||
typescript_1.default.isFunctionExpression(node.initializer)) &&
(0, reactSpecific_1.isReactComponent)(node.initializer)) {
componentNode = node.initializer;
return;
}
}
}
// Check for exported function declarations
if (typescript_1.default.isExportAssignment(node) &&
typescript_1.default.isFunctionDeclaration(node.expression)) {
const func = node.expression;
if (func.name &&
func.name.text === componentName &&
(0, reactSpecific_1.isReactComponent)(func)) {
componentNode = func;
return;
}
}
// Check for export declarations
if (typescript_1.default.isExportDeclaration(node) &&
node.exportClause &&
typescript_1.default.isNamedExports(node.exportClause)) {
node.exportClause.elements.forEach((element) => {
if (typescript_1.default.isExportSpecifier(element) && typescript_1.default.isIdentifier(element.name)) {
if (element.name.text === componentName) {
// Need to find the actual declaration
const declaration = this.findDeclarationInSourceFile(sourceFile, componentName);
if (declaration) {
componentNode = declaration;
}
}
}
});
}
typescript_1.default.forEachChild(node, visit);
};
visit(sourceFile);
return componentNode;
}
/**
* Find a declaration by name in the source file
*/
findDeclarationInSourceFile(sourceFile, name) {
let declaration = null;
const visit = (node) => {
if (declaration)
return;
if (typescript_1.default.isFunctionDeclaration(node) &&
node.name &&
node.name.text === name) {
if ((0, reactSpecific_1.isReactComponent)(node)) {
declaration = node;
}
}
else if (typescript_1.default.isVariableDeclaration(node) &&
typescript_1.default.isIdentifier(node.name) &&
node.name.text === name) {
if (node.initializer &&
(typescript_1.default.isArrowFunction(node.initializer) ||
typescript_1.default.isFunctionExpression(node.initializer)) &&
(0, reactSpecific_1.isReactComponent)(node.initializer)) {
declaration = node.initializer;
}
}
typescript_1.default.forEachChild(node, visit);
};
visit(sourceFile);
return declaration;
}
/**
* Traverses AST and extracts functions, limited to the component scope
*/
traverseAndExtract(node, context, functions, componentNode) {
const visit = (node) => {
if (this.isFunctionLikeNode(node)) {
// Only extract if this function is within the component scope
if (this.isFunctionWithinComponent(node, componentNode)) {
const extractedFunction = this.extractSingleFunction(node, context);
if (extractedFunction) {
functions.push(extractedFunction);
}
}
}
typescript_1.default.forEachChild(node, visit);
};
visit(node);
return functions;
}
/**
* Check if a function node is within the component scope (not a separate component)
*/
isFunctionWithinComponent(functionNode, componentNode) {
// If it's the component function itself, include it
if (functionNode === componentNode) {
return true;
}
// Check if it's a nested function within the component
let parent = functionNode.parent;
while (parent) {
if (parent === componentNode) {
return true;
}
// If we encounter another React component, this function belongs to that component
if (this.isFunctionLikeNode(parent) &&
parent !== componentNode &&
(0, reactSpecific_1.isReactComponent)(parent)) {
return false;
}
parent = parent.parent;
}
return false;
}
/**
* Extracts a single function with comprehensive error handling
*/
extractSingleFunction(node, context) {
const filePath = context.sourceFile.fileName;
try {
// Extract function name with error handling
const functionName = this.errorHandler.safeFunctionName(node, (n) => ASTUtils_1.ASTUtils.getFunctionName(n), filePath);
// Check if function should be included
const shouldInclude = this.errorHandler.safeExecute(() => this.functionFilter.shouldIncludeFunction(node, functionName), false, "function filter check", undefined, undefined, filePath, functionName);
if (!shouldInclude) {
return null;
}
// Perform classification
const classification = this.classifier.classifyFunction(node, filePath, functionName);
// Check if should include based on classification
const shouldIncludeByClassification = this.classifier.shouldIncludeBasedOnClassification(classification, filePath, functionName);
if (!shouldIncludeByClassification) {
return null;
}
// Extract all function properties with error handling
const params = this.errorHandler.safeParameters(node, (n) => this.parameterParser.parseParameters(n), filePath, functionName);
const returnType = this.errorHandler.safeReturnType(node, (n) => this.typeResolver.resolveFunctionReturnType(n), filePath, functionName);
const body = this.errorHandler.safeFunctionBody(node, (n) => this.bodyParser.extractBody(n), filePath, functionName);
const dependencies = this.errorHandler.safeDependencies(node, (n) => (0, analysisUtils_1.extractDependencies)(n), filePath, functionName);
const calledFunctions = this.errorHandler.safeCalledFunctions(node, (n) => (0, analysisUtils_1.extractCalledFunctions)(n), filePath, functionName);
const isAsync = this.errorHandler.safeAsyncDetection(node, (n) => this.asyncDetector.isAsync(n), filePath, functionName);
return {
componentName: context.componentName,
functionName,
params,
returnType,
body,
dependencies,
calledFunctions,
isAsync,
};
}
catch (error) {
// Handle unexpected errors during extraction
this.errorHandler.handleFileError(filePath, `function extraction for ${ASTUtils_1.ASTUtils.safeGetNodeText(node).substring(0, 50)}`, error);
return null;
}
}
/**
* Type guard for function-like nodes
*/
isFunctionLikeNode(node) {
return (typescript_1.default.isFunctionDeclaration(node) ||
typescript_1.default.isMethodDeclaration(node) ||
typescript_1.default.isArrowFunction(node));
}
/**
* Extracts detailed function information (for advanced analysis)
*/
extractFunctionsDetailed(context) {
const functions = [];
const filePath = context.sourceFile.fileName;
// Find the specific component node
const componentNode = this.findComponentNode(context.sourceFile, context.componentName);
if (!componentNode) {
return functions;
}
const visit = (node) => {
if (this.isFunctionLikeNode(node) &&
this.isFunctionWithinComponent(node, componentNode)) {
const extractedFunction = this.extractDetailedFunction(node, context);
if (extractedFunction) {
functions.push(extractedFunction);
}
}
typescript_1.default.forEachChild(node, visit);
};
this.errorHandler.safeASTTraversal(componentNode, (node) => {
visit(node);
return functions;
}, functions, filePath);
return functions;
}
/**
* Extracts detailed function information
*/
extractDetailedFunction(node, context) {
const filePath = context.sourceFile.fileName;
try {
const functionName = this.errorHandler.safeFunctionName(node, (n) => ASTUtils_1.ASTUtils.getFunctionName(n), filePath);
// Check inclusion filters
const shouldInclude = this.errorHandler.safeExecute(() => this.functionFilter.shouldIncludeFunction(node, functionName), false, "detailed function filter check", undefined, undefined, filePath, functionName);
if (!shouldInclude) {
return null;
}
const classification = this.classifier.classifyFunction(node, filePath, functionName);
if (!this.classifier.shouldIncludeBasedOnClassification(classification, filePath, functionName)) {
return null;
}
return {
name: functionName,
node,
params: this.errorHandler.safeParameters(node, (n) => this.parameterParser.parseParameters(n), filePath, functionName),
returnType: this.errorHandler.safeReturnType(node, (n) => this.typeResolver.resolveFunctionReturnType(n), filePath, functionName),
body: this.errorHandler.safeFunctionBody(node, (n) => this.bodyParser.extractBody(n), filePath, functionName),
isAsync: this.errorHandler.safeAsyncDetection(node, (n) => this.asyncDetector.isAsync(n), filePath, functionName),
};
}
catch (error) {
this.errorHandler.handleFileError(filePath, `detailed function extraction`, error);
return null;
}
}
/**
* Gets extraction statistics
*/
getExtractionStats() {
const errorSummary = this.errorHandler.getErrorSummary();
return {
totalErrors: errorSummary.total,
errorsByCategory: errorSummary.byCategory,
canContinue: this.errorHandler.canContinueAnalysis(),
summary: `Extracted functions with ${errorSummary.total} errors across ${errorSummary.filesWithErrors} files`,
};
}
/**
* Clears all accumulated errors
*/
clearErrors() {
this.errorHandler.clearErrors();
}
/**
* Gets all errors for debugging
*/
getErrors() {
return this.errorHandler.getErrors();
}
/**
* Validates extraction results
*/
validateExtractionResults(results) {
const issues = [];
let validCount = 0;
let invalidCount = 0;
results.forEach((result, index) => {
let isValidResult = true;
// Validate required fields
if (!result.componentName || result.componentName.trim() === "") {
issues.push(`Result ${index}: Missing or empty componentName`);
isValidResult = false;
}
if (!result.functionName || result.functionName === "Unknown Function") {
issues.push(`Result ${index}: Invalid functionName`);
isValidResult = false;
}
if (!Array.isArray(result.params)) {
issues.push(`Result ${index}: params should be an array`);
isValidResult = false;
}
if (!result.returnType || result.returnType.trim() === "") {
issues.push(`Result ${index}: Missing returnType`);
isValidResult = false;
}
if (typeof result.body !== "string") {
issues.push(`Result ${index}: body should be a string`);
isValidResult = false;
}
if (!Array.isArray(result.dependencies)) {
issues.push(`Result ${index}: dependencies should be an array`);
isValidResult = false;
}
if (!Array.isArray(result.calledFunctions)) {
issues.push(`Result ${index}: calledFunctions should be an array`);
isValidResult = false;
}
if (typeof result.isAsync !== "boolean") {
issues.push(`Result ${index}: isAsync should be a boolean`);
isValidResult = false;
}
if (isValidResult) {
validCount++;
}
else {
invalidCount++;
}
});
return {
isValid: issues.length === 0,
issues,
validCount,
invalidCount,
};
}
}
exports.FunctionExtractor = FunctionExtractor;