@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
JavaScript
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 };