UNPKG

sicua

Version:

A tool for analyzing project structure and dependencies

287 lines (286 loc) 12.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.ErrorHandlingAnalyzer = void 0; const typescript_1 = __importDefault(require("typescript")); const functionAnalyzer_1 = require("./analyzers/functionAnalyzer"); const serializationUtils_1 = require("./utils/serializationUtils"); const componentAnalyzer_1 = require("./analyzers/componentAnalyzer"); const analysisUtils_1 = require("../../utils/common/analysisUtils"); const configManager_1 = require("../../core/configManager"); const path_1 = __importDefault(require("path")); /** * Enhanced main analyzer for error handling in React applications with dynamic project structure support */ class ErrorHandlingAnalyzer { /** * Create a new ErrorHandlingAnalyzer with enhanced project structure integration * * @param files - Array of file paths to analyze * @param components - Component relations to analyze * @param config - Enhanced config manager with project structure info * @param scanResult - Complete scan result with all project data * @param program - Optional pre-built TypeScript program from projectAnalyzer * @param typeChecker - Optional pre-built type checker from projectAnalyzer */ constructor(files, components, config, scanResult, program, typeChecker) { this.program = null; this.sourceFiles = new Map(); this.typeChecker = null; this.components = components; this.config = config || new configManager_1.ConfigManager(process.cwd()); this.scanResult = scanResult || this.createFallbackScanResult(files); // Use provided TypeScript program or create a new one if (program && typeChecker) { this.program = program; this.typeChecker = typeChecker; this.sourceFiles = new Map(files .map((filePath) => [filePath, program.getSourceFile(filePath)]) .filter(([, sourceFile]) => sourceFile !== undefined)); } else { this.initializeTypeScriptProgram(files); } } /** * Initialize TypeScript program with enhanced configuration detection */ initializeTypeScriptProgram(files) { try { let compilerOptions = { target: typescript_1.default.ScriptTarget.Latest, module: typescript_1.default.ModuleKind.ESNext, jsx: typescript_1.default.JsxEmit.React, allowJs: true, esModuleInterop: true, skipLibCheck: true, noEmit: true, incremental: false, }; // Enhanced TypeScript config detection const possibleTsConfigPaths = [ path_1.default.join(this.config.projectPath, "tsconfig.json"), path_1.default.join(this.config.srcDir, "tsconfig.json"), ]; // Try to load existing TypeScript configuration for (const configPath of possibleTsConfigPaths) { try { const configFile = typescript_1.default.readConfigFile(configPath, typescript_1.default.sys.readFile); if (!configFile.error) { const parsedConfig = typescript_1.default.parseJsonConfigFileContent(configFile.config, typescript_1.default.sys, path_1.default.dirname(configPath)); if (!parsedConfig.errors.length) { compilerOptions = { ...compilerOptions, ...parsedConfig.options }; break; } } } catch (error) { // Continue to next config path } } // Create TypeScript program this.program = typescript_1.default.createProgram(files, compilerOptions); this.typeChecker = this.program.getTypeChecker(); // Create source files map this.sourceFiles = new Map(files .map((filePath) => [filePath, this.program.getSourceFile(filePath)]) .filter(([, sourceFile]) => sourceFile !== undefined)); } catch (error) { // Fallback: create minimal program this.program = typescript_1.default.createProgram(files, { target: typescript_1.default.ScriptTarget.Latest, module: typescript_1.default.ModuleKind.ESNext, allowJs: true, noEmit: true, }); this.typeChecker = this.program.getTypeChecker(); this.sourceFiles = new Map(files .map((filePath) => [filePath, this.program.getSourceFile(filePath)]) .filter(([, sourceFile]) => sourceFile !== undefined)); } } /** * Create fallback scan result if not provided */ createFallbackScanResult(files) { return { filePaths: files, sourceFiles: new Map(), fileContents: new Map(), fileMetadata: new Map(), securityFiles: [], configFiles: [], environmentFiles: [], apiRoutes: [], middlewareFiles: [], packageInfo: [], securityScanMetadata: { scanTimestamp: Date.now(), scanDuration: 0, filesScanned: files.length, securityIssuesFound: 0, riskLevel: "low", coveragePercentage: 0, }, }; } /** * Analyze error handling across all components and functions with enhanced project structure awareness */ analyze() { if (!this.typeChecker) { throw new Error("TypeScript type checker is not available for error handling analysis"); } // Initialize analysis results const results = new Map(); let totalErrorBoundaries = 0; let totalTryCatch = 0; let totalFunctionsWithErrorHandling = 0; let totalFunctionsNeedingErrorHandling = 0; const functionsWithMissingErrorHandling = []; const riskBreakdown = { high: 0, medium: 0, low: 0 }; // Analyze all source files for functions first with enhanced context this.sourceFiles.forEach((sourceFile, filePath) => { const functionAnalyzer = new functionAnalyzer_1.FunctionAnalyzer(sourceFile, this.typeChecker, this.config, this.scanResult); const functionErrorHandling = functionAnalyzer.analyzeFunctions(); functionErrorHandling.forEach((func) => { if (func.hasErrorHandling) { totalFunctionsWithErrorHandling++; } if (func.riskAnalysis.shouldHaveErrorHandling) { totalFunctionsNeedingErrorHandling++; if (!func.hasErrorHandling) { functionsWithMissingErrorHandling.push(`${func.functionName} (${filePath}:${func.location.line})`); } // Categorize risk with enhanced scoring if (func.riskAnalysis.riskScore >= 5) riskBreakdown.high++; else if (func.riskAnalysis.riskScore >= 3) riskBreakdown.medium++; else riskBreakdown.low++; } }); }); // Analyze components with enhanced project structure awareness for (const component of this.components) { const sourceFile = this.sourceFiles.get(component.fullPath); if (!sourceFile) continue; const componentAnalyzer = new componentAnalyzer_1.ComponentAnalyzer(component, sourceFile, this.typeChecker, this.config, this.scanResult); const result = componentAnalyzer.analyzeComponent(); const significantResult = componentAnalyzer.getSignificantAnalysis(result); // Only include significant results if (significantResult) { results.set((0, analysisUtils_1.generateComponentId)(component), significantResult); totalErrorBoundaries += significantResult.errorBoundaries.length; totalTryCatch += significantResult.tryCatchBlocks.length; } } const componentsWithErrorHandling = results.size; const analysis = { componentResults: Object.fromEntries(results), summary: { totalComponents: this.components.length, componentsWithErrorHandling, errorHandlingCoverage: (componentsWithErrorHandling / this.components.length) * 100, totalErrorBoundaries, totalTryCatch, totalFunctionsWithErrorHandling, libraryUsage: this.collectLibraryUsage(results), functionErrorHandlingGaps: { totalFunctionsNeedingErrorHandling, functionsWithMissingErrorHandling, riskBreakdown, }, }, }; // Add project-specific insights this.addProjectSpecificInsights(analysis); // Verify the result is serializable serializationUtils_1.SerializationUtils.verifySerializable(analysis); return analysis; } /** * Add project-specific insights based on detected structure */ addProjectSpecificInsights(analysis) { const projectStructure = this.config.getProjectStructure(); if (projectStructure?.projectType === "nextjs") { // Add Next.js specific insights const nextjsInsights = this.analyzeNextJsSpecificPatterns(); // Add insights to summary (extend the type if needed) if (analysis.summary && typeof analysis.summary === "object") { analysis.summary.nextjsInsights = nextjsInsights; } } } /** * Analyze Next.js specific error handling patterns */ analyzeNextJsSpecificPatterns() { const projectStructure = this.config.getProjectStructure(); const insights = { routerType: projectStructure?.routerType, hasErrorPages: false, hasGlobalErrorBoundary: false, apiRouteErrorHandling: 0, serverComponentErrors: 0, }; // Check for Next.js specific error handling patterns this.components.forEach((component) => { const fileName = path_1.default.basename(component.fullPath, path_1.default.extname(component.fullPath)); // Check for error pages if (fileName === "error" || fileName === "_error" || fileName === "404" || fileName === "500") { insights.hasErrorPages = true; } // Check for global error boundary in app router if (fileName === "global-error" || fileName === "layout") { insights.hasGlobalErrorBoundary = true; } // Count API route error handling if (component.fullPath.includes("/api/")) { insights.apiRouteErrorHandling++; } }); return insights; } /** * Collect library usage statistics from error boundaries with enhanced detection */ collectLibraryUsage(results) { const usage = {}; results.forEach((result) => { result.errorBoundaries.forEach((boundary) => { const libName = boundary.library.name; usage[libName] = (usage[libName] || 0) + 1; }); }); return usage; } /** * Get project structure information */ getProjectStructure() { return this.config.getProjectStructure(); } /** * Get configuration manager */ getConfig() { return this.config; } /** * Get scan result data */ getScanResult() { return this.scanResult; } } exports.ErrorHandlingAnalyzer = ErrorHandlingAnalyzer;