swagger-auto-generate
Version:
Automatically generate Swagger JSDoc documentation for Express applications
243 lines • 8.19 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.ASTParser = void 0;
const parser_1 = require("@babel/parser");
class ASTParser {
/**
* Parse a JavaScript/TypeScript file and extract route information
*/
static parseFile(content, filePath) {
try {
const ast = (0, parser_1.parse)(content, {
sourceType: 'module',
plugins: ['typescript', 'jsx'],
errorRecovery: true,
});
const routes = [];
const functions = this.extractFunctions(ast, content);
for (const func of functions) {
if (func.isRouteHandler && func.httpMethod && func.routePath) {
const routeInfo = this.createRouteInfo(func, filePath);
if (routeInfo) {
routes.push(routeInfo);
}
}
}
return routes;
}
catch (error) {
console.warn(`Warning: Could not parse file ${filePath}:`, error);
return [];
}
}
/**
* Recursively extract functions from Babel AST
*/
static extractFunctions(node, content) {
const functions = [];
if (!node || typeof node !== 'object')
return functions;
// Check for function nodes
if (node.type === 'FunctionDeclaration' ||
node.type === 'FunctionExpression' ||
node.type === 'ArrowFunctionExpression') {
let func = null;
if (node.type === 'ArrowFunctionExpression') {
func = this.parseArrowFunction(node, content);
}
else {
func = this.parseFunction(node, content);
}
if (func)
functions.push(func);
}
// Recursively traverse all child properties
for (const key in node) {
if (node.hasOwnProperty(key)) {
const child = node[key];
if (Array.isArray(child)) {
for (const c of child) {
functions.push(...this.extractFunctions(c, content));
}
}
else if (typeof child === 'object' && child !== null && child.type) {
functions.push(...this.extractFunctions(child, content));
}
}
}
return functions;
}
/**
* Parse a function declaration or expression
*/
static parseFunction(node, content) {
if (!node.params || !node.body)
return null;
const functionName = node.id?.name || 'anonymous';
const parameters = node.params.map((param) => param.name || 'param');
const body = content.substring(node.body.start, node.body.end);
const lineNumber = node.loc?.start.line || 0;
const isRouteHandler = this.isRouteHandler(parameters);
const { httpMethod, routePath } = this.extractRouteInfo(node, content);
return {
name: functionName,
parameters,
body,
lineNumber,
isRouteHandler,
httpMethod,
routePath,
};
}
/**
* Parse an arrow function
*/
static parseArrowFunction(node, content) {
if (!node.params || !node.body)
return null;
const parameters = node.params.map((param) => param.name || 'param');
const body = content.substring(node.body.start, node.body.end);
const lineNumber = node.loc?.start.line || 0;
const isRouteHandler = this.isRouteHandler(parameters);
const { httpMethod, routePath } = this.extractRouteInfo(node, content);
return {
name: 'anonymous',
parameters,
body,
lineNumber,
isRouteHandler,
httpMethod,
routePath,
};
}
/**
* Check if a function is a route handler
*/
static isRouteHandler(parameters) {
return parameters.some(param => param === 'req' ||
param === 'request' ||
param.includes('req') ||
param.includes('request')) && parameters.some(param => param === 'res' ||
param === 'response' ||
param.includes('res') ||
param.includes('response'));
}
/**
* Extract route information from function
*/
static extractRouteInfo(node, content) {
// Look for common Express route patterns
const patterns = [
/\.(get|post|put|delete|patch|all)\s*\(\s*['"`]([^'"`]+)['"`]/g,
/\.(get|post|put|delete|patch|all)\s*\(\s*`([^`]+)`/g,
];
const nodeContent = content.substring(node.start, node.end);
for (const pattern of patterns) {
const matches = [...nodeContent.matchAll(pattern)];
if (matches.length > 0) {
const match = matches[0];
return {
httpMethod: match[1].toLowerCase(),
routePath: match[2],
};
}
}
return {};
}
/**
* Create RouteInfo from ParsedFunction
*/
static createRouteInfo(func, filePath) {
if (!func.httpMethod || !func.routePath)
return null;
const parameters = this.extractParameters(func);
const responses = this.extractResponses(func);
return {
method: func.httpMethod,
path: func.routePath,
functionName: func.name,
parameters,
responses,
filePath,
lineNumber: func.lineNumber,
summary: `Auto-generated documentation for ${func.name}`,
description: `Endpoint for ${func.httpMethod.toUpperCase()} ${func.routePath}`,
};
}
/**
* Extract parameters from function
*/
static extractParameters(func) {
const parameters = [];
// Extract path parameters from route path
const pathParams = func.routePath?.match(/:[^/]+/g) || [];
for (const param of pathParams) {
const paramName = param.substring(1);
parameters.push({
name: paramName,
in: 'path',
required: true,
type: 'string',
description: `Path parameter: ${paramName}`,
});
}
// Add basic request and response parameters
parameters.push({
name: 'req',
in: 'body',
required: true,
type: 'object',
description: 'Request object',
});
parameters.push({
name: 'res',
in: 'body',
required: true,
type: 'object',
description: 'Response object',
});
return parameters;
}
/**
* Extract responses from function
*/
static extractResponses(func) {
return [
{
code: '200',
description: 'Successful response',
schema: {
type: 'object',
properties: {
success: { type: 'boolean' },
data: { type: 'object' },
},
},
},
{
code: '400',
description: 'Bad request',
schema: {
type: 'object',
properties: {
success: { type: 'boolean' },
error: { type: 'string' },
},
},
},
{
code: '500',
description: 'Internal server error',
schema: {
type: 'object',
properties: {
success: { type: 'boolean' },
error: { type: 'string' },
},
},
},
];
}
}
exports.ASTParser = ASTParser;
//# sourceMappingURL=astParser.js.map