mirror of
https://github.com/samiyev/puaros.git
synced 2025-12-27 23:06:54 +05:00
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
110 lines
3.0 KiB
TypeScript
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),
|
|
}
|
|
}
|
|
}
|