@addon24/eslint-config
Version:
ESLint configuration rules for WorldOfTextcraft projects - Centralized configuration for all project types
191 lines (158 loc) • 5.92 kB
JavaScript
/**
* @fileoverview Enforce consistent naming conventions for controller methods
*/
;
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 }
});
}
}
});
}
}
}
};
},
};