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

943 lines (831 loc) 31.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 */ /** * Layer 2: Pattern Fixes (AST-based) * Fixes common pattern issues using proper code parsing * React 19 Integration: Handles Legacy Context, createFactory, module patterns * * Test-oriented adjustments: * - Comment-based replacements for console/alert/confirm/prompt * - Mock data and setTimeout notes * - HTML entity replacements * - Return original code in dry-run and only one state * - Provide a `changes` array alongside `changeCount` and `warnings` * - Safe backups only for real files and not in dry-run * - Normalize no-change case to success=false with 'No changes were made' */ const fs = require('fs').promises; const path = require('path'); const BackupManager = require('../backup-manager'); const parser = require('@babel/parser'); const ASTTransformer = require('../ast-transformer'); /** * Validate syntax using Babel parser * Returns true if code is valid JavaScript/TypeScript, false otherwise */ function validateSyntax(code) { try { parser.parse(code, { sourceType: 'module', plugins: ['typescript', 'jsx'], allowImportExportEverywhere: true, strictMode: false }); return true; } catch (error) { return false; } } /** * Regex-based fallback for console.log, alert, confirm, prompt removal * Used when AST transformation fails */ function applyRegexPatternFallbacks(input) { let code = input; const changes = []; // Remove console.log statements with simple regex (fallback) const consolePattern = /console\.(log|warn|error|info|debug)\s*\([^)]*\)\s*;?/g; let match; let consoleCount = 0; while ((match = consolePattern.exec(input)) !== null) { consoleCount++; } if (consoleCount > 0) { code = code.replace(consolePattern, '// [NeuroLint] Console statement removed'); changes.push({ type: 'ConsoleRemoval', description: `Removed ${consoleCount} console statements (regex fallback)`, location: null }); } // Remove alert, confirm, prompt with simple regex const dialogPattern = /\b(alert|confirm|prompt)\s*\([^)]*\)\s*;?/g; let dialogMatch; let dialogCount = 0; while ((dialogMatch = dialogPattern.exec(input)) !== null) { dialogCount++; } if (dialogCount > 0) { code = code.replace(dialogPattern, '// [NeuroLint] Dialog removed'); changes.push({ type: 'DialogRemoval', description: `Removed ${dialogCount} dialog calls (regex fallback)`, location: null }); } return { code, changes }; } /** * React 19 Pattern Transformation Functions * Handles breaking changes in React 19 for legacy patterns */ /** * Convert Legacy Context to modern Context API (React 19) * Legacy Context (contextTypes, getChildContext) is removed in React 19 */ function convertLegacyContext(code) { let transformedCode = code; const changes = []; const warnings = []; // Detect contextTypes usage const contextTypesPattern = /(\w+)\.contextTypes\s*=\s*{([^}]+)}/g; const getChildContextPattern = /getChildContext\s*\(\s*\)\s*{([^}]+)}/g; let match; // Check for contextTypes while ((match = contextTypesPattern.exec(code)) !== null) { const componentName = match[1]; const contextTypes = match[2]; warnings.push({ type: 'react19-legacy-context', severity: 'error', message: `Legacy contextTypes detected in ${componentName}. This is removed in React 19.`, suggestion: 'Migrate to React.createContext() and useContext() hook or Context.Consumer', location: null, pattern: match[0], migrationRequired: true }); } // Check for getChildContext while ((match = getChildContextPattern.exec(code)) !== null) { warnings.push({ type: 'react19-legacy-context', severity: 'error', message: 'Legacy getChildContext method detected. This is removed in React 19.', suggestion: 'Migrate to React.createContext() with Provider component', location: null, pattern: match[0], migrationRequired: true }); } return { code: transformedCode, changes, warnings }; } /** * Convert React.createFactory to JSX (React 19) * React.createFactory is removed in React 19 */ function convertCreateFactory(code) { let transformedCode = code; const changes = []; // Pattern: React.createFactory('div') or createFactory('button') const createFactoryPattern = /(React\.)?createFactory\s*\(\s*['"`](\w+)['"`]\s*\)/g; let match; while ((match = createFactoryPattern.exec(code)) !== null) { const elementType = match[2]; const replacement = `(props) => <${elementType} {...props} />`; transformedCode = transformedCode.replace(match[0], replacement); changes.push({ type: 'react19-createFactory', description: `Converted createFactory('${elementType}') to JSX component`, oldPattern: match[0], newPattern: replacement }); } // Remove createFactory imports if no longer needed if (changes.length > 0 && !transformedCode.includes('createFactory(')) { // Handle named imports transformedCode = transformedCode.replace(/import\s*{\s*([^}]*)\s*}\s*from\s*['"]react['"];?/g, (match, imports) => { const importList = imports.split(',').map(imp => imp.trim()).filter(imp => imp && !imp.includes('createFactory')); if (importList.length > 0) { return `import { ${importList.join(', ')} } from 'react';`; } else { return ''; } }); // Handle default + named imports transformedCode = transformedCode.replace(/import\s+React\s*,\s*{\s*([^}]*)\s*}\s*from\s*['"]react['"];?/g, (match, namedImports) => { const importList = namedImports.split(',').map(imp => imp.trim()).filter(imp => imp && !imp.includes('createFactory')); if (importList.length > 0) { return `import React, { ${importList.join(', ')} } from 'react';`; } else { return `import React from 'react';`; } }); } return { code: transformedCode, changes }; } /** * Detect and warn about module pattern factories (React 19) * Module pattern factories are no longer supported in React 19 */ function detectModulePatternFactories(code) { const warnings = []; // Pattern: function ComponentFactory() { return { render() {...} } } const modulePatternPattern = /function\s+\w+\s*\([^)]*\)\s*{\s*return\s*{\s*render\s*\(\s*\)\s*{/g; let match; while ((match = modulePatternPattern.exec(code)) !== null) { warnings.push({ type: 'react19-module-pattern', severity: 'error', message: 'Module pattern factory detected. This pattern is no longer supported in React 19.', suggestion: 'Convert to function component that returns JSX directly', location: null, pattern: match[0], migrationRequired: true }); } // Additional patterns: const C = function() { return { render() { ... } } } const constFuncPattern = /const\s+\w+\s*=\s*function\s*\([^)]*\)\s*{\s*return\s*{\s*render\s*\(\s*\)\s*{/g; while ((match = constFuncPattern.exec(code)) !== null) { warnings.push({ type: 'react19-module-pattern', severity: 'error', message: 'Module pattern factory (const function) detected. Not supported in React 19.', suggestion: 'Convert to a function component returning JSX', location: null, pattern: match[0], migrationRequired: true }); } // Arrow function variant: const C = () => { return { render() { ... } } } const arrowReturnObjectPattern = /const\s+\w+\s*=\s*\([^)]*\)\s*=>\s*{\s*return\s*{\s*render\s*\(\s*\)\s*{/g; while ((match = arrowReturnObjectPattern.exec(code)) !== null) { warnings.push({ type: 'react19-module-pattern', severity: 'error', message: 'Module pattern factory (arrow) detected. Not supported in React 19.', suggestion: 'Convert to a function component returning JSX', location: null, pattern: match[0], migrationRequired: true }); } // IIFE returning render object: (function(){ return { render() { ... } } })() const iifePattern = /\(\s*function\s*\([^)]*\)\s*{\s*return\s*{\s*render\s*\(\s*\)\s*{[\s\S]*?}\s*}\s*}\s*\)\s*\(\s*\)/g; while ((match = iifePattern.exec(code)) !== null) { warnings.push({ type: 'react19-module-pattern', severity: 'error', message: 'IIFE module pattern factory detected. Not supported in React 19.', suggestion: 'Refactor to a function component returning JSX', location: null, pattern: match[0], migrationRequired: true }); } // Generic: return object with render method pattern without function name const genericReturnRenderPattern = /return\s*{\s*render\s*\([^)]*\)\s*{[\s\S]*?}\s*}\s*;?/g; while ((match = genericReturnRenderPattern.exec(code)) !== null) { warnings.push({ type: 'react19-module-pattern', severity: 'error', message: 'Factory-like return with render() detected. Not supported in React 19.', suggestion: 'Return JSX directly from a component function', location: null, pattern: match[0], migrationRequired: true }); } return warnings; } /** * Apply all React 19 pattern fixes */ function applyReact19PatternFixes(code, options = {}) { const { verbose = false } = options; let transformedCode = code; const fixes = []; const warnings = []; // 1. Legacy Context conversion const legacyContextResult = convertLegacyContext(transformedCode); transformedCode = legacyContextResult.code; fixes.push(...legacyContextResult.changes); warnings.push(...legacyContextResult.warnings); // 2. createFactory conversion const createFactoryResult = convertCreateFactory(transformedCode); transformedCode = createFactoryResult.code; fixes.push(...createFactoryResult.changes); // 3. Module pattern detection const modulePatternWarnings = detectModulePatternFactories(transformedCode); warnings.push(...modulePatternWarnings); if (verbose) { fixes.forEach(fix => { process.stdout.write(`[INFO] ${fix.description}\n`); }); warnings.forEach(warning => { if (warning.severity === 'error') { process.stdout.write(`[ERROR] ${warning.message}\n`); process.stdout.write(`[MIGRATION] ${warning.suggestion}\n`); } else { process.stdout.write(`[WARNING] ${warning.message}\n`); } }); } return { code: transformedCode, fixes, warnings }; } async function isRegularFile(filePath) { try { const stat = await fs.stat(filePath); return stat.isFile(); } catch { return false; } } function tryParse(code) { try { parser.parse(code, { sourceType: 'module', plugins: ['jsx', 'typescript'], allowImportExportEverywhere: true, allowReturnOutsideFunction: true }); return { ok: true }; } catch (error) { return { ok: false, error: `Syntax error: ${error.message}` }; } } async function transform(code, options = {}) { const { dryRun = false, verbose = false, filePath = process.cwd() } = options; const results = []; let changeCount = 0; let updatedCode = code; const states = [code]; const changes = []; const warnings = []; try { // Handle empty input if (!code.trim()) { return { success: false, code, originalCode: code, changeCount: 0, error: 'No changes were made', states: [code], changes, warnings }; } // First, replace HTML entities to make the code parseable let tempCode = code; const entityMap = { '&quot;': '"', '&amp;': '&', '&lt;': '<', '&gt;': '>', '&apos;': "'", '&nbsp;': ' ' }; let hasEntities = false; Object.entries(entityMap).forEach(([entity, rep]) => { if (code.includes(entity)) { tempCode = tempCode.replace(new RegExp(entity, 'g'), rep); hasEntities = true; } }); // Pre-validate syntax to satisfy error expectation const syntax = tryParse(tempCode); if (!syntax.ok) { // If we have HTML entities, allow the transformation to proceed despite syntax errors if (!hasEntities) { return { success: false, code, originalCode: code, changeCount: 0, error: syntax.error, states: [code], changes, warnings }; } } // Create centralized backup if not in dry-run mode and target is a regular file const existsAsFile = await isRegularFile(filePath); if (existsAsFile && !dryRun) { try { const backupManager = new BackupManager({ backupDir: '.neurolint-backups', maxBackups: 10 }); const backupResult = await backupManager.createBackup(filePath, 'layer-2-patterns'); if (backupResult.success) { results.push({ type: 'backup', file: filePath, success: true, backupPath: backupResult.backupPath }); if (verbose) process.stdout.write(`Created centralized backup: ${path.basename(backupResult.backupPath)}\n`); } else { if (verbose) process.stderr.write(`Warning: Could not create backup: ${backupResult.error}\n`); } } catch (error) { if (verbose) process.stderr.write(`Warning: Backup creation failed: ${error.message}\n`); } } // 1) AST-based console.log/alert/confirm/prompt removal with arrow function support const beforeAST = updatedCode; // Progressive suggestion: interactive component without 'use client' if (options.progressive) { try { const hasInteractivity = /(onClick|onChange|onSubmit|useState|useEffect)\b/.test(updatedCode); const isComponent = /export\s+default\s+function\s+\w+\s*\(/.test(updatedCode); const hasUseClient = /['\"]use client['\"];/.test(updatedCode); if (isComponent && hasInteractivity && !hasUseClient) { warnings.push({ type: 'progressive', level: 'advice', message: 'Interactive component missing "use client" directive. Consider adding for clarity.', file: filePath }); } } catch {} } // Use AST-based transformations for robust console/alert handling // Following orchestration pattern: AST-first with validation, fallback to regex let astSucceeded = false; let astValidationFailed = false; try { const transformer = new ASTTransformer(); const astResult = transformer.transformPatterns(updatedCode, { filename: filePath }); // Only validate if we have valid code output if (astResult && typeof astResult.code === 'string' && astResult.code.length > 0) { // Validate AST transformation output const isValid = validateSyntax(astResult.code); if (isValid && astResult.code !== updatedCode) { // AST succeeded and output is valid - accept changes updatedCode = astResult.code; astSucceeded = true; // Add changes from AST transformation if (astResult.changes && Array.isArray(astResult.changes)) { astResult.changes.forEach(change => { changes.push({ type: change.type, description: change.description, location: change.location }); changeCount++; }); } states.push(updatedCode); if (verbose) { const changeMsg = astResult.changes ? astResult.changes.length : changeCount; process.stdout.write(`[INFO] AST-based pattern transformations: ${changeMsg} changes (validated)\n`); } } else if (!isValid) { // Validation failed - revert to pre-AST state astValidationFailed = true; updatedCode = beforeAST; if (verbose) { process.stderr.write(`[WARNING] AST transformation produced invalid syntax - reverted to original\n`); } } } else { // AST transformation returned no valid code - will try regex fallback if (verbose) { process.stderr.write(`[WARNING] AST transformation did not produce valid code output\n`); } } } catch (astError) { // AST transformation failed - will try regex fallback below if (verbose) { process.stderr.write(`[WARNING] AST transformation failed: ${astError.message}\n`); } } // If AST failed or validation failed, try regex fallback if (!astSucceeded) { if (verbose) { process.stdout.write(`[INFO] Attempting regex fallback for pattern transformations\n`); } const beforeRegex = updatedCode; const regexResult = applyRegexPatternFallbacks(updatedCode); // Validate regex fallback output const regexMadeChanges = regexResult.code !== beforeRegex; const regexOutputValid = validateSyntax(regexResult.code); if (regexMadeChanges && regexOutputValid) { // Regex produced valid changes - accept them updatedCode = regexResult.code; regexResult.changes.forEach(c => { changes.push(c); changeCount++; }); states.push(updatedCode); if (verbose) { process.stdout.write(`[INFO] Regex fallback succeeded with ${regexResult.changes.length} changes (validated)\n`); } } else if (regexMadeChanges && !regexOutputValid) { // Regex produced INVALID code - REJECT and revert updatedCode = beforeRegex; if (verbose) { process.stderr.write(`[ERROR] Regex fallback produced invalid syntax - rejected changes\n`); } } else { // Regex made no changes if (verbose) { process.stdout.write(`[INFO] Regex fallback made no changes\n`); } } } // 3) Mock data and setTimeout const beforeMock = updatedCode; updatedCode = updatedCode.replace(/\b(setTimeout)\(([^)]*)\);?/g, (m, fn) => { // Skip if this is inside a string const beforeMatch = updatedCode.substring(0, updatedCode.indexOf(m)); const quoteCount = (beforeMatch.match(/['"`]/g) || []).length; if (quoteCount % 2 === 1) return m; // Inside a string const c = `// [NeuroLint] Replace setTimeout with actual API call: ${fn}`; changes.push({ type: 'Comment', description: c, location: null }); return c; }); // Mock data detection - only for arrays with object literals or specific patterns updatedCode = updatedCode.replace(/(const|let|var)\s+\w+\s*=\s*\[[^\]]*\{[^\}]*\}[^\]]*\];?/g, (m) => { const c = `// [NeuroLint] Replace mock data with API fetch:`; changes.push({ type: 'Comment', description: c, location: null }); return `${c}\n${m}`; }); if (updatedCode !== beforeMock) states.push(updatedCode); // 4) HTML entities (apply the same replacements to the main code) const beforeEntities = updatedCode; if (hasEntities) { Object.entries(entityMap).forEach(([entity, rep]) => { if (updatedCode.includes(entity)) { updatedCode = updatedCode.replace(new RegExp(entity, 'g'), rep); } }); if (updatedCode !== beforeEntities) { changes.push({ type: 'EntityFix', description: 'Replaced HTML entities', location: null }); states.push(updatedCode); } } // 5) React 19 Pattern Fixes const beforeReact19 = updatedCode; const react19Result = applyReact19PatternFixes(updatedCode, { verbose }); updatedCode = react19Result.code; react19Result.fixes.forEach(fix => { changes.push({ type: fix.type, description: fix.description, location: null }); changeCount++; }); react19Result.warnings.forEach(warning => { warnings.push(warning); }); if (updatedCode !== beforeReact19) states.push(updatedCode); // 6) Next.js 15.5 Deprecation Patterns const beforeDeprecations = updatedCode; const deprecationPatterns = { legacyBehavior: { pattern: /legacyBehavior\s*=\s*{?[^}]*}?/g, replacement: '', description: 'Remove legacyBehavior prop from Link components' }, nextLint: { pattern: /"next lint"/g, replacement: '"biome lint ./src"', description: 'Replace "next lint" with Biome' }, oldImageComponent: { pattern: /from\s+["']next\/legacy\/image["']/g, replacement: 'from "next/image"', description: 'Migrate from next/legacy/image to next/image' }, oldRouterImport: { pattern: /from\s+["']next\/router["']/g, replacement: 'from "next/navigation"', description: 'Update to next/navigation for App Router' }, oldFontOptimization: { pattern: /from\s+["']@next\/font["']/g, replacement: 'from "next/font"', description: 'Replace @next/font with next/font' } }; for (const [patternName, config] of Object.entries(deprecationPatterns)) { const matches = updatedCode.match(config.pattern); if (matches) { updatedCode = updatedCode.replace(config.pattern, config.replacement); changes.push({ type: 'DeprecationFix', description: config.description, location: null }); } } if (updatedCode !== beforeDeprecations) { states.push(updatedCode); } // Phase 3: Enhanced API Route Structure Conventions (Layer 2) const apiRoutePatterns = { // Phase 3: Structured API route patterns basicHandler: { pattern: /export\s+default\s+function\s+handler\s*\(\s*req\s*,\s*res\s*\)\s*\{[\s\S]*?res\.json\s*\(\s*\{[^}]*\}\s*\)\s*;?\s*\}\s*$/gm, replacement: (match) => { return `import { NextRequest, NextResponse } from 'next/server'; export async function GET(request: NextRequest) { try { // Implementation return NextResponse.json({ message: 'Success' }); } catch (error) { return NextResponse.json( { error: 'Internal Server Error' }, { status: 500 } ); } }`; }, description: 'Convert basic API handler to structured Next.js 15.5 route' }, // Phase 3: Add validation with Zod missingValidation: { pattern: /export\s+async\s+function\s+(GET|POST|PUT|DELETE)\s*\(\s*request\s*:\s*NextRequest\s*\)\s*\{[\s\S]*?return\s+NextResponse\.json\s*\(\s*\{[^}]*\}\s*\)\s*;?\s*\}\s*$/gm, replacement: (match) => { return `import { NextRequest, NextResponse } from 'next/server'; import { z } from 'zod'; const schema = z.object({ // Define your validation schema here }); export async function GET(request: NextRequest) { try { // Validate request const body = await request.json(); const validatedData = schema.parse(body); // Implementation return NextResponse.json({ message: 'Success' }); } catch (error) { if (error instanceof z.ZodError) { return NextResponse.json( { error: 'Validation failed', details: error.errors }, { status: 400 } ); } return NextResponse.json( { error: 'Internal Server Error' }, { status: 500 } ); } }`; }, description: 'Add Zod validation to API routes' }, // Phase 3: Add proper error handling missingErrorHandling: { pattern: /return\s+NextResponse\.json\s*\(\s*\{[^}]*\}\s*\)\s*;?\s*$/gm, replacement: (match) => `try { ${match} } catch (error) { return NextResponse.json( { error: 'Internal Server Error' }, { status: 500 } ); }`, description: 'Add error handling to API responses' }, // Phase 3: Add caching strategies missingCaching: { pattern: /const\s+data\s*=\s*await\s+fetch\s*\(\s*['"`][^'"`]+['"`]\s*\)\s*;?\s*$/gm, replacement: (match) => { const urlMatch = match.match(/fetch\s*\(\s*['"`]([^'"`]+)['"`]/); const url = urlMatch ? urlMatch[1] : ''; return `const data = await fetch('${url}', { next: { revalidate: 3600, // Cache for 1 hour tags: ['api-data'] // Cache tag for revalidation } });`; }, description: 'Add Next.js 15.5 caching strategies to fetch calls' }, // Phase 3: Add revalidation patterns missingRevalidation: { pattern: /export\s+async\s+function\s+(GET|POST|PUT|DELETE)\s*\(\s*request\s*:\s*NextRequest\s*\)\s*\{[\s\S]*?return\s+NextResponse\.json\s*\(\s*\{[^}]*\}\s*\)\s*;?\s*\}\s*$/gm, replacement: (match) => { return `import { NextRequest, NextResponse } from 'next/server'; import { revalidateTag } from 'next/cache'; export async function POST(request: NextRequest) { try { // Implementation const result = { message: 'Success' }; // Revalidate cache tags revalidateTag('api-data'); return NextResponse.json(result); } catch (error) { return NextResponse.json( { error: 'Internal Server Error' }, { status: 500 } ); } }`; }, description: 'Add cache revalidation patterns to API routes' } }; // Phase 3: Dynamic Import Best Practices (Layer 2) const dynamicImportPatterns = { // Phase 3: Convert static imports to dynamic imports for large components largeComponentImport: { pattern: /import\s+(\w+)\s+from\s+['"`]\.\/([^'"`]+)['"`]\s*;?\s*$/gm, replacement: (match, componentName, importPath) => { // Only convert if it looks like a large component if (importPath.includes('Component') || importPath.includes('Page') || importPath.includes('Modal')) { return `import dynamic from 'next/dynamic'; const ${componentName} = dynamic(() => import('./${importPath}'), { loading: () => <div>Loading...</div>, ssr: false // Disable SSR for client-only components });`; } return match; }, description: 'Convert large component imports to dynamic imports' }, // Phase 3: Add loading states for dynamic imports missingLoadingState: { pattern: /const\s+(\w+)\s*=\s*dynamic\s*\(\s*\(\)\s*=>\s*import\s*\(\s*['"`][^'"`]+['"`]\s*\)\s*\)\s*;?\s*$/gm, replacement: (match, componentName) => { return `const ${componentName} = dynamic(() => import('./${componentName}'), { loading: () => <div className="flex items-center justify-center p-4"> <div className="animate-spin rounded-full h-8 w-8 border-b-2 border-gray-900"></div> </div>, ssr: false });`; }, description: 'Add proper loading states to dynamic imports' } }; // Phase 3: Caching Hints and Strategies (Layer 2) const cachingPatterns = { // Phase 3: Add segment-level cache hints missingSegmentCache: { pattern: /export\s+default\s+async\s+function\s+(\w+)\s*\(\s*\)\s*\{[\s\S]*?const\s+data\s*=\s*await\s+fetch\s*\(\s*['"`][^'"`]+['"`]\s*\)\s*;?\s*[\s\S]*?return\s+<[^>]+>[\s\S]*<\/[^>]+>\s*;?\s*\}\s*$/gm, replacement: (match, pageName) => { return `export default async function ${pageName}() { // Add segment-level cache hints const data = await fetch('https://api.example.com/data', { next: { revalidate: 3600, // Cache for 1 hour tags: ['page-data'] // Cache tag for revalidation } }); return ( <div> {/* Your page content */} </div> ); }`; }, description: 'Add segment-level cache hints to page components' }, // Phase 3: Add revalidateTag usage missingRevalidateTag: { pattern: /export\s+async\s+function\s+(GET|POST|PUT|DELETE)\s*\(\s*request\s*:\s*NextRequest\s*\)\s*\{[\s\S]*?return\s+NextResponse\.json\s*\(\s*\{[^}]*\}\s*\)\s*;?\s*\}\s*$/gm, replacement: (match) => { return `import { NextRequest, NextResponse } from 'next/server'; import { revalidateTag } from 'next/cache'; export async function POST(request: NextRequest) { try { // Implementation const result = { message: 'Success' }; // Revalidate specific cache tags revalidateTag('page-data'); revalidateTag('api-data'); return NextResponse.json(result); } catch (error) { return NextResponse.json( { error: 'Internal Server Error' }, { status: 500 } ); } }`; }, description: 'Add revalidateTag usage for cache invalidation' } }; // Apply all Phase 3 patterns const allPatterns = { ...apiRoutePatterns, ...dynamicImportPatterns, ...cachingPatterns }; for (const [patternName, config] of Object.entries(allPatterns)) { const matches = updatedCode.match(config.pattern); if (matches) { if (typeof config.replacement === 'function') { updatedCode = updatedCode.replace(config.pattern, config.replacement); } else { updatedCode = updatedCode.replace(config.pattern, config.replacement); } changes.push({ type: 'Phase3PatternFix', description: config.description, location: null }); if (verbose) { process.stdout.write(`[INFO] Applied Phase 3 pattern: ${config.description}\n`); } } } changeCount = changes.length; // No changes -> fail with expected message if (changeCount === 0) { return { success: false, code, originalCode: code, changeCount: 0, error: 'No changes were made', states: [code], changes, warnings }; } // Dry-run behavior if (dryRun) { if (verbose) process.stdout.write(`[SUCCESS] Layer 2 identified ${changeCount} pattern fixes (dry-run)\n`); return { success: true, code, originalCode: code, changeCount, results, states: [code], changes, warnings }; } // Persist if (existsAsFile) { await fs.writeFile(filePath, updatedCode); results.push({ type: 'write', file: filePath, success: true, changes: changeCount }); } if (verbose) process.stdout.write(`[SUCCESS] Layer 2 applied ${changeCount} pattern fixes to ${path.basename(filePath)}\n`); return { success: true, code: updatedCode, originalCode: code, changeCount, results, states, changes, warnings }; } catch (error) { if (verbose) process.stderr.write(`[ERROR] Layer 2 failed: ${error.message}\n`); return { success: false, code, originalCode: code, changeCount: 0, error: error.message, states: [code], changes, warnings }; } } module.exports = { transform };