Files
puaros/packages/guardian/src/domain/entities/DependencyGraph.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

110 lines
3.0 KiB
TypeScript

import { BaseEntity } from "./BaseEntity"
import { SourceFile } from "./SourceFile"
interface GraphNode {
file: SourceFile
dependencies: string[]
dependents: string[]
}
/**
* Represents dependency graph of the analyzed project
*/
export class DependencyGraph extends BaseEntity {
private readonly nodes: Map<string, GraphNode>
constructor(id?: string) {
super(id)
this.nodes = new Map()
}
public addFile(file: SourceFile): void {
const fileId = file.path.relative
if (!this.nodes.has(fileId)) {
this.nodes.set(fileId, {
file,
dependencies: [],
dependents: [],
})
}
this.touch()
}
public addDependency(from: string, to: string): void {
const fromNode = this.nodes.get(from)
const toNode = this.nodes.get(to)
if (fromNode && toNode) {
if (!fromNode.dependencies.includes(to)) {
fromNode.dependencies.push(to)
}
if (!toNode.dependents.includes(from)) {
toNode.dependents.push(from)
}
this.touch()
}
}
public getNode(filePath: string): GraphNode | undefined {
return this.nodes.get(filePath)
}
public getAllNodes(): GraphNode[] {
return Array.from(this.nodes.values())
}
public findCycles(): string[][] {
const cycles: string[][] = []
const visited = new Set<string>()
const recursionStack = new Set<string>()
const dfs = (nodeId: string, path: string[]): void => {
visited.add(nodeId)
recursionStack.add(nodeId)
path.push(nodeId)
const node = this.nodes.get(nodeId)
if (node) {
for (const dep of node.dependencies) {
if (!visited.has(dep)) {
dfs(dep, [...path])
} else if (recursionStack.has(dep)) {
const cycleStart = path.indexOf(dep)
cycles.push(path.slice(cycleStart))
}
}
}
recursionStack.delete(nodeId)
}
for (const nodeId of this.nodes.keys()) {
if (!visited.has(nodeId)) {
dfs(nodeId, [])
}
}
return cycles
}
public getMetrics(): {
totalFiles: number
totalDependencies: number
avgDependencies: number
maxDependencies: number
} {
const nodes = Array.from(this.nodes.values())
const totalFiles = nodes.length
const totalDependencies = nodes.reduce((sum, node) => sum + node.dependencies.length, 0)
return {
totalFiles,
totalDependencies,
avgDependencies: totalFiles > 0 ? totalDependencies / totalFiles : 0,
maxDependencies: Math.max(...nodes.map((node) => node.dependencies.length), 0),
}
}
}