UNPKG

@addon24/eslint-config

Version:

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

138 lines (121 loc) 4.9 kB
export default { rules: { "enforce-entity-creation-pattern": { meta: { type: "problem", docs: { description: "Enforce entity creation pattern: use DTOs and convert with .toEntity() before saving", category: "Best Practices", recommended: true, }, fixable: null, schema: [], messages: { directEntityCreation: "Direct entity creation with 'new {{entityName}}()' is not allowed. Use DTOs and convert with .toEntity() instead.", missingToEntityConversion: "Entity must be converted with .toEntity() before saving. Use '{{dtoName}}.toEntity({{dtoVariable}})' instead of direct entity creation.", invalidEntityManagerUsage: "entityManager.create() should only be used with DTOs that have been converted with .toEntity().", }, }, create(context) { const entityDtos = new Map(); const entityManagerCalls = new Map(); function isEntityDtoFile(filename) { return (filename.includes("/dto/Entity/") || filename.includes("test-fixtures")) && (filename.endsWith("EntityDto.ts") || filename.includes("EntityDto")); } function getEntityNameFromDto(dtoName) { if (dtoName.endsWith("EntityDto")) { return dtoName.replace("EntityDto", "Entity"); } return dtoName; } function isInsideToEntityMethod(node) { let parent = node.parent; while (parent) { if (parent.type === "MethodDefinition" || parent.type === "Property") { if (parent.key && parent.key.name === "toEntity") { return true; } } parent = parent.parent; } return false; } function isEntityManagerCall(node) { if (node.type === "CallExpression" && node.callee) { if (node.callee.type === "MemberExpression") { const objectName = node.callee.object.name; const methodName = node.callee.property.name; // Check for entityManager.create, transactionalEntityManager.create, etc. if ((objectName === "entityManager" || objectName === "transactionalEntityManager" || objectName === "manager") && methodName === "create") { return true; } } } return false; } return { ClassDeclaration(node) { const filename = context.getFilename(); const className = node.id.name; if (isEntityDtoFile(filename) && className.endsWith("EntityDto")) { entityDtos.set(className, { node, hasToEntityMethod: node.body.body.some(member => member.type === "MethodDefinition" && member.key.name === "toEntity" && member.static ), }); } }, NewExpression(node) { const filename = context.getFilename(); const entityName = node.callee.name; if (entityName && entityName.endsWith("Entity")) { // Allow entity creation inside toEntity methods if (isInsideToEntityMethod(node)) { return; } // Check if this is a direct entity creation outside of toEntity const dtoName = entityName.replace("Entity", "EntityDto"); const dtoInfo = entityDtos.get(dtoName); if (dtoInfo) { context.report({ node, messageId: "directEntityCreation", data: { entityName }, }); } } }, CallExpression(node) { if (isEntityManagerCall(node)) { // Check if the second argument is a direct entity creation if (node.arguments && node.arguments.length >= 2) { const secondArg = node.arguments[1]; if (secondArg.type === "NewExpression" && secondArg.callee.name && secondArg.callee.name.endsWith("Entity")) { const entityName = secondArg.callee.name; const dtoName = entityName.replace("Entity", "EntityDto"); context.report({ node: secondArg, messageId: "missingToEntityConversion", data: { dtoName, dtoVariable: "dtoData" }, }); } } } }, }; }, }, }, };