UNPKG

mira-consciousness

Version:

Memory & Intelligence Retention Archive - Preserving The Spark

216 lines 8.92 kB
/** * Memory Leak Detector * Identifies potential memory leak patterns in code */ import fs from 'fs-extra'; import * as path from 'path'; import { glob } from 'glob'; export class MemoryLeakDetector { projectRoot; constructor(projectRoot) { this.projectRoot = projectRoot; } async analyze() { const issues = []; await Promise.all([ this.checkMemoryLeakPatterns(issues), this.checkAsyncPatterns(issues) ]); return issues; } async checkMemoryLeakPatterns(issues) { const memoryLeakPatterns = [ { pattern: /setInterval\s*\([^}]*\}[^,]*\)/, type: 'uncleaned_interval', message: 'setInterval without clearInterval may cause memory leaks', impact: 'medium' }, { pattern: /addEventListener\s*\([^}]*\}[^,]*\)/, type: 'uncleaned_listener', message: 'Event listener without removeEventListener may cause memory leaks', impact: 'medium' }, { pattern: /new\s+Array\s*\(\s*\d{6,}\s*\)/, type: 'large_array_allocation', message: 'Large array allocation detected - consider lazy loading', impact: 'high' }, { pattern: /global\s*\[\s*['"][^'"]*['"]\s*\]\s*=|window\s*\[\s*['"][^'"]*['"]\s*\]\s*=/, type: 'global_variable_leak', message: 'Global variable assignment may cause memory leaks', impact: 'medium' }, { pattern: /\.on\s*\([^)]+\)\s*(?!.*\.off\s*\()/, type: 'uncleaned_event_emitter', message: 'Event emitter without corresponding off/removeListener', impact: 'medium' }, { pattern: /new\s+Buffer\s*\(\s*\d{8,}\s*\)/, type: 'large_buffer_allocation', message: 'Large buffer allocation detected', impact: 'high' }, { pattern: /\$\s*\(\s*[^)]+\)\s*\.data\s*\(/, type: 'jquery_data_leak', message: 'jQuery data() can cause memory leaks if not cleaned up', impact: 'medium' }, { pattern: /requestAnimationFrame\s*\([^}]*\}[^,]*\)/, type: 'uncleaned_animation_frame', message: 'requestAnimationFrame without cancelAnimationFrame may cause leaks', impact: 'medium' } ]; try { const files = await glob('**/*.{ts,js,jsx,tsx}', { cwd: this.projectRoot, ignore: ['node_modules/**', '.git/**', 'dist/**'] }); for (const file of files) { const filePath = path.join(this.projectRoot, file); try { const content = await fs.readFile(filePath, 'utf-8'); const lines = content.split('\n'); lines.forEach((line, index) => { for (const pattern of memoryLeakPatterns) { if (pattern.pattern.test(line)) { issues.push({ file, line: index + 1, type: pattern.type, message: pattern.message, impact: pattern.impact, recommendation: 'Review memory management and cleanup procedures' }); } } }); // Check for cleanup patterns this.checkForMissingCleanup(content, file, issues); } catch (error) { // Skip files that can't be read } } } catch (error) { // Skip if glob fails } } async checkAsyncPatterns(issues) { const asyncPatterns = [ { pattern: /await\s+.*await\s+.*await/, type: 'sequential_awaits', message: 'Sequential awaits detected - consider Promise.all() for parallel execution', impact: 'medium' }, { pattern: /\.then\s*\(\s*[^}]*\.then\s*\(\s*[^}]*\.then/, type: 'promise_chain', message: 'Long promise chain detected - consider async/await', impact: 'low' }, { pattern: /new\s+Promise\s*\(\s*\(\s*resolve\s*\)\s*=>\s*{\s*resolve\s*\(/, type: 'unnecessary_promise', message: 'Unnecessary Promise wrapper detected', impact: 'low' }, { pattern: /setTimeout\s*\(\s*[^,]+,\s*0\s*\)/, type: 'zero_timeout', message: 'setTimeout with 0 delay - consider setImmediate or queueMicrotask', impact: 'low' }, { pattern: /Promise\s*\.\s*all\s*\(\s*\[\s*\]\s*\)/, type: 'empty_promise_all', message: 'Empty Promise.all() is unnecessary', impact: 'low' }, { pattern: /for\s*\([^)]+\)\s*{[^}]*await\s+/, type: 'await_in_loop', message: 'Await inside loop - consider Promise.all() for parallel execution', impact: 'medium' } ]; try { const files = await glob('**/*.{ts,js,jsx,tsx}', { cwd: this.projectRoot, ignore: ['node_modules/**', '.git/**', 'dist/**'] }); for (const file of files) { const filePath = path.join(this.projectRoot, file); try { const content = await fs.readFile(filePath, 'utf-8'); const lines = content.split('\n'); lines.forEach((line, index) => { for (const pattern of asyncPatterns) { if (pattern.pattern.test(line)) { issues.push({ file, line: index + 1, type: pattern.type, message: pattern.message, impact: pattern.impact, recommendation: 'Optimize async patterns for better performance' }); } } }); } catch (error) { // Skip files that can't be read } } } catch (error) { // Skip if glob fails } } checkForMissingCleanup(content, file, issues) { // Check React useEffect cleanup const useEffectPattern = /useEffect\s*\(\s*\(\s*\)\s*=>\s*{([^}]+)}/g; let match; while ((match = useEffectPattern.exec(content)) !== null) { const effectBody = match[1]; if ((effectBody.includes('setInterval') || effectBody.includes('setTimeout') || effectBody.includes('addEventListener')) && !effectBody.includes('return')) { const line = content.substring(0, match.index).split('\n').length; issues.push({ file, line, type: 'missing_useeffect_cleanup', message: 'useEffect with side effects but no cleanup function', impact: 'high', recommendation: 'Add a cleanup function to useEffect' }); } } // Check for constructor/destructor pairs if (content.includes('constructor') && !content.includes('componentWillUnmount') && !content.includes('ngOnDestroy') && !content.includes('dispose') && !content.includes('cleanup') && !content.includes('destroy')) { if (content.includes('setInterval') || content.includes('addEventListener')) { issues.push({ file, type: 'missing_cleanup_method', message: 'Class with resource allocation but no apparent cleanup method', impact: 'medium', recommendation: 'Add appropriate cleanup/destroy method' }); } } } } //# sourceMappingURL=MemoryLeakDetector.js.map