mirror of
https://github.com/samiyev/puaros.git
synced 2025-12-28 15:26:53 +05:00
Refactored three largest detectors to improve maintainability and reduce complexity: - AggregateBoundaryDetector: 381 → 162 lines (57% reduction) - HardcodeDetector: 459 → 89 lines (81% reduction) - RepositoryPatternDetector: 479 → 106 lines (78% reduction) Added 13 new strategy classes: - FolderRegistry - centralized DDD folder name management - AggregatePathAnalyzer - path parsing and aggregate extraction - ImportValidator - import validation logic - BraceTracker - brace and bracket counting - ConstantsFileChecker - constants file detection - ExportConstantAnalyzer - export const analysis - MagicNumberMatcher - magic number detection - MagicStringMatcher - magic string detection - OrmTypeMatcher - ORM type matching - MethodNameValidator - repository method validation - RepositoryFileAnalyzer - file role detection - RepositoryViolationDetector - violation detection logic All 519 tests passing, zero ESLint errors, no breaking changes.
168 lines
5.6 KiB
TypeScript
168 lines
5.6 KiB
TypeScript
import { IAggregateBoundaryDetector } from "../../domain/services/IAggregateBoundaryDetector"
|
|
import { AggregateBoundaryViolation } from "../../domain/value-objects/AggregateBoundaryViolation"
|
|
import { LAYERS } from "../../shared/constants/rules"
|
|
import { AggregatePathAnalyzer } from "../strategies/AggregatePathAnalyzer"
|
|
import { FolderRegistry } from "../strategies/FolderRegistry"
|
|
import { ImportValidator } from "../strategies/ImportValidator"
|
|
|
|
/**
|
|
* Detects aggregate boundary violations in Domain-Driven Design
|
|
*
|
|
* This detector enforces DDD aggregate rules:
|
|
* - Aggregates should reference each other only by ID or Value Objects
|
|
* - Direct entity references across aggregates create tight coupling
|
|
* - Each aggregate should be independently modifiable
|
|
*
|
|
* Folder structure patterns detected:
|
|
* - domain/aggregates/order/Order.ts
|
|
* - domain/order/Order.ts (aggregate name from parent folder)
|
|
* - domain/entities/order/Order.ts
|
|
*
|
|
* @example
|
|
* ```typescript
|
|
* const detector = new AggregateBoundaryDetector()
|
|
*
|
|
* // Detect violations in order aggregate
|
|
* const code = `
|
|
* import { User } from '../user/User'
|
|
* import { UserId } from '../user/value-objects/UserId'
|
|
* `
|
|
* const violations = detector.detectViolations(
|
|
* code,
|
|
* 'src/domain/aggregates/order/Order.ts',
|
|
* 'domain'
|
|
* )
|
|
*
|
|
* // violations will contain 1 violation for direct User entity import
|
|
* // but not for UserId (value object is allowed)
|
|
* console.log(violations.length) // 1
|
|
* ```
|
|
*/
|
|
export class AggregateBoundaryDetector implements IAggregateBoundaryDetector {
|
|
private readonly folderRegistry: FolderRegistry
|
|
private readonly pathAnalyzer: AggregatePathAnalyzer
|
|
private readonly importValidator: ImportValidator
|
|
|
|
constructor() {
|
|
this.folderRegistry = new FolderRegistry()
|
|
this.pathAnalyzer = new AggregatePathAnalyzer(this.folderRegistry)
|
|
this.importValidator = new ImportValidator(this.folderRegistry, this.pathAnalyzer)
|
|
}
|
|
|
|
/**
|
|
* Detects aggregate boundary violations in the given code
|
|
*
|
|
* Analyzes import statements to identify direct entity references
|
|
* across aggregate boundaries in the domain layer.
|
|
*
|
|
* @param code - Source code to analyze
|
|
* @param filePath - Path to the file being analyzed
|
|
* @param layer - The architectural layer of the file (should be 'domain')
|
|
* @returns Array of detected aggregate boundary violations
|
|
*/
|
|
public detectViolations(
|
|
code: string,
|
|
filePath: string,
|
|
layer: string | undefined,
|
|
): AggregateBoundaryViolation[] {
|
|
if (layer !== LAYERS.DOMAIN) {
|
|
return []
|
|
}
|
|
|
|
const currentAggregate = this.pathAnalyzer.extractAggregateFromPath(filePath)
|
|
if (!currentAggregate) {
|
|
return []
|
|
}
|
|
|
|
return this.analyzeImports(code, filePath, currentAggregate)
|
|
}
|
|
|
|
/**
|
|
* Checks if a file path belongs to an aggregate
|
|
*
|
|
* Extracts aggregate name from paths like:
|
|
* - domain/aggregates/order/Order.ts → 'order'
|
|
* - domain/order/Order.ts → 'order'
|
|
* - domain/entities/order/Order.ts → 'order'
|
|
*
|
|
* @param filePath - The file path to check
|
|
* @returns The aggregate name if found, undefined otherwise
|
|
*/
|
|
public extractAggregateFromPath(filePath: string): string | undefined {
|
|
return this.pathAnalyzer.extractAggregateFromPath(filePath)
|
|
}
|
|
|
|
/**
|
|
* Checks if an import path references an entity from another aggregate
|
|
*
|
|
* @param importPath - The import path to analyze
|
|
* @param currentAggregate - The aggregate of the current file
|
|
* @returns True if the import crosses aggregate boundaries inappropriately
|
|
*/
|
|
public isAggregateBoundaryViolation(importPath: string, currentAggregate: string): boolean {
|
|
return this.importValidator.isViolation(importPath, currentAggregate)
|
|
}
|
|
|
|
/**
|
|
* Analyzes all imports in code and detects violations
|
|
*/
|
|
private analyzeImports(
|
|
code: string,
|
|
filePath: string,
|
|
currentAggregate: string,
|
|
): AggregateBoundaryViolation[] {
|
|
const violations: AggregateBoundaryViolation[] = []
|
|
const lines = code.split("\n")
|
|
|
|
for (let i = 0; i < lines.length; i++) {
|
|
const line = lines[i]
|
|
const lineNumber = i + 1
|
|
|
|
const imports = this.importValidator.extractImports(line)
|
|
for (const importPath of imports) {
|
|
const violation = this.checkImport(
|
|
importPath,
|
|
currentAggregate,
|
|
filePath,
|
|
lineNumber,
|
|
)
|
|
if (violation) {
|
|
violations.push(violation)
|
|
}
|
|
}
|
|
}
|
|
|
|
return violations
|
|
}
|
|
|
|
/**
|
|
* Checks a single import for boundary violations
|
|
*/
|
|
private checkImport(
|
|
importPath: string,
|
|
currentAggregate: string,
|
|
filePath: string,
|
|
lineNumber: number,
|
|
): AggregateBoundaryViolation | undefined {
|
|
if (!this.importValidator.isViolation(importPath, currentAggregate)) {
|
|
return undefined
|
|
}
|
|
|
|
const targetAggregate = this.pathAnalyzer.extractAggregateFromImport(importPath)
|
|
const entityName = this.pathAnalyzer.extractEntityName(importPath)
|
|
|
|
if (targetAggregate && entityName) {
|
|
return AggregateBoundaryViolation.create(
|
|
currentAggregate,
|
|
targetAggregate,
|
|
entityName,
|
|
importPath,
|
|
filePath,
|
|
lineNumber,
|
|
)
|
|
}
|
|
|
|
return undefined
|
|
}
|
|
}
|