UNPKG

@addon24/eslint-config

Version:

ESLint configuration rules for WorldOfTextcraft projects - Centralized configuration for all project types

120 lines (99 loc) 4.07 kB
/** * @fileoverview Ensure all controller methods have proper Swagger documentation including success and error status codes */ "use strict"; export default { meta: { type: "problem", docs: { description: "enforce that all controller methods have proper @ApiResponse documentation", category: "Best Practices", recommended: true, }, fixable: null, schema: [], messages: { missingApiResponse: "Controller method '{{methodName}}' is missing @ApiResponse with status: {{status}}. All controller methods should document potential server errors (BaseController.sendError defaults to 500)", }, }, create(context) { function isControllerClass(node) { if (node.type !== "ClassDeclaration") return false; return node.decorators && node.decorators.some(decorator => { return decorator.expression && ((decorator.expression.type === "Identifier" && decorator.expression.name === "Controller") || (decorator.expression.type === "CallExpression" && decorator.expression.callee.name === "Controller")); }); } function hasApiResponseDecorator(decorators, statuses) { if (!decorators) return false; const statusArray = Array.isArray(statuses) ? statuses : [statuses]; return statusArray.some(status => { return decorators.some(decorator => { if (decorator.expression.type === "CallExpression" && decorator.expression.callee.name === "ApiResponse") { const args = decorator.expression.arguments; if (args.length > 0 && args[0].type === "ObjectExpression") { const statusProp = args[0].properties.find(prop => prop.key && prop.key.name === "status" ); if (statusProp && statusProp.value) { return statusProp.value.value === status || statusProp.value.raw === status.toString(); } } } return false; }); }); } function getHttpMethodType(decorators) { if (!decorators) return null; const httpMethods = ["Get", "Post", "Put", "Delete", "Patch"]; for (const decorator of decorators) { const decoratorName = decorator.expression?.name || decorator.expression?.callee?.name; if (httpMethods.includes(decoratorName)) { return decoratorName.toLowerCase(); } } return null; } function validateApiResponseDecorators(member) { const httpMethod = getHttpMethodType(member.decorators); if (!httpMethod) return; const methodName = member.key.name; // Check for success status codes based on HTTP method let successCodes = [200]; if (httpMethod === "post") { successCodes = [200, 201]; // POST can return 200 or 201 } const hasSuccessResponse = hasApiResponseDecorator(member.decorators, successCodes); const has500Response = hasApiResponseDecorator(member.decorators, 500); if (!hasSuccessResponse) { const expectedCodes = successCodes.join(" or "); context.report({ node: member, message: `Controller method '${methodName}' is missing @ApiResponse with status: ${expectedCodes}. All controller methods should document successful responses.`, }); } if (!has500Response) { context.report({ node: member, message: `Controller method '${methodName}' is missing @ApiResponse with status: 500. All controller methods should document potential server errors (BaseController.sendError defaults to 500).`, }); } } return { ClassDeclaration(node) { if (!isControllerClass(node)) return; node.body.body.forEach(member => { if (member.type === "MethodDefinition" && member.kind === "method" && member.accessibility === "public") { validateApiResponseDecorators(member); } }); }, }; }, };