@addon24/eslint-config
Version:
ESLint configuration rules for WorldOfTextcraft projects - Centralized configuration for all project types
158 lines (134 loc) • 4.92 kB
JavaScript
export default {
meta: {
type: "problem",
docs: {
description: "Enforce that Entity DTOs use convertEntityToDto in fromEntity method",
category: "Best Practices",
recommended: true,
},
messages: {
missingConvertImport: "Entity-DTO '{{className}}' muss convertEntityToDto importieren: import { convertEntityToDto } from '@/dto/BaseEntityDto'",
fromEntityMustUseConvert: "fromEntity-Methode in '{{className}}' muss 'return convertEntityToDto(...)' verwenden, um zirkuläre Abhängigkeiten zu vermeiden",
invalidConvertStructure: "convertEntityToDto in '{{className}}' muss 3 Argumente haben: (entity, factory, populate)",
factoryMustBeArrowFunction: "Zweites Argument von convertEntityToDto muss eine Arrow-Function sein: () => new {{className}}()",
populateMustBeArrowFunction: "Drittes Argument von convertEntityToDto muss eine Arrow-Function sein: (dto, entity) => {{ ... }}",
},
fixable: null,
schema: [],
},
create (context) {
const filename = context.getFilename();
// Skip non-DTO files
if (!filename.includes("/dto/Entity/")) {
return {};
}
// Skip BaseEntityDto itself
if (filename.endsWith("/BaseEntityDto.ts")) {
return {};
}
// Skip test files
if (filename.includes(".test.") || filename.includes(".spec.") || filename.includes("__tests__")) {
return {};
}
let hasConvertImport = false;
let className = null;
return {
ImportDeclaration (node) {
// Check for convertEntityToDto import (absolute or relative)
const isBaseImport = node.source.value === "@/dto/BaseEntityDto" ||
node.source.value.endsWith("/BaseEntityDto") ||
node.source.value === "./BaseEntityDto";
if (isBaseImport) {
const hasConvert = node.specifiers.some(
(spec) => spec.type === "ImportSpecifier" && spec.imported.name === "convertEntityToDto",
);
if (hasConvert) {
hasConvertImport = true;
}
}
},
ClassDeclaration (node) {
// Only check Entity DTOs
if (!node.id || !node.id.name.endsWith("EntityDto")) {
return;
}
className = node.id.name;
},
MethodDefinition (node) {
// Only check static fromEntity methods
if (!node.static || !node.key || node.key.name !== "fromEntity") {
return;
}
// Check if import exists
if (!hasConvertImport) {
context.report({
node,
messageId: "missingConvertImport",
data: { className },
});
return;
}
// Check if method body uses convertEntityToDto
const methodBody = node.value.body;
if (!methodBody || methodBody.type !== "BlockStatement") {
return;
}
// Find return statement
const returnStatement = methodBody.body.find((stmt) => stmt.type === "ReturnStatement");
if (!returnStatement || !returnStatement.argument) {
return;
}
// Check if return statement calls convertEntityToDto
const returnArg = returnStatement.argument;
if (
returnArg.type !== "CallExpression"
|| !returnArg.callee
|| returnArg.callee.name !== "convertEntityToDto"
) {
context.report({
node: returnStatement,
messageId: "fromEntityMustUseConvert",
data: { className },
});
return;
}
// Validate convertEntityToDto arguments
const args = returnArg.arguments;
if (args.length !== 3) {
context.report({
node: returnArg,
messageId: "invalidConvertStructure",
data: { className },
});
return;
}
// Validate factory function (second argument)
const factoryArg = args[1];
if (factoryArg.type !== "ArrowFunctionExpression") {
context.report({
node: factoryArg,
messageId: "factoryMustBeArrowFunction",
data: { className },
});
}
// Validate populate function (third argument)
const populateArg = args[2];
if (populateArg.type !== "ArrowFunctionExpression") {
context.report({
node: populateArg,
messageId: "populateMustBeArrowFunction",
data: { className },
});
}
// Validate populate function has 2 parameters (dto, entity)
if (populateArg.type === "ArrowFunctionExpression" && populateArg.params.length !== 2) {
context.report({
node: populateArg,
messageId: "populateMustBeArrowFunction",
data: { className },
});
}
},
};
},
};