@addon24/eslint-config
Version:
ESLint configuration rules for WorldOfTextcraft projects - Centralized configuration for all project types
165 lines (149 loc) • 5.64 kB
JavaScript
/**
* ESLint-Regel: no-dto-default-values
* Verbietet Default Values in DTO-Klassen
* Verbesserte Version für TypeScript
*/
/** @type {import('eslint').Rule.RuleModule} */
const noDtoDefaultValuesRule = {
meta: {
type: "problem",
docs: {
description: "DTO-Klassen dürfen keine Default Values haben",
category: "Best Practices",
recommended: true,
},
fixable: "code",
schema: [],
messages: {
noDefaultValue: "DTO property '{{propertyName}}' darf keinen Default Value haben. Verwende stattdessen explizite Typ-Annotation ohne Initialisierung.",
noDefaultValueInConstructor: "DTO-Properties dürfen nicht im Constructor initialisiert werden. Verwende stattdessen die create()-Methode.",
},
},
create(context) {
function isDtoClass(node) {
return node.id && node.id.name && node.id.name.endsWith('Dto');
}
function hasDefaultValue(member) {
return member.value !== null && member.value !== undefined;
}
function isPrivateProperty(propertyName) {
return propertyName.startsWith('_');
}
function inferTypeFromValue(value) {
if (!value) return "unknown";
switch (value.type) {
case "Literal":
if (typeof value.value === "string") return "string";
if (typeof value.value === "number") return "number";
if (typeof value.value === "boolean") return "boolean";
return "unknown";
case "ArrayExpression": return "any[]";
case "ObjectExpression": return "object";
case "NewExpression": return "object";
case "Identifier":
if (value.name === "true" || value.name === "false") return "boolean";
if (value.name === "null") return "null";
if (value.name === "undefined") return "undefined";
return "unknown";
default: return "unknown";
}
}
function checkPropertyForDefaultValue(member) {
if (!member.key?.name) return;
const propertyName = member.key.name;
// Überspringe private Properties und statische Properties
if (isPrivateProperty(propertyName) || member.static) {
return;
}
// Prüfe ob Default Value vorhanden ist
if (hasDefaultValue(member)) {
context.report({
node: member,
messageId: "noDefaultValue",
data: { propertyName },
fix(fixer) {
if (member.typeAnnotation) {
// Hat bereits Type Annotation, entferne nur den Default Value
const valueStart = member.value.range[0];
// Finde das "=" Zeichen vor dem Wert
let equalPos = valueStart - 1;
const sourceCode = context.getSourceCode();
const text = sourceCode.getText();
while (equalPos > 0 && text[equalPos] !== '=') {
equalPos--;
}
// Entferne alles von "=" bis zum Ende des Wertes (inklusive Whitespace)
return fixer.removeRange([equalPos - 1, member.value.range[1]]);
} else {
// Keine Type Annotation, infere Typ und ersetze
const inferredType = inferTypeFromValue(member.value);
if (inferredType !== "unknown") {
return fixer.replaceText(member, `${propertyName}: ${inferredType};`);
}
}
return null;
},
});
}
}
return {
ClassDeclaration(node) {
if (!isDtoClass(node)) return;
node.body.body.forEach(member => {
// Unterstütze verschiedene Property-Typen für TypeScript
if ((member.type === "PropertyDefinition" ||
member.type === "ClassProperty" ||
member.type === "TSPropertySignature" ||
member.type === "MethodDefinition") &&
member.key?.name &&
member.kind !== "method" &&
member.kind !== "constructor") {
checkPropertyForDefaultValue(member);
}
});
},
ClassExpression(node) {
if (!isDtoClass(node)) return;
node.body.body.forEach(member => {
if ((member.type === "PropertyDefinition" ||
member.type === "ClassProperty" ||
member.type === "TSPropertySignature" ||
member.type === "MethodDefinition") &&
member.key?.name &&
member.kind !== "method" &&
member.kind !== "constructor") {
checkPropertyForDefaultValue(member);
}
});
},
MethodDefinition(node) {
if (node.kind === "constructor") {
const classNode = node.parent.parent;
if (!isDtoClass(classNode)) return;
if (node.value.body) {
node.value.body.body.forEach(stmt => {
if (stmt.type === "ExpressionStatement" &&
stmt.expression.type === "AssignmentExpression" &&
stmt.expression.left.type === "MemberExpression" &&
stmt.expression.left.object.type === "ThisExpression") {
const propertyName = stmt.expression.left.property.name;
if (!isPrivateProperty(propertyName)) {
context.report({
node: stmt,
messageId: "noDefaultValueInConstructor",
data: { propertyName },
});
}
}
});
}
}
},
};
},
};
export default {
rules: {
"no-dto-default-values": noDtoDefaultValuesRule,
},
};