@addon24/eslint-config
Version:
ESLint configuration rules for WorldOfTextcraft projects - Centralized configuration for all project types
148 lines (129 loc) • 5.16 kB
JavaScript
/**
* @fileoverview Enforce 1:1 type matching for DTO create method parameters.
* @author Addon24
*/
import { AST_NODE_TYPES } from '@typescript-eslint/utils';
export default {
meta: {
type: "problem",
docs: {
description: "Enforce that DTO create method parameters match the DTO's properties 1:1 regarding optionality.",
category: "Possible Errors",
recommended: true,
},
messages: {
parameterOptionalityMismatch: "Parameter '{{paramName}}' in create method has optionality mismatch with DTO property '{{propertyName}}'. DTO property is '{{dtoOptionality}}', but parameter is '{{paramOptionality}}'.",
propertyNotFound: "Parameter '{{paramName}}' in create method does not correspond to a property in the DTO.",
},
schema: [],
},
create(context) {
/**
* Extrahiert Properties aus einer Klasse
*/
function getClassProperties(classNode) {
const properties = new Map();
for (const member of classNode.body.body) {
if (member.type === AST_NODE_TYPES.PropertyDefinition) {
const propertyName = member.key.name;
const isOptional = member.optional || false;
properties.set(propertyName, { isOptional });
}
}
return properties;
}
/**
* Extrahiert Properties aus einem Type Literal
*/
function getTypeLiteralProperties(typeLiteral) {
const properties = new Map();
for (const member of typeLiteral.members) {
if (member.type === AST_NODE_TYPES.TSPropertySignature) {
const propertyName = member.key.name;
const isOptional = member.optional || false;
properties.set(propertyName, { isOptional });
}
}
return properties;
}
return {
MethodDefinition(node) {
if (
node.kind === 'method' &&
node.static &&
node.key.type === AST_NODE_TYPES.Identifier &&
node.key.name === 'create' &&
node.parent.type === AST_NODE_TYPES.ClassBody &&
node.parent.parent.type === AST_NODE_TYPES.ClassDeclaration
) {
const classNode = node.parent.parent;
const classProperties = getClassProperties(classNode);
// Find the 'data' parameter (with or without default value)
const dataParam = node.value.params.find(
(param) => {
if (param.type === AST_NODE_TYPES.Identifier && param.name === 'data') {
return true;
}
// Handle default parameters: AssignmentPattern with Identifier
if (param.type === AST_NODE_TYPES.AssignmentPattern &&
param.left.type === AST_NODE_TYPES.Identifier &&
param.left.name === 'data') {
return true;
}
return false;
}
);
if (!dataParam) {
return; // 'data' parameter not found
}
// Get type annotation from the parameter (handle both regular and default parameters)
let typeAnnotation;
if (dataParam.type === AST_NODE_TYPES.Identifier) {
typeAnnotation = dataParam.typeAnnotation;
} else if (dataParam.type === AST_NODE_TYPES.AssignmentPattern) {
typeAnnotation = dataParam.left.typeAnnotation;
}
if (!typeAnnotation?.typeAnnotation || typeAnnotation.typeAnnotation.type !== AST_NODE_TYPES.TSTypeLiteral) {
return; // type annotation not found or not an object literal
}
const dataTypeLiteral = typeAnnotation.typeAnnotation;
const dataProperties = getTypeLiteralProperties(dataTypeLiteral);
// Check each parameter property against class properties
for (const [paramName, paramInfo] of dataProperties) {
const classProperty = classProperties.get(paramName);
if (!classProperty) {
context.report({
node: dataTypeLiteral,
messageId: "propertyNotFound",
data: { paramName },
});
continue;
}
if (paramInfo.isOptional !== classProperty.isOptional) {
context.report({
node: dataTypeLiteral,
messageId: "parameterOptionalityMismatch",
data: {
paramName,
propertyName: paramName,
dtoOptionality: classProperty.isOptional ? 'optional' : 'required',
paramOptionality: paramInfo.isOptional ? 'optional' : 'required',
},
});
}
}
// Check if all required class properties are present in parameters
for (const [className, classProperty] of classProperties) {
if (!classProperty.isOptional && !dataProperties.has(className)) {
context.report({
node: dataTypeLiteral,
messageId: "propertyNotFound",
data: { paramName: className },
});
}
}
}
},
};
},
};