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.
286 lines
9.3 KiB
TypeScript
286 lines
9.3 KiB
TypeScript
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,
|
|
),
|
|
)
|
|
}
|
|
}
|
|
}
|
|
}
|