UNPKG

@addon24/eslint-config

Version:

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

191 lines (158 loc) 5.92 kB
/** * @fileoverview Enforce consistent naming conventions for controller methods */ "use strict"; export default { meta: { type: "problem", docs: { description: "enforce consistent naming conventions for controller HTTP methods", category: "Best Practices", recommended: true, }, fixable: null, schema: [], messages: { invalidMethodName: "Controller method '{{methodName}}' does not follow naming conventions. Use: getById (GET with params), getAll (GET without params), create (POST), update (PUT/PATCH), delete (DELETE)", entityInResponse: "Entity-Variable '{{variableName}}' darf nicht direkt in sendSuccess() verwendet werden. Controller müssen Entities zu DTOs konvertieren bevor sie als API-Response zurückgegeben werden.", }, }, create(context) { const filename = context.getFilename(); if (!filename.includes("Controller.ts") || filename.includes("test") || filename.includes("spec")) { return {}; } 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 getHttpMethodType(decorators) { if (!decorators) return null; const httpMethodMap = { "Get": "get", "Post": "post", "Put": "put", "Delete": "delete", "Patch": "patch" }; for (const decorator of decorators) { const decoratorName = decorator.expression?.name || decorator.expression?.callee?.name; if (httpMethodMap[decoratorName]) { let route = ""; let hasParameter = false; if (decorator.expression?.type === "CallExpression" && decorator.expression.arguments.length > 0 && decorator.expression.arguments[0].type === "Literal") { route = decorator.expression.arguments[0].value; hasParameter = route.includes(":"); } return { type: httpMethodMap[decoratorName], hasParameter, route, isSubResource: route.includes("/") && route.includes(":") }; } } return null; } function validateMethodName(methodName, httpMethod) { const conventions = { get: { withParam: "getById", withoutParam: "getAll" }, post: "create", put: "update", delete: "delete", patch: "update" }; // Allow flexible naming for sub-resource routes (e.g., ":id/effects", ":id/assignments") if (httpMethod.isSubResource) { return true; } if (httpMethod.type === "get") { const expectedName = httpMethod.hasParameter ? conventions.get.withParam : conventions.get.withoutParam; return methodName === expectedName; } return methodName === conventions[httpMethod.type]; } function getExpectedName(httpMethod) { if (httpMethod.type === "get") { return httpMethod.hasParameter ? "getById" : "getAll"; } const expectedNames = { post: "create", put: "update", delete: "delete", patch: "update" }; return expectedNames[httpMethod.type]; } // Entity-Pattern erkennen function isEntityPattern(variableName) { return ( variableName.endsWith("Definition") || variableName.endsWith("Entity") || variableName.endsWith("Model") || variableName.endsWith("Record") || variableName.endsWith("Instance") || variableName.startsWith("new") || variableName.includes("Entity") || variableName.includes("Definition") ); } return { ClassDeclaration(node) { if (!isControllerClass(node)) return; node.body.body.forEach(member => { if (member.type === "MethodDefinition" && member.kind === "method" && member.accessibility === "public") { const httpMethod = getHttpMethodType(member.decorators); if (!httpMethod) return; const methodName = member.key.name; if (!validateMethodName(methodName, httpMethod)) { const expectedName = getExpectedName(httpMethod); context.report({ node: member, message: `Controller method '${methodName}' must be named '${expectedName}' for ${httpMethod.type.toUpperCase()} methods. Use consistent naming across all controllers.`, }); } } }); }, // Prüfe Entity-Patterns in sendSuccess CallExpression(node) { if (node.callee?.type === "MemberExpression" && node.callee.object?.type === "ThisExpression" && node.callee.property?.name === "sendSuccess") { if (node.arguments.length >= 2 && node.arguments[1]?.type === "ObjectExpression") { const responseObject = node.arguments[1]; responseObject.properties.forEach(property => { if (property.type === "Property" && property.value?.type === "Identifier") { const variableName = property.value.name; if (isEntityPattern(variableName)) { context.report({ node: property.value, messageId: "entityInResponse", data: { variableName } }); } } }); } } } }; }, };