@addon24/eslint-config
Version:
ESLint configuration rules for WorldOfTextcraft projects - Centralized configuration for all project types
350 lines (314 loc) • 13 kB
JavaScript
/**
* @fileoverview Erzwingt die Verwendung von ResponseDtos in Controller-Responses
*
* Diese Regel stellt sicher, dass:
* 1. Controller niemals Entities direkt zurückgeben
* 2. Controller immer ResponseDtos verwenden
* 3. EntityDtos nur als Zwischenschritt verwendet werden
* 4. Konsistente API-Response-Struktur
*/
"use strict";
const enforceControllerResponseDtoRule = {
meta: {
type: "problem",
docs: {
description: "Controller müssen ResponseDtos verwenden, nie Entities direkt zurückgeben",
category: "Architecture",
recommended: true,
},
fixable: "code",
hasSuggestions: true,
schema: [],
messages: {
entityDirectlyReturned: "Entity '{{entityName}}' darf nicht direkt in sendSuccess() verwendet werden. Verwende stattdessen ein ResponseDto.",
entityDtoInResponse: "EntityDto '{{entityDtoName}}' sollte nicht direkt in sendSuccess() verwendet werden. Verwende stattdessen ein ResponseDto.",
missingResponseDto: "Controller-Response sollte ein ResponseDto verwenden. Erstelle ein {{suggestedResponseDto}}.",
useResponseDtoPattern: "Verwende das ResponseDto-Pattern: {{responseDtoName}}.create({ {{propertyName}}: {{entityDtoName}}.fromEntity({{entityName}}) })",
},
},
create(context) {
const filename = context.getFilename();
// Nur Controller-Dateien prüfen, außer Tests und BaseController
if (!filename.includes("Controller.ts") ||
filename.includes("test") ||
filename.includes("spec") ||
filename.includes("BaseController")) {
return {};
}
// Import-Map für DTO-Typen basierend auf Pfaden
const importMap = new Map();
// DTO-Typ basierend auf Import-Pfad bestimmen
function getDtoTypeFromImportPath(importPath) {
if (importPath.includes("/dto/Response/")) {
return "ResponseDto";
}
if (importPath.includes("/dto/Entity/")) {
return "EntityDto";
}
if (importPath.includes("/dto/Request/")) {
return "RequestDto";
}
if (importPath.includes("/dto/Config/")) {
return "ConfigDto";
}
if (importPath.includes("/dto/Filter/")) {
return "FilterDto";
}
if (importPath.includes("/dto/Common/")) {
return "CommonDto";
}
return "Unknown";
}
// Variable-Typ basierend auf Import-Map bestimmen
function getVariableType(variableName) {
const importInfo = importMap.get(variableName);
if (importInfo) {
return importInfo.type;
}
// Fallback: Pattern-basierte Erkennung für nicht-importierte Variablen
if (variableName.endsWith("ResponseDto")) {
return "ResponseDto";
}
if (variableName.endsWith("EntityDto") ||
variableName.endsWith("DefinitionDto") ||
variableName === "entityDto" ||
variableName === "userDto" ||
variableName === "gameDto" ||
variableName === "characterDto") {
return "EntityDto";
}
if (variableName.endsWith("RequestDto")) {
return "RequestDto";
}
if (variableName.endsWith("Entity") ||
variableName.endsWith("Definition") ||
variableName.endsWith("Model") ||
variableName.endsWith("Record") ||
variableName.endsWith("Instance") ||
variableName === "entity" ||
variableName === "user" ||
variableName === "game" ||
variableName === "character") {
return "Entity";
}
return "Unknown";
}
// ResponseDto-Namen aus EntityDto-Namen generieren
function generateResponseDtoName(entityDtoName) {
// Spezielle Fälle für bekannte Entity-Namen
if (entityDtoName === "entity" || entityDtoName === "userEntity" || entityDtoName === "userModel") {
return "UserResponseDto";
}
if (entityDtoName === "configRecord") {
return "ConfigResponseDto";
}
if (entityDtoName === "sessionInstance") {
return "SessionResponseDto";
}
if (entityDtoName === "auraDefinition") {
return "AuraDefinitionResponseDto";
}
if (entityDtoName === "auraDefinitionDto") {
return "AuraDefinitionResponseDto";
}
if (entityDtoName === "entityDto") {
return "AuraDefinitionResponseDto";
}
// Generische Fälle
if (entityDtoName.endsWith("EntityDto")) {
return entityDtoName.replace("EntityDto", "ResponseDto");
}
if (entityDtoName.endsWith("DefinitionDto")) {
return entityDtoName.replace("DefinitionDto", "ResponseDto");
}
if (entityDtoName.endsWith("Entity")) {
const baseName = entityDtoName.replace("Entity", "");
return `${baseName.charAt(0).toUpperCase() + baseName.slice(1)}ResponseDto`;
}
if (entityDtoName.endsWith("Definition")) {
return entityDtoName.replace("Definition", "ResponseDto");
}
// Fallback für unbekannte Namen
const baseName = entityDtoName.charAt(0).toUpperCase() + entityDtoName.slice(1);
return `${baseName}ResponseDto`;
}
// EntityDto-Namen aus Entity-Namen generieren
function generateEntityDtoName(entityName) {
// Spezielle Fälle für bekannte Entity-Namen
if (entityName === "entity" || entityName === "userEntity" || entityName === "userModel") {
return "UserEntityDto";
}
if (entityName === "configRecord") {
return "ConfigEntityDto";
}
if (entityName === "sessionInstance") {
return "SessionEntityDto";
}
if (entityName === "auraDefinition") {
return "AuraDefinitionEntityDto";
}
// Generische Fälle
if (entityName.endsWith("Entity")) {
return entityName.replace("Entity", "EntityDto");
}
if (entityName.endsWith("Definition")) {
return entityName.replace("Definition", "EntityDto");
}
// Fallback für unbekannte Namen
const baseName = entityName.charAt(0).toUpperCase() + entityName.slice(1);
return `${baseName}EntityDto`;
}
return {
// Import-Statements sammeln
ImportDeclaration(node) {
const importPath = node.source.value;
const dtoType = getDtoTypeFromImportPath(importPath);
if (dtoType !== "Unknown") {
node.specifiers.forEach(specifier => {
if (specifier.type === "ImportDefaultSpecifier") {
const variableName = specifier.local.name;
importMap.set(variableName, {
type: dtoType,
path: importPath
});
}
});
}
},
// Variable-Declarations sammeln (für importierte DTOs)
VariableDeclarator(node) {
if (node.init && node.init.type === "CallExpression") {
const callExpression = node.init;
if (callExpression.callee?.type === "MemberExpression" &&
callExpression.callee.object?.type === "Identifier") {
const importedDtoName = callExpression.callee.object.name;
const importInfo = importMap.get(importedDtoName);
if (importInfo && importInfo.type === "EntityDto") {
const variableName = node.id.name;
importMap.set(variableName, {
type: "EntityDto",
path: importInfo.path
});
}
}
}
},
CallExpression(node) {
// Prüfe auf this.sendSuccess() Aufrufe
if (node.callee?.type === "MemberExpression" &&
node.callee.object?.type === "ThisExpression" &&
node.callee.property?.name === "sendSuccess") {
// Prüfe das zweite Argument (Response-Objekt)
if (node.arguments.length >= 2) {
const responseArg = node.arguments[1];
// Fall 1: Direktes Entity-Objekt
if (responseArg.type === "ObjectExpression") {
responseArg.properties.forEach(property => {
if (property.type === "Property" && property.value) {
const propertyName = property.key?.name || property.key?.value;
// Prüfe auf Identifier (Variable)
if (property.value.type === "Identifier") {
const variableName = property.value.name;
const variableType = getVariableType(variableName);
// Entity direkt verwendet
if (variableType === "Entity") {
const suggestedEntityDto = generateEntityDtoName(variableName);
const suggestedResponseDto = generateResponseDtoName(suggestedEntityDto);
context.report({
node: property.value,
messageId: "entityDirectlyReturned",
data: { entityName: variableName },
suggest: [{
desc: "Verwende ResponseDto-Pattern",
fix(fixer) {
return fixer.replaceText(
property,
`${propertyName}: ${suggestedResponseDto}.create({ ${propertyName}: ${suggestedEntityDto}.fromEntity(${variableName}) })`
);
}
}]
});
}
// EntityDto direkt verwendet
if (variableType === "EntityDto") {
const suggestedResponseDto = generateResponseDtoName(variableName);
context.report({
node: property.value,
messageId: "entityDtoInResponse",
data: { entityDtoName: variableName },
suggest: [{
desc: "Verwende ResponseDto-Pattern",
fix(fixer) {
return fixer.replaceText(
property,
`${propertyName}: ${suggestedResponseDto}.create({ ${propertyName}: ${variableName} })`
);
}
}]
});
}
}
}
});
}
// Fall 2: Direkte Variable (nicht ResponseDto)
if (responseArg.type === "Identifier") {
const variableName = responseArg.name;
const variableType = getVariableType(variableName);
// Entity direkt verwendet
if (variableType === "Entity") {
const suggestedEntityDto = generateEntityDtoName(variableName);
const suggestedResponseDto = generateResponseDtoName(variableName);
// Spezielle Property-Namen für verschiedene Entity-Typen
let propertyName = "data";
if (variableName === "userEntity") {
propertyName = "userEntity";
} else if (variableName === "auraDefinitionDto") {
propertyName = "auraDefinitionDto";
}
context.report({
node: responseArg,
messageId: "entityDirectlyReturned",
data: { entityName: variableName },
suggest: [{
desc: "Verwende ResponseDto-Pattern",
fix(fixer) {
return fixer.replaceText(
responseArg,
`${suggestedResponseDto}.create({ ${propertyName}: ${suggestedEntityDto}.fromEntity(${variableName}) })`
);
}
}]
});
}
// EntityDto direkt verwendet
if (variableType === "EntityDto") {
const suggestedResponseDto = generateResponseDtoName(variableName);
// Spezielle Property-Namen für verschiedene EntityDto-Typen
let propertyName = "data";
if (variableName === "auraDefinitionDto") {
propertyName = "auraDefinitionDto";
}
context.report({
node: responseArg,
messageId: "entityDtoInResponse",
data: { entityDtoName: variableName },
suggest: [{
desc: "Verwende ResponseDto-Pattern",
fix(fixer) {
return fixer.replaceText(
responseArg,
`${suggestedResponseDto}.create({ ${propertyName}: ${variableName} })`
);
}
}]
});
}
}
}
}
}
};
},
};
export default enforceControllerResponseDtoRule;