Files
puaros/packages/guardian/src/domain/value-objects/DependencyViolation.ts
imfozilbek a34ca85241 chore: refactor hardcoded values to constants (v0.5.1)
Major internal refactoring to eliminate hardcoded values and improve
maintainability. Guardian now fully passes its own quality checks!

Changes:
- Extract all RepositoryViolation messages to domain constants
- Extract all framework leak template strings to centralized constants
- Extract all layer paths to infrastructure constants
- Extract all regex patterns to IMPORT_PATTERNS constant
- Add 30+ new constants for better maintainability

New files:
- src/infrastructure/constants/paths.ts (layer paths, patterns)
- src/domain/constants/Messages.ts (25+ repository messages)
- src/domain/constants/FrameworkCategories.ts (framework categories)
- src/shared/constants/layers.ts (layer names)

Impact:
- Reduced hardcoded values from 37 to 1 (97% improvement)
- Guardian passes its own src/ directory checks with 0 violations
- All 292 tests still passing (100% pass rate)
- No breaking changes - fully backwards compatible

Test results:
- 292 tests passing (100% pass rate)
- 96.77% statement coverage
- 83.82% branch coverage
2025-11-24 20:12:08 +05:00

172 lines
5.0 KiB
TypeScript

import { ValueObject } from "./ValueObject"
import {
LAYER_APPLICATION,
LAYER_DOMAIN,
LAYER_INFRASTRUCTURE,
} from "../../shared/constants/layers"
import { DEPENDENCY_VIOLATION_MESSAGES } from "../constants/Messages"
interface DependencyViolationProps {
readonly fromLayer: string
readonly toLayer: string
readonly importPath: string
readonly filePath: string
readonly line?: number
}
/**
* Represents a dependency direction violation in the codebase
*
* Dependency direction violations occur when a layer imports from a layer
* that it should not depend on according to Clean Architecture principles:
* - Domain → should not import from Application or Infrastructure
* - Application → should not import from Infrastructure
* - Infrastructure → can import from Application and Domain (allowed)
* - Shared → can be imported by all layers (allowed)
*
* @example
* ```typescript
* // Bad: Domain importing from Application
* const violation = DependencyViolation.create(
* 'domain',
* 'application',
* '../../application/dtos/UserDto',
* 'src/domain/entities/User.ts',
* 5
* )
*
* console.log(violation.getMessage())
* // "Domain layer should not import from Application layer"
* ```
*/
export class DependencyViolation extends ValueObject<DependencyViolationProps> {
private constructor(props: DependencyViolationProps) {
super(props)
}
public static create(
fromLayer: string,
toLayer: string,
importPath: string,
filePath: string,
line?: number,
): DependencyViolation {
return new DependencyViolation({
fromLayer,
toLayer,
importPath,
filePath,
line,
})
}
public get fromLayer(): string {
return this.props.fromLayer
}
public get toLayer(): string {
return this.props.toLayer
}
public get importPath(): string {
return this.props.importPath
}
public get filePath(): string {
return this.props.filePath
}
public get line(): number | undefined {
return this.props.line
}
public getMessage(): string {
return `${this.capitalizeFirst(this.props.fromLayer)} layer should not import from ${this.capitalizeFirst(this.props.toLayer)} layer`
}
public getSuggestion(): string {
const suggestions: string[] = []
if (this.props.fromLayer === LAYER_DOMAIN) {
suggestions.push(
DEPENDENCY_VIOLATION_MESSAGES.DOMAIN_INDEPENDENCE,
DEPENDENCY_VIOLATION_MESSAGES.DOMAIN_MOVE_TO_DOMAIN,
DEPENDENCY_VIOLATION_MESSAGES.DOMAIN_USE_DI,
)
} else if (this.props.fromLayer === LAYER_APPLICATION) {
suggestions.push(
DEPENDENCY_VIOLATION_MESSAGES.APPLICATION_NO_INFRA,
DEPENDENCY_VIOLATION_MESSAGES.APPLICATION_DEFINE_PORT,
DEPENDENCY_VIOLATION_MESSAGES.APPLICATION_IMPLEMENT_ADAPTER,
DEPENDENCY_VIOLATION_MESSAGES.APPLICATION_USE_DI,
)
}
return suggestions.join("\n")
}
public getExampleFix(): string {
if (this.props.fromLayer === LAYER_DOMAIN && this.props.toLayer === LAYER_INFRASTRUCTURE) {
return `
// ❌ Bad: Domain depends on Infrastructure (PrismaClient)
// domain/services/UserService.ts
class UserService {
constructor(private prisma: PrismaClient) {}
}
// ✅ Good: Domain defines interface, Infrastructure implements
// domain/repositories/IUserRepository.ts
interface IUserRepository {
findById(id: UserId): Promise<User | null>
save(user: User): Promise<void>
}
// domain/services/UserService.ts
class UserService {
constructor(private userRepo: IUserRepository) {}
}
// infrastructure/repositories/PrismaUserRepository.ts
class PrismaUserRepository implements IUserRepository {
constructor(private prisma: PrismaClient) {}
async findById(id: UserId): Promise<User | null> { }
async save(user: User): Promise<void> { }
}`
}
if (
this.props.fromLayer === LAYER_APPLICATION &&
this.props.toLayer === LAYER_INFRASTRUCTURE
) {
return `
// ❌ Bad: Application depends on Infrastructure (SmtpEmailService)
// application/use-cases/SendEmail.ts
class SendWelcomeEmail {
constructor(private emailService: SmtpEmailService) {}
}
// ✅ Good: Application defines Port, Infrastructure implements Adapter
// application/ports/IEmailService.ts
interface IEmailService {
send(to: string, subject: string, body: string): Promise<void>
}
// application/use-cases/SendEmail.ts
class SendWelcomeEmail {
constructor(private emailService: IEmailService) {}
}
// infrastructure/adapters/SmtpEmailService.ts
class SmtpEmailService implements IEmailService {
async send(to: string, subject: string, body: string): Promise<void> { }
}`
}
return ""
}
private capitalizeFirst(str: string): string {
return str.charAt(0).toUpperCase() + str.slice(1)
}
}