UNPKG

sicua

Version:

A tool for analyzing project structure and dependencies

397 lines (396 loc) 16.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.FunctionAnalyzer = void 0; const rxjs_1 = require("rxjs"); const rxjs_2 = require("rxjs"); const sourceFileManager_1 = require("./utils/sourceFileManager"); const functionExtractor_1 = require("./utils/functionExtractor"); const errorHandler_1 = require("./utils/errorHandler"); const dataValidator_1 = require("./utils/dataValidator"); /** * Enhanced analyzer for business logic functions within components */ class FunctionAnalyzer { constructor(components, typeChecker, config = {}) { this.components = components; this.typeChecker = typeChecker; this.config = { concurrency: 4, enableValidation: true, enableErrorRecovery: true, skipEmptyFiles: true, maxRetries: 2, timeoutMs: 30000, enableDetailedLogging: false, ...config, }; // Initialize utilities this.sourceFileManager = new sourceFileManager_1.SourceFileManager(); this.errorHandler = new errorHandler_1.ErrorHandler({ logErrors: this.config.enableDetailedLogging, throwOnCritical: false, maxErrorsPerFile: 50, includeStackTrace: this.config.enableDetailedLogging, enableRecovery: this.config.enableErrorRecovery, }); this.functionExtractor = new functionExtractor_1.FunctionExtractor(typeChecker); this.dataValidator = new dataValidator_1.DataValidator({ strictMode: false, allowEmptyBodies: true, allowUnknownTypes: true, checkNameConventions: true, }); this.statistics = this.initializeStatistics(); } /** * Enhanced analysis with comprehensive error handling and validation * @returns Promise resolving to an array of analyzed function data */ async analyze() { const startTime = Date.now(); const functionDataList = []; const processingResults = []; try { this.logInfo("Starting function analysis..."); this.logInfo(`Processing ${this.components.length} components with concurrency ${this.config.concurrency}`); // Reset statistics this.statistics = this.initializeStatistics(); this.statistics.totalComponents = this.components.length; return new Promise((resolve, reject) => { (0, rxjs_1.from)(this.components) .pipe((0, rxjs_1.mergeMap)(async (component) => { return this.processComponent(component); }, this.config.concurrency), (0, rxjs_1.map)((result) => { processingResults.push(result); if (result.success) { functionDataList.push(...result.functions); this.statistics.processedComponents++; this.statistics.totalFunctions += result.functions.length; } else { this.statistics.failedComponents++; this.logError(`Failed to process component ${result.component.name}: ${result.errorMessage}`); } return result; }), (0, rxjs_1.catchError)((error) => { this.logError(`Critical error during analysis: ${error}`); this.statistics.errorSummary.criticalErrors++; return (0, rxjs_2.of)(null); }), (0, rxjs_1.finalize)(() => { this.finalizeAnalysis(startTime, functionDataList, processingResults); })) .subscribe({ complete: () => { this.logInfo(`Analysis completed. Total functions extracted: ${functionDataList.length}`); resolve(functionDataList); }, error: (error) => { this.logError(`Analysis failed: ${error}`); reject(error); }, }); }); } catch (error) { this.statistics.processingTimeMs = Date.now() - startTime; this.logError(`Analysis initialization failed: ${error}`); throw error; } } /** * Processes a single component with comprehensive error handling */ async processComponent(component) { const startTime = Date.now(); try { this.logDebug(`Processing component: ${component.name} (${component.fullPath})`); // Validate component if (!this.isValidComponent(component)) { this.statistics.skippedComponents++; return { component, functions: [], success: false, errorMessage: "Invalid component data", processingTimeMs: Date.now() - startTime, }; } // Check if file should be skipped if (this.config.skipEmptyFiles && (await this.isEmptyFile(component.fullPath))) { this.statistics.skippedComponents++; return { component, functions: [], success: true, processingTimeMs: Date.now() - startTime, }; } // Process with retries let lastError = null; for (let attempt = 1; attempt <= this.config.maxRetries; attempt++) { try { const result = await this.processComponentWithTimeout(component); // Validate extracted functions if enabled if (this.config.enableValidation && result.length > 0) { const validationResult = this.dataValidator.validateBatch(result); this.updateValidationStatistics(validationResult); // Filter out invalid functions if in strict mode const validFunctions = this.filterValidFunctions(result, validationResult); this.statistics.validFunctions += validFunctions.length; this.statistics.invalidFunctions += result.length - validFunctions.length; return { component, functions: validFunctions, success: true, processingTimeMs: Date.now() - startTime, }; } this.statistics.validFunctions += result.length; return { component, functions: result, success: true, processingTimeMs: Date.now() - startTime, }; } catch (error) { lastError = error instanceof Error ? error : new Error(String(error)); this.logWarning(`Attempt ${attempt}/${this.config.maxRetries} failed for ${component.name}: ${lastError.message}`); if (attempt < this.config.maxRetries) { // Brief delay before retry await new Promise((resolve) => setTimeout(resolve, 100 * attempt)); } } } // All retries failed this.errorHandler.handleFileError(component.fullPath, "component processing", lastError || new Error("Unknown error")); return { component, functions: [], success: false, errorMessage: lastError?.message || "Processing failed after retries", processingTimeMs: Date.now() - startTime, }; } catch (error) { this.errorHandler.handleFileError(component.fullPath, "component processing setup", error); return { component, functions: [], success: false, errorMessage: error instanceof Error ? error.message : "Unknown error", processingTimeMs: Date.now() - startTime, }; } } /** * Processes component with timeout protection */ async processComponentWithTimeout(component) { return new Promise(async (resolve, reject) => { const timeoutId = setTimeout(() => { reject(new Error(`Component processing timeout (${this.config.timeoutMs}ms)`)); }, this.config.timeoutMs); try { const sourceFile = await this.sourceFileManager.getOrCreateSourceFile(component.fullPath); const functions = this.functionExtractor.extractFunctions({ componentName: component.name, sourceFile, }); clearTimeout(timeoutId); resolve(functions); } catch (error) { clearTimeout(timeoutId); reject(error); } }); } /** * Validates component data */ isValidComponent(component) { return !!(component && component.name && component.fullPath && typeof component.name === "string" && typeof component.fullPath === "string" && component.name.trim() !== "" && component.fullPath.trim() !== ""); } /** * Checks if file is empty or should be skipped */ async isEmptyFile(filePath) { try { const sourceFile = await this.sourceFileManager.getOrCreateSourceFile(filePath); return sourceFile.text.trim().length === 0; } catch (error) { // If we can't read the file, consider it "empty" for processing purposes return true; } } /** * Filters valid functions based on validation results */ filterValidFunctions(functions, validationResult) { // In non-strict mode, only filter out functions with critical errors return functions.filter((_, index) => { const functionKey = `${functions[index].componentName}.${functions[index].functionName}`; const functionResult = validationResult.functionResults.get(functionKey); return !functionResult || functionResult.errors === 0; }); } /** * Updates validation statistics */ updateValidationStatistics(validationResult) { if (!this.statistics.validationSummary) { this.statistics.validationSummary = { overallScore: 0, issuesByCategory: {}, issuesBySeverity: {}, }; } this.statistics.validationSummary.overallScore = (this.statistics.validationSummary.overallScore + validationResult.overallScore) / 2; Object.keys(validationResult.issuesByCategory).forEach((category) => { this.statistics.validationSummary.issuesByCategory[category] = (this.statistics.validationSummary.issuesByCategory[category] || 0) + validationResult.issuesByCategory[category]; }); Object.keys(validationResult.issuesBySeverity).forEach((severity) => { this.statistics.validationSummary.issuesBySeverity[severity] = (this.statistics.validationSummary.issuesBySeverity[severity] || 0) + validationResult.issuesBySeverity[severity]; }); } /** * Finalizes analysis and updates statistics */ finalizeAnalysis(startTime, functionDataList, processingResults) { this.statistics.processingTimeMs = Date.now() - startTime; this.statistics.averageTimePerComponent = this.statistics.processedComponents > 0 ? this.statistics.processingTimeMs / this.statistics.processedComponents : 0; // Update error statistics const extractorStats = this.functionExtractor.getExtractionStats(); this.statistics.errorSummary = { totalErrors: extractorStats.totalErrors, errorsByCategory: extractorStats.errorsByCategory, criticalErrors: this.statistics.errorSummary.criticalErrors, }; // Log final statistics this.logAnalysisResults(); // Clean up resources this.sourceFileManager.clearCache(); this.functionExtractor.clearErrors(); } /** * Initializes statistics object */ initializeStatistics() { return { totalComponents: 0, processedComponents: 0, skippedComponents: 0, failedComponents: 0, totalFunctions: 0, validFunctions: 0, invalidFunctions: 0, processingTimeMs: 0, averageTimePerComponent: 0, errorSummary: { totalErrors: 0, errorsByCategory: {}, criticalErrors: 0, }, }; } /** * Logs analysis results */ logAnalysisResults() { const stats = this.statistics; this.logInfo("=== Function Analysis Results ==="); this.logInfo(`Components: ${stats.totalComponents} total, ${stats.processedComponents} processed, ${stats.skippedComponents} skipped, ${stats.failedComponents} failed`); this.logInfo(`Functions: ${stats.totalFunctions} total, ${stats.validFunctions} valid, ${stats.invalidFunctions} invalid`); this.logInfo(`Processing: ${stats.processingTimeMs}ms total, ${stats.averageTimePerComponent.toFixed(1)}ms average per component`); this.logInfo(`Errors: ${stats.errorSummary.totalErrors} total, ${stats.errorSummary.criticalErrors} critical`); if (stats.validationSummary) { this.logInfo(`Validation: ${stats.validationSummary.overallScore.toFixed(1)} average quality score`); } if (stats.errorSummary.totalErrors > 0) { this.logWarning(`Analysis completed with ${stats.errorSummary.totalErrors} errors. Check logs for details.`); } } /** * Gets detailed analysis statistics */ getStatistics() { return { ...this.statistics }; } /** * Gets error details for debugging */ getErrors() { return this.errorHandler.getErrors(); } /** * Updates analysis configuration */ updateConfig(newConfig) { this.config = { ...this.config, ...newConfig }; // Recreate ErrorHandler with new configuration if needed if (newConfig.enableDetailedLogging !== undefined || newConfig.enableErrorRecovery !== undefined) { this.errorHandler = new errorHandler_1.ErrorHandler({ logErrors: this.config.enableDetailedLogging, throwOnCritical: false, maxErrorsPerFile: 50, includeStackTrace: this.config.enableDetailedLogging, enableRecovery: this.config.enableErrorRecovery, }); } } /** * Gets current configuration */ getConfig() { return { ...this.config }; } /** * Logging utilities */ logInfo(message) { if (this.config.enableDetailedLogging) { console.log(`[FunctionAnalyzer] ${message}`); } } logWarning(message) { console.warn(`[FunctionAnalyzer] ${message}`); } logError(message) { console.error(`[FunctionAnalyzer] ${message}`); } logDebug(message) { if (this.config.enableDetailedLogging) { console.debug(`[FunctionAnalyzer] ${message}`); } } /** * Cleanup method for proper resource management */ dispose() { this.sourceFileManager.clearCache(); this.functionExtractor.clearErrors(); this.errorHandler.clearErrors(); } } exports.FunctionAnalyzer = FunctionAnalyzer;