agentsqripts
Version:
Comprehensive static code analysis toolkit for identifying technical debt, security vulnerabilities, performance issues, and code quality problems
195 lines (173 loc) • 7.35 kB
JavaScript
/**
* @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) {
// Simple route cleaning function - avoid missing dependency issues
const cleanRoute = (route) => {
if (!route || typeof route !== 'string') return '/';
return route.trim().replace(/\/$/, '') || '/';
};
// Simple middleware extraction placeholder - avoid missing dependency issues
const extractMiddleware = () => ({ type: 'none', details: [] });
// 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 - check if second group is HTTP method
const method2Upper = (match[2] || '').toUpperCase();
if (validMethods.has(method2Upper)) {
method = method2Upper;
route = match[1];
} else {
// Assume first group is method, second is route
const method1Upper = (match[1] || '').toUpperCase();
if (validMethods.has(method1Upper)) {
method = method1Upper;
route = match[2];
} 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
};