UNPKG

@addon24/eslint-config

Version:

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

112 lines (100 loc) 4.5 kB
export default { rules: { "dto-annotation-property-consistency": { meta: { type: "problem", docs: { description: "Ensure consistency between DTO annotations and property optionality", category: "Best Practices", recommended: true, }, messages: { inconsistentOptionality: "Property '{{propName}}' has inconsistent optionality: {{annotation}} but property is {{propertyType}}", inconsistentRequired: "Property '{{propName}}' has inconsistent required setting: {{annotation}} but property is {{propertyType}}", }, schema: [], }, create(context) { return { ClassDeclaration(node) { const rawFilename = context.getFilename(); const filename = typeof rawFilename === 'string' ? rawFilename.replace(/\\/g, '/') : ''; // Nur für DTO-Dateien anwenden (auch in Test-Fixtures) const isDtoPath = filename.toLowerCase().includes('/dto/'); const isDtoName = filename.endsWith('Dto.ts'); const isFixtureDto = filename.includes('/__tests__/fixtures/'); if (!(isDtoPath || isDtoName || isFixtureDto)) { return; } // Durchlaufe alle Properties der Klasse for (const member of node.body.body) { if (member.type === 'PropertyDefinition') { const propName = member.key.name; const decorators = member.decorators || []; // Debug-Logging entfernt für saubere JSON-Ausgabe // Finde @IsOptional und @ApiProperty Decorators let hasIsOptional = false; let apiPropertyRequired = null; for (const decorator of decorators) { if (decorator.expression.type === 'CallExpression') { const callee = decorator.expression.callee; // Prüfe @IsOptional if (callee.type === 'Identifier' && callee.name === 'IsOptional') { hasIsOptional = true; } // Prüfe @ApiProperty if (callee.type === 'Identifier' && callee.name === 'ApiProperty') { const args = decorator.expression.arguments; if (args.length > 0 && args[0].type === 'ObjectExpression') { for (const prop of args[0].properties) { if (prop.key.name === 'required' && prop.value.type === 'Literal') { apiPropertyRequired = prop.value.value; } } } } } else if (decorator.expression.type === 'Identifier') { // Prüfe @IsOptional ohne Argumente if (decorator.expression.name === 'IsOptional') { hasIsOptional = true; } } } // Prüfe Property-Optionalität const isPropertyOptional = member.optional || false; const propertyType = isPropertyOptional ? 'optional (?)' : 'required'; // Prüfe Konsistenz zwischen @IsOptional und Property-Optionalität if (hasIsOptional && !isPropertyOptional) { context.report({ node: member, messageId: 'inconsistentOptionality', data: { propName, annotation: '@IsOptional()', propertyType, }, }); } // Prüfe Konsistenz zwischen required: false/true und Property-Optionalität if (apiPropertyRequired !== null) { const expectedOptionality = !apiPropertyRequired; if (expectedOptionality !== isPropertyOptional) { context.report({ node: member, messageId: 'inconsistentRequired', data: { propName, annotation: `required: ${apiPropertyRequired}`, propertyType, }, }); } } } } }, }; }, }, }, };