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