UNPKG

@samiyev/guardian

Version:

Research-backed code quality guardian for AI-assisted development. Detects hardcodes, secrets, circular deps, framework leaks, entity exposure, and 9 architecture violations. Enforces Clean Architecture/DDD principles. Works with GitHub Copilot, Cursor, W

206 lines (190 loc) 6.49 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.AnemicModelViolation = void 0; const ValueObject_1 = require("./ValueObject"); const Messages_1 = require("../constants/Messages"); const constants_1 = require("../../shared/constants"); /** * Represents an anemic domain model violation in the codebase * * Anemic domain model occurs when entities have only getters/setters * without business logic. This violates Domain-Driven Design principles * and leads to procedural code instead of object-oriented design. * * @example * ```typescript * // Bad: Anemic model with only getters/setters * const violation = AnemicModelViolation.create( * 'Order', * 'src/domain/entities/Order.ts', * 'domain', * 10, * 4, * 2, * true, * true * ) * * console.log(violation.getMessage()) * // "Class 'Order' is anemic: 4 methods (all getters/setters) for 2 properties" * ``` */ class AnemicModelViolation extends ValueObject_1.ValueObject { constructor(props) { super(props); } static create(className, filePath, layer, line, methodCount, propertyCount, hasOnlyGettersSetters, hasPublicSetters) { return new AnemicModelViolation({ className, filePath, layer, line, methodCount, propertyCount, hasOnlyGettersSetters, hasPublicSetters, }); } get className() { return this.props.className; } get filePath() { return this.props.filePath; } get layer() { return this.props.layer; } get line() { return this.props.line; } get methodCount() { return this.props.methodCount; } get propertyCount() { return this.props.propertyCount; } get hasOnlyGettersSetters() { return this.props.hasOnlyGettersSetters; } get hasPublicSetters() { return this.props.hasPublicSetters; } getMessage() { if (this.props.hasPublicSetters) { return `Class '${this.props.className}' has public setters (anti-pattern in DDD)`; } if (this.props.hasOnlyGettersSetters) { return `Class '${this.props.className}' is anemic: ${String(this.props.methodCount)} methods (all getters/setters) for ${String(this.props.propertyCount)} properties`; } const ratio = this.props.methodCount / Math.max(this.props.propertyCount, 1); return `Class '${this.props.className}' appears anemic: low method-to-property ratio (${ratio.toFixed(1)}:1)`; } getSuggestion() { const suggestions = []; if (this.props.hasPublicSetters) { suggestions.push(Messages_1.ANEMIC_MODEL_MESSAGES.REMOVE_PUBLIC_SETTERS); suggestions.push(Messages_1.ANEMIC_MODEL_MESSAGES.USE_METHODS_FOR_CHANGES); suggestions.push(Messages_1.ANEMIC_MODEL_MESSAGES.ENCAPSULATE_INVARIANTS); } if (this.props.hasOnlyGettersSetters || this.props.methodCount < 2) { suggestions.push(Messages_1.ANEMIC_MODEL_MESSAGES.ADD_BUSINESS_METHODS); suggestions.push(Messages_1.ANEMIC_MODEL_MESSAGES.MOVE_LOGIC_FROM_SERVICES); suggestions.push(Messages_1.ANEMIC_MODEL_MESSAGES.ENCAPSULATE_BUSINESS_RULES); suggestions.push(Messages_1.ANEMIC_MODEL_MESSAGES.USE_DOMAIN_EVENTS); } return suggestions.join("\n"); } getExampleFix() { if (this.props.hasPublicSetters) { return ` // ❌ Bad: Public setters allow uncontrolled state changes class ${this.props.className} { private status: string public setStatus(status: string): void { this.status = status // No validation! } public getStatus(): string { return this.status } } // ✅ Good: Business methods with validation class ${this.props.className} { private status: OrderStatus public approve(): void { if (!this.canBeApproved()) { throw new CannotApproveOrderError() } this.status = OrderStatus.APPROVED this.events.push(new OrderApprovedEvent(this.id)) } public reject(reason: string): void { if (!this.canBeRejected()) { throw new CannotRejectOrderError() } this.status = OrderStatus.REJECTED this.rejectionReason = reason this.events.push(new OrderRejectedEvent(this.id, reason)) } public getStatus(): OrderStatus { return this.status } private canBeApproved(): boolean { return this.status === OrderStatus.PENDING && this.hasItems() } }`; } return ` // ❌ Bad: Anemic model (only getters/setters) class ${this.props.className} { getStatus() { return this.status } setStatus(status: string) { this.status = status } getTotal() { return this.total } setTotal(total: number) { this.total = total } } class OrderService { approve(order: ${this.props.className}): void { if (order.getStatus() !== '${constants_1.EXAMPLE_CODE_CONSTANTS.ORDER_STATUS_PENDING}') { throw new Error('${constants_1.EXAMPLE_CODE_CONSTANTS.CANNOT_APPROVE_ERROR}') } order.setStatus('${constants_1.EXAMPLE_CODE_CONSTANTS.ORDER_STATUS_APPROVED}') } } // ✅ Good: Rich domain model with business logic class ${this.props.className} { private readonly id: OrderId private status: OrderStatus private items: OrderItem[] private events: DomainEvent[] = [] public approve(): void { if (!this.isPending()) { throw new CannotApproveOrderError() } this.status = OrderStatus.APPROVED this.events.push(new OrderApprovedEvent(this.id)) } public calculateTotal(): Money { return this.items.reduce( (sum, item) => sum.add(item.getPrice()), Money.zero() ) } public addItem(item: OrderItem): void { if (this.isApproved()) { throw new CannotModifyApprovedOrderError() } this.items.push(item) } public getStatus(): OrderStatus { return this.status } private isPending(): boolean { return this.status === OrderStatus.PENDING } private isApproved(): boolean { return this.status === OrderStatus.APPROVED } }`; } } exports.AnemicModelViolation = AnemicModelViolation; //# sourceMappingURL=AnemicModelViolation.js.map