sicua
Version:
A tool for analyzing project structure and dependencies
287 lines (286 loc) • 12.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.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;