rpg-combat-pi-01/eslint/no-primitive-value-properties.js
Willem van den Ende ba0903714c refactor: replace number health with Health value object in MagicalObject
- MagicalObject: internal #health and #maxHealth now Health type
- HealingObject: constructor, create, and heal use Health value object
- MagicalWeapon: constructor, create, and use use Health value object
- DamageDealer and Healer interfaces: health: number -> health: Health
- magical-objects.spec.ts: all assertions use .health.value
- Run npm run checks: 0 errors, 70 tests passing
2026-06-14 13:35:44 +01:00

146 lines
4.3 KiB
JavaScript

import { readdirSync } from 'node:fs';
import { join, parse } from 'node:path';
const VALUE_OBJECTS_DIR = join(import.meta.dirname, '..', 'src', 'value-objects');
function discoverValueObjects() {
const files = readdirSync(VALUE_OBJECTS_DIR).filter((f) => f.endsWith('.ts') && f !== 'index.ts');
return files.map((f) => parse(f).name);
}
const VALUE_OBJECTS = discoverValueObjects();
function buildPropertyMap() {
const map = {};
for (const name of VALUE_OBJECTS) {
const propertyName = name.charAt(0).toLowerCase() + name.slice(1);
map[propertyName] = name;
}
return map;
}
const PROPERTY_MAP = buildPropertyMap();
/** Map ESLint AST type names to the primitive keyword string. */
const PRIMITIVE_TYPE_MAP = {
TSNumberKeyword: 'number',
TSStringKeyword: 'string',
TSBooleanKeyword: 'boolean',
};
function isPrimitiveType(typeNode) {
if (!typeNode) return false;
const keyword = PRIMITIVE_TYPE_MAP[typeNode.type];
return keyword !== undefined;
}
function getPrimitiveKeyword(typeNode) {
if (!typeNode) return null;
return PRIMITIVE_TYPE_MAP[typeNode.type] || null;
}
function getParamName(param) {
if (param.type === 'TSParameterProperty') {
return getParamName(param.parameter);
}
if (param.type === 'Identifier') {
return param.name;
}
return null;
}
export default {
meta: {
type: 'suggestion',
docs: {
description:
'Warn when primitive types are used in class properties or constructor parameters that should be value objects',
recommended: false,
},
schema: [
{
type: 'object',
properties: {
includeConstructorParams: { type: 'boolean', default: true },
includeProperties: { type: 'boolean', default: true },
},
additionalProperties: false,
},
],
messages: {
primitiveProperty:
'Use value object "{{valueObject}}" instead of primitive "{{primitive}}" for property "{{propertyName}}". Available: {{available}}',
primitiveParam:
'Use value object "{{valueObject}}" instead of primitive "{{primitive}}" for parameter "{{paramName}}". Available: {{available}}',
},
},
create(context) {
const options = context.options[0] || {};
const includeConstructorParams = options.includeConstructorParams !== false;
const includeProperties = options.includeProperties !== false;
function reportPrimitive(node, propName, primitiveType, messageId) {
const valueObject = PROPERTY_MAP[propName];
if (valueObject) {
context.report({
node,
messageId,
data: {
valueObject,
primitive: primitiveType,
propertyName: propName,
paramName: propName,
available: VALUE_OBJECTS.join(', '),
},
});
}
}
return {
ClassProperty(node) {
if (!includeProperties) return;
if (!node.typeAnnotation) return;
const typeNode = node.typeAnnotation.typeAnnotation;
if (!isPrimitiveType(typeNode)) return;
reportPrimitive(node, node.key.name, getPrimitiveKeyword(typeNode), 'primitiveProperty');
},
PropertyDefinition(node) {
if (!includeProperties) return;
if (!node.typeAnnotation) return;
const typeNode = node.typeAnnotation.typeAnnotation;
if (!isPrimitiveType(typeNode)) return;
reportPrimitive(node, node.key.name, getPrimitiveKeyword(typeNode), 'primitiveProperty');
},
MethodDefinition(node) {
if (!includeConstructorParams) return;
if (node.key.name !== 'constructor') return;
if (!node.value.params) return;
for (const param of node.value.params) {
let paramName = null;
let typeNode = null;
if (param.type === 'TSParameterProperty') {
paramName = getParamName(param.parameter);
typeNode = param.parameter.typeAnnotation?.typeAnnotation;
} else if (param.type === 'Identifier') {
paramName = param.name;
typeNode = param.typeAnnotation?.typeAnnotation;
}
if (!paramName || !typeNode || !isPrimitiveType(typeNode)) continue;
reportPrimitive(param, paramName, getPrimitiveKeyword(typeNode), 'primitiveParam');
}
},
};
},
};