UNPKG

tree-ast-grep-mcp

Version:

Simple, direct ast-grep wrapper for AI coding agents. Zero abstractions, maximum performance.

747 lines (626 loc) 21.5 kB
/** * Real-World Scenario Tests * Tests based on actual usage patterns and real-world code examples */ import { SearchTool } from '../../build/tools/search.js'; import { ReplaceTool } from '../../build/tools/replace.js'; import { ScanTool } from '../../build/tools/scan.js'; import { AstGrepBinaryManager } from '../../build/core/binary-manager.js'; import { WorkspaceManager } from '../../build/core/workspace-manager.js'; import { TestSuite, TestAssert, withTimeout } from '../utils/test-helpers.js'; import fs from 'fs/promises'; import path from 'path'; import { tmpdir } from 'os'; export default async function runRealisticScenariosTests() { const suite = new TestSuite('Real-World Scenario Tests'); let binaryManager; let workspaceManager; let searchTool; let replaceTool; let scanTool; let tempDir; suite.beforeAll(async () => { binaryManager = new AstGrepBinaryManager({ useSystem: true }); workspaceManager = new WorkspaceManager(); searchTool = new SearchTool(binaryManager, workspaceManager); replaceTool = new ReplaceTool(binaryManager, workspaceManager); scanTool = new ScanTool(binaryManager, workspaceManager); try { await binaryManager.initialize(); } catch (error) { console.log(' ⚠️ Binary manager initialization failed:', error.message); } tempDir = await fs.mkdtemp(path.join(tmpdir(), 'ast-grep-realworld-test-')); }); suite.afterAll(async () => { try { await fs.rm(tempDir, { recursive: true, force: true }); } catch (error) { console.log(' ⚠️ Failed to clean up temp directory'); } }); // ============================================================================= // LEGACY CODE MODERNIZATION SCENARIOS // ============================================================================= suite.test('should modernize legacy JavaScript to ES6+', async () => { const legacyCode = ` // Legacy ES5 code that needs modernization var UserService = function() { this.users = []; }; UserService.prototype.addUser = function(user) { this.users.push(user); console.log("Added user: " + user.name); }; UserService.prototype.getUser = function(id) { for (var i = 0; i < this.users.length; i++) { if (this.users[i].id === id) { return this.users[i]; } } return null; }; var service = new UserService(); service.addUser({ id: 1, name: "John" }); function processData(callback) { setTimeout(function() { callback(null, "processed"); }, 1000); } `; console.log(' 🔄 Testing legacy ES5 to ES6+ modernization...'); try { // Step 1: Convert var to const/let const varToConstResult = await replaceTool.execute({ pattern: 'var $NAME = $VALUE;', replacement: 'const $NAME = $VALUE;', language: 'javascript', code: legacyCode, dryRun: true }); TestAssert.assertTrue(typeof varToConstResult === 'object'); // Step 2: Convert function expressions to arrow functions const arrowFunctionResult = await replaceTool.execute({ pattern: 'function($ARGS) { $$$BODY }', replacement: '($ARGS) => { $$$BODY }', language: 'javascript', code: legacyCode, dryRun: true }); TestAssert.assertTrue(typeof arrowFunctionResult === 'object'); // Step 3: Convert string concatenation to template literals const templateLiteralResult = await replaceTool.execute({ pattern: 'console.log($STR1 + $STR2)', replacement: 'console.log(`${$STR1}${$STR2}`)', language: 'javascript', code: legacyCode, dryRun: true }); TestAssert.assertTrue(typeof templateLiteralResult === 'object'); } catch (error) { if (error.message.includes('ast-grep not found')) { console.log(' ⚠️ Skipping legacy modernization test - ast-grep not available'); return; } throw error; } }); suite.test('should refactor React class components to hooks', async () => { const reactClassCode = ` import React, { Component } from 'react'; class UserProfile extends Component { constructor(props) { super(props); this.state = { user: null, loading: true, error: null }; } componentDidMount() { this.fetchUser(); } fetchUser = async () => { try { const response = await fetch(\`/api/users/\${this.props.userId}\`); const user = await response.json(); this.setState({ user, loading: false }); } catch (error) { this.setState({ error: error.message, loading: false }); } }; render() { const { user, loading, error } = this.state; if (loading) return <div>Loading...</div>; if (error) return <div>Error: {error}</div>; return ( <div> <h1>{user.name}</h1> <p>{user.email}</p> </div> ); } } `; console.log(' ⚛️ Testing React class to hooks conversion...'); try { // Find class components that can be converted const classComponentSearch = await searchTool.execute({ pattern: 'class $NAME extends Component { $$$BODY }', language: 'javascript', code: reactClassCode }); TestAssert.assertTrue(typeof classComponentSearch === 'object'); TestAssert.assertTrue('matches' in classComponentSearch); // Find state initialization patterns const stateSearch = await searchTool.execute({ pattern: 'this.state = { $$$PROPS };', language: 'javascript', code: reactClassCode }); TestAssert.assertTrue(typeof stateSearch === 'object'); } catch (error) { if (error.message.includes('ast-grep not found')) { console.log(' ⚠️ Skipping React conversion test - ast-grep not available'); return; } throw error; } }); // ============================================================================= // CODE QUALITY IMPROVEMENT SCENARIOS // ============================================================================= suite.test('should identify and fix common code smells', async () => { const codeWithSmells = ` // Code smells that should be detected and fixed function processUser(user) { // Long parameter list smell function updateUserProfile(name, email, age, address, phone, preferences, settings, metadata) { console.log("Updating user with many parameters"); } // Magic numbers if (user.age > 18 && user.score > 100 && user.level < 5) { console.log("User qualifies"); } // Nested conditions (arrow anti-pattern) if (user.isActive) { if (user.hasPermission) { if (user.isVerified) { if (user.balance > 0) { console.log("All checks passed"); } } } } // Console.log in production code console.log("Debug: processing user", user.id); console.log("Debug: user data", JSON.stringify(user)); // Empty catch blocks try { processPayment(user.id); } catch (error) { // TODO: handle error } } `; console.log(' 🔍 Testing code smell detection...'); try { // Detect console.log statements (should be removed in production) const consoleLogRule = ` rule: pattern: console.log($$$ARGS) language: javascript message: "Remove console.log statements from production code" severity: warning `; const consoleLogScan = await scanTool.execute({ rule: consoleLogRule, code: codeWithSmells }); TestAssert.assertTrue(typeof consoleLogScan === 'object'); // Detect magic numbers const magicNumberSearch = await searchTool.execute({ pattern: 'user.$PROP > $NUMBER', language: 'javascript', code: codeWithSmells }); TestAssert.assertTrue(typeof magicNumberSearch === 'object'); // Detect deeply nested conditions const nestedConditionSearch = await searchTool.execute({ pattern: 'if ($COND1) { if ($COND2) { if ($COND3) { $$$BODY } } }', language: 'javascript', code: codeWithSmells }); TestAssert.assertTrue(typeof nestedConditionSearch === 'object'); } catch (error) { if (error.message.includes('ast-grep not found')) { console.log(' ⚠️ Skipping code smell test - ast-grep not available'); return; } throw error; } }); suite.test('should enforce coding standards across large codebase', async () => { console.log(' 📏 Testing coding standards enforcement...'); // Create a mock large codebase const files = [ { name: 'userService.js', content: ` const UserService = { getUser: function(id) { return users.find(u => u.id === id); }, updateUser: function(id, data) { const user = this.getUser(id); if (!user) throw new Error("User not found"); Object.assign(user, data); return user; } }; ` }, { name: 'authController.js', content: ` class AuthController { async login(req, res) { try { const { email, password } = req.body; const user = await User.findOne({ email }); if (!user || !user.comparePassword(password)) { return res.status(401).json({ error: "Invalid credentials" }); } const token = jwt.sign({ userId: user.id }, process.env.JWT_SECRET); res.json({ token, user: user.toPublicJSON() }); } catch (error) { console.error("Login error:", error); res.status(500).json({ error: "Internal server error" }); } } } ` }, { name: 'utils.js', content: ` // Utility functions with various patterns const formatDate = (date) => { return date.toLocaleDateString(); }; function validateEmail(email) { const regex = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/; return regex.test(email); } var globalCounter = 0; // Should be const function incrementCounter() { globalCounter = globalCounter + 1; // Should use += console.log("Counter:", globalCounter); // Should use logger } ` } ]; // Write files to temp directory for (const file of files) { await fs.writeFile(path.join(tempDir, file.name), file.content); } try { // Standard 1: No var declarations const varUsageRule = ` rule: pattern: var $NAME = $VALUE language: javascript message: "Use const or let instead of var" severity: error `; const varScan = await scanTool.execute({ rule: varUsageRule, paths: [tempDir] }); TestAssert.assertTrue(typeof varScan === 'object'); // Standard 2: Prefer arrow functions for callbacks const functionExpressionSearch = await searchTool.execute({ pattern: 'function($ARGS) { $$$BODY }', language: 'javascript', paths: [tempDir] }); TestAssert.assertTrue(typeof functionExpressionSearch === 'object'); // Standard 3: No console.log in production code const consoleLogSearch = await searchTool.execute({ pattern: 'console.log($$$ARGS)', language: 'javascript', paths: [tempDir] }); TestAssert.assertTrue(typeof consoleLogSearch === 'object'); } catch (error) { if (error.message.includes('ast-grep not found')) { console.log(' ⚠️ Skipping coding standards test - ast-grep not available'); return; } throw error; } }); // ============================================================================= // MIGRATION AND UPGRADE SCENARIOS // ============================================================================= suite.test('should assist with framework migration', async () => { const jqueryCode = ` // jQuery code that needs migration to vanilla JS $(document).ready(function() { $('.user-item').click(function() { var userId = $(this).data('user-id'); $.ajax({ url: '/api/users/' + userId, method: 'GET', success: function(data) { $('#user-details').html(data.name); $('#user-email').text(data.email); }, error: function() { alert('Error loading user data'); } }); }); $('.btn-submit').on('click', function(e) { e.preventDefault(); var formData = $('#user-form').serialize(); $.post('/api/users', formData, function(response) { console.log('User created:', response); }); }); }); `; console.log(' 🔄 Testing jQuery to vanilla JS migration...'); try { // Find jQuery selectors const jquerySelectorSearch = await searchTool.execute({ pattern: '$($SELECTOR)', language: 'javascript', code: jqueryCode }); TestAssert.assertTrue(typeof jquerySelectorSearch === 'object'); // Find jQuery AJAX calls const ajaxSearch = await searchTool.execute({ pattern: '$.ajax({ $$$OPTIONS })', language: 'javascript', code: jqueryCode }); TestAssert.assertTrue(typeof ajaxSearch === 'object'); // Convert jQuery ready to DOMContentLoaded const readyConversion = await replaceTool.execute({ pattern: '$(document).ready(function() { $$$BODY });', replacement: 'document.addEventListener("DOMContentLoaded", function() { $$$BODY });', language: 'javascript', code: jqueryCode, dryRun: true }); TestAssert.assertTrue(typeof readyConversion === 'object'); } catch (error) { if (error.message.includes('ast-grep not found')) { console.log(' ⚠️ Skipping jQuery migration test - ast-grep not available'); return; } throw error; } }); // ============================================================================= // SECURITY AUDIT SCENARIOS // ============================================================================= suite.test('should identify security vulnerabilities', async () => { const vulnerableCode = ` // Code with security vulnerabilities const express = require('express'); const app = express(); // SQL Injection vulnerability app.get('/users/:id', (req, res) => { const query = \`SELECT * FROM users WHERE id = \${req.params.id}\`; db.query(query, (err, results) => { res.json(results); }); }); // XSS vulnerability app.get('/profile', (req, res) => { const html = \`<h1>Welcome \${req.query.name}</h1>\`; res.send(html); }); // Command injection vulnerability app.post('/backup', (req, res) => { const filename = req.body.filename; exec(\`tar -czf \${filename}.tar.gz /data\`, (error, stdout) => { res.json({ message: 'Backup created' }); }); }); // Weak cryptography const crypto = require('crypto'); const hash = crypto.createHash('md5').update('password').digest('hex'); // Hardcoded secrets const apiKey = 'sk-1234567890abcdef'; const dbPassword = 'admin123'; `; console.log(' 🔒 Testing security vulnerability detection...'); try { // Detect SQL injection patterns const sqlInjectionRule = ` rule: pattern: \`SELECT * FROM $TABLE WHERE $CONDITION = \${$VAR}\` language: javascript message: "Potential SQL injection vulnerability" severity: error `; const sqlScan = await scanTool.execute({ rule: sqlInjectionRule, code: vulnerableCode }); TestAssert.assertTrue(typeof sqlScan === 'object'); // Detect command injection const commandInjectionSearch = await searchTool.execute({ pattern: 'exec(`$CMD`)', language: 'javascript', code: vulnerableCode }); TestAssert.assertTrue(typeof commandInjectionSearch === 'object'); // Detect hardcoded secrets const hardcodedSecretSearch = await searchTool.execute({ pattern: 'const $NAME = $VALUE;', language: 'javascript', code: vulnerableCode }); TestAssert.assertTrue(typeof hardcodedSecretSearch === 'object'); } catch (error) { if (error.message.includes('ast-grep not found')) { console.log(' ⚠️ Skipping security audit test - ast-grep not available'); return; } throw error; } }); // ============================================================================= // PERFORMANCE OPTIMIZATION SCENARIOS // ============================================================================= suite.test('should identify performance optimization opportunities', async () => { const performanceCode = ` // Code with performance issues class DataProcessor { processLargeDataset(data) { // Inefficient loop in loop const results = []; for (let i = 0; i < data.length; i++) { for (let j = 0; j < data.length; j++) { if (data[i].id === data[j].parentId) { results.push({ parent: data[i], child: data[j] }); } } } return results; } // Synchronous file operations loadConfig() { const config1 = fs.readFileSync('config1.json'); const config2 = fs.readFileSync('config2.json'); const config3 = fs.readFileSync('config3.json'); return { config1, config2, config3 }; } // Inefficient DOM queries updateUI() { for (let i = 0; i < 1000; i++) { document.getElementById('item-' + i).innerHTML = 'Updated'; document.querySelector('.status').textContent = 'Processing...'; } } // Memory leaks - event listeners not removed setupEventListeners() { const items = document.querySelectorAll('.item'); items.forEach(item => { item.addEventListener('click', function() { console.log('Item clicked'); }); }); } } `; console.log(' ⚡ Testing performance optimization detection...'); try { // Detect nested loops const nestedLoopSearch = await searchTool.execute({ pattern: 'for ($INIT1; $COND1; $UPDATE1) { for ($INIT2; $COND2; $UPDATE2) { $$$BODY } }', language: 'javascript', code: performanceCode }); TestAssert.assertTrue(typeof nestedLoopSearch === 'object'); // Detect synchronous file operations const syncOpsSearch = await searchTool.execute({ pattern: 'fs.readFileSync($PATH)', language: 'javascript', code: performanceCode }); TestAssert.assertTrue(typeof syncOpsSearch === 'object'); // Detect repeated DOM queries const domQuerySearch = await searchTool.execute({ pattern: 'document.querySelector($SELECTOR)', language: 'javascript', code: performanceCode }); TestAssert.assertTrue(typeof domQuerySearch === 'object'); } catch (error) { if (error.message.includes('ast-grep not found')) { console.log(' ⚠️ Skipping performance optimization test - ast-grep not available'); return; } throw error; } }); // ============================================================================= // LARGE-SCALE REFACTORING SCENARIOS // ============================================================================= suite.test('should handle enterprise-scale refactoring', async () => { console.log(' 🏢 Testing enterprise-scale refactoring scenarios...'); // Simulate a large enterprise codebase structure const enterpriseFiles = [ 'src/services/UserService.js', 'src/services/AuthService.js', 'src/controllers/UserController.js', 'src/controllers/AuthController.js', 'src/models/User.js', 'src/utils/Logger.js', 'src/config/Database.js', 'test/unit/UserService.test.js', 'test/integration/UserController.test.js' ]; // Create directory structure for (const filePath of enterpriseFiles) { const fullPath = path.join(tempDir, filePath); await fs.mkdir(path.dirname(fullPath), { recursive: true }); const content = ` // ${filePath} const Logger = require('../utils/Logger'); class ${path.basename(filePath, '.js')} { constructor() { this.logger = new Logger(); } process(data) { this.logger.info('Processing data in ${path.basename(filePath)}'); // Legacy error handling if (!data) { throw new Error('Data is required'); } return data; } } module.exports = ${path.basename(filePath, '.js')}; `; await fs.writeFile(fullPath, content); } try { // Enterprise refactoring: Convert CommonJS to ES modules const commonjsSearch = await searchTool.execute({ pattern: 'module.exports = $EXPORT', language: 'javascript', paths: [tempDir] }); TestAssert.assertTrue(typeof commonjsSearch === 'object'); // Find all require statements for conversion const requireSearch = await searchTool.execute({ pattern: 'const $NAME = require($PATH)', language: 'javascript', paths: [tempDir] }); TestAssert.assertTrue(typeof requireSearch === 'object'); // Test large-scale replacement across multiple files const errorHandlingReplace = await replaceTool.execute({ pattern: 'throw new Error($MSG)', replacement: 'this.logger.error($MSG); throw new Error($MSG)', language: 'javascript', paths: [tempDir], dryRun: true }); TestAssert.assertTrue(typeof errorHandlingReplace === 'object'); } catch (error) { if (error.message.includes('ast-grep not found')) { console.log(' ⚠️ Skipping enterprise refactoring test - ast-grep not available'); return; } throw error; } }); return suite.run(); }