UNPKG

smart-ast-analyzer

Version:

Advanced AST-based project analysis tool with deep complexity analysis, security scanning, and optional AI enhancement

494 lines (400 loc) 14.8 kB
class WebSocketAnalyzer { constructor(framework) { this.framework = framework; } async analyze(aiResult, files) { if (!aiResult || aiResult.error) { return this.createEmptyResult(aiResult?.error); } // Enhance AI results with additional analysis const enhancedResult = { ...aiResult, metadata: this.generateMetadata(aiResult, files), architecture: this.analyzeArchitecture(aiResult), security: this.enhanceSecurityAnalysis(aiResult), performance: this.enhancePerformanceAnalysis(aiResult), recommendations: this.generateRecommendations(aiResult) }; return enhancedResult; } createEmptyResult(error) { return { websocket: {}, connections: [], events: { client: {}, server: {} }, eventFlow: [], rooms: {}, issues: { security: [], performance: [], reliability: [], memoryLeaks: [] }, metadata: { totalEvents: 0, analysisDate: new Date().toISOString(), framework: this.framework, error: error }, recommendations: [ 'Check if your project uses WebSocket connections', 'Ensure WebSocket files are in the expected locations (socket/, ws/, realtime/)', 'Verify that the AI analysis service is working correctly' ] }; } generateMetadata(result, files) { const clientEvents = Object.keys(result.events?.client || {}).length; const serverEvents = Object.keys(result.events?.server || {}).length; return { totalEvents: clientEvents + serverEvents, clientEvents: clientEvents, serverEvents: serverEvents, totalConnections: result.connections?.length || 0, totalRooms: Object.keys(result.rooms || {}).length, totalFiles: files.length, analysisDate: new Date().toISOString(), framework: this.framework, library: result.websocket?.library || 'unknown', eventComplexity: this.assessEventComplexity(result) }; } assessEventComplexity(result) { const allEvents = { ...result.events?.client || {}, ...result.events?.server || {} }; const complexity = { simple: 0, moderate: 0, complex: 0 }; Object.values(allEvents).forEach(event => { const payloadSize = JSON.stringify(event.payload || {}).length; const handlersCount = event.handlers?.length || 0; const score = payloadSize / 100 + handlersCount * 2; if (score < 5) { complexity.simple++; } else if (score < 15) { complexity.moderate++; } else { complexity.complex++; } }); return complexity; } analyzeArchitecture(result) { return { connectionPattern: this.identifyConnectionPattern(result), eventPattern: this.identifyEventPattern(result), scalabilityAssessment: this.assessScalability(result), communicationFlow: this.analyzeCommunicationFlow(result), namespaceUsage: this.analyzeNamespaceUsage(result) }; } identifyConnectionPattern(result) { const connections = result.connections || []; if (connections.length === 0) return 'none'; if (connections.length === 1) return 'single'; // Check for multiple namespaces const namespaces = new Set(connections.map(c => c.namespace).filter(Boolean)); if (namespaces.size > 1) return 'multi-namespace'; return 'multi-connection'; } identifyEventPattern(result) { const eventFlow = result.eventFlow || []; if (eventFlow.length === 0) return 'none'; // Analyze bidirectional vs unidirectional const clientToServer = eventFlow.filter(flow => flow.from === 'client').length; const serverToClient = eventFlow.filter(flow => flow.from === 'server').length; if (clientToServer > 0 && serverToClient > 0) { return 'bidirectional'; } else if (clientToServer > 0) { return 'client-to-server'; } else if (serverToClient > 0) { return 'server-to-client'; } return 'unknown'; } assessScalability(result) { const assessment = { level: 'unknown', concerns: [], recommendations: [] }; const totalEvents = Object.keys(result.events?.client || {}).length + Object.keys(result.events?.server || {}).length; const totalRooms = Object.keys(result.rooms || {}).length; if (totalEvents < 10 && totalRooms < 5) { assessment.level = 'small'; } else if (totalEvents < 50 && totalRooms < 20) { assessment.level = 'medium'; assessment.recommendations.push('Consider connection pooling'); } else { assessment.level = 'large'; assessment.concerns.push('High event volume'); assessment.concerns.push('Many rooms to manage'); assessment.recommendations.push('Implement horizontal scaling'); assessment.recommendations.push('Consider Redis adapter for Socket.io'); } return assessment; } analyzeCommunicationFlow(result) { const eventFlow = result.eventFlow || []; return { totalFlows: eventFlow.length, patterns: this.identifyFlowPatterns(eventFlow), cycles: this.detectEventCycles(eventFlow), bottlenecks: this.identifyFlowBottlenecks(eventFlow) }; } identifyFlowPatterns(eventFlow) { const patterns = []; // Request-response pattern const requestResponsePairs = eventFlow.filter(flow => eventFlow.some(response => response.trigger === flow.event && response.from !== flow.from ) ); if (requestResponsePairs.length > 0) { patterns.push('request-response'); } // Broadcast pattern const broadcasts = eventFlow.filter(flow => flow.to === 'broadcast'); if (broadcasts.length > 0) { patterns.push('broadcast'); } // Room-based communication const roomEvents = eventFlow.filter(flow => flow.payload && typeof flow.payload === 'object' && flow.payload.room ); if (roomEvents.length > 0) { patterns.push('room-based'); } return patterns; } detectEventCycles(eventFlow) { // Simplified cycle detection const cycles = []; const visited = new Set(); eventFlow.forEach(flow => { if (visited.has(flow.event)) { cycles.push(flow.event); } else { visited.add(flow.event); } }); return cycles; } identifyFlowBottlenecks(eventFlow) { const eventCounts = {}; eventFlow.forEach(flow => { eventCounts[flow.event] = (eventCounts[flow.event] || 0) + 1; }); // Events that appear frequently might be bottlenecks return Object.entries(eventCounts) .filter(([event, count]) => count > 5) .map(([event, count]) => ({ event, frequency: count })); } analyzeNamespaceUsage(result) { const connections = result.connections || []; const namespaces = {}; connections.forEach(conn => { if (conn.namespace) { namespaces[conn.namespace] = { connections: (namespaces[conn.namespace]?.connections || 0) + 1, purpose: conn.purpose || 'unknown' }; } }); return { total: Object.keys(namespaces).length, details: namespaces, organized: Object.keys(namespaces).length > 1 }; } enhanceSecurityAnalysis(result) { const security = { ...result.security, authenticationStatus: this.assessAuthentication(result), authorizationStatus: this.assessAuthorization(result), inputValidation: this.assessInputValidation(result), rateLimiting: this.assessRateLimiting(result), securityScore: 0 }; // Calculate security score let score = 100; if (security.authenticationStatus === 'missing') score -= 30; if (security.authorizationStatus === 'inadequate') score -= 20; if (security.inputValidation === 'insufficient') score -= 25; if (security.rateLimiting === 'missing') score -= 15; security.securityScore = Math.max(0, score); return security; } assessAuthentication(result) { const connections = result.connections || []; const authenticatedConnections = connections.filter(conn => conn.auth && Object.keys(conn.auth).length > 0 ); if (authenticatedConnections.length === 0) return 'missing'; if (authenticatedConnections.length < connections.length) return 'partial'; return 'complete'; } assessAuthorization(result) { const events = { ...result.events?.client || {}, ...result.events?.server || {} }; const authorizedEvents = Object.values(events).filter(event => event.authorization || event.permissions ); const totalEvents = Object.keys(events).length; if (totalEvents === 0) return 'unknown'; if (authorizedEvents.length === 0) return 'missing'; if (authorizedEvents.length < totalEvents * 0.5) return 'inadequate'; return 'adequate'; } assessInputValidation(result) { const events = { ...result.events?.client || {}, ...result.events?.server || {} }; const validatedEvents = Object.values(events).filter(event => event.validation || (event.payload && Object.keys(event.payload).length > 0) ); const totalEvents = Object.keys(events).length; if (totalEvents === 0) return 'unknown'; if (validatedEvents.length < totalEvents * 0.3) return 'insufficient'; if (validatedEvents.length < totalEvents * 0.7) return 'partial'; return 'adequate'; } assessRateLimiting(result) { // Check if rate limiting is mentioned in issues or configuration const hasRateLimiting = result.websocket?.config?.rateLimiting || result.issues?.performance?.some(issue => issue.toLowerCase().includes('rate limit') ); return hasRateLimiting ? 'present' : 'missing'; } enhancePerformanceAnalysis(result) { const performance = { connectionPerformance: this.assessConnectionPerformance(result), eventPerformance: this.assessEventPerformance(result), memoryUsage: this.assessMemoryUsage(result), networkEfficiency: this.assessNetworkEfficiency(result), performanceScore: 0 }; // Calculate performance score let score = 100; if (performance.connectionPerformance.issues > 0) score -= 20; if (performance.eventPerformance.heavyEvents > 3) score -= 25; if (performance.memoryUsage.risk === 'high') score -= 30; if (performance.networkEfficiency < 0.7) score -= 15; performance.performanceScore = Math.max(0, score); return performance; } assessConnectionPerformance(result) { const connections = result.connections || []; return { totalConnections: connections.length, issues: connections.filter(conn => !conn.options?.reconnection).length, recommendations: connections.length > 100 ? ['Consider connection pooling'] : [] }; } assessEventPerformance(result) { const allEvents = { ...result.events?.client || {}, ...result.events?.server || {} }; let heavyEvents = 0; let totalPayloadSize = 0; Object.values(allEvents).forEach(event => { const payloadSize = JSON.stringify(event.payload || {}).length; totalPayloadSize += payloadSize; if (payloadSize > 1000) { // 1KB threshold heavyEvents++; } }); return { totalEvents: Object.keys(allEvents).length, heavyEvents, averagePayloadSize: Object.keys(allEvents).length > 0 ? totalPayloadSize / Object.keys(allEvents).length : 0 }; } assessMemoryUsage(result) { const memoryLeaks = result.issues?.memoryLeaks || []; const totalRooms = Object.keys(result.rooms || {}).length; let risk = 'low'; if (memoryLeaks.length > 0 || totalRooms > 100) { risk = 'medium'; } if (memoryLeaks.length > 3 || totalRooms > 500) { risk = 'high'; } return { risk, leakCount: memoryLeaks.length, roomCount: totalRooms }; } assessNetworkEfficiency(result) { const eventFlow = result.eventFlow || []; if (eventFlow.length === 0) return 1; // Simple efficiency calculation based on bidirectional communication const bidirectionalFlows = eventFlow.filter(flow => eventFlow.some(response => response.trigger === flow.event && response.from !== flow.from ) ); return bidirectionalFlows.length / eventFlow.length; } generateRecommendations(result) { const recommendations = []; // Security recommendations const securityAnalysis = this.enhanceSecurityAnalysis(result); if (securityAnalysis.authenticationStatus === 'missing') { recommendations.push({ priority: 'high', category: 'security', title: 'Implement Authentication', description: 'Add authentication to WebSocket connections to prevent unauthorized access' }); } if (securityAnalysis.inputValidation === 'insufficient') { recommendations.push({ priority: 'high', category: 'security', title: 'Add Input Validation', description: 'Validate all incoming WebSocket event payloads' }); } // Performance recommendations const performanceAnalysis = this.enhancePerformanceAnalysis(result); if (performanceAnalysis.eventPerformance.heavyEvents > 0) { recommendations.push({ priority: 'medium', category: 'performance', title: 'Optimize Event Payloads', description: `${performanceAnalysis.eventPerformance.heavyEvents} events have large payloads. Consider compression or data structure optimization.` }); } if (performanceAnalysis.memoryUsage.risk === 'high') { recommendations.push({ priority: 'high', category: 'performance', title: 'Address Memory Leaks', description: 'Implement proper cleanup for WebSocket connections and room management' }); } // Architecture recommendations const architecture = this.analyzeArchitecture(result); if (architecture.scalabilityAssessment.level === 'large') { recommendations.push({ priority: 'medium', category: 'architecture', title: 'Plan for Scalability', description: 'Consider horizontal scaling solutions and Redis adapter for Socket.io' }); } return recommendations; } } module.exports = WebSocketAnalyzer;