UNPKG

mira-consciousness

Version:

Memory & Intelligence Retention Archive - Preserving The Spark

443 lines 17 kB
/** * AST-Based Import Analyzer * Replaces the naive string-based import detection that caused the emergency * Uses proper Abstract Syntax Tree parsing for accurate import/usage analysis */ import * as fs from 'fs-extra'; import * as path from 'path'; export class ASTImportAnalyzer { projectRoot; constructor(projectRoot) { this.projectRoot = projectRoot; } /** * Analyze imports in a file using AST parsing */ async analyzeFileImports(filePath) { try { const fullPath = path.join(this.projectRoot, filePath); const content = await fs.readFile(fullPath, 'utf-8'); // Determine if it's TypeScript or JavaScript const isTypeScript = filePath.endsWith('.ts') || filePath.endsWith('.tsx'); // Parse imports and usage const imports = await this.parseImports(content, isTypeScript); const usage = await this.parseUsage(content, isTypeScript); // Find unused imports const unusedImports = []; for (const imp of imports) { if (!this.isImportUsed(imp, usage, content)) { unusedImports.push({ name: imp.name, file: filePath, line: imp.line, source: imp.source, type: imp.type, confidence: this.calculateConfidence(imp, content, usage) }); } } return unusedImports; } catch (error) { console.warn(`Failed to analyze imports in ${filePath}:`, error); return []; } } /** * Parse import statements using AST */ async parseImports(content, isTypeScript) { const imports = []; try { // Use babel parser for robust AST parsing const parser = await this.getBabelParser(); const ast = parser.parse(content, { sourceType: 'module', allowImportExportEverywhere: true, allowReturnOutsideFunction: true, plugins: [ 'jsx', 'typescript', 'decorators-legacy', 'classProperties', 'asyncGenerators', 'functionBind', 'exportDefaultFrom', 'exportNamespaceFrom', 'dynamicImport', 'nullishCoalescingOperator', 'optionalChaining', 'importMeta' ] }); // Walk the AST to find import statements this.walkAST(ast, (node) => { if (node.type === 'ImportDeclaration') { this.extractImportsFromDeclaration(node, imports); } }); // Also parse dynamic imports this.walkAST(ast, (node) => { if (node.type === 'CallExpression' && node.callee.type === 'Import') { imports.push({ name: 'dynamic', localName: 'dynamic', source: node.arguments[0]?.value || 'unknown', line: node.loc?.start?.line || 0, type: 'side-effect' }); } }); } catch (error) { // Fallback to regex-based parsing if AST fails console.warn('AST parsing failed, falling back to regex:', error); return this.parseImportsRegex(content); } return imports; } /** * Parse usage of imported identifiers using AST */ async parseUsage(content, isTypeScript) { const usage = []; try { const parser = await this.getBabelParser(); const ast = parser.parse(content, { sourceType: 'module', allowImportExportEverywhere: true, allowReturnOutsideFunction: true, plugins: [ 'jsx', 'typescript', 'decorators-legacy', 'classProperties', 'asyncGenerators', 'functionBind', 'exportDefaultFrom', 'exportNamespaceFrom', 'dynamicImport', 'nullishCoalescingOperator', 'optionalChaining', 'importMeta' ] }); this.walkAST(ast, (node) => { // Find identifier usage if (node.type === 'Identifier' && node.loc) { // Determine usage context let context = 'assignment'; if (node.parent?.type === 'CallExpression' && node.parent.callee === node) { context = 'call'; } else if (node.parent?.type === 'MemberExpression' && node.parent.object === node) { context = 'property'; } else if (node.parent?.type === 'ObjectPattern' || node.parent?.type === 'ArrayPattern') { context = 'destructuring'; } else if (node.parent?.type === 'FunctionDeclaration' || node.parent?.type === 'FunctionExpression') { context = 'parameter'; } usage.push({ name: node.name, line: node.loc.start.line, column: node.loc.start.column, context }); } }); } catch (error) { console.warn('AST usage parsing failed:', error); } return usage; } /** * Check if an import is actually used */ isImportUsed(imp, usage, content) { // Skip side-effect imports (always considered used) if (imp.type === 'side-effect') { return true; } // Check AST-based usage first const astUsage = usage.filter(u => u.name === imp.localName || u.name === imp.name).length; // If found in AST, it's used if (astUsage > 1) { // More than just the import declaration return true; } // Additional checks for complex patterns AST might miss // 1. Check for property access patterns if (this.hasPropertyAccess(imp, content)) { return true; } // 2. Check for destructuring usage if (this.hasDestructuringUsage(imp, content)) { return true; } // 3. Check for template literal usage if (this.hasTemplateLiteralUsage(imp, content)) { return true; } // 4. Check for dynamic property access if (this.hasDynamicPropertyAccess(imp, content)) { return true; } // 5. Check for usage in comments/JSDoc (might indicate planned usage) if (this.hasCommentUsage(imp, content)) { return true; } // 6. Check for aliased usage if (this.hasAliasedUsage(imp, content)) { return true; } return false; } /** * Calculate confidence score for unused import detection */ calculateConfidence(imp, content, usage) { let confidence = 0.9; // Base confidence // Reduce confidence for complex patterns if (imp.name.length > 15) { confidence -= 0.1; } // Reduce confidence for critical modules const criticalModules = [ 'fs', 'fs-extra', 'path', 'child_process', 'util', 'events', 'UnifiedMIRADaemonV2', 'MagicalContextPreparationSystem', 'ConsciousnessSeed' ]; if (criticalModules.some(mod => imp.source.includes(mod) || imp.name.includes(mod))) { confidence -= 0.2; } // Reduce confidence if mentioned in comments if (this.hasCommentUsage(imp, content)) { confidence -= 0.3; } // Reduce confidence for namespace imports (often used dynamically) if (imp.type === 'namespace') { confidence -= 0.15; } // Reduce confidence for TypeScript type imports if (imp.isTypeOnly) { confidence -= 0.1; } // Reduce confidence if other imports from same source are used const sourceUsage = usage.filter(u => content.includes(`from '${imp.source}'`) || content.includes(`from "${imp.source}"`)); if (sourceUsage.length > 1) { confidence -= 0.1; } return Math.max(0.1, Math.min(0.95, confidence)); } // Helper methods for complex usage pattern detection hasPropertyAccess(imp, content) { const patterns = [ new RegExp(`\\b${imp.localName}\\.[a-zA-Z_$][a-zA-Z0-9_$]*`, 'g'), new RegExp(`\\b${imp.localName}\\[['"'][^'"]+['"]\\]`, 'g') ]; return patterns.some(pattern => pattern.test(content)); } hasDestructuringUsage(imp, content) { const patterns = [ new RegExp(`const\\s*{[^}]*${imp.localName}[^}]*}\\s*=`, 'g'), new RegExp(`let\\s*{[^}]*${imp.localName}[^}]*}\\s*=`, 'g'), new RegExp(`var\\s*{[^}]*${imp.localName}[^}]*}\\s*=`, 'g'), new RegExp(`{[^}]*${imp.localName}[^}]*}\\s*=`, 'g') ]; return patterns.some(pattern => pattern.test(content)); } hasTemplateLiteralUsage(imp, content) { const pattern = new RegExp(`\\$\\{[^}]*\\b${imp.localName}\\b[^}]*\\}`, 'g'); return pattern.test(content); } hasDynamicPropertyAccess(imp, content) { const patterns = [ new RegExp(`\\b${imp.localName}\\[\\w+\\]`, 'g'), new RegExp(`\\b${imp.localName}\\[.*\\$\\{.*\\}.*\\]`, 'g') ]; return patterns.some(pattern => pattern.test(content)); } hasCommentUsage(imp, content) { const patterns = [ new RegExp(`//.*\\b${imp.localName}\\b`, 'g'), new RegExp(`/\\*[^*]*\\b${imp.localName}\\b[^*]*\\*/`, 'g'), new RegExp(`\\*\\s*@param[^\\n]*\\b${imp.localName}\\b`, 'g'), new RegExp(`\\*\\s*@returns?[^\\n]*\\b${imp.localName}\\b`, 'g') ]; return patterns.some(pattern => pattern.test(content)); } hasAliasedUsage(imp, content) { // Check for renamed imports being used if (imp.localName !== imp.name) { const aliasPattern = new RegExp(`\\b${imp.localName}\\b`, 'g'); const matches = content.match(aliasPattern); return matches ? matches.length > 1 : false; // More than just the import } return false; } // AST walking helper walkAST(node, callback) { if (!node || typeof node !== 'object') return; // Add parent reference for context if (node.type) { callback(node); } // Recursively walk all object properties Object.keys(node).forEach(key => { const child = node[key]; if (Array.isArray(child)) { child.forEach(item => { if (item && typeof item === 'object') { item.parent = node; this.walkAST(item, callback); } }); } else if (child && typeof child === 'object') { child.parent = node; this.walkAST(child, callback); } }); } // Extract imports from AST import declaration extractImportsFromDeclaration(node, imports) { const source = node.source.value; const line = node.loc?.start?.line || 0; if (node.specifiers) { for (const spec of node.specifiers) { let importInfo; if (spec.type === 'ImportDefaultSpecifier') { importInfo = { name: 'default', localName: spec.local.name, source, line, type: 'default', isTypeOnly: node.importKind === 'type' }; } else if (spec.type === 'ImportSpecifier') { importInfo = { name: spec.imported.name, localName: spec.local.name, source, line, type: 'named', isTypeOnly: node.importKind === 'type' || spec.importKind === 'type' }; } else if (spec.type === 'ImportNamespaceSpecifier') { importInfo = { name: '*', localName: spec.local.name, source, line, type: 'namespace', isTypeOnly: node.importKind === 'type' }; } else { continue; } imports.push(importInfo); } } else { // Side-effect import imports.push({ name: 'side-effect', localName: 'side-effect', source, line, type: 'side-effect' }); } } // Fallback regex-based parsing parseImportsRegex(content) { const imports = []; const lines = content.split('\n'); lines.forEach((line, index) => { // ES6 imports const es6Match = line.match(/import\s+(.+?)\s+from\s+['"]([^'"]+)['"]/); if (es6Match) { const importClause = es6Match[1].trim(); const source = es6Match[2]; if (importClause.startsWith('{') && importClause.endsWith('}')) { // Named imports const names = importClause.slice(1, -1).split(',').map(n => n.trim()); names.forEach(name => { const [imported, local] = name.split(' as ').map(n => n.trim()); imports.push({ name: imported, localName: local || imported, source, line: index + 1, type: 'named' }); }); } else if (importClause.startsWith('* as ')) { // Namespace import const localName = importClause.substring(5).trim(); imports.push({ name: '*', localName, source, line: index + 1, type: 'namespace' }); } else { // Default import imports.push({ name: 'default', localName: importClause, source, line: index + 1, type: 'default' }); } } // CommonJS require const requireMatch = line.match(/(?:const|let|var)\s+(.+?)\s*=\s*require\s*\(\s*['"]([^'"]+)['"]\s*\)/); if (requireMatch) { imports.push({ name: 'default', localName: requireMatch[1].trim(), source: requireMatch[2], line: index + 1, type: 'default' }); } }); return imports; } // Get babel parser dynamically async getBabelParser() { try { // Try to import @babel/parser const babel = await import('@babel/parser'); return babel; } catch (error) { // Fallback to typescript parser if available try { const ts = await import('typescript'); return { parse: (code, options) => { const sourceFile = ts.createSourceFile('temp.ts', code, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS); return sourceFile; } }; } catch (tsError) { throw new Error('No suitable parser available. Install @babel/parser or typescript.'); } } } } //# sourceMappingURL=ASTImportAnalyzer.js.map