"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ArchitecturalAnalyzer = void 0; exports.analyzeArchitecture = analyzeArchitecture; const DEFAULT_LAYERS = [ { name: 'ui', patterns: [ '**/components/**', '**/pages/**', '**/views/**', '**/screens/**', '**/ui/**', '**/frontend/**' ], priority: 1 }, { name: 'api', patterns: [ '**/api/**', '**/controllers/**', '**/handlers/**', '**/routes/**', '**/endpoints/**' ], priority: 2 }, { name: 'services', patterns: [ '**/services/**', '**/business/**', '**/domain/**', '**/usecases/**' ], priority: 3 }, { name: 'data', patterns: [ '**/models/**', '**/schemas/**', '**/entities/**', '**/repositories/**', '**/dal/**' ], priority: 4 }, { name: 'infrastructure', patterns: [ '**/db/**', '**/database/**', '**/cache/**', '**/queue/**', '**/storage/**', '**/external/**' ], priority: 5 }, { name: 'shared', patterns: [ '**/shared/**', '**/common/**', '**/utils/**', '**/lib/**', '**/types/**' ], priority: 0 } ]; const DEFAULT_RULES = [ { fromLayer: 'ui', toLayer: 'data', allowed: false, severity: 'critical', description: 'UI should not directly access data layer' }, { fromLayer: 'ui', toLayer: 'infrastructure', allowed: false, severity: 'critical', description: 'UI should not directly access infrastructure' }, { fromLayer: 'api', toLayer: 'ui', allowed: false, severity: 'major', description: 'API layer should not depend on UI' }, { fromLayer: 'api', toLayer: 'infrastructure', allowed: true, severity: 'minor', description: 'API may access infrastructure (database, cache)' }, { fromLayer: 'services', toLayer: 'ui', allowed: false, severity: 'major', description: 'Services should not depend on UI' }, { fromLayer: 'services', toLayer: 'infrastructure', allowed: true, severity: 'minor', description: 'Services may access infrastructure' }, { fromLayer: 'data', toLayer: 'api', allowed: false, severity: 'major', description: 'Data layer should not depend on API layer' }, { fromLayer: 'data', toLayer: 'infrastructure', allowed: false, severity: 'critical', description: 'Data layer should not depend on infrastructure' }, { fromLayer: 'shared', toLayer: 'ui', allowed: false, severity: 'minor', description: 'Shared code should not depend on UI' }, { fromLayer: 'shared', toLayer: 'api', allowed: false, severity: 'minor', description: 'Shared code should not depend on API' } ]; class ArchitecturalAnalyzer { layers; rules; layerCache; constructor(layers = DEFAULT_LAYERS, rules = DEFAULT_RULES) { this.layers = layers; this.rules = rules; this.layerCache = new Map(); } analyze(symbolGraph) { const violations = []; const layerAssignments = new Map(); const unassignedFiles = []; for (const [key, symbol] of symbolGraph.symbols) { const file = symbol.file; if (!layerAssignments.has(file)) { const layer = this.assignLayer(file); layerAssignments.set(file, layer); if (layer === 'unassigned') { unassignedFiles.push(file); } } } for (const edge of symbolGraph.edges) { const fromLayer = layerAssignments.get(edge.from) || 'unassigned'; const toLayer = this.resolveImportToLayer(edge.to, symbolGraph); if (fromLayer === 'unassigned' || toLayer === 'unassigned') continue; const rule = this.findRule(fromLayer, toLayer); if (rule && !rule.allowed) { violations.push({ from: edge.from, to: edge.to, fromLayer, toLayer, severity: rule.severity, description: rule.description, file: edge.from }); } } const statistics = { totalViolations: violations.length, criticalCount: violations.filter(v => v.severity === 'critical').length, majorCount: violations.filter(v => v.severity === 'major').length, minorCount: violations.filter(v => v.severity === 'minor').length }; return { violations, layerAssignments, unassignedFiles, statistics }; } assignLayer(filePath) { const cached = this.layerCache.get(filePath); if (cached) return cached; const normalizedPath = filePath.replace(/\\/g, '/').toLowerCase(); for (const layer of this.layers) { for (const pattern of layer.patterns) { if (this.matchPattern(normalizedPath, pattern.toLowerCase())) { this.layerCache.set(filePath, layer.name); return layer.name; } } } this.layerCache.set(filePath, 'unassigned'); return 'unassigned'; } matchPattern(path, pattern) { const regexPattern = pattern .replace(/\*\*/g, '<>') .replace(/\*/g, '[^/]*') .replace(/<>/g, '.*') .replace(/\?/g, '.'); try { const regex = new RegExp(`^${regexPattern}$`); return regex.test(path) || this.matchGlob(path, pattern); } catch { return this.matchGlob(path, pattern); } } matchGlob(path, pattern) { const parts = path.split('/'); const patternParts = pattern.split('/'); let pi = 0; let psi = 0; while (pi < parts.length && psi < patternParts.length) { const patternPart = patternParts[psi]; if (patternPart === '**') { if (psi === patternParts.length - 1) { return true; } const nextPattern = patternParts[psi + 1]; while (pi < parts.length) { if (this.matchSingle(parts[pi], nextPattern)) { if (this.matchGlob(parts.slice(pi).join('/'), patternParts.slice(psi + 1).join('/'))) { return true; } } pi++; } return false; } if (!this.matchSingle(parts[pi], patternPart)) { return false; } pi++; psi++; } return pi === parts.length && psi === patternParts.length; } matchSingle(part, pattern) { if (pattern === '*') return part.length > 0; if (pattern === '**') return true; const regexPattern = pattern .replace(/\./g, '\\.') .replace(/\*/g, '[^/]*') .replace(/\?/g, '.'); try { const regex = new RegExp(`^${regexPattern}$`); return regex.test(part); } catch { return part === pattern; } } resolveImportToLayer(importPath, symbolGraph) { const normalizedImport = importPath.replace(/['"]/g, '').replace(/\\/g, '/'); for (const [key, symbol] of symbolGraph.symbols) { const symbolPath = symbol.file; if (symbolPath.includes(normalizedImport) || normalizedImport.includes(symbolPath)) { return this.assignLayer(symbolPath); } } if (normalizedImport.startsWith('.')) { return 'unassigned'; } if (normalizedImport.includes('/node_modules/')) { return 'external'; } if (normalizedImport.includes('/shared/') || normalizedImport.includes('/common/')) { return 'shared'; } return 'unassigned'; } findRule(fromLayer, toLayer) { return this.rules.find(r => r.fromLayer === fromLayer && r.toLayer === toLayer); } getDependencyGraph(layerAssignments) { const graph = new Map(); for (const [file, layer] of layerAssignments) { if (!graph.has(layer)) { graph.set(layer, new Set()); } } return graph; } generateReport(analysis) { const lines = []; lines.push('# Architectural Violation Report'); lines.push(''); lines.push(`Total Violations: ${analysis.statistics.totalViolations}`); lines.push(` - Critical: ${analysis.statistics.criticalCount}`); lines.push(` - Major: ${analysis.statistics.majorCount}`); lines.push(` - Minor: ${analysis.statistics.minorCount}`); lines.push(''); if (analysis.violations.length > 0) { lines.push('## Violations'); lines.push(''); const groupedBySeverity = { critical: analysis.violations.filter(v => v.severity === 'critical'), major: analysis.violations.filter(v => v.severity === 'major'), minor: analysis.violations.filter(v => v.severity === 'minor') }; for (const severity of ['critical', 'major', 'minor']) { const violations = groupedBySeverity[severity]; if (violations.length === 0) continue; lines.push(`### ${severity.toUpperCase()} (${violations.length})`); lines.push(''); for (const v of violations) { lines.push(`- **${v.fromLayer}** → **${v.toLayer}**: ${v.description}`); lines.push(` - File: \`${v.file || v.from}\``); lines.push(` - Import: \`${v.to}\``); } lines.push(''); } } return lines.join('\n'); } } exports.ArchitecturalAnalyzer = ArchitecturalAnalyzer; function analyzeArchitecture(symbolGraph) { const analyzer = new ArchitecturalAnalyzer(); return analyzer.analyze(symbolGraph); } //# sourceMappingURL=architectural-analyzer.js.map