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