sicua
Version:
A tool for analyzing project structure and dependencies
397 lines (396 loc) • 16.3 kB
JavaScript
"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;