UNPKG

@neurolint/cli

Version:

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

458 lines (401 loc) 12.6 kB
const { parse } = require('@babel/parser'); const traverse = require('@babel/traverse').default; const generate = require('@babel/generator').default; const t = require('@babel/types'); const { PluginSystem } = require('./plugin-system'); /** * Plugin-aware AST Engine * Extends the enhanced AST engine with plugin support */ class PluginAwareEngine { constructor(config = {}) { this.pluginSystem = new PluginSystem(); this.cache = new Map(); this.config = { enablePlugins: true, maxPluginExecutionTime: 30000, ...config }; this.transformationStats = { missingKeys: 0, propTypes: 0, imports: 0, hooks: 0, pluginTransformations: 0 }; } /** * Initialize the engine with plugins */ async initialize() { if (this.config.enablePlugins) { await this.pluginSystem.initialize(); // Listen to plugin events this.pluginSystem.on('plugin-registered', (data) => { console.log(`Plugin registered: ${data.plugin.name} v${data.plugin.version}`); }); this.pluginSystem.on('transformer-executed', (data) => { this.transformationStats.pluginTransformations++; }); } } /** * Parse source code with plugin pre-processing */ async parseCode(code, filename = 'unknown.tsx') { const cacheKey = `${filename}:${code.length}`; if (this.cache.has(cacheKey)) { return this.cache.get(cacheKey); } try { // Execute pre-parse hooks const preParseContext = { code, filename, options: { sourceType: 'module', allowImportExportEverywhere: true, allowReturnOutsideFunction: true, plugins: [ 'jsx', 'typescript', 'decorators-legacy', 'dynamicImport', 'nullishCoalescingOperator', 'optionalChaining', 'exportDefaultFrom', 'exportNamespaceFrom', 'asyncGenerators', 'functionBind', 'objectRestSpread', 'classProperties' ] } }; if (this.config.enablePlugins) { await this.pluginSystem.executeHook('before-parse', preParseContext); } const ast = parse(preParseContext.code, preParseContext.options); // Execute post-parse hooks if (this.config.enablePlugins) { const postParseContext = { ast, filename, code: preParseContext.code }; await this.pluginSystem.executeHook('after-parse', postParseContext); } this.cache.set(cacheKey, ast); return ast; } catch (error) { console.warn(`[AST] Parse failed for ${filename}:`, error.message); return null; } } /** * Transform code with layer-specific processing and plugins */ async transformWithLayer(ast, layerId, context = {}) { const transformations = []; try { // Execute before-layer hooks if (this.config.enablePlugins) { const beforeLayerContext = { layerId, ast, context, transformations: [] }; await this.pluginSystem.executeHook('before-layer', beforeLayerContext); transformations.push(...beforeLayerContext.transformations); } // Execute core layer transformations const coreTransformations = await this.executeCoreLayerTransformations(ast, layerId, context); transformations.push(...coreTransformations); // Execute plugin transformers for this layer if (this.config.enablePlugins) { const pluginResults = await this.pluginSystem.executeLayerTransformers(layerId, ast, context); pluginResults.forEach(result => { if (result.result && Array.isArray(result.result)) { transformations.push(...result.result.map(t => ({ ...t, pluginId: result.pluginId, source: 'plugin' }))); } }); } // Execute after-layer hooks if (this.config.enablePlugins) { const afterLayerContext = { layerId, ast, context, transformations }; await this.pluginSystem.executeHook('after-layer', afterLayerContext); } return transformations; } catch (error) { console.error(`Layer ${layerId} transformation failed:`, error.message); return []; } } /** * Execute core layer transformations (existing NeuroLint logic) */ async executeCoreLayerTransformations(ast, layerId, context) { switch (layerId) { case 1: return this.transformLayer1Configuration(ast, context); case 2: return this.transformLayer2Content(ast, context); case 3: return this.transformLayer3Components(ast, context); case 4: return this.transformLayer4Hydration(ast, context); case 5: return this.transformLayer5AppRouter(ast, context); case 6: return this.transformLayer6Quality(ast, context); default: return []; } } /** * Validate code with plugins */ async validateCode(code, context = {}) { const issues = []; try { // Execute before-validation hooks if (this.config.enablePlugins) { const beforeValidationContext = { code, context, issues: [] }; await this.pluginSystem.executeHook('before-validation', beforeValidationContext); issues.push(...beforeValidationContext.issues); } // Execute core validations const coreIssues = await this.executeCoreValidations(code, context); issues.push(...coreIssues); // Execute plugin validators if (this.config.enablePlugins) { const validatorNames = Array.from(this.pluginSystem.validators.keys()); for (const validatorName of validatorNames) { try { const validatorResult = await this.pluginSystem.executeValidator(validatorName, code, context); if (validatorResult && Array.isArray(validatorResult)) { issues.push(...validatorResult.map(issue => ({ ...issue, source: 'plugin', validator: validatorName }))); } } catch (error) { console.warn(`Plugin validator ${validatorName} failed:`, error.message); } } } // Execute after-validation hooks if (this.config.enablePlugins) { const afterValidationContext = { code, context, issues }; await this.pluginSystem.executeHook('after-validation', afterValidationContext); } return issues; } catch (error) { console.error('Code validation failed:', error.message); return []; } } /** * Apply transformations with plugin support */ async applyTransformations(ast, transformations) { try { // Execute before-apply hooks if (this.config.enablePlugins) { const beforeApplyContext = { ast, transformations }; await this.pluginSystem.executeHook('before-apply', beforeApplyContext); } // Group transformations by source const coreTransformations = transformations.filter(t => t.source !== 'plugin'); const pluginTransformations = transformations.filter(t => t.source === 'plugin'); // Apply core transformations first coreTransformations.forEach(transformation => { if (transformation.action && typeof transformation.action === 'function') { try { transformation.action(); } catch (error) { console.warn(`Failed to apply transformation ${transformation.type}:`, error.message); } } }); // Apply plugin transformations pluginTransformations.forEach(transformation => { if (transformation.action && typeof transformation.action === 'function') { try { transformation.action(); } catch (error) { console.warn(`Failed to apply plugin transformation ${transformation.type}:`, error.message); } } }); // Execute after-apply hooks if (this.config.enablePlugins) { const afterApplyContext = { ast, transformations, appliedCount: transformations.length }; await this.pluginSystem.executeHook('after-apply', afterApplyContext); } return true; } catch (error) { console.error('Failed to apply transformations:', error.message); return false; } } /** * Generate code with plugin post-processing */ async generateCode(ast, context = {}) { try { // Execute before-generate hooks if (this.config.enablePlugins) { const beforeGenerateContext = { ast, context }; await this.pluginSystem.executeHook('before-generate', beforeGenerateContext); } const result = generate(ast, { retainLines: false, comments: true, compact: false }); // Execute after-generate hooks if (this.config.enablePlugins) { const afterGenerateContext = { ast, context, code: result.code, map: result.map }; await this.pluginSystem.executeHook('after-generate', afterGenerateContext); } return result.code; } catch (error) { console.error('Code generation failed:', error.message); return null; } } /** * Register a plugin (convenience method) */ async registerPlugin(plugin) { if (!this.config.enablePlugins) { throw new Error('Plugins are disabled'); } return this.pluginSystem.registerPlugin(plugin); } /** * Get plugin statistics */ getPluginStatistics() { if (!this.config.enablePlugins) { return { pluginsEnabled: false }; } return { pluginsEnabled: true, ...this.pluginSystem.getStatistics(), transformationStats: this.transformationStats }; } // Core transformation methods (simplified versions) transformLayer1Configuration(ast, context) { // Configuration-related transformations return []; } transformLayer2Content(ast, context) { // Content cleanup transformations return []; } transformLayer3Components(ast, context) { // Component-related transformations (missing keys, PropTypes, etc.) const transformations = []; // Add missing keys transformation traverse(ast, { JSXElement(path) { const isInMapCallback = this._isInMapCallback(path); const hasKeyProp = this._hasKeyProperty(path.node); if (isInMapCallback && !hasKeyProp) { transformations.push({ type: 'missing-key', location: path.node.loc, action: () => { const keyAttr = t.jsxAttribute( t.jsxIdentifier('key'), t.jsxExpressionContainer(t.identifier('index')) ); path.node.openingElement.attributes.push(keyAttr); }, description: 'Added key prop to JSX element in map operation', source: 'core' }); } } }); return transformations; } transformLayer4Hydration(ast, context) { // Hydration safety transformations return []; } transformLayer5AppRouter(ast, context) { // App Router optimization transformations return []; } transformLayer6Quality(ast, context) { // Quality enforcement transformations return []; } async executeCoreValidations(code, context) { // Core validation logic return []; } // Helper methods _isInMapCallback(path) { let parent = path.parent; while (parent) { if (t.isCallExpression(parent) && t.isMemberExpression(parent.callee) && t.isIdentifier(parent.callee.property) && (parent.callee.property.name === 'map' || parent.callee.property.name === 'forEach')) { return true; } parent = parent.parent; } return false; } _hasKeyProperty(node) { if (!node.openingElement || !node.openingElement.attributes) { return false; } return node.openingElement.attributes.some(attr => t.isJSXAttribute(attr) && t.isJSXIdentifier(attr.name) && attr.name.name === 'key' ); } } module.exports = { PluginAwareEngine };