mirror of
https://github.com/samiyev/puaros.git
synced 2025-12-28 07:16: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.
178 lines
5.5 KiB
TypeScript
178 lines
5.5 KiB
TypeScript
import { DDD_FOLDER_NAMES } from "../constants/detectorPatterns"
|
|
import { IMPORT_PATTERNS } from "../constants/paths"
|
|
import { FolderRegistry } from "./FolderRegistry"
|
|
|
|
/**
|
|
* Analyzes file paths and imports to extract aggregate information
|
|
*
|
|
* Handles path normalization, aggregate extraction, and entity name detection
|
|
* for aggregate boundary validation.
|
|
*/
|
|
export class AggregatePathAnalyzer {
|
|
constructor(private readonly folderRegistry: FolderRegistry) {}
|
|
|
|
/**
|
|
* Extracts the aggregate name from a file path
|
|
*
|
|
* Handles patterns like:
|
|
* - domain/aggregates/order/Order.ts → 'order'
|
|
* - domain/order/Order.ts → 'order'
|
|
* - domain/entities/order/Order.ts → 'order'
|
|
*/
|
|
public extractAggregateFromPath(filePath: string): string | undefined {
|
|
const normalizedPath = this.normalizePath(filePath)
|
|
const segments = this.getPathSegmentsAfterDomain(normalizedPath)
|
|
|
|
if (!segments || segments.length < 2) {
|
|
return undefined
|
|
}
|
|
|
|
return this.findAggregateInSegments(segments)
|
|
}
|
|
|
|
/**
|
|
* Extracts the aggregate name from an import path
|
|
*/
|
|
public extractAggregateFromImport(importPath: string): string | undefined {
|
|
const normalizedPath = importPath.replace(IMPORT_PATTERNS.QUOTE, "").toLowerCase()
|
|
const segments = normalizedPath.split("/").filter((seg) => seg !== ".." && seg !== ".")
|
|
|
|
if (segments.length === 0) {
|
|
return undefined
|
|
}
|
|
|
|
return this.findAggregateInImportSegments(segments)
|
|
}
|
|
|
|
/**
|
|
* Extracts the entity name from an import path
|
|
*/
|
|
public extractEntityName(importPath: string): string | undefined {
|
|
const normalizedPath = importPath.replace(IMPORT_PATTERNS.QUOTE, "")
|
|
const segments = normalizedPath.split("/")
|
|
const lastSegment = segments[segments.length - 1]
|
|
|
|
if (lastSegment) {
|
|
return lastSegment.replace(/\.(ts|js)$/, "")
|
|
}
|
|
|
|
return undefined
|
|
}
|
|
|
|
/**
|
|
* Normalizes a file path for consistent processing
|
|
*/
|
|
private normalizePath(filePath: string): string {
|
|
return filePath.toLowerCase().replace(/\\/g, "/")
|
|
}
|
|
|
|
/**
|
|
* Gets path segments after the 'domain' folder
|
|
*/
|
|
private getPathSegmentsAfterDomain(normalizedPath: string): string[] | undefined {
|
|
const domainMatch = /(?:^|\/)(domain)\//.exec(normalizedPath)
|
|
if (!domainMatch) {
|
|
return undefined
|
|
}
|
|
|
|
const domainEndIndex = domainMatch.index + domainMatch[0].length
|
|
const pathAfterDomain = normalizedPath.substring(domainEndIndex)
|
|
return pathAfterDomain.split("/").filter(Boolean)
|
|
}
|
|
|
|
/**
|
|
* Finds aggregate name in path segments after domain folder
|
|
*/
|
|
private findAggregateInSegments(segments: string[]): string | undefined {
|
|
if (this.folderRegistry.isEntityFolder(segments[0])) {
|
|
return this.extractFromEntityFolder(segments)
|
|
}
|
|
|
|
const aggregate = segments[0]
|
|
if (this.folderRegistry.isNonAggregateFolder(aggregate)) {
|
|
return undefined
|
|
}
|
|
|
|
return aggregate
|
|
}
|
|
|
|
/**
|
|
* Extracts aggregate from entity folder structure
|
|
*/
|
|
private extractFromEntityFolder(segments: string[]): string | undefined {
|
|
if (segments.length < 3) {
|
|
return undefined
|
|
}
|
|
|
|
const aggregate = segments[1]
|
|
if (this.folderRegistry.isNonAggregateFolder(aggregate)) {
|
|
return undefined
|
|
}
|
|
|
|
return aggregate
|
|
}
|
|
|
|
/**
|
|
* Finds aggregate in import path segments
|
|
*/
|
|
private findAggregateInImportSegments(segments: string[]): string | undefined {
|
|
const aggregateFromDomainFolder = this.findAggregateAfterDomainFolder(segments)
|
|
if (aggregateFromDomainFolder) {
|
|
return aggregateFromDomainFolder
|
|
}
|
|
|
|
return this.findAggregateFromSecondLastSegment(segments)
|
|
}
|
|
|
|
/**
|
|
* Finds aggregate after 'domain' or 'aggregates' folder in import
|
|
*/
|
|
private findAggregateAfterDomainFolder(segments: string[]): string | undefined {
|
|
for (let i = 0; i < segments.length; i++) {
|
|
const isDomainOrAggregatesFolder =
|
|
segments[i] === DDD_FOLDER_NAMES.DOMAIN ||
|
|
segments[i] === DDD_FOLDER_NAMES.AGGREGATES
|
|
|
|
if (!isDomainOrAggregatesFolder) {
|
|
continue
|
|
}
|
|
|
|
if (i + 1 >= segments.length) {
|
|
continue
|
|
}
|
|
|
|
const nextSegment = segments[i + 1]
|
|
const isEntityOrAggregateFolder =
|
|
this.folderRegistry.isEntityFolder(nextSegment) ||
|
|
nextSegment === DDD_FOLDER_NAMES.AGGREGATES
|
|
|
|
if (isEntityOrAggregateFolder) {
|
|
return i + 2 < segments.length ? segments[i + 2] : undefined
|
|
}
|
|
|
|
return nextSegment
|
|
}
|
|
return undefined
|
|
}
|
|
|
|
/**
|
|
* Extracts aggregate from second-to-last segment if applicable
|
|
*/
|
|
private findAggregateFromSecondLastSegment(segments: string[]): string | undefined {
|
|
if (segments.length >= 2) {
|
|
const secondLastSegment = segments[segments.length - 2]
|
|
|
|
if (
|
|
!this.folderRegistry.isEntityFolder(secondLastSegment) &&
|
|
!this.folderRegistry.isValueObjectFolder(secondLastSegment) &&
|
|
!this.folderRegistry.isAllowedFolder(secondLastSegment) &&
|
|
secondLastSegment !== DDD_FOLDER_NAMES.DOMAIN
|
|
) {
|
|
return secondLastSegment
|
|
}
|
|
}
|
|
|
|
return undefined
|
|
}
|
|
}
|