UNPKG

agentsqripts

Version:

Comprehensive static code analysis toolkit for identifying technical debt, security vulnerabilities, performance issues, and code quality problems

186 lines (165 loc) 6.95 kB
/** * @file Multi-framework backend endpoint extraction for integration analysis * @description Single responsibility: Extract API endpoint definitions from diverse backend frameworks * * This module implements comprehensive endpoint extraction across multiple backend frameworks * including Express.js, NestJS, FastAPI, Django, and Flask. It uses framework-specific * pattern matching to identify route definitions, HTTP methods, and middleware configurations, * enabling accurate frontend-backend integration analysis. * * Design rationale: * - Multi-framework support covers diverse technology stacks * - Regex-based extraction balances accuracy with performance * - Framework-specific patterns handle syntax variations effectively * - Extensible pattern system supports adding new frameworks */ const fs = require('fs'); const path = require('path'); /** * Extract backend API endpoints using multi-framework pattern matching * * Technical function: Comprehensive endpoint extraction with framework-specific pattern recognition * * Implementation rationale: * - File extension detection routes to appropriate framework patterns * - Async file reading enables concurrent processing of multiple files * - Pattern-based extraction handles diverse route definition syntaxes * - Error handling ensures analysis continues despite individual file failures * * Framework coverage strategy: * - Express.js: app.get/post/put/delete patterns, router definitions * - NestJS: Decorator-based route definitions (@Get, @Post, etc.) * - FastAPI: Python decorator patterns with route definitions * - Django: path() and url() patterns for URL configuration * - Flask: @app.route decorators with method specifications * * Pattern matching approach: * - Regex patterns optimized for each framework's syntax * - Global matching captures all route definitions in files * - Method extraction handles various syntax patterns * - Route cleaning normalizes paths for comparison * * Extraction accuracy considerations: * - Framework-specific patterns reduce false positives * - File extension filtering improves pattern selection * - Middleware extraction captures authentication and validation layers * - Method validation ensures only valid HTTP methods are captured * * @param {Array<string>} files - Array of backend file paths to analyze for endpoint definitions * @returns {Promise<Array<Object>>} Array of endpoint objects with method, route, file, and metadata * @example * const endpoints = await extractBackendEndpoints([ * 'api/users.js', // Express.js routes * 'controllers/auth.py' // FastAPI routes * ]); * // Returns: [{ method: 'GET', route: '/api/users', file: 'api/users.js', ... }] */ async function extractBackendEndpoints(files) { const endpoints = []; // Express.js route patterns const expressPatterns = [ /(app|router)\.(get|post|put|delete|patch|all)\s*\(\s*['"`]([^'"`]+)['"`]/gi, /\.route\s*\(\s*['"`]([^'"`]+)['"`]\s*\)\s*\.(get|post|put|delete|patch)/gi, /@(Get|Post|Put|Delete|Patch)\s*\(\s*['"`]([^'"`]+)['"`]\s*\)/gi // NestJS decorators ]; // FastAPI patterns const fastApiPatterns = [ /@app\.(get|post|put|delete|patch)\s*\(\s*['"`]([^'"`]+)['"`]/gi, /@router\.(get|post|put|delete|patch)\s*\(\s*['"`]([^'"`]+)['"`]/gi ]; // Django patterns const djangoPatterns = [ /path\s*\(\s*['"`]([^'"`]+)['"`]/gi, /url\s*\(\s*['"`]([^'"`]+)['"`]/gi ]; // Flask patterns const flaskPatterns = [ /@app\.route\s*\(\s*['"`]([^'"`]+)['"`]\s*,?\s*methods\s*=\s*\[['"`]([^'"`]+)['"`]\]/gi, /@app\.(get|post|put|delete|patch)\s*\(\s*['"`]([^'"`]+)['"`]\s*\)/gi ]; for (const file of files) { try { const content = await fs.promises.readFile(file, 'utf8'); const ext = path.extname(file); // JavaScript/TypeScript backend if (['.js', '.ts'].includes(ext)) { extractWithPatterns(content, file, expressPatterns, endpoints); } // Python backend else if (ext === '.py') { extractWithPatterns(content, file, [...fastApiPatterns, ...djangoPatterns, ...flaskPatterns], endpoints); } // Generic patterns for other backends else { extractWithPatterns(content, file, expressPatterns, endpoints); } } catch (error) { console.warn(`Warning: Could not read backend file ${file}: ${error.message}`); } } return endpoints; } /** * Helper function to extract endpoints using regex patterns */ function extractWithPatterns(content, file, patterns, endpoints) { const { cleanRoute } = require('./routeCleaner'); const { extractMiddleware } = require('./middlewareExtractor'); // Pre-compile method check for efficiency const validMethods = new Set(['GET', 'POST', 'PUT', 'DELETE', 'PATCH']); // Process patterns efficiently using single-pass approach where possible const processedPatterns = new Set(); for (let i = 0; i < patterns.length; i++) { const pattern = patterns[i]; if (processedPatterns.has(pattern.source)) continue; processedPatterns.add(pattern.source); // Use more efficient regex execution with single iteration let match; while ((match = pattern.exec(content)) !== null) { // eslint-disable-line eqeqeq let method, route; // Handle different match group arrangements efficiently if (match[1] && match[2] && match[3]) { // app.get('/route') pattern: match[1]=app, match[2]=get, match[3]=/route method = match[2].toUpperCase(); route = match[3]; } else if (match[1] && match[2]) { // Two-group patterns const method1Upper = match[1].toUpperCase(); const method2Upper = match[2].toUpperCase(); if (validMethods.has(method1Upper)) { method = method1Upper; route = match[2]; } else if (validMethods.has(method2Upper)) { method = method2Upper; route = match[1]; } else { method = 'GET'; // Default for route definitions without explicit method route = match[1] || match[2]; } } else if (match[1]) { // Single group - assume it's a route method = 'GET'; route = match[1]; } // Reset regex state to prevent infinite loops if (pattern.global && match.index === pattern.lastIndex) { pattern.lastIndex++; } if (route) { const lines = content.substring(0, match.index).split('\n'); const lineNumber = lines.length; endpoints.push({ method: method || 'GET', route: cleanRoute(route), file, line: lineNumber, originalMatch: match[0], middlewareInfo: extractMiddleware(content, match.index) }); } } } } module.exports = { extractBackendEndpoints };