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

232 lines (227 loc) 9.55 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.RepositoryViolation = void 0; const ValueObject_1 = require("./ValueObject"); const rules_1 = require("../../shared/constants/rules"); const Messages_1 = require("../constants/Messages"); /** * Represents a Repository Pattern violation in the codebase * * Repository Pattern violations occur when: * 1. Repository interfaces contain ORM-specific types * 2. Use cases depend on concrete repository implementations instead of interfaces * 3. Repositories are instantiated with 'new' in use cases * 4. Repository methods use technical names instead of domain language * * @example * ```typescript * // Violation: ORM type in interface * const violation = RepositoryViolation.create( * 'orm-type-in-interface', * 'src/domain/repositories/IUserRepository.ts', * 'domain', * 15, * 'Repository interface uses Prisma-specific type', * 'Prisma.UserWhereInput' * ) * ``` */ class RepositoryViolation extends ValueObject_1.ValueObject { constructor(props) { super(props); } static create(violationType, filePath, layer, line, details, ormType, repositoryName, methodName) { return new RepositoryViolation({ violationType, filePath, layer, line, details, ormType, repositoryName, methodName, }); } get violationType() { return this.props.violationType; } get filePath() { return this.props.filePath; } get layer() { return this.props.layer; } get line() { return this.props.line; } get details() { return this.props.details; } get ormType() { return this.props.ormType; } get repositoryName() { return this.props.repositoryName; } get methodName() { return this.props.methodName; } getMessage() { switch (this.props.violationType) { case rules_1.REPOSITORY_VIOLATION_TYPES.ORM_TYPE_IN_INTERFACE: return `Repository interface uses ORM-specific type '${this.props.ormType || Messages_1.VIOLATION_EXAMPLE_VALUES.UNKNOWN}'. Domain should not depend on infrastructure concerns.`; case rules_1.REPOSITORY_VIOLATION_TYPES.CONCRETE_REPOSITORY_IN_USE_CASE: return `Use case depends on concrete repository '${this.props.repositoryName || Messages_1.VIOLATION_EXAMPLE_VALUES.UNKNOWN}' instead of interface. Use dependency inversion.`; case rules_1.REPOSITORY_VIOLATION_TYPES.NEW_REPOSITORY_IN_USE_CASE: return `Use case creates repository with 'new ${this.props.repositoryName || "Repository"}()'. Use dependency injection instead.`; case rules_1.REPOSITORY_VIOLATION_TYPES.NON_DOMAIN_METHOD_NAME: return `Repository method '${this.props.methodName || Messages_1.VIOLATION_EXAMPLE_VALUES.UNKNOWN}' uses technical name. Use domain language instead.`; default: return `Repository pattern violation: ${this.props.details}`; } } getSuggestion() { switch (this.props.violationType) { case rules_1.REPOSITORY_VIOLATION_TYPES.ORM_TYPE_IN_INTERFACE: return this.getOrmTypeSuggestion(); case rules_1.REPOSITORY_VIOLATION_TYPES.CONCRETE_REPOSITORY_IN_USE_CASE: return this.getConcreteRepositorySuggestion(); case rules_1.REPOSITORY_VIOLATION_TYPES.NEW_REPOSITORY_IN_USE_CASE: return this.getNewRepositorySuggestion(); case rules_1.REPOSITORY_VIOLATION_TYPES.NON_DOMAIN_METHOD_NAME: return this.getNonDomainMethodSuggestion(); default: return Messages_1.REPOSITORY_PATTERN_MESSAGES.DEFAULT_SUGGESTION; } } getOrmTypeSuggestion() { return [ Messages_1.REPOSITORY_PATTERN_MESSAGES.STEP_REMOVE_ORM_TYPES, Messages_1.REPOSITORY_PATTERN_MESSAGES.STEP_USE_DOMAIN_TYPES, Messages_1.REPOSITORY_PATTERN_MESSAGES.STEP_KEEP_CLEAN, "", Messages_1.REPOSITORY_PATTERN_MESSAGES.EXAMPLE_PREFIX, Messages_1.REPOSITORY_PATTERN_MESSAGES.BAD_ORM_EXAMPLE, Messages_1.REPOSITORY_PATTERN_MESSAGES.GOOD_DOMAIN_EXAMPLE, ].join("\n"); } getConcreteRepositorySuggestion() { return [ Messages_1.REPOSITORY_PATTERN_MESSAGES.STEP_DEPEND_ON_INTERFACE, Messages_1.REPOSITORY_PATTERN_MESSAGES.STEP_MOVE_TO_INFRASTRUCTURE, Messages_1.REPOSITORY_PATTERN_MESSAGES.STEP_USE_DI, "", Messages_1.REPOSITORY_PATTERN_MESSAGES.EXAMPLE_PREFIX, `❌ Bad: constructor(private repo: ${this.props.repositoryName || Messages_1.VIOLATION_EXAMPLE_VALUES.USER_REPOSITORY})`, `✅ Good: constructor(private repo: I${this.props.repositoryName?.replace(/^.*?([A-Z]\w+)$/, "$1") || Messages_1.VIOLATION_EXAMPLE_VALUES.USER_REPOSITORY})`, ].join("\n"); } getNewRepositorySuggestion() { return [ Messages_1.REPOSITORY_PATTERN_MESSAGES.STEP_REMOVE_NEW, Messages_1.REPOSITORY_PATTERN_MESSAGES.STEP_INJECT_CONSTRUCTOR, Messages_1.REPOSITORY_PATTERN_MESSAGES.STEP_CONFIGURE_DI, "", Messages_1.REPOSITORY_PATTERN_MESSAGES.EXAMPLE_PREFIX, Messages_1.REPOSITORY_PATTERN_MESSAGES.BAD_NEW_REPO, Messages_1.REPOSITORY_PATTERN_MESSAGES.GOOD_INJECT_REPO, ].join("\n"); } getNonDomainMethodSuggestion() { const detailsMatch = /Consider: (.+)$/.exec(this.props.details); const smartSuggestion = detailsMatch ? detailsMatch[1] : null; const technicalToDomain = { findOne: Messages_1.REPOSITORY_PATTERN_MESSAGES.SUGGESTION_FINDONE, findMany: Messages_1.REPOSITORY_PATTERN_MESSAGES.SUGGESTION_FINDMANY, insert: Messages_1.REPOSITORY_PATTERN_MESSAGES.SUGGESTION_INSERT, update: Messages_1.REPOSITORY_PATTERN_MESSAGES.SUGGESTION_UPDATE, delete: Messages_1.REPOSITORY_PATTERN_MESSAGES.SUGGESTION_DELETE, query: Messages_1.REPOSITORY_PATTERN_MESSAGES.SUGGESTION_QUERY, }; const fallbackSuggestion = technicalToDomain[this.props.methodName]; const finalSuggestion = smartSuggestion || fallbackSuggestion || Messages_1.REPOSITORY_FALLBACK_SUGGESTIONS.DEFAULT; return [ Messages_1.REPOSITORY_PATTERN_MESSAGES.STEP_RENAME_METHOD, Messages_1.REPOSITORY_PATTERN_MESSAGES.STEP_REFLECT_BUSINESS, Messages_1.REPOSITORY_PATTERN_MESSAGES.STEP_AVOID_TECHNICAL, "", Messages_1.REPOSITORY_PATTERN_MESSAGES.EXAMPLE_PREFIX, `❌ Bad: ${this.props.methodName || Messages_1.VIOLATION_EXAMPLE_VALUES.FIND_ONE}()`, `✅ Good: ${finalSuggestion}`, ].join("\n"); } getExampleFix() { switch (this.props.violationType) { case rules_1.REPOSITORY_VIOLATION_TYPES.ORM_TYPE_IN_INTERFACE: return this.getOrmTypeExample(); case rules_1.REPOSITORY_VIOLATION_TYPES.CONCRETE_REPOSITORY_IN_USE_CASE: return this.getConcreteRepositoryExample(); case rules_1.REPOSITORY_VIOLATION_TYPES.NEW_REPOSITORY_IN_USE_CASE: return this.getNewRepositoryExample(); case rules_1.REPOSITORY_VIOLATION_TYPES.NON_DOMAIN_METHOD_NAME: return this.getNonDomainMethodExample(); default: return Messages_1.REPOSITORY_PATTERN_MESSAGES.NO_EXAMPLE; } } getOrmTypeExample() { return ` // ❌ BAD: ORM-specific interface // domain/repositories/IUserRepository.ts interface IUserRepository { findOne(query: { where: { id: string } }) // Prisma-specific create(data: UserCreateInput) // ORM types in domain } // ✅ GOOD: Clean domain interface interface IUserRepository { findById(id: UserId): Promise<User | null> save(user: User): Promise<void> delete(id: UserId): Promise<void> }`; } getConcreteRepositoryExample() { return ` // ❌ BAD: Use Case with concrete implementation class CreateUser { constructor(private prisma: PrismaClient) {} // VIOLATION! } // ✅ GOOD: Use Case with interface class CreateUser { constructor(private userRepo: IUserRepository) {} // OK }`; } getNewRepositoryExample() { return ` // ❌ BAD: Creating repository in use case class CreateUser { async execute(data: CreateUserRequest) { const repo = new UserRepository() // VIOLATION! await repo.save(user) } } // ✅ GOOD: Inject repository via constructor class CreateUser { constructor(private readonly userRepo: IUserRepository) {} async execute(data: CreateUserRequest) { await this.userRepo.save(user) // OK } }`; } getNonDomainMethodExample() { return ` // ❌ BAD: Technical method names interface IUserRepository { findOne(id: string) // Database terminology insert(user: User) // SQL terminology query(filter: any) // Technical term } // ✅ GOOD: Domain language interface IUserRepository { findById(id: UserId): Promise<User | null> save(user: User): Promise<void> findByEmail(email: Email): Promise<User | null> }`; } } exports.RepositoryViolation = RepositoryViolation; //# sourceMappingURL=RepositoryViolation.js.map