UNPKG

sicua

Version:

A tool for analyzing project structure and dependencies

383 lines (382 loc) 17.3 kB
"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;