@addon24/eslint-config
Version:
ESLint configuration rules for WorldOfTextcraft projects - Centralized configuration for all project types
138 lines (121 loc) • 4.9 kB
JavaScript
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"
},
});
}
}
}
},
};
},
},
},
};