UNPKG

@neurolint/cli

Version:

NeuroLint CLI - Deterministic code fixing for TypeScript, JavaScript, React, and Next.js with 8-layer architecture including Security Forensics, Next.js 16, React Compiler, and Turbopack support

631 lines (551 loc) 17.1 kB
/** * NeuroLint Shared Core * * Unified interface for CLI, VS Code, and Web App platforms. * Provides rule engine, configuration management, and analytics. * * Copyright (c) 2025 NeuroLint * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ const ruleEngine = require('./rule-engine'); const configManager = require('./config-manager'); const analytics = require('./analytics'); const path = require('path'); /** * Smart Layer Selector for analyzing and recommending layers * This provides the intelligent analysis that rule analysis expects */ class SmartLayerSelector { static analyzeAndRecommend(code, filePath) { const issues = []; const ext = path.extname(filePath || ''); try { // Use AST-based analysis for more accurate detection const ASTTransformer = require('../ast-transformer'); const transformer = new ASTTransformer(); const astIssues = transformer.analyzeCode(code, { filename: filePath }); // Convert AST issues to layer recommendations astIssues.forEach(issue => { issues.push({ layer: issue.layer, reason: issue.message, confidence: 0.9, location: issue.location }); }); } catch (error) { // Fallback to regex-based detection if AST parsing fails issues.push(...this.fallbackAnalysis(code, filePath)); } return { detectedIssues: issues, recommendedLayers: [...new Set(issues.map(i => i.layer))].sort(), reasons: issues.map(i => i.reason), confidence: issues.reduce((acc, i) => acc + i.confidence, 0) / issues.length || 0 }; } static fallbackAnalysis(code, filePath) { const issues = []; const ext = path.extname(filePath || ''); // Layer 1: Configuration issues if (code.includes('tsconfig.json') || code.includes('next.config.js')) { issues.push({ layer: 1, reason: 'Configuration file detected', confidence: 0.8, location: { line: 1, column: 1 } }); } // Layer 2: HTML entities if (code.includes('"') || code.includes('&') || code.includes('<') || code.includes('>')) { issues.push({ layer: 2, reason: 'HTML entities detected', confidence: 0.9, location: { line: 1, column: 1 } }); } // Layer 3: React component issues if (ext === '.tsx' || ext === '.jsx' || code.includes('React') || code.includes('useState')) { if (code.includes('.map(') && !code.includes('key=')) { issues.push({ layer: 3, reason: 'Missing key props in React lists', confidence: 0.9, location: { line: 1, column: 1 } }); } } // Layer 4: SSR safety if (code.includes('window.') || code.includes('document.') || code.includes('localStorage')) { issues.push({ layer: 4, reason: 'Client-side APIs detected', confidence: 0.8, location: { line: 1, column: 1 } }); } // Layer 5: Next.js App Router if (code.includes('use client') || code.includes('use server')) { issues.push({ layer: 5, reason: 'App Router directives detected', confidence: 0.7, location: { line: 1, column: 1 } }); } // Layer 6: Testing and accessibility if (code.includes('test') || code.includes('spec') || code.includes('aria-')) { issues.push({ layer: 6, reason: 'Testing or accessibility patterns detected', confidence: 0.6, location: { line: 1, column: 1 } }); } return issues; } } /** * Main NeuroLint Core class */ class NeuroLintCore { constructor() { this.ruleEngine = ruleEngine; this.configManager = configManager; this.analytics = analytics; this.initialized = false; } /** * Initialize the core with configuration */ async initialize(options = {}) { try { // Load configuration await this.configManager.loadConfig(options.configPath); // Initialize analytics await this.analytics.loadAnalytics(); this.initialized = true; // Track initialization this.analytics.trackCommand('initialize', { platform: options.platform || 'cli', success: true }); return true; } catch (error) { // Track error without console.log this.analytics.trackCommand('initialize', { platform: options.platform || 'cli', success: false, error: error.message }); return false; } } /** * Analyze code and return issues */ async analyze(code, options = {}) { if (!this.initialized) { await this.initialize(options); } const startTime = Date.now(); try { // Get platform-specific configuration const platformConfig = this.configManager.getPlatformConfig(options.platform || 'cli'); // Merge options with configuration const analysisOptions = { ...platformConfig, ...options, layers: options.layers || this.configManager.getEnabledLayers() }; // Use SmartLayerSelector for intelligent analysis const smartAnalysis = SmartLayerSelector.analyzeAndRecommend(code, options.filename); // Perform rule engine analysis const ruleResult = await this.ruleEngine.analyze(code, analysisOptions); // Convert SmartLayerSelector issues to proper format const smartIssues = smartAnalysis.detectedIssues.map(issue => ({ type: `layer-${issue.layer}-issue`, severity: 'medium', description: issue.reason, fixedByLayer: issue.layer, line: issue.location?.line, column: issue.location?.column, ruleId: `smart-layer-${issue.layer}` })); // Combine results with SmartLayerSelector recommendations const result = { ...ruleResult, // Merge issues from both rule engine and SmartLayerSelector issues: [...(ruleResult.issues || []), ...smartIssues], summary: { ...ruleResult.summary, recommendedLayers: smartAnalysis.recommendedLayers, confidence: smartAnalysis.confidence, reasoning: smartAnalysis.reasons }, detectedIssues: smartAnalysis.detectedIssues, // Add properties that SmartLayerSelector provides hasModernConfig: smartAnalysis.detectedIssues.some(issue => issue.layer === 1), hasHtmlEntities: smartAnalysis.detectedIssues.some(issue => issue.layer === 2), hasAppRouter: smartAnalysis.detectedIssues.some(issue => issue.layer === 5) }; // Track analytics this.analytics.trackAnalysis({ files: [options.filename || 'unknown'], issues: result.issues, executionTime: Date.now() - startTime, layers: analysisOptions.layers, platform: options.platform || 'cli' }); // Calculate quality metrics if (result.issues.length > 0) { this.analytics.calculateQualityScore(result.issues, 1); this.analytics.calculateModernizationProgress(result.summary.issuesByLayer); this.analytics.calculateTechnicalDebt(result.summary.issuesByLayer); } return result; } catch (error) { // Track error this.analytics.trackAnalysis({ files: [options.filename || 'unknown'], issues: [], executionTime: Date.now() - startTime, layers: options.layers || [], platform: options.platform || 'cli' }); throw error; } } /** * Get configuration */ getConfig(key = null) { if (key) { return this.configManager.get(key); } return this.configManager.config; } /** * Set configuration */ setConfig(key, value) { this.configManager.set(key, value); } /** * Save configuration */ async saveConfig(config = null, configPath = null) { return await this.configManager.saveConfig(config, configPath); } /** * Get analytics report */ getAnalyticsReport(options = {}) { return this.analytics.generateReport(options); } /** * Save analytics data */ async saveAnalytics() { return await this.analytics.saveAnalytics(); } /** * Export analytics */ exportAnalytics(format = 'json') { return this.analytics.exportAnalytics(format); } /** * Get rule engine */ getRuleEngine() { return this.ruleEngine; } /** * Get configuration manager */ getConfigManager() { return this.configManager; } /** * Get analytics */ getAnalytics() { return this.analytics; } /** * Validate configuration */ validateConfig(config = null) { return this.configManager.validateConfig(config); } /** * Get platform-specific configuration */ getPlatformConfig(platform) { return this.configManager.getPlatformConfig(platform); } /** * Create default configuration */ createDefaultConfig() { return this.configManager.createDefaultConfig(); } /** * Export configuration */ exportConfig() { return this.configManager.exportConfig(); } /** * Import configuration */ async importConfig(configData) { return await this.configManager.importConfig(configData); } /** * Add custom rule */ addRule(name, rule) { this.ruleEngine.addRule(name, rule); } /** * Get all rules */ getRules() { return Array.from(this.ruleEngine.rules.entries()).map(([name, rule]) => ({ name, description: rule.description, layer: rule.layer })); } /** * Check if layer is enabled */ isLayerEnabled(layerId) { return this.configManager.isLayerEnabled(layerId); } /** * Get enabled layers */ getEnabledLayers() { return this.configManager.getEnabledLayers(); } /** * Get include patterns */ getIncludePatterns() { return this.configManager.getIncludePatterns(); } /** * Get exclude patterns */ getExcludePatterns() { return this.configManager.getExcludePatterns(); } /** * Get team preferences */ getTeamPrefs() { return this.configManager.getTeamPrefs(); } /** * Track user activity */ trackUser(userId, action) { this.analytics.trackUser(userId, action); } /** * Track command usage */ trackCommand(command, options = {}) { this.analytics.trackCommand(command, options); } /** * Apply fixes to code using the 8-layer transformation system * Delegates to fix-master.js executeLayers for proper orchestration with: * - Layer dependency ordering * - Rollback capabilities (for CLI platform) * - Unified error handling * - Validation between layers * * For VS Code: Uses dryRun=true since VS Code manages its own undo/redo stack * For CLI: Supports full backup/rollback via fix-master BackupManager */ async applyFixes(code, issues, options = {}) { if (!this.initialized) { await this.initialize(options); } const startTime = Date.now(); try { const { layers = [1, 2, 3, 4, 5, 6, 7, 8], filename = 'unknown', verbose = false, platform = 'cli', strictTs = false, apiRoutes = false, ecommerce = false, nextjs155 = false, noDeps = false } = options; // For VS Code platform, always use dryRun=true since VS Code manages undo/redo // For CLI platform, use the provided dryRun option for full backup support const dryRun = platform === 'vscode' ? true : (options.dryRun || false); // Track fix attempt this.analytics.trackCommand('applyFixes', { platform, layers, issueCount: issues?.length || 0 }); // Delegate to fix-master.js executeLayers for proper orchestration // This ensures same layer ordering, dependency resolution, and validation as CLI const fixMaster = require('../fix-master'); const result = await fixMaster.executeLayers(code, layers, { dryRun, verbose, filePath: filename, strictTs, apiRoutes, ecommerce, nextjs155, noDeps }); // Convert fix-master result format to shared-core format const appliedFixes = []; let totalChanges = 0; if (result.results && Array.isArray(result.results)) { for (const layerResult of result.results) { // Count changes from successful layers const changes = layerResult.changes || layerResult.changeCount || 0; if (layerResult.success && changes > 0) { totalChanges += typeof changes === 'number' ? changes : changes.length || 0; if (Array.isArray(layerResult.changes)) { appliedFixes.push(...layerResult.changes.map(change => ({ ...change, layer: layerResult.layer }))); } else { appliedFixes.push({ layer: layerResult.layer, description: `Layer ${layerResult.layer} applied ${changes} fix(es)`, location: { line: 1, column: 1 } }); } } } } // Derive success from fix-master's result (successfulLayers > 0) const success = result.success === true || (result.successfulLayers && result.successfulLayers > 0); // Track successful fix this.analytics.trackCommand('applyFixes', { platform, success, fixCount: totalChanges, executionTime: Date.now() - startTime }); // Collect errors from failed layers const errors = result.results?.filter(r => !r.success && r.error).map(r => ({ layer: r.layer, error: r.error })); return { success, code: result.finalCode || code, originalCode: code, appliedFixes, totalFixes: totalChanges, successfulLayers: result.successfulLayers || 0, errors: errors && errors.length > 0 ? errors : undefined, metadata: { platform, layers: layers.sort((a, b) => a - b), executionTime: Date.now() - startTime, dryRun } }; } catch (error) { // Track error this.analytics.trackCommand('applyFixes', { platform: options.platform || 'cli', success: false, error: error.message, executionTime: Date.now() - startTime }); return { success: false, code, originalCode: code, appliedFixes: [], error: error.message }; } } /** * Shutdown and cleanup */ async shutdown() { try { // Save analytics data await this.analytics.saveAnalytics(); // Track shutdown this.analytics.trackCommand('shutdown', { platform: 'cli', success: true }); this.initialized = false; return true; } catch (error) { // Track error without console.log this.analytics.trackCommand('shutdown', { platform: 'cli', success: false, error: error.message }); return false; } } } // Create and export singleton instance const neurolintCore = new NeuroLintCore(); // Export individual modules for direct access module.exports = { // Main core instance core: neurolintCore, // Individual modules ruleEngine, configManager, analytics, // Smart Layer Selector SmartLayerSelector, // Convenience methods analyze: (code, options) => neurolintCore.analyze(code, options), applyFixes: (code, issues, options) => neurolintCore.applyFixes(code, issues, options), analyzeAndRecommend: (code, filePath) => SmartLayerSelector.analyzeAndRecommend(code, filePath), getConfig: (key) => neurolintCore.getConfig(key), setConfig: (key, value) => neurolintCore.setConfig(key, value), getAnalyticsReport: (options) => neurolintCore.getAnalyticsReport(options), trackCommand: (command, options) => neurolintCore.trackCommand(command, options), trackUser: (userId, action) => neurolintCore.trackUser(userId, action), // Version info (dynamically read from package.json) get version() { try { const pkg = require('../package.json'); return pkg.version; } catch { return '1.4.0'; } }, description: 'NeuroLint Shared Core - Unified code modernization engine' };