UNPKG

@neurolint/cli

Version:

NeuroLint CLI for React/Next.js modernization with advanced 6-layer orchestration and intelligent AST transformations

609 lines (505 loc) 17.1 kB
const { parse } = require('@babel/parser'); const traverse = require('@babel/traverse').default; const generate = require('@babel/generator').default; const t = require('@babel/types'); const { CacheManager } = require('./cache-manager'); const { PluginAwareEngine } = require('./plugin-aware-engine'); /** * Cache-aware AST Engine * Extends the plugin-aware engine with intelligent caching for maximum performance */ class CachedASTEngine extends PluginAwareEngine { constructor(config = {}) { super(config); this.cacheManager = new CacheManager({ cacheDir: config.cacheDir, maxCacheSize: config.maxCacheSize || 50 * 1024 * 1024, // 50MB maxAge: config.maxAge || 24 * 60 * 60 * 1000, // 24 hours compressionEnabled: config.compressionEnabled !== false, ...config.cache }); this.performanceStats = { parseTime: 0, transformTime: 0, cacheHits: 0, cacheMisses: 0, totalOperations: 0 }; this.config = { ...this.config, enableCaching: config.enableCaching !== false, cacheAST: config.cacheAST !== false, cacheTransformations: config.cacheTransformations !== false, cacheAnalysisResults: config.cacheAnalysisResults !== false, cachePluginResults: config.cachePluginResults !== false, ...config }; } /** * Initialize engine with cache manager */ async initialize() { await super.initialize(); if (this.config.enableCaching) { await this.cacheManager.initialize(); // Listen to cache events this.cacheManager.on('cache-hit', () => { this.performanceStats.cacheHits++; }); this.cacheManager.on('cache-miss', () => { this.performanceStats.cacheMisses++; }); this.cacheManager.on('analysis-cache-hit', (data) => { this.emit('cache-hit', { type: 'analysis', ...data }); }); this.cacheManager.on('ast-cache-hit', (data) => { this.emit('cache-hit', { type: 'ast', ...data }); }); } } /** * Parse code with caching support */ async parseCode(code, filename = 'unknown.tsx') { const startTime = Date.now(); try { // Check cache first if (this.config.enableCaching && this.config.cacheAST) { const parseOptions = this.getParseOptions(); const cachedAST = await this.cacheManager.getCachedAST(code, filename, parseOptions); if (cachedAST) { this.performanceStats.parseTime += Date.now() - startTime; this.emit('ast-cache-hit', { filename }); return cachedAST; } } // Parse with parent implementation const ast = await super.parseCode(code, filename); if (ast && this.config.enableCaching && this.config.cacheAST) { // Cache the parsed AST const parseOptions = this.getParseOptions(); await this.cacheManager.cacheAST(code, filename, ast, parseOptions); } this.performanceStats.parseTime += Date.now() - startTime; return ast; } catch (error) { this.performanceStats.parseTime += Date.now() - startTime; throw error; } } /** * Analyze file with comprehensive caching */ async analyzeFile(filePath, code, layers = 'auto', options = {}) { const startTime = Date.now(); this.performanceStats.totalOperations++; try { // Check for cached analysis result if (this.config.enableCaching && this.config.cacheAnalysisResults) { const cachedResult = await this.cacheManager.getCachedAnalysisResult( filePath, code, layers, options ); if (cachedResult) { this.emit('analysis-cache-hit', { filePath, layers }); return cachedResult; } } // Perform analysis const ast = await this.parseCode(code, filePath); if (!ast) { throw new Error('Failed to parse code'); } const result = await this.performAnalysis(ast, layers, { ...options, filePath, code }); // Cache the result if (this.config.enableCaching && this.config.cacheAnalysisResults) { await this.cacheManager.cacheAnalysisResult(filePath, code, layers, result, options); } this.emit('analysis-completed', { filePath, layers, duration: Date.now() - startTime, cached: false }); return result; } catch (error) { this.emit('analysis-failed', { filePath, error: error.message }); throw error; } } /** * Transform with layer-specific caching */ async transformWithLayer(ast, layerId, context = {}) { const startTime = Date.now(); try { // Check cache for transformations if (this.config.enableCaching && this.config.cacheTransformations) { const cachedTransformations = await this.cacheManager.getCachedTransformations( ast, layerId, context ); if (cachedTransformations) { this.emit('transformations-cache-hit', { layerId }); return cachedTransformations; } } // Perform transformations const transformations = await super.transformWithLayer(ast, layerId, context); // Cache the transformations if (this.config.enableCaching && this.config.cacheTransformations) { await this.cacheManager.cacheTransformations(ast, layerId, transformations, context); } this.performanceStats.transformTime += Date.now() - startTime; return transformations; } catch (error) { this.performanceStats.transformTime += Date.now() - startTime; throw error; } } /** * Execute plugin with caching */ async executePluginTransformer(pluginId, transformerName, ast, context = {}) { if (!this.config.enableCaching || !this.config.cachePluginResults) { return super.executePluginTransformer(pluginId, transformerName, ast, context); } const input = { ast: this.cacheManager.generateASTHash(ast), context }; const cacheKey = `${pluginId}_${transformerName}`; // Check cache const cachedResult = await this.cacheManager.getCachedPluginResult(cacheKey, input); if (cachedResult) { this.emit('plugin-cache-hit', { pluginId, transformerName }); return cachedResult; } // Execute plugin const result = await super.executePluginTransformer(pluginId, transformerName, ast, context); // Cache result await this.cacheManager.cachePluginResult(cacheKey, input, result); return result; } /** * Batch analysis with intelligent caching and parallelization */ async analyzeBatch(files, layers = 'auto', options = {}) { const batchStartTime = Date.now(); const batchSize = options.batchSize || 10; const maxConcurrency = options.maxConcurrency || 3; // Separate cached and uncached files const { cachedFiles, uncachedFiles } = await this.separateCachedFiles(files, layers, options); this.emit('batch-analysis-started', { totalFiles: files.length, cachedFiles: cachedFiles.length, uncachedFiles: uncachedFiles.length }); const results = new Map(); // Get cached results immediately for (const { filePath, result } of cachedFiles) { results.set(filePath, result); } // Process uncached files in batches if (uncachedFiles.length > 0) { const batches = this.createBatches(uncachedFiles, batchSize); for (const batch of batches) { const batchPromises = batch.map(async ({ filePath, code }) => { try { const result = await this.analyzeFile(filePath, code, layers, options); return { filePath, result, success: true }; } catch (error) { return { filePath, error: error.message, success: false }; } }); // Control concurrency const batchResults = await this.processWithConcurrency(batchPromises, maxConcurrency); // Collect results batchResults.forEach(({ filePath, result, error, success }) => { if (success) { results.set(filePath, result); } else { results.set(filePath, { error, success: false }); } }); this.emit('batch-progress', { processed: results.size, total: files.length, percentage: Math.round((results.size / files.length) * 100) }); } } const batchDuration = Date.now() - batchStartTime; this.emit('batch-analysis-completed', { totalFiles: files.length, successfulFiles: Array.from(results.values()).filter(r => r.success !== false).length, duration: batchDuration, cacheHitRate: cachedFiles.length / files.length }); return results; } /** * Separate files into cached and uncached */ async separateCachedFiles(files, layers, options) { const cachedFiles = []; const uncachedFiles = []; for (const { filePath, code } of files) { try { const cachedResult = await this.cacheManager.getCachedAnalysisResult( filePath, code, layers, options ); if (cachedResult) { cachedFiles.push({ filePath, result: cachedResult }); } else { uncachedFiles.push({ filePath, code }); } } catch (error) { // If cache check fails, treat as uncached uncachedFiles.push({ filePath, code }); } } return { cachedFiles, uncachedFiles }; } /** * Create batches from array */ createBatches(items, batchSize) { const batches = []; for (let i = 0; i < items.length; i += batchSize) { batches.push(items.slice(i, i + batchSize)); } return batches; } /** * Process promises with controlled concurrency */ async processWithConcurrency(promises, maxConcurrency) { const results = []; const executing = []; for (const promise of promises) { const p = Promise.resolve(promise).then(result => { executing.splice(executing.indexOf(p), 1); return result; }); results.push(p); executing.push(p); if (executing.length >= maxConcurrency) { await Promise.race(executing); } } return Promise.all(results); } /** * Perform complete analysis pipeline */ async performAnalysis(ast, layers, context = {}) { const analysisStartTime = Date.now(); // Determine layers to process const targetLayers = this.determineLayers(layers, ast, context); // Execute hooks await this.pluginSystem.executeHook('before-analysis', { ast, layers: targetLayers, context }); const allTransformations = []; const layerResults = []; // Process each layer for (const layerId of targetLayers) { const layerStartTime = Date.now(); try { const transformations = await this.transformWithLayer(ast, layerId, context); allTransformations.push(...transformations); layerResults.push({ layerId, success: true, transformationCount: transformations.length, executionTime: Date.now() - layerStartTime }); this.emit('layer-completed', { layerId, transformationCount: transformations.length, executionTime: Date.now() - layerStartTime }); } catch (error) { layerResults.push({ layerId, success: false, error: error.message, executionTime: Date.now() - layerStartTime }); this.emit('layer-failed', { layerId, error: error.message }); } } // Validate code const issues = await this.validateCode(context.code || '', context); // Execute hooks await this.pluginSystem.executeHook('after-analysis', { ast, layers: targetLayers, transformations: allTransformations, issues, context }); const analysisResult = { success: layerResults.every(r => r.success), layers: layerResults, transformations: allTransformations, issues, metadata: { processingTime: Date.now() - analysisStartTime, targetLayers, cacheEnabled: this.config.enableCaching, pluginSystemEnabled: this.config.enablePlugins } }; return analysisResult; } /** * Determine which layers to process */ determineLayers(layers, ast, context) { if (Array.isArray(layers)) { return layers.filter(l => l >= 1 && l <= 6); } if (layers === 'all') { return [1, 2, 3, 4, 5, 6]; } if (layers === 'auto') { // Intelligent layer detection based on code analysis return this.detectRequiredLayers(ast, context); } // Default layers return [1, 2, 3, 4]; } /** * Detect required layers based on code content */ detectRequiredLayers(ast, context) { const requiredLayers = [1]; // Always include configuration layer try { let hasReactComponents = false; let hasSSRCode = false; let hasNextJSCode = false; let hasTestCode = false; traverse(ast, { ImportDeclaration(path) { const source = path.node.source.value; if (source === 'react' || source.startsWith('react/')) { hasReactComponents = true; } if (source === 'next' || source.startsWith('next/')) { hasNextJSCode = true; } if (source.includes('test') || source.includes('jest') || source.includes('vitest')) { hasTestCode = true; } }, JSXElement() { hasReactComponents = true; }, CallExpression(path) { if (path.node.callee.name === 'useEffect' || path.node.callee.name === 'useState') { hasReactComponents = true; } if (path.node.callee.name === 'getServerSideProps' || path.node.callee.name === 'getStaticProps') { hasSSRCode = true; } } }); // Add layer 2 for content cleanup requiredLayers.push(2); // Add layer 3 for React components if (hasReactComponents) { requiredLayers.push(3); } // Add layer 4 for SSR/hydration if (hasSSRCode || hasReactComponents) { requiredLayers.push(4); } // Add layer 5 for Next.js optimizations if (hasNextJSCode) { requiredLayers.push(5); } // Add layer 6 for testing enhancements if (hasTestCode) { requiredLayers.push(6); } } catch (error) { console.warn('Layer detection failed, using defaults:', error.message); return [1, 2, 3, 4]; } return requiredLayers; } /** * Get parse options for caching */ getParseOptions() { return { sourceType: 'module', allowImportExportEverywhere: true, allowReturnOutsideFunction: true, plugins: [ 'jsx', 'typescript', 'decorators-legacy', 'dynamicImport', 'nullishCoalescingOperator', 'optionalChaining', 'exportDefaultFrom', 'exportNamespaceFrom', 'asyncGenerators', 'functionBind', 'objectRestSpread', 'classProperties' ] }; } /** * Cache management methods */ async clearCache() { if (this.config.enableCaching) { await this.cacheManager.clearCache(); this.emit('cache-cleared'); } } async warmupCache(filePaths) { if (this.config.enableCaching) { await this.cacheManager.warmupCache(filePaths); this.emit('cache-warmed-up', { fileCount: filePaths.length }); } } getCacheStatistics() { if (!this.config.enableCaching) { return { enabled: false }; } return { enabled: true, ...this.cacheManager.getStatistics(), performance: this.performanceStats }; } getPerformanceStatistics() { const stats = this.performanceStats; const total = stats.cacheHits + stats.cacheMisses; return { ...stats, cacheHitRate: total > 0 ? (stats.cacheHits / total) : 0, avgParseTime: stats.totalOperations > 0 ? (stats.parseTime / stats.totalOperations) : 0, avgTransformTime: stats.totalOperations > 0 ? (stats.transformTime / stats.totalOperations) : 0 }; } /** * Cleanup resources */ async cleanup() { if (this.config.enableCaching) { await this.cacheManager.cleanup(); } this.emit('cleanup'); } } module.exports = { CachedASTEngine };