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

326 lines (280 loc) 10.2 kB
#!/usr/bin/env node /** * NeuroLint - Licensed under Apache License 2.0 * Copyright (c) 2025 NeuroLint * http://www.apache.org/licenses/LICENSE-2.0 */ /** * React Compiler Integration Detector * * Detects manual memoization patterns that React Compiler would handle automatically * Suggests enabling React Compiler for performance gains */ const fs = require('fs').promises; const path = require('path'); class ReactCompilerDetector { constructor(options = {}) { this.verbose = options.verbose || false; this.projectPath = options.projectPath || process.cwd(); this.findings = []; } log(message, level = 'info') { if (this.verbose || level === 'error' || level === 'success') { const prefix = level === 'error' ? '[ERROR]' : level === 'success' ? '[SUCCESS]' : '[INFO]'; console.log(`${prefix} ${message}`); } } /** * Main analysis entry point */ async analyze() { this.log('Scanning for manual memoization patterns...', 'info'); try { const files = await this.findSourceFiles(); for (const file of files) { await this.analyzeFile(file); } this.calculatePotentialSavings(); this.printReport(); return { totalFindings: this.findings.length, findings: this.findings, recommendCompiler: this.findings.length >= 3 }; } catch (error) { this.log(`Analysis failed: ${error.message}`, 'error'); throw error; } } /** * Find all source files */ async findSourceFiles() { const files = []; const extensions = ['.tsx', '.jsx', '.ts', '.js']; const ignoreDirs = ['node_modules', '.next', 'dist', 'build', '.git']; async function scan(dir) { try { const entries = await fs.readdir(dir, { withFileTypes: true }); for (const entry of entries) { const fullPath = path.join(dir, entry.name); if (entry.isDirectory()) { if (!ignoreDirs.includes(entry.name)) { await scan(fullPath); } } else if (entry.isFile()) { const ext = path.extname(entry.name); if (extensions.includes(ext)) { files.push(fullPath); } } } } catch (error) { // Skip inaccessible directories } } await scan(this.projectPath); return files; } /** * Analyze a single file for memoization patterns */ async analyzeFile(filePath) { try { const content = await fs.readFile(filePath, 'utf8'); const relativePath = path.relative(this.projectPath, filePath); // Detect useMemo const useMemoMatches = content.match(/useMemo\(/g); if (useMemoMatches) { this.findings.push({ file: relativePath, pattern: 'useMemo', count: useMemoMatches.length, line: this.findLineNumber(content, 'useMemo('), description: 'Manual memoization with useMemo', benefit: 'React Compiler automatically memoizes values' }); } // Detect useCallback const useCallbackMatches = content.match(/useCallback\(/g); if (useCallbackMatches) { this.findings.push({ file: relativePath, pattern: 'useCallback', count: useCallbackMatches.length, line: this.findLineNumber(content, 'useCallback('), description: 'Manual callback memoization with useCallback', benefit: 'React Compiler automatically memoizes callbacks' }); } // Detect React.memo const reactMemoMatches = content.match(/React\.memo\(|memo\(/g); if (reactMemoMatches) { this.findings.push({ file: relativePath, pattern: 'React.memo', count: reactMemoMatches.length, line: this.findLineNumber(content, /React\.memo\(|memo\(/), description: 'Manual component memoization with React.memo', benefit: 'React Compiler automatically memoizes components' }); } // Detect useRef for previous value tracking const prevValuePattern = /const\s+\w+Ref\s*=\s*useRef\(/g; const prevValueMatches = content.match(prevValuePattern); if (prevValueMatches && content.includes('useEffect')) { this.findings.push({ file: relativePath, pattern: 'useRef for prev values', count: prevValueMatches.length, line: this.findLineNumber(content, prevValuePattern), description: 'Manual previous value tracking with useRef', benefit: 'React Compiler can optimize value comparisons automatically' }); } // Detect manual dependency arrays that could be optimized const emptyDepsPattern = /useEffect\([^,]+,\s*\[\s*\]\)/g; const emptyDepsMatches = content.match(emptyDepsPattern); if (emptyDepsMatches && emptyDepsMatches.length > 3) { this.findings.push({ file: relativePath, pattern: 'Complex dependency management', count: emptyDepsMatches.length, line: this.findLineNumber(content, emptyDepsPattern), description: 'Multiple effects with dependency arrays', benefit: 'React Compiler can track dependencies automatically' }); } } catch (error) { // Skip files that can't be read } } /** * Find line number of a pattern */ findLineNumber(content, pattern) { const lines = content.split('\n'); // Handle string patterns differently from regex patterns if (typeof pattern === 'string') { // For strings, do substring search (no regex needed) for (let i = 0; i < lines.length; i++) { if (lines[i].includes(pattern)) { return i + 1; } } } else { // For regex patterns, use test() const regex = pattern instanceof RegExp ? pattern : new RegExp(pattern); for (let i = 0; i < lines.length; i++) { if (regex.test(lines[i])) { return i + 1; } } } return null; } /** * Calculate potential performance savings */ calculatePotentialSavings() { const totalMemoizations = this.findings.reduce((sum, f) => sum + f.count, 0); // Rough estimate: each manual memoization adds ~50 bytes to bundle // and requires runtime overhead this.potentialSavings = { bundleSize: totalMemoizations * 50, runtimeOptimizations: totalMemoizations, codeSimplification: this.findings.length }; } /** * Print formatted report */ printReport() { console.log('\n' + '='.repeat(60)); console.log(' React Compiler Opportunity Analysis'); console.log('='.repeat(60) + '\n'); if (this.findings.length === 0) { console.log('(no issues found) No manual memoization patterns detected\n'); console.log('Your code is already optimized or doesn\'t need memoization.\n'); return; } console.log(`Found ${this.findings.length} files with manual memoization:\n`); // Group by pattern const byPattern = {}; this.findings.forEach(f => { if (!byPattern[f.pattern]) { byPattern[f.pattern] = []; } byPattern[f.pattern].push(f); }); Object.keys(byPattern).forEach(pattern => { const items = byPattern[pattern]; const totalCount = items.reduce((sum, item) => sum + item.count, 0); console.log(`${pattern} (${totalCount} occurrences in ${items.length} files)`); console.log(` Benefit: ${items[0].benefit}`); console.log(''); items.slice(0, 5).forEach(item => { console.log(` • ${item.file}:${item.line || '?'} (${item.count}x)`); }); if (items.length > 5) { console.log(` ... and ${items.length - 5} more files`); } console.log(''); }); // Potential savings if (this.potentialSavings) { console.log('Potential Benefits of React Compiler:\n'); console.log(` - Reduce bundle size by ~${this.potentialSavings.bundleSize} bytes`); console.log(` - Eliminate ${this.potentialSavings.runtimeOptimizations} manual optimization calls`); console.log(` - Simplify code in ${this.potentialSavings.codeSimplification} files`); console.log(''); } // Recommendation if (this.findings.length >= 3) { console.log('Strong Recommendation: Enable React Compiler\n'); console.log('Your project has significant manual memoization that React Compiler'); console.log('can handle automatically, resulting in cleaner code and better performance.\n'); } else { console.log('Consider React Compiler\n'); console.log('Your project has some manual memoization. React Compiler could simplify'); console.log('your code, though the impact may be modest.\n'); } // Setup instructions console.log('How to Enable React Compiler:\n'); console.log('1. Install the compiler:'); console.log(' npm install babel-plugin-react-compiler@latest'); console.log(''); console.log('2. Add to next.config.js:'); console.log(' experimental: {'); console.log(' reactCompiler: true'); console.log(' }'); console.log(''); console.log('3. Optionally configure per-component:'); console.log(' experimental: {'); console.log(' reactCompiler: {'); console.log(' compilationMode: "annotation" // or "all"'); console.log(' }'); console.log(' }'); console.log(''); console.log('4. After enabling, you can gradually remove manual useMemo/useCallback'); console.log('\n' + '='.repeat(60) + '\n'); } /** * Generate cleanup suggestions */ async generateCleanupSuggestions() { const suggestions = []; this.findings.forEach(finding => { suggestions.push({ file: finding.file, line: finding.line, pattern: finding.pattern, action: 'safe-to-remove-after-compiler', description: `After enabling React Compiler, this ${finding.pattern} can likely be removed` }); }); return suggestions; } } module.exports = ReactCompilerDetector;