@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
JavaScript
"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