mira-consciousness
Version:
Memory & Intelligence Retention Archive - Preserving The Spark
216 lines • 8.92 kB
JavaScript
/**
* 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