@addon24/eslint-config
Version:
ESLint configuration rules for WorldOfTextcraft projects - Centralized configuration for all project types
142 lines (128 loc) • 4.87 kB
JavaScript
const enforceBodyRequestDtoRule = {
meta: {
type: "problem",
docs: {
description: "Enforces that DTOs used in @Body() decorators must be RequestDto",
category: "Architecture",
recommended: true,
},
schema: [],
messages: {
bodyMustBeRequestDto: "DTO '{{dtoName}}' in @Body() must be a RequestDto. Rename to '{{suggestedName}}' or move to /dto/Request/ folder.",
bodyMustEndWithRequestDto: "DTO '{{dtoName}}' in @Body() must end with 'RequestDto' suffix.",
},
},
create(context) {
const checkBodyParameter = (param, node) => {
if (!param.typeAnnotation) return;
// Extrahiere den DTO-Namen aus dem Type
const typeAnnotation = param.typeAnnotation.typeAnnotation;
let dtoName = null;
if (typeAnnotation.type === "TSTypeReference") {
// Prüfe, ob es ein Partial<T> Type ist
if (typeAnnotation.typeName.type === "TSQualifiedName" &&
typeAnnotation.typeName.left.name === "Partial" &&
typeAnnotation.typeParameters) {
const innerType = typeAnnotation.typeParameters.params[0];
if (innerType.type === "TSTypeReference") {
dtoName = innerType.typeName.name;
}
} else if (typeAnnotation.typeName.name === "Partial" && typeAnnotation.typeParameters) {
// Fallback für einfache Partial<T> Syntax
const innerType = typeAnnotation.typeParameters.params[0];
if (innerType.type === "TSTypeReference") {
dtoName = innerType.typeName.name;
}
} else {
dtoName = typeAnnotation.typeName.name;
}
} else if (typeAnnotation.type === "TSUnionType") {
// Für Union Types wie "Partial<CreateUserRequestDto>"
const unionTypes = typeAnnotation.types;
const partialType = unionTypes.find(t =>
t.type === "TSTypeReference" &&
t.typeName.type === "TSQualifiedName" &&
t.typeName.left.name === "Partial"
);
if (partialType && partialType.typeParameters) {
const innerType = partialType.typeParameters.params[0];
if (innerType.type === "TSTypeReference") {
dtoName = innerType.typeName.name;
}
}
}
if (!dtoName) return;
// Skip built-in types
if (["Partial", "Record", "Array", "Promise", "Response", "Request"].includes(dtoName)) {
return;
}
// Prüfe, ob es ein RequestDto ist
const isRequestDto = dtoName.endsWith("RequestDto");
const isInRequestFolder = context.getFilename().includes("/dto/Request/");
if (!isRequestDto) {
if (isInRequestFolder) {
// DTO ist im Request-Ordner aber hat falschen Namen
context.report({
node: node,
messageId: "bodyMustEndWithRequestDto",
data: { dtoName: dtoName },
});
} else {
// DTO ist nicht im Request-Ordner und hat falschen Namen
let suggestedName = dtoName;
if (dtoName.endsWith("ResponseDto")) {
suggestedName = dtoName.replace("ResponseDto", "RequestDto");
} else if (dtoName.endsWith("Dto")) {
suggestedName = dtoName.replace("Dto", "RequestDto");
} else {
suggestedName = dtoName + "RequestDto";
}
context.report({
node: node,
messageId: "bodyMustBeRequestDto",
data: {
dtoName: dtoName,
suggestedName: suggestedName
},
});
}
}
};
return {
MethodDefinition(node) {
// Finde Parameter mit @Body() Decorators
if (!node.value || !node.value.params) return;
node.value.params.forEach(param => {
const paramDecorators = param.decorators || [];
const bodyDecorator = paramDecorators.find(decorator => {
if (decorator.expression.type === "CallExpression") {
return decorator.expression.callee.name === "Body";
}
return decorator.expression.name === "Body";
});
if (bodyDecorator) {
checkBodyParameter(param, param);
}
});
},
TSParameterProperty(node) {
// Prüfe Parameter mit Decorators
const decorators = node.decorators || [];
const bodyDecorator = decorators.find(decorator => {
if (decorator.expression.type === "CallExpression") {
return decorator.expression.callee.name === "Body";
}
return decorator.expression.name === "Body";
});
if (bodyDecorator) {
checkBodyParameter(node.parameter, node.parameter);
}
},
};
},
};
export default {
rules: {
'enforce-body-request-dto': enforceBodyRequestDtoRule,
},
};