mirror of
https://github.com/samiyev/puaros.git
synced 2025-12-28 07:16:53 +05:00
refactor: extract detector logic into focused strategy classes
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.
This commit is contained in:
@@ -0,0 +1,285 @@
|
||||
import { RepositoryViolation } from "../../domain/value-objects/RepositoryViolation"
|
||||
import { LAYERS, REPOSITORY_VIOLATION_TYPES } from "../../shared/constants/rules"
|
||||
import { REPOSITORY_PATTERN_MESSAGES } from "../../domain/constants/Messages"
|
||||
import { OrmTypeMatcher } from "./OrmTypeMatcher"
|
||||
import { MethodNameValidator } from "./MethodNameValidator"
|
||||
|
||||
/**
|
||||
* Detects specific repository pattern violations
|
||||
*
|
||||
* Handles detection of ORM types, non-domain methods, concrete repositories,
|
||||
* and repository instantiation violations.
|
||||
*/
|
||||
export class RepositoryViolationDetector {
|
||||
constructor(
|
||||
private readonly ormMatcher: OrmTypeMatcher,
|
||||
private readonly methodValidator: MethodNameValidator,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Detects ORM types in repository interface
|
||||
*/
|
||||
public detectOrmTypes(
|
||||
code: string,
|
||||
filePath: string,
|
||||
layer: string | undefined,
|
||||
): RepositoryViolation[] {
|
||||
const violations: RepositoryViolation[] = []
|
||||
const lines = code.split("\n")
|
||||
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const line = lines[i]
|
||||
const lineNumber = i + 1
|
||||
|
||||
this.detectOrmInMethod(line, lineNumber, filePath, layer, violations)
|
||||
this.detectOrmInLine(line, lineNumber, filePath, layer, violations)
|
||||
}
|
||||
|
||||
return violations
|
||||
}
|
||||
|
||||
/**
|
||||
* Detects non-domain method names
|
||||
*/
|
||||
public detectNonDomainMethods(
|
||||
code: string,
|
||||
filePath: string,
|
||||
layer: string | undefined,
|
||||
): RepositoryViolation[] {
|
||||
const violations: RepositoryViolation[] = []
|
||||
const lines = code.split("\n")
|
||||
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const line = lines[i]
|
||||
const lineNumber = i + 1
|
||||
|
||||
const methodMatch = /^\s*(\w+)\s*\(/.exec(line)
|
||||
|
||||
if (methodMatch) {
|
||||
const methodName = methodMatch[1]
|
||||
|
||||
if (
|
||||
!this.methodValidator.isDomainMethodName(methodName) &&
|
||||
!line.trim().startsWith("//")
|
||||
) {
|
||||
const suggestion = this.methodValidator.suggestDomainMethodName(methodName)
|
||||
violations.push(
|
||||
RepositoryViolation.create(
|
||||
REPOSITORY_VIOLATION_TYPES.NON_DOMAIN_METHOD_NAME,
|
||||
filePath,
|
||||
layer || LAYERS.DOMAIN,
|
||||
lineNumber,
|
||||
`Method '${methodName}' uses technical name instead of domain language. ${suggestion}`,
|
||||
undefined,
|
||||
undefined,
|
||||
methodName,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return violations
|
||||
}
|
||||
|
||||
/**
|
||||
* Detects concrete repository usage
|
||||
*/
|
||||
public detectConcreteRepositoryUsage(
|
||||
code: string,
|
||||
filePath: string,
|
||||
layer: string | undefined,
|
||||
): RepositoryViolation[] {
|
||||
const violations: RepositoryViolation[] = []
|
||||
const lines = code.split("\n")
|
||||
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const line = lines[i]
|
||||
const lineNumber = i + 1
|
||||
|
||||
this.detectConcreteInConstructor(line, lineNumber, filePath, layer, violations)
|
||||
this.detectConcreteInField(line, lineNumber, filePath, layer, violations)
|
||||
}
|
||||
|
||||
return violations
|
||||
}
|
||||
|
||||
/**
|
||||
* Detects new Repository() instantiation
|
||||
*/
|
||||
public detectNewInstantiation(
|
||||
code: string,
|
||||
filePath: string,
|
||||
layer: string | undefined,
|
||||
): RepositoryViolation[] {
|
||||
const violations: RepositoryViolation[] = []
|
||||
const lines = code.split("\n")
|
||||
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const line = lines[i]
|
||||
const lineNumber = i + 1
|
||||
|
||||
const newRepositoryMatch = /new\s+([A-Z]\w*Repository)\s*\(/.exec(line)
|
||||
|
||||
if (newRepositoryMatch && !line.trim().startsWith("//")) {
|
||||
const repositoryName = newRepositoryMatch[1]
|
||||
violations.push(
|
||||
RepositoryViolation.create(
|
||||
REPOSITORY_VIOLATION_TYPES.NEW_REPOSITORY_IN_USE_CASE,
|
||||
filePath,
|
||||
layer || LAYERS.APPLICATION,
|
||||
lineNumber,
|
||||
`Use case creates repository with 'new ${repositoryName}()'`,
|
||||
undefined,
|
||||
repositoryName,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return violations
|
||||
}
|
||||
|
||||
/**
|
||||
* Detects ORM types in method signatures
|
||||
*/
|
||||
private detectOrmInMethod(
|
||||
line: string,
|
||||
lineNumber: number,
|
||||
filePath: string,
|
||||
layer: string | undefined,
|
||||
violations: RepositoryViolation[],
|
||||
): void {
|
||||
const methodMatch =
|
||||
/(\w+)\s*\([^)]*:\s*([^)]+)\)\s*:\s*.*?(?:Promise<([^>]+)>|([A-Z]\w+))/.exec(line)
|
||||
|
||||
if (methodMatch) {
|
||||
const params = methodMatch[2]
|
||||
const returnType = methodMatch[3] || methodMatch[4]
|
||||
|
||||
if (this.ormMatcher.isOrmType(params)) {
|
||||
const ormType = this.ormMatcher.extractOrmType(params)
|
||||
violations.push(
|
||||
RepositoryViolation.create(
|
||||
REPOSITORY_VIOLATION_TYPES.ORM_TYPE_IN_INTERFACE,
|
||||
filePath,
|
||||
layer || LAYERS.DOMAIN,
|
||||
lineNumber,
|
||||
`Method parameter uses ORM type: ${ormType}`,
|
||||
ormType,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
if (returnType && this.ormMatcher.isOrmType(returnType)) {
|
||||
const ormType = this.ormMatcher.extractOrmType(returnType)
|
||||
violations.push(
|
||||
RepositoryViolation.create(
|
||||
REPOSITORY_VIOLATION_TYPES.ORM_TYPE_IN_INTERFACE,
|
||||
filePath,
|
||||
layer || LAYERS.DOMAIN,
|
||||
lineNumber,
|
||||
`Method return type uses ORM type: ${ormType}`,
|
||||
ormType,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Detects ORM types in general code line
|
||||
*/
|
||||
private detectOrmInLine(
|
||||
line: string,
|
||||
lineNumber: number,
|
||||
filePath: string,
|
||||
layer: string | undefined,
|
||||
violations: RepositoryViolation[],
|
||||
): void {
|
||||
if (this.ormMatcher.isOrmType(line) && !line.trim().startsWith("//")) {
|
||||
const ormType = this.ormMatcher.extractOrmType(line)
|
||||
violations.push(
|
||||
RepositoryViolation.create(
|
||||
REPOSITORY_VIOLATION_TYPES.ORM_TYPE_IN_INTERFACE,
|
||||
filePath,
|
||||
layer || LAYERS.DOMAIN,
|
||||
lineNumber,
|
||||
`Repository interface contains ORM-specific type: ${ormType}`,
|
||||
ormType,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Detects concrete repository in constructor
|
||||
*/
|
||||
private detectConcreteInConstructor(
|
||||
line: string,
|
||||
lineNumber: number,
|
||||
filePath: string,
|
||||
layer: string | undefined,
|
||||
violations: RepositoryViolation[],
|
||||
): void {
|
||||
const constructorParamMatch =
|
||||
/constructor\s*\([^)]*(?:private|public|protected)\s+(?:readonly\s+)?(\w+)\s*:\s*([A-Z]\w*Repository)/.exec(
|
||||
line,
|
||||
)
|
||||
|
||||
if (constructorParamMatch) {
|
||||
const repositoryType = constructorParamMatch[2]
|
||||
|
||||
if (!repositoryType.startsWith("I")) {
|
||||
violations.push(
|
||||
RepositoryViolation.create(
|
||||
REPOSITORY_VIOLATION_TYPES.CONCRETE_REPOSITORY_IN_USE_CASE,
|
||||
filePath,
|
||||
layer || LAYERS.APPLICATION,
|
||||
lineNumber,
|
||||
`Use case depends on concrete repository '${repositoryType}'`,
|
||||
undefined,
|
||||
repositoryType,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Detects concrete repository in field
|
||||
*/
|
||||
private detectConcreteInField(
|
||||
line: string,
|
||||
lineNumber: number,
|
||||
filePath: string,
|
||||
layer: string | undefined,
|
||||
violations: RepositoryViolation[],
|
||||
): void {
|
||||
const fieldMatch =
|
||||
/(?:private|public|protected)\s+(?:readonly\s+)?(\w+)\s*:\s*([A-Z]\w*Repository)/.exec(
|
||||
line,
|
||||
)
|
||||
|
||||
if (fieldMatch) {
|
||||
const repositoryType = fieldMatch[2]
|
||||
|
||||
if (
|
||||
!repositoryType.startsWith("I") &&
|
||||
!line.includes(REPOSITORY_PATTERN_MESSAGES.CONSTRUCTOR)
|
||||
) {
|
||||
violations.push(
|
||||
RepositoryViolation.create(
|
||||
REPOSITORY_VIOLATION_TYPES.CONCRETE_REPOSITORY_IN_USE_CASE,
|
||||
filePath,
|
||||
layer || LAYERS.APPLICATION,
|
||||
lineNumber,
|
||||
`Use case field uses concrete repository '${repositoryType}'`,
|
||||
undefined,
|
||||
repositoryType,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user