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