UNPKG

smart-ast-analyzer

Version:

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

962 lines (816 loc) 31.3 kB
const fs = require('fs').promises; const path = require('path'); class PerformanceProfiler { constructor(framework, options = {}) { this.framework = framework; this.options = options; this.performancePatterns = this.loadPerformancePatterns(); this.bundleAnalyzer = new BundleAnalyzer(); this.runtimeAnalyzer = new RuntimeAnalyzer(); this.memoryAnalyzer = new MemoryAnalyzer(); } async profile(aiResult, files, projectInfo) { if (!aiResult || aiResult.error) { return this.createEmptyResult(aiResult?.error); } const profile = { ...aiResult, metadata: this.generateMetadata(files, projectInfo), bundleAnalysis: await this.bundleAnalyzer.analyze(files, projectInfo), runtimeAnalysis: await this.runtimeAnalyzer.analyze(files, this.framework), memoryAnalysis: await this.memoryAnalyzer.analyze(files, this.framework), performanceMetrics: this.calculatePerformanceMetrics(aiResult, files), bottlenecks: this.identifyBottlenecks(aiResult, files), optimizationOpportunities: this.findOptimizationOpportunities(aiResult, files), performanceScore: this.calculatePerformanceScore(aiResult), recommendations: this.generatePerformanceRecommendations(aiResult, files) }; return profile; } createEmptyResult(error) { return { bundle: { estimatedSize: 'unknown', largeDependencies: [], codeSplitting: [] }, rendering: { heavyComponents: [], unnecessaryRenders: [] }, api: { slowEndpoints: [], overfetching: [] }, memory: { potentialLeaks: [] }, optimization: { immediate: [], shortTerm: [], longTerm: [] }, metrics: { complexity: 'unknown', maintainability: 'unknown', performance: 'unknown' }, performanceScore: 0, metadata: { analysisDate: new Date().toISOString(), framework: this.framework, error: error }, recommendations: [ 'Performance analysis could not be performed', 'Check if your project has performance-critical files', 'Verify that the analysis service is working correctly' ] }; } generateMetadata(files, projectInfo) { return { totalFiles: files.length, analysisDate: new Date().toISOString(), framework: this.framework, projectSize: this.calculateProjectSize(files), complexity: this.estimateComplexity(files), dependencies: projectInfo.dependencies?.total || 0, analysisScope: this.determineAnalysisScope(files) }; } calculateProjectSize(files) { const totalSize = files.reduce((sum, file) => sum + (file.size || 0), 0); const totalLines = files.reduce((sum, file) => sum + (file.lines || 0), 0); return { bytes: totalSize, lines: totalLines, files: files.length, avgFileSize: files.length > 0 ? Math.round(totalSize / files.length) : 0, avgLinesPerFile: files.length > 0 ? Math.round(totalLines / files.length) : 0 }; } estimateComplexity(files) { let complexityScore = 0; files.forEach(file => { const content = file.content.toLowerCase(); // Count complexity indicators const indicators = { loops: (content.match(/for\s*\(|while\s*\(|\.map\s*\(|\.forEach\s*\(/g) || []).length, conditionals: (content.match(/if\s*\(|switch\s*\(|\?\s*:/g) || []).length, functions: (content.match(/function\s+\w+|=>\s*\{|async\s+/g) || []).length, classes: (content.match(/class\s+\w+|\.prototype\./g) || []).length, promises: (content.match(/\.then\s*\(|await\s+|Promise\./g) || []).length, callbacks: (content.match(/callback|cb\s*\(|\)\s*=>/g) || []).length }; complexityScore += indicators.loops * 3; complexityScore += indicators.conditionals * 2; complexityScore += indicators.functions * 2; complexityScore += indicators.classes * 4; complexityScore += indicators.promises * 3; complexityScore += indicators.callbacks * 3; }); if (complexityScore < 15) return 'low'; if (complexityScore < 25) return 'medium'; return 'high'; } determineAnalysisScope(files) { const scopes = { frontend: files.filter(f => f.path.includes('component') || f.path.includes('src') || f.extension === '.jsx' || f.extension === '.tsx' ).length, backend: files.filter(f => f.path.includes('api') || f.path.includes('server') || f.path.includes('route') ).length, fullstack: 0 }; scopes.fullstack = scopes.frontend > 0 && scopes.backend > 0; if (scopes.fullstack) return 'fullstack'; if (scopes.frontend > scopes.backend) return 'frontend'; if (scopes.backend > 0) return 'backend'; return 'unknown'; } calculatePerformanceMetrics(aiResult, files) { const metrics = { loadTime: this.estimateLoadTime(files), bundleSize: this.estimateBundleSize(files), renderTime: this.estimateRenderTime(aiResult), apiResponseTime: this.estimateAPIResponseTime(aiResult), memoryUsage: this.estimateMemoryUsage(files), cacheEfficiency: this.assessCacheEfficiency(files), optimizationLevel: this.assessOptimizationLevel(aiResult) }; return metrics; } estimateLoadTime(files) { // Simplified load time estimation based on file sizes and count const totalSize = files.reduce((sum, file) => sum + (file.size || 0), 0); const jsFiles = files.filter(f => f.extension === '.js' || f.extension === '.jsx').length; const cssFiles = files.filter(f => f.extension === '.css' || f.extension === '.scss').length; // Rough estimation: 1KB = 10ms load time const baseLoadTime = Math.round(totalSize / 100); // Convert bytes to approximate ms const additionalTime = jsFiles * 50 + cssFiles * 30; // Overhead per file const estimatedTime = baseLoadTime + additionalTime; return { estimated: `${estimatedTime}ms`, category: estimatedTime < 1000 ? 'fast' : estimatedTime < 3000 ? 'moderate' : 'slow', factors: { totalSize: `${Math.round(totalSize / 1024)}KB`, jsFiles: jsFiles, cssFiles: cssFiles } }; } estimateBundleSize(files) { const jsFiles = files.filter(f => ['.js', '.jsx', '.ts', '.tsx'].includes(f.extension)); const totalJSSize = jsFiles.reduce((sum, file) => sum + (file.size || 0), 0); // Estimate minified size (roughly 70% of original) const estimatedMinified = Math.round(totalJSSize * 0.7); // Estimate gzipped size (roughly 30% of minified) const estimatedGzipped = Math.round(estimatedMinified * 0.3); return { raw: `${Math.round(totalJSSize / 1024)}KB`, minified: `${Math.round(estimatedMinified / 1024)}KB`, gzipped: `${Math.round(estimatedGzipped / 1024)}KB`, category: estimatedGzipped < 50000 ? 'small' : estimatedGzipped < 200000 ? 'medium' : 'large' }; } estimateRenderTime(aiResult) { // Estimate based on component complexity const components = aiResult.rendering?.heavyComponents || []; const renderTime = components.length * 16; // Assume 16ms per complex component return { estimated: `${renderTime}ms`, category: renderTime < 16 ? 'fast' : renderTime < 50 ? 'moderate' : 'slow', heavyComponents: components.length }; } estimateAPIResponseTime(aiResult) { const slowEndpoints = aiResult.api?.slowEndpoints || []; const avgResponseTime = slowEndpoints.length > 0 ? 200 + (slowEndpoints.length * 50) : 100; return { estimated: `${avgResponseTime}ms`, category: avgResponseTime < 200 ? 'fast' : avgResponseTime < 500 ? 'moderate' : 'slow', slowEndpoints: slowEndpoints.length }; } estimateMemoryUsage(files) { // Rough memory usage estimation const totalSize = files.reduce((sum, file) => sum + (file.size || 0), 0); const estimatedMemory = totalSize * 2; // Assume 2x file size in memory return { estimated: `${Math.round(estimatedMemory / 1024)}KB`, category: estimatedMemory < 1024000 ? 'low' : estimatedMemory < 5120000 ? 'moderate' : 'high', factors: ['File parsing', 'DOM manipulation', 'Event listeners'] }; } assessCacheEfficiency(files) { // Look for caching patterns let cacheScore = 30; // Start with lower score to represent poor caching by default files.forEach(file => { const content = file.content.toLowerCase(); if (content.includes('cache') || content.includes('memoiz')) cacheScore += 15; if (content.includes('usememo') || content.includes('usecallback')) cacheScore += 20; if (content.includes('react.memo')) cacheScore += 15; if (content.includes('service worker') || content.includes('sw.js')) cacheScore += 30; }); return { score: Math.min(100, cacheScore), level: cacheScore < 50 ? 'poor' : cacheScore < 75 ? 'moderate' : 'good', recommendations: cacheScore < 75 ? ['Implement component memoization', 'Add service worker caching'] : [] }; } assessOptimizationLevel(aiResult) { let score = 0; let total = 0; // Check various optimization indicators if (aiResult.bundle?.codeSplitting?.length > 0) { score += 20; total += 20; } else { total += 20; } if (aiResult.rendering?.unnecessaryRenders?.length === 0) { score += 15; } total += 15; if (aiResult.memory?.potentialLeaks?.length === 0) { score += 15; } total += 15; const percentage = total > 0 ? Math.round((score / total) * 100) : 0; return { score: percentage, level: percentage < 40 ? 'poor' : percentage < 70 ? 'moderate' : 'good', optimizations: { codeSplitting: aiResult.bundle?.codeSplitting?.length > 0, memoization: aiResult.rendering?.unnecessaryRenders?.length === 0, memoryManagement: aiResult.memory?.potentialLeaks?.length === 0 } }; } identifyBottlenecks(aiResult, files) { const bottlenecks = []; // Bundle size bottlenecks if (aiResult.bundle?.largeDependencies?.length > 0) { bottlenecks.push({ type: 'Bundle Size', severity: 'high', description: `${aiResult.bundle.largeDependencies.length} large dependencies affecting load time`, impact: 'Load Performance', location: 'Dependencies', solution: 'Code splitting and tree shaking' }); } // Rendering bottlenecks const heavyComponents = aiResult.rendering?.heavyComponents || []; if (heavyComponents.length > 0) { bottlenecks.push({ type: 'Rendering Performance', severity: 'medium', description: `${heavyComponents.length} components causing render performance issues`, impact: 'User Experience', location: heavyComponents.map(c => c.name).join(', '), solution: 'Component memoization and virtualization' }); } // API bottlenecks const slowEndpoints = aiResult.api?.slowEndpoints || []; if (slowEndpoints.length > 0) { bottlenecks.push({ type: 'API Performance', severity: 'high', description: `${slowEndpoints.length} slow API endpoints`, impact: 'Data Loading', location: slowEndpoints.map(e => e.endpoint).join(', '), solution: 'Query optimization and caching' }); } // Memory bottlenecks const memoryLeaks = aiResult.memory?.potentialLeaks || []; if (memoryLeaks.length > 0) { bottlenecks.push({ type: 'Memory Usage', severity: 'medium', description: `${memoryLeaks.length} potential memory leaks detected`, impact: 'Long-term Performance', location: memoryLeaks.map(l => l.file).join(', '), solution: 'Proper cleanup and event listener removal' }); } return bottlenecks; } findOptimizationOpportunities(aiResult, files) { const opportunities = { immediate: [], shortTerm: [], longTerm: [] }; // Immediate optimizations const unusedCode = this.findUnusedCode(files); if (unusedCode.length > 0) { opportunities.immediate.push({ type: 'Dead Code Elimination', description: `Remove ${unusedCode.length} unused functions/components`, impact: 'Bundle Size Reduction', effort: 'low', files: unusedCode }); } // Short-term optimizations if (aiResult.bundle?.codeSplitting?.length === 0) { opportunities.shortTerm.push({ type: 'Code Splitting', description: 'Implement route-based and component-based code splitting', impact: 'Faster Initial Load', effort: 'medium', examples: ['React.lazy()', 'Dynamic imports', 'Webpack chunks'] }); } const unmemoizedComponents = this.findUnmemoizedComponents(aiResult); if (unmemoizedComponents.length > 0) { opportunities.shortTerm.push({ type: 'Component Memoization', description: `Add memoization to ${unmemoizedComponents.length} components`, impact: 'Render Performance', effort: 'medium', components: unmemoizedComponents }); } // Long-term optimizations opportunities.longTerm.push({ type: 'Service Worker Implementation', description: 'Add service worker for caching and offline functionality', impact: 'Load Performance & User Experience', effort: 'high', benefits: ['Asset caching', 'API response caching', 'Offline functionality'] }); if (this.framework === 'react' || this.framework === 'nextjs') { opportunities.longTerm.push({ type: 'Server-Side Rendering Optimization', description: 'Optimize SSR/SSG for better initial load performance', impact: 'First Contentful Paint', effort: 'high', techniques: ['Static generation', 'Incremental regeneration', 'Edge caching'] }); } return opportunities; } findUnusedCode(files) { const unused = []; files.forEach(file => { const content = file.content; // Find all function declarations using a more comprehensive regex const functionDeclarations = []; const functionMatches = content.match(/\bfunction\s+(\w+)/g); if (functionMatches) { functionMatches.forEach((match, matchIndex) => { const functionNameMatch = match.match(/function\s+(\w+)/); if (functionNameMatch) { const functionName = functionNameMatch[1]; // Count function calls (but exclude the declaration itself) const callRegex = new RegExp(`\\b${functionName}\\s*\\(`, 'g'); const allMatches = content.match(callRegex) || []; // Subtract 1 to exclude the declaration itself (function unused() contains "unused(") const actualCalls = allMatches.length - (content.includes(`function ${functionName}(`) ? 1 : 0); if (actualCalls === 0) { unused.push({ type: 'function', name: functionName, file: file.path, line: matchIndex + 1 // Approximate line number }); } } }); } }); return unused.slice(0, 10); } findUnmemoizedComponents(aiResult) { const unmemoized = []; if (aiResult.rendering?.heavyComponents) { aiResult.rendering.heavyComponents.forEach(comp => { if (!comp.performance?.memoized) { unmemoized.push(comp.name); } }); } return unmemoized; } calculatePerformanceScore(aiResult) { let score = 100; // Deduct for bundle issues const largeDeps = aiResult.bundle?.largeDependencies?.length || 0; score -= largeDeps * 5; // Deduct for rendering issues const heavyComponents = aiResult.rendering?.heavyComponents?.length || 0; score -= heavyComponents * 10; // Deduct for API issues const slowEndpoints = aiResult.api?.slowEndpoints?.length || 0; score -= slowEndpoints * 15; // Deduct for memory issues const memoryLeaks = aiResult.memory?.potentialLeaks?.length || 0; score -= memoryLeaks * 8; // Add points for optimizations if (aiResult.bundle?.codeSplitting?.length > 0) score += 10; if (aiResult.optimization?.immediate?.length === 0) score += 5; return Math.max(0, Math.min(100, score)); } generatePerformanceRecommendations(aiResult, files) { const recommendations = []; const performanceScore = this.calculatePerformanceScore(aiResult); // Overall performance recommendations if (performanceScore < 50) { recommendations.push({ priority: 'high', category: 'performance', title: 'Critical Performance Issues Detected', description: `Performance score is ${performanceScore}/100. Immediate optimization needed.`, actions: ['Bundle size optimization', 'Component performance audit', 'API response optimization'] }); } // Bundle size recommendations const largeDeps = aiResult.bundle?.largeDependencies || []; if (largeDeps.length > 0) { recommendations.push({ priority: 'high', category: 'bundle', title: 'Optimize Bundle Size', description: `${largeDeps.length} large dependencies detected. Consider alternatives or code splitting.`, packages: largeDeps.map(dep => dep.name) }); } // Rendering recommendations const heavyComponents = aiResult.rendering?.heavyComponents || []; if (heavyComponents.length > 0) { recommendations.push({ priority: 'medium', category: 'rendering', title: 'Optimize Component Performance', description: `${heavyComponents.length} components need performance optimization.`, techniques: ['React.memo', 'useMemo', 'useCallback', 'Component splitting'] }); } // Code splitting recommendations if (!aiResult.bundle?.codeSplitting?.length) { recommendations.push({ priority: 'medium', category: 'optimization', title: 'Implement Code Splitting', description: 'Break down the bundle into smaller chunks for faster loading.', implementation: this.framework === 'react' ? 'Use React.lazy() and Suspense' : 'Dynamic imports' }); } // Memory optimization recommendations const memoryLeaks = aiResult.memory?.potentialLeaks || []; if (memoryLeaks.length > 0) { recommendations.push({ priority: 'medium', category: 'memory', title: 'Fix Memory Leaks', description: `${memoryLeaks.length} potential memory leaks detected.`, solutions: ['Cleanup event listeners', 'Cancel async operations', 'Clear intervals/timeouts'] }); } return recommendations; } loadPerformancePatterns() { return { antiPatterns: [ 'document.write', 'eval(', 'with(', 'innerHTML +=', 'sync XMLHttpRequest', 'blocking CSS', 'render-blocking JavaScript' ], optimizationPatterns: [ 'React.memo', 'useMemo', 'useCallback', 'lazy loading', 'code splitting', 'service worker', 'intersection observer' ] }; } } // Helper classes for specific analysis types class BundleAnalyzer { async analyze(files, projectInfo) { const jsFiles = files.filter(f => ['.js', '.jsx', '.ts', '.tsx'].includes(f.extension)); const totalSize = jsFiles.reduce((sum, file) => sum + (file.size || 0), 0); return { totalSize: `${Math.round(totalSize / 1024)}KB`, fileCount: jsFiles.length, largeDependencies: this.findLargeDependencies(projectInfo), codeSplitting: this.assessCodeSplitting(files), treeShakingt: this.assessTreeShaking(files) }; } findLargeDependencies(projectInfo) { const knownLarge = ['react', 'vue', 'angular', 'lodash', 'moment', 'd3', 'three']; const dependencies = Object.keys(projectInfo.dependencies?.dependencies || {}); return dependencies .filter(dep => knownLarge.includes(dep)) .map(dep => ({ name: dep, estimatedSize: this.estimatePackageSize(dep), alternatives: this.suggestAlternatives(dep) })); } estimatePackageSize(packageName) { const sizes = { 'react': '42KB', 'vue': '32KB', 'angular': '130KB', 'lodash': '70KB', 'moment': '67KB', 'd3': '250KB', 'three': '580KB' }; return sizes[packageName] || 'Unknown'; } suggestAlternatives(packageName) { const alternatives = { 'lodash': ['Native ES6 methods', 'lodash-es'], 'moment': ['date-fns', 'dayjs'], 'three': ['babylonjs (if compatible)'] }; return alternatives[packageName] || []; } assessCodeSplitting(files) { const splittingIndicators = []; files.forEach(file => { const content = file.content; // Check for React.lazy if (content.includes('React.lazy')) { splittingIndicators.push({ file: file.path, type: 'React.lazy' }); } // Check for dynamic imports (excluding React.lazy import) const dynamicImportRegex = /import\s*\(/g; const lazyImportRegex = /React\.lazy\s*\(\s*\(\s*\)\s*=>\s*import\s*\(/g; const dynamicImports = (content.match(dynamicImportRegex) || []).length; const lazyImports = (content.match(lazyImportRegex) || []).length; if (dynamicImports > lazyImports) { splittingIndicators.push({ file: file.path, type: 'Dynamic import' }); } // Check for loadable components if (content.includes('loadable')) { splittingIndicators.push({ file: file.path, type: 'Loadable components' }); } }); return splittingIndicators; } detectSplittingType(content) { if (content.includes('React.lazy')) return 'React.lazy'; if (content.includes('import(')) return 'Dynamic import'; if (content.includes('loadable')) return 'Loadable components'; return 'Unknown'; } assessTreeShaking(files) { // Simple tree shaking assessment const moduleImports = files.filter(file => file.content.includes('import {') || file.content.includes('from ') ).length; const wildcardImports = files.filter(file => file.content.includes('import *') ).length; return { moduleImports, wildcardImports, efficiency: wildcardImports === 0 ? 'good' : 'needs improvement' }; } } class RuntimeAnalyzer { async analyze(files, framework) { return { renderingPatterns: this.analyzeRenderingPatterns(files, framework), eventHandlers: this.analyzeEventHandlers(files), asyncOperations: this.analyzeAsyncOperations(files), domManipulation: this.analyzeDOMManipulation(files) }; } analyzeRenderingPatterns(files, framework) { const patterns = { react: this.analyzeReactRendering(files), vue: this.analyzeVueRendering(files), angular: this.analyzeAngularRendering(files) }; return patterns[framework] || {}; } analyzeReactRendering(files) { const reactFiles = files.filter(f => f.extension === '.jsx' || f.extension === '.tsx' || f.content.includes('import React') ); return { components: reactFiles.length, memoizedComponents: reactFiles.filter(f => f.content.includes('React.memo')).length, hooksUsage: reactFiles.filter(f => f.content.includes('use')).length, unnecessaryRenders: this.findUnnecessaryRenders(reactFiles) }; } findUnnecessaryRenders(files) { const issues = []; files.forEach(file => { const lines = file.content.split('\n'); lines.forEach((line, index) => { // Look for potential unnecessary render triggers if (line.includes('setState') && line.includes('prevState')) { issues.push({ file: file.path, line: index + 1, issue: 'Potential unnecessary state update', suggestion: 'Check if state actually changes' }); } }); }); return issues; } analyzeVueRendering(files) { // Vue-specific rendering analysis return {}; } analyzeAngularRendering(files) { // Angular-specific rendering analysis return {}; } analyzeEventHandlers(files) { let handlerCount = 0; let cleanupCount = 0; files.forEach(file => { handlerCount += (file.content.match(/addEventListener|onClick|onSubmit/g) || []).length; cleanupCount += (file.content.match(/removeEventListener|cleanup|unmount/g) || []).length; }); return { totalHandlers: handlerCount, withCleanup: cleanupCount, cleanupRatio: handlerCount > 0 ? Math.round((cleanupCount / handlerCount) * 100) : 0 }; } analyzeAsyncOperations(files) { let promiseCount = 0; let errorHandlingCount = 0; files.forEach(file => { promiseCount += (file.content.match(/async|await|\.then\(|Promise\./g) || []).length; errorHandlingCount += (file.content.match(/catch\(|try\s*\{|\\.catch\(/g) || []).length; }); return { totalAsync: promiseCount, withErrorHandling: errorHandlingCount, errorHandlingRatio: promiseCount > 0 ? Math.round((errorHandlingCount / promiseCount) * 100) : 0 }; } analyzeDOMManipulation(files) { let manipulationCount = 0; let directManipulation = 0; files.forEach(file => { manipulationCount += (file.content.match(/querySelector|getElementById|innerHTML|appendChild/g) || []).length; directManipulation += (file.content.match(/document\.|window\./g) || []).length; }); return { totalManipulation: manipulationCount, directManipulation: directManipulation, recommendation: directManipulation > 0 ? 'Consider using framework patterns instead of direct DOM manipulation' : null }; } } class MemoryAnalyzer { async analyze(files, framework) { return { potentialLeaks: this.findPotentialLeaks(files), memoryPatterns: this.analyzeMemoryPatterns(files), garbageCollection: this.analyzeGCPatterns(files), recommendations: this.generateMemoryRecommendations(files) }; } findPotentialLeaks(files) { const leaks = []; files.forEach(file => { const lines = file.content.split('\n'); lines.forEach((line, index) => { // Look for common memory leak patterns if (line.includes('setInterval') && !file.content.includes('clearInterval')) { leaks.push({ type: 'interval', file: file.path, line: index + 1, description: 'setInterval without clearInterval', solution: 'Add clearInterval in cleanup' }); } if (line.includes('setTimeout') && !file.content.includes('clearTimeout')) { leaks.push({ type: 'timeout', file: file.path, line: index + 1, description: 'setTimeout without clearTimeout in cleanup', solution: 'Clear timeout in component cleanup' }); } if (line.includes('addEventListener') && !file.content.includes('removeEventListener')) { leaks.push({ type: 'event', file: file.path, line: index + 1, description: 'Event listener without cleanup', solution: 'Remove event listeners in cleanup' }); } }); }); return leaks; } analyzeMemoryPatterns(files) { return { closures: this.countClosures(files), circularReferences: this.findCircularReferences(files), largeObjects: this.findLargeObjects(files) }; } countClosures(files) { let closureCount = 0; files.forEach(file => { const content = file.content; // Count nested functions (function within function) const nestedFunctions = (content.match(/function[^{]*{[^}]*function/g) || []).length; // Count nested arrow functions (=> within =>) const nestedArrows = (content.match(/=>[^}]*=>/g) || []).length; // Count returns of functions/arrows const returnedFunctions = (content.match(/return\s+(function|\(\)\s*=>|.*=>)/g) || []).length; closureCount += nestedFunctions + nestedArrows + returnedFunctions; }); return { count: closureCount, risk: closureCount > 20 ? 'high' : closureCount > 10 ? 'medium' : 'low' }; } findCircularReferences(files) { // Simplified circular reference detection const references = []; files.forEach(file => { if (file.content.includes('this.') && file.content.includes('parent')) { references.push({ file: file.path, type: 'parent-child reference', risk: 'medium' }); } }); return references; } findLargeObjects(files) { const largeObjects = []; files.forEach(file => { const lines = file.content.split('\n'); lines.forEach((line, index) => { if (line.includes('new Array(') || line.includes('Array.from')) { const sizeMatch = line.match(/\d+/); if (sizeMatch && parseInt(sizeMatch[0]) > 1000) { largeObjects.push({ file: file.path, line: index + 1, type: 'large array', size: sizeMatch[0] }); } } }); }); return largeObjects; } analyzeGCPatterns(files) { return { explicitCleanup: files.filter(f => f.content.includes('cleanup') || f.content.includes('dispose') || f.content.includes('destroy') ).length, recommendation: 'Implement explicit cleanup in component lifecycle methods' }; } generateMemoryRecommendations(files) { const recommendations = []; const leaks = this.findPotentialLeaks(files); if (leaks.length > 0) { recommendations.push({ priority: 'high', title: 'Fix Memory Leaks', description: `${leaks.length} potential memory leaks detected`, actions: ['Add proper cleanup', 'Remove event listeners', 'Clear intervals/timeouts'] }); } const patterns = this.analyzeMemoryPatterns(files); if (patterns.closures.risk === 'high') { recommendations.push({ priority: 'medium', title: 'Optimize Closure Usage', description: 'High number of closures may impact memory usage', actions: ['Review closure necessity', 'Use WeakMap for private data'] }); } return recommendations; } } module.exports = PerformanceProfiler;