UNPKG

@addon24/eslint-config

Version:

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

350 lines (314 loc) 13 kB
/** * @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;