using MarketAlly.AIPlugin; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text.RegularExpressions; using System.Threading.Tasks; namespace MarketAlly.AIPlugin.Analysis.Plugins { [AIPlugin("ArchitectureValidator", "Validates architectural patterns, layer boundaries, and design principles")] public class ArchitectureValidatorPlugin : IAIPlugin { [AIParameter("Full path to the project or solution directory", required: true)] public string ProjectPath { get; set; } = string.Empty; [AIParameter("Architecture pattern to validate: mvc, mvvm, clean, layered, hexagonal", required: false)] public string ArchitecturePattern { get; set; } = "auto"; [AIParameter("Check for layer boundary violations", required: false)] public bool CheckLayerBoundaries { get; set; } = true; [AIParameter("Detect circular dependencies", required: false)] public bool CheckCircularDependencies { get; set; } = true; [AIParameter("Validate naming conventions", required: false)] public bool ValidateNaming { get; set; } = true; [AIParameter("Check for anti-patterns", required: false)] public bool CheckAntiPatterns { get; set; } = true; [AIParameter("Generate architecture documentation", required: false)] public bool GenerateDocumentation { get; set; } = false; public IReadOnlyDictionary SupportedParameters => new Dictionary { ["projectPath"] = typeof(string), ["architecturePattern"] = typeof(string), ["checkLayerBoundaries"] = typeof(bool), ["checkCircularDependencies"] = typeof(bool), ["validateNaming"] = typeof(bool), ["checkAntiPatterns"] = typeof(bool), ["generateDocumentation"] = typeof(bool) }; public async Task ExecuteAsync(IReadOnlyDictionary parameters) { try { // Extract parameters string projectPath = parameters["projectPath"].ToString() ?? string.Empty; string architecturePattern = parameters.TryGetValue("architecturePattern", out var pattern) ? pattern?.ToString() ?? "auto" : "auto"; bool checkLayerBoundaries = GetBoolParameter(parameters, "checkLayerBoundaries", true); bool checkCircularDependencies = GetBoolParameter(parameters, "checkCircularDependencies", true); bool validateNaming = GetBoolParameter(parameters, "validateNaming", true); bool checkAntiPatterns = GetBoolParameter(parameters, "checkAntiPatterns", true); bool generateDocumentation = GetBoolParameter(parameters, "generateDocumentation", false); // Validate path if (!Directory.Exists(projectPath)) { return new AIPluginResult( new DirectoryNotFoundException($"Directory not found: {projectPath}"), "Directory not found" ); } // Initialize architecture analysis var analysis = new ArchitectureAnalysis { ProjectPath = projectPath, AnalysisDate = DateTime.UtcNow, LayerViolations = new List(), CircularDependencies = new List(), NamingViolations = new List(), AntiPatterns = new List(), ProjectStructure = new ProjectStructure() }; // Discover project structure await DiscoverProjectStructure(projectPath, analysis); // Detect architecture pattern if auto if (architecturePattern?.ToLowerInvariant() == "auto") { architecturePattern = DetectArchitecturePattern(analysis.ProjectStructure); } analysis.DetectedPattern = architecturePattern ?? "Unknown"; // Validate layer boundaries if (checkLayerBoundaries) { await ValidateLayerBoundaries(analysis); } // Check for circular dependencies if (checkCircularDependencies) { await CheckCircularDependenciesMethod(analysis); } // Validate naming conventions if (validateNaming) { await ValidateNamingConventions(analysis); } // Check for anti-patterns if (checkAntiPatterns) { await CheckAntiPatternsMethod(analysis); } // Calculate architecture score var architectureScore = CalculateArchitectureScore(analysis); // Generate documentation if requested string? documentation = null; if (generateDocumentation) { documentation = GenerateArchitectureDocumentation(analysis); } var result = new { ProjectPath = projectPath, DetectedPattern = analysis.DetectedPattern, ArchitectureScore = architectureScore, ProjectStructure = new { analysis.ProjectStructure.TotalProjects, analysis.ProjectStructure.TotalNamespaces, analysis.ProjectStructure.TotalClasses, Layers = analysis.ProjectStructure.Layers.Select(l => new { l.Name, l.Type, ProjectCount = l.Projects.Count, ClassCount = l.Classes.Count }).ToList() }, LayerViolations = checkLayerBoundaries ? analysis.LayerViolations.Select(v => new { v.ViolationType, v.SourceLayer, v.TargetLayer, v.SourceClass, v.TargetClass, v.FilePath, v.LineNumber, v.Severity, v.Description, v.Recommendation }).ToList() : null, CircularDependencies = checkCircularDependencies ? analysis.CircularDependencies.Select(c => new { c.DependencyType, c.DependencyChain, c.Severity, c.Description, c.Recommendation }).ToList() : null, NamingViolations = validateNaming ? analysis.NamingViolations.Select(n => new { n.ViolationType, n.ElementName, n.ElementType, n.CurrentNaming, n.ExpectedNaming, n.FilePath, n.LineNumber, n.Severity, n.Recommendation }).ToList() : null, AntiPatterns = checkAntiPatterns ? analysis.AntiPatterns.Select(a => new { a.PatternName, a.PatternType, a.Description, a.Location, a.Severity, a.Impact, a.Recommendation, a.RefactoringEffort }).ToList() : null, Documentation = documentation, Summary = new { TotalViolations = analysis.LayerViolations.Count + analysis.CircularDependencies.Count + analysis.NamingViolations.Count + analysis.AntiPatterns.Count, CriticalIssues = CountCriticalIssues(analysis), ArchitectureHealth = GetArchitectureHealth(architectureScore), TopRecommendations = GetTopArchitectureRecommendations(analysis), ComplianceLevel = GetComplianceLevel(analysis, architecturePattern ?? "Unknown") } }; return new AIPluginResult(result, $"Architecture validation completed. Pattern: {analysis.DetectedPattern}, Score: {architectureScore}/100. " + $"Found {result.Summary.TotalViolations} architectural issues."); } catch (Exception ex) { return new AIPluginResult(ex, "Failed to validate architecture"); } } private async Task DiscoverProjectStructure(string projectPath, ArchitectureAnalysis analysis) { var sourceFiles = Directory.GetFiles(projectPath, "*.cs", SearchOption.AllDirectories) .Where(f => !f.Contains("\\bin\\") && !f.Contains("\\obj\\") && !f.EndsWith(".Designer.cs") && !f.EndsWith(".g.cs")) .ToList(); var projectFiles = Directory.GetFiles(projectPath, "*.csproj", SearchOption.AllDirectories).ToList(); analysis.ProjectStructure.TotalProjects = projectFiles.Count; var namespaces = new HashSet(); var classes = new List(); var layerMap = new Dictionary(); foreach (var filePath in sourceFiles) { var sourceCode = await File.ReadAllTextAsync(filePath); var syntaxTree = CSharpSyntaxTree.ParseText(sourceCode, path: filePath); var root = await syntaxTree.GetRootAsync(); // Extract namespace information var namespaceDeclarations = root.DescendantNodes().OfType(); foreach (var ns in namespaceDeclarations) { namespaces.Add(ns.Name?.ToString() ?? string.Empty); } // Extract class information var classDeclarations = root.DescendantNodes().OfType(); foreach (var cls in classDeclarations) { var classInfo = new ClassInfo { Name = cls.Identifier.ValueText, FullName = GetFullClassName(cls, filePath), Namespace = GetNamespace(cls), FilePath = filePath, IsPublic = cls.Modifiers.Any(m => m.IsKind(SyntaxKind.PublicKeyword)), IsAbstract = cls.Modifiers.Any(m => m.IsKind(SyntaxKind.AbstractKeyword)), IsStatic = cls.Modifiers.Any(m => m.IsKind(SyntaxKind.StaticKeyword)), BaseTypes = cls.BaseList?.Types.Select(t => t.Type?.ToString() ?? string.Empty).ToList() ?? new List(), Dependencies = ExtractClassDependencies(cls), Methods = ExtractMethodInfo(cls), Properties = ExtractPropertyInfo(cls) }; classes.Add(classInfo); // Determine layer based on namespace and class characteristics var layer = DetermineLayer(classInfo); if (!layerMap.ContainsKey(layer.Name)) { layerMap[layer.Name] = layer; } layerMap[layer.Name].Classes.Add(classInfo); } } analysis.ProjectStructure.TotalNamespaces = namespaces.Count; analysis.ProjectStructure.TotalClasses = classes.Count; analysis.ProjectStructure.Layers = layerMap.Values.ToList(); analysis.ProjectStructure.AllClasses = classes; // Associate projects with layers foreach (var projectFile in projectFiles) { var projectName = Path.GetFileNameWithoutExtension(projectFile); var projectDir = Path.GetDirectoryName(projectFile); foreach (var layer in analysis.ProjectStructure.Layers) { var layerClasses = classes.Where(c => c.FilePath.StartsWith(projectDir ?? string.Empty)).ToList(); if (layerClasses.Any()) { layer.Projects.Add(projectName); } } } } private string DetectArchitecturePattern(ProjectStructure structure) { var layerNames = structure.Layers.Select(l => l.Name.ToLowerInvariant()).ToList(); var namespaces = structure.AllClasses.Select(c => c.Namespace.ToLowerInvariant()).ToList(); // Clean Architecture detection if (HasCleanArchitectureLayers(layerNames, namespaces)) { return "Clean Architecture"; } // MVC detection if (HasMvcPattern(layerNames, namespaces)) { return "MVC"; } // MVVM detection if (HasMvvmPattern(layerNames, namespaces)) { return "MVVM"; } // Layered Architecture detection if (HasLayeredArchitecture(layerNames, namespaces)) { return "Layered"; } // Hexagonal Architecture detection if (HasHexagonalArchitecture(layerNames, namespaces)) { return "Hexagonal"; } return "Unknown"; } private Task ValidateLayerBoundaries(ArchitectureAnalysis analysis) { var layers = analysis.ProjectStructure.Layers; var layerHierarchy = GetLayerHierarchy(analysis.DetectedPattern); foreach (var sourceLayer in layers) { foreach (var sourceClass in sourceLayer.Classes) { foreach (var dependency in sourceClass.Dependencies) { var targetClass = analysis.ProjectStructure.AllClasses .FirstOrDefault(c => c.FullName == dependency || c.Name == dependency); if (targetClass != null) { var targetLayer = layers.FirstOrDefault(l => l.Classes.Contains(targetClass)); if (targetLayer != null && IsLayerViolation(sourceLayer, targetLayer, layerHierarchy)) { analysis.LayerViolations.Add(new LayerViolation { ViolationType = "Invalid Layer Dependency", SourceLayer = sourceLayer.Name, TargetLayer = targetLayer.Name, SourceClass = sourceClass.Name, TargetClass = targetClass.Name, FilePath = sourceClass.FilePath, LineNumber = 1, // Would need more sophisticated parsing for exact line Severity = GetViolationSeverity(sourceLayer.Name, targetLayer.Name), Description = $"{sourceLayer.Name} should not directly depend on {targetLayer.Name}", Recommendation = GetLayerViolationRecommendation(sourceLayer.Name, targetLayer.Name, analysis.DetectedPattern) }); } } } } } return Task.CompletedTask; } private async Task CheckCircularDependenciesMethod(ArchitectureAnalysis analysis) { var classes = analysis.ProjectStructure.AllClasses; var dependencyGraph = BuildDependencyGraph(classes); // Detect circular dependencies using DFS var visited = new HashSet(); var recursionStack = new HashSet(); var path = new List(); foreach (var className in dependencyGraph.Keys) { if (!visited.Contains(className)) { DetectCircularDependenciesRecursive(className, dependencyGraph, visited, recursionStack, path, analysis); } } // Check for namespace-level circular dependencies await CheckNamespaceCircularDependencies(analysis); } private Task ValidateNamingConventions(ArchitectureAnalysis analysis) { foreach (var classInfo in analysis.ProjectStructure.AllClasses) { // Validate class naming ValidateClassName(classInfo, analysis); // Validate method naming foreach (var method in classInfo.Methods) { ValidateMethodName(method, classInfo, analysis); } // Validate property naming foreach (var property in classInfo.Properties) { ValidatePropertyName(property, classInfo, analysis); } } // Validate namespace naming var namespaces = analysis.ProjectStructure.AllClasses.Select(c => c.Namespace).Distinct(); foreach (var ns in namespaces) { ValidateNamespace(ns, analysis); } return Task.CompletedTask; } private Task CheckAntiPatternsMethod(ArchitectureAnalysis analysis) { foreach (var classInfo in analysis.ProjectStructure.AllClasses) { // God Class anti-pattern CheckGodClass(classInfo, analysis); // Data Class anti-pattern CheckDataClass(classInfo, analysis); // Feature Envy anti-pattern CheckFeatureEnvy(classInfo, analysis); // Shotgun Surgery anti-pattern CheckShotgunSurgery(classInfo, analysis); // Large Class anti-pattern CheckLargeClass(classInfo, analysis); } // Check architectural anti-patterns CheckArchitecturalAntiPatterns(analysis); return Task.CompletedTask; } private int CalculateArchitectureScore(ArchitectureAnalysis analysis) { var score = 100; // Deduct points for violations score -= analysis.LayerViolations.Count * 5; score -= analysis.CircularDependencies.Count * 10; score -= analysis.NamingViolations.Count * 2; score -= analysis.AntiPatterns.Count(ap => ap.Severity == "High") * 15; score -= analysis.AntiPatterns.Count(ap => ap.Severity == "Medium") * 8; score -= analysis.AntiPatterns.Count(ap => ap.Severity == "Low") * 3; // Bonus points for good practices if (analysis.DetectedPattern != "Unknown") { score += 10; // Bonus for identifiable pattern } var layerCount = analysis.ProjectStructure.Layers.Count; if (layerCount >= 3 && layerCount <= 6) { score += 5; // Bonus for appropriate layer count } return Math.Max(0, Math.Min(100, score)); } private string GenerateArchitectureDocumentation(ArchitectureAnalysis analysis) { var doc = new List { "# Architecture Documentation", "", $"**Generated**: {analysis.AnalysisDate:yyyy-MM-dd HH:mm:ss}", $"**Project**: {Path.GetFileName(analysis.ProjectPath)}", $"**Pattern**: {analysis.DetectedPattern}", "", "## Project Structure", "" }; foreach (var layer in analysis.ProjectStructure.Layers) { doc.Add($"### {layer.Name} Layer"); doc.Add($"- **Type**: {layer.Type}"); doc.Add($"- **Classes**: {layer.Classes.Count}"); doc.Add($"- **Projects**: {string.Join(", ", layer.Projects)}"); doc.Add(""); } if (analysis.LayerViolations.Any()) { doc.Add("## Architecture Violations"); doc.Add(""); foreach (var violation in analysis.LayerViolations.Take(10)) { doc.Add($"- **{violation.ViolationType}**: {violation.Description}"); doc.Add($" - Location: {violation.SourceClass} → {violation.TargetClass}"); doc.Add($" - Recommendation: {violation.Recommendation}"); doc.Add(""); } } if (analysis.AntiPatterns.Any()) { doc.Add("## Detected Anti-Patterns"); doc.Add(""); foreach (var pattern in analysis.AntiPatterns.Take(10)) { doc.Add($"- **{pattern.PatternName}**: {pattern.Description}"); doc.Add($" - Location: {pattern.Location}"); doc.Add($" - Impact: {pattern.Impact}"); doc.Add($" - Recommendation: {pattern.Recommendation}"); doc.Add(""); } } return string.Join(Environment.NewLine, doc); } // Helper methods for pattern detection private bool HasCleanArchitectureLayers(List layers, List namespaces) { var cleanLayers = new[] { "domain", "application", "infrastructure", "presentation", "core", "usecases" }; return cleanLayers.Count(cl => layers.Any(l => l.Contains(cl)) || namespaces.Any(n => n.Contains(cl))) >= 3; } private bool HasMvcPattern(List layers, List namespaces) { var mvcComponents = new[] { "controller", "model", "view" }; return mvcComponents.All(mvc => layers.Any(l => l.Contains(mvc)) || namespaces.Any(n => n.Contains(mvc))); } private bool HasMvvmPattern(List layers, List namespaces) { var mvvmComponents = new[] { "viewmodel", "model", "view" }; return mvvmComponents.Count(mvvm => layers.Any(l => l.Contains(mvvm)) || namespaces.Any(n => n.Contains(mvvm))) >= 2; } private bool HasLayeredArchitecture(List layers, List namespaces) { var layeredComponents = new[] { "presentation", "business", "data", "service", "repository" }; return layeredComponents.Count(lc => layers.Any(l => l.Contains(lc)) || namespaces.Any(n => n.Contains(lc))) >= 3; } private bool HasHexagonalArchitecture(List layers, List namespaces) { var hexComponents = new[] { "adapter", "port", "domain", "infrastructure" }; return hexComponents.Count(hc => layers.Any(l => l.Contains(hc)) || namespaces.Any(n => n.Contains(hc))) >= 3; } private ArchitectureLayer DetermineLayer(ClassInfo classInfo) { var namespaceLower = classInfo.Namespace.ToLowerInvariant(); var classNameLower = classInfo.Name.ToLowerInvariant(); // Determine layer based on naming patterns if (namespaceLower.Contains("controller") || classNameLower.EndsWith("controller")) { return new ArchitectureLayer { Name = "Presentation", Type = "Controller Layer" }; } if (namespaceLower.Contains("service") || classNameLower.EndsWith("service")) { return new ArchitectureLayer { Name = "Business", Type = "Service Layer" }; } if (namespaceLower.Contains("repository") || classNameLower.EndsWith("repository")) { return new ArchitectureLayer { Name = "Data", Type = "Repository Layer" }; } if (namespaceLower.Contains("model") || namespaceLower.Contains("entity")) { return new ArchitectureLayer { Name = "Domain", Type = "Domain Layer" }; } if (namespaceLower.Contains("infrastructure")) { return new ArchitectureLayer { Name = "Infrastructure", Type = "Infrastructure Layer" }; } if (namespaceLower.Contains("application") || namespaceLower.Contains("usecase")) { return new ArchitectureLayer { Name = "Application", Type = "Application Layer" }; } return new ArchitectureLayer { Name = "Common", Type = "Utility Layer" }; } private Dictionary GetLayerHierarchy(string pattern) { return pattern.ToLowerInvariant() switch { "clean architecture" => new Dictionary { ["Domain"] = 1, ["Application"] = 2, ["Infrastructure"] = 3, ["Presentation"] = 4 }, "mvc" => new Dictionary { ["Domain"] = 1, ["Business"] = 2, ["Presentation"] = 3 }, "layered" => new Dictionary { ["Domain"] = 1, ["Business"] = 2, ["Data"] = 3, ["Presentation"] = 4 }, _ => new Dictionary() }; } private bool IsLayerViolation(ArchitectureLayer sourceLayer, ArchitectureLayer targetLayer, Dictionary hierarchy) { if (!hierarchy.ContainsKey(sourceLayer.Name) || !hierarchy.ContainsKey(targetLayer.Name)) return false; // Higher layers should not depend on lower layers (reverse dependency) return hierarchy[sourceLayer.Name] < hierarchy[targetLayer.Name]; } private string GetViolationSeverity(string sourceLayer, string targetLayer) { // Infrastructure depending on Presentation is high severity if (sourceLayer == "Infrastructure" && targetLayer == "Presentation") return "High"; // Domain depending on Application/Infrastructure is high severity if (sourceLayer == "Domain" && (targetLayer == "Application" || targetLayer == "Infrastructure")) return "High"; return "Medium"; } private string GetLayerViolationRecommendation(string sourceLayer, string targetLayer, string pattern) { return pattern.ToLowerInvariant() switch { "clean architecture" => $"Use dependency inversion: {sourceLayer} should depend on abstractions, not {targetLayer} implementations", "mvc" => $"Route dependencies through the controller layer instead of direct {sourceLayer} to {targetLayer} access", "layered" => $"Access {targetLayer} through intermediate layers or use dependency injection", _ => $"Refactor to eliminate direct dependency from {sourceLayer} to {targetLayer}" }; } private Dictionary> BuildDependencyGraph(List classes) { var graph = new Dictionary>(); foreach (var classInfo in classes) { graph[classInfo.FullName] = classInfo.Dependencies .Where(dep => classes.Any(c => c.FullName == dep || c.Name == dep)) .ToList(); } return graph; } private void DetectCircularDependenciesRecursive(string current, Dictionary> graph, HashSet visited, HashSet recursionStack, List path, ArchitectureAnalysis analysis) { visited.Add(current); recursionStack.Add(current); path.Add(current); if (graph.ContainsKey(current)) { foreach (var neighbor in graph[current]) { if (!visited.Contains(neighbor)) { DetectCircularDependenciesRecursive(neighbor, graph, visited, recursionStack, path, analysis); } else if (recursionStack.Contains(neighbor)) { // Found circular dependency var cycleStart = path.IndexOf(neighbor); var cycle = path.Skip(cycleStart).Concat(new[] { neighbor }).ToList(); analysis.CircularDependencies.Add(new CircularDependency { DependencyType = "Class Level", DependencyChain = string.Join(" → ", cycle.Select(c => c.Split('.').Last())), Severity = "High", Description = $"Circular dependency detected: {string.Join(" → ", cycle.Select(c => c.Split('.').Last()))}", Recommendation = "Break the cycle using interfaces, dependency injection, or event-driven patterns" }); } } } path.RemoveAt(path.Count - 1); recursionStack.Remove(current); } private Task CheckNamespaceCircularDependencies(ArchitectureAnalysis analysis) { var namespaces = analysis.ProjectStructure.AllClasses .GroupBy(c => c.Namespace) .ToDictionary(g => g.Key, g => g.ToList()); var namespaceGraph = new Dictionary>(); foreach (var ns in namespaces.Keys) { namespaceGraph[ns] = new HashSet(); foreach (var classInfo in namespaces[ns]) { foreach (var dependency in classInfo.Dependencies) { var dependentClass = analysis.ProjectStructure.AllClasses .FirstOrDefault(c => c.FullName == dependency || c.Name == dependency); if (dependentClass != null && dependentClass.Namespace != ns) { namespaceGraph[ns].Add(dependentClass.Namespace); } } } } // Simple cycle detection for namespaces foreach (var ns1 in namespaceGraph.Keys) { foreach (var ns2 in namespaceGraph[ns1]) { if (namespaceGraph.ContainsKey(ns2) && namespaceGraph[ns2].Contains(ns1)) { analysis.CircularDependencies.Add(new CircularDependency { DependencyType = "Namespace Level", DependencyChain = $"{ns1} ↔ {ns2}", Severity = "Medium", Description = $"Circular dependency between namespaces: {ns1} and {ns2}", Recommendation = "Refactor to establish clear namespace hierarchy or extract common interfaces" }); } } } return Task.CompletedTask; } // Naming validation methods private void ValidateClassName(ClassInfo classInfo, ArchitectureAnalysis analysis) { if (!IsPascalCase(classInfo.Name)) { analysis.NamingViolations.Add(new NamingViolation { ViolationType = "Case Convention", ElementName = classInfo.Name, ElementType = "Class", CurrentNaming = classInfo.Name, ExpectedNaming = ToPascalCase(classInfo.Name), FilePath = classInfo.FilePath, LineNumber = 1, Severity = "Low", Recommendation = "Use descriptive class names that clearly indicate purpose" }); } } private void ValidateMethodName(MethodInfo method, ClassInfo classInfo, ArchitectureAnalysis analysis) { if (!IsPascalCase(method.Name)) { analysis.NamingViolations.Add(new NamingViolation { ViolationType = "Case Convention", ElementName = method.Name, ElementType = "Method", CurrentNaming = method.Name, ExpectedNaming = ToPascalCase(method.Name), FilePath = classInfo.FilePath, LineNumber = method.LineNumber, Severity = "Medium", Recommendation = "Use PascalCase for method names" }); } // Check for verb-based method names if (!StartsWithVerb(method.Name) && !IsPropertyAccessor(method.Name)) { analysis.NamingViolations.Add(new NamingViolation { ViolationType = "Method Naming Convention", ElementName = method.Name, ElementType = "Method", CurrentNaming = method.Name, ExpectedNaming = "Verb-based name (e.g., GetData, ProcessOrder)", FilePath = classInfo.FilePath, LineNumber = method.LineNumber, Severity = "Low", Recommendation = "Method names should start with verbs to indicate action" }); } } private void ValidatePropertyName(PropertyInfo property, ClassInfo classInfo, ArchitectureAnalysis analysis) { if (!IsPascalCase(property.Name)) { analysis.NamingViolations.Add(new NamingViolation { ViolationType = "Case Convention", ElementName = property.Name, ElementType = "Property", CurrentNaming = property.Name, ExpectedNaming = ToPascalCase(property.Name), FilePath = classInfo.FilePath, LineNumber = property.LineNumber, Severity = "Medium", Recommendation = "Use PascalCase for property names" }); } } private void ValidateNamespace(string namespaceName, ArchitectureAnalysis analysis) { if (!IsPascalCase(namespaceName.Split('.').Last())) { analysis.NamingViolations.Add(new NamingViolation { ViolationType = "Namespace Convention", ElementName = namespaceName, ElementType = "Namespace", CurrentNaming = namespaceName, ExpectedNaming = "PascalCase segments", FilePath = "Multiple files", LineNumber = 1, Severity = "Low", Recommendation = "Use PascalCase for namespace segments" }); } } // Anti-pattern detection methods private void CheckGodClass(ClassInfo classInfo, ArchitectureAnalysis analysis) { var methodCount = classInfo.Methods.Count; var propertyCount = classInfo.Properties.Count; var totalMembers = methodCount + propertyCount; if (totalMembers > 20 || methodCount > 15) { analysis.AntiPatterns.Add(new AntiPattern { PatternName = "God Class", PatternType = "Design", Description = $"Class has too many responsibilities ({totalMembers} members)", Location = $"{classInfo.Name} in {Path.GetFileName(classInfo.FilePath)}", Severity = totalMembers > 30 ? "High" : "Medium", Impact = "High coupling, low cohesion, difficult to maintain and test", Recommendation = "Split into smaller, focused classes following Single Responsibility Principle", RefactoringEffort = Math.Min(totalMembers / 5, 20) // 5 members per hour, max 20 hours }); } } private void CheckDataClass(ClassInfo classInfo, ArchitectureAnalysis analysis) { var publicProperties = classInfo.Properties.Count(p => p.IsPublic); var behaviorMethods = classInfo.Methods.Count(m => !IsPropertyAccessor(m.Name) && !m.Name.Equals("ToString") && !m.Name.Equals("GetHashCode") && !m.Name.Equals("Equals")); if (publicProperties > 5 && behaviorMethods == 0) { analysis.AntiPatterns.Add(new AntiPattern { PatternName = "Data Class", PatternType = "Design", Description = $"Class contains only data ({publicProperties} properties) with no behavior", Location = $"{classInfo.Name} in {Path.GetFileName(classInfo.FilePath)}", Severity = "Medium", Impact = "Poor encapsulation, scattered business logic", Recommendation = "Add behavior methods or consider if this should be a record/DTO", RefactoringEffort = 4 }); } } private void CheckFeatureEnvy(ClassInfo classInfo, ArchitectureAnalysis analysis) { // Simple heuristic: if class has many dependencies to other classes var externalDependencies = classInfo.Dependencies.Count(d => !d.StartsWith("System.")); if (externalDependencies > 8) { analysis.AntiPatterns.Add(new AntiPattern { PatternName = "Feature Envy", PatternType = "Design", Description = $"Class depends heavily on external classes ({externalDependencies} dependencies)", Location = $"{classInfo.Name} in {Path.GetFileName(classInfo.FilePath)}", Severity = "Medium", Impact = "High coupling, potential for scattered functionality", Recommendation = "Move methods closer to the data they operate on", RefactoringEffort = 6 }); } } private void CheckShotgunSurgery(ClassInfo classInfo, ArchitectureAnalysis analysis) { // This would require more sophisticated analysis of change patterns // For now, we'll check for classes with many small methods (indicator of scattered responsibilities) var smallMethods = classInfo.Methods.Count(m => m.LinesOfCode <= 3); var totalMethods = classInfo.Methods.Count; if (totalMethods > 10 && (double)smallMethods / totalMethods > 0.7) { analysis.AntiPatterns.Add(new AntiPattern { PatternName = "Shotgun Surgery", PatternType = "Design", Description = $"Class has many small methods ({smallMethods}/{totalMethods}), indicating scattered functionality", Location = $"{classInfo.Name} in {Path.GetFileName(classInfo.FilePath)}", Severity = "Medium", Impact = "Changes require modifications in many places", Recommendation = "Consolidate related functionality into cohesive methods", RefactoringEffort = 8 }); } } private void CheckLargeClass(ClassInfo classInfo, ArchitectureAnalysis analysis) { var totalLinesOfCode = classInfo.Methods.Sum(m => m.LinesOfCode); if (totalLinesOfCode > 500) { analysis.AntiPatterns.Add(new AntiPattern { PatternName = "Large Class", PatternType = "Size", Description = $"Class is too large ({totalLinesOfCode} lines of code)", Location = $"{classInfo.Name} in {Path.GetFileName(classInfo.FilePath)}", Severity = totalLinesOfCode > 1000 ? "High" : "Medium", Impact = "Difficult to understand, maintain, and test", Recommendation = "Extract smaller, focused classes", RefactoringEffort = Math.Min(totalLinesOfCode / 50, 25) // 50 lines per hour, max 25 hours }); } } private void CheckArchitecturalAntiPatterns(ArchitectureAnalysis analysis) { // Check for monolithic structure (all classes in one layer) var layerDistribution = analysis.ProjectStructure.Layers .ToDictionary(l => l.Name, l => l.Classes.Count); var totalClasses = layerDistribution.Values.Sum(); var maxLayerSize = layerDistribution.Values.Max(); if (totalClasses > 20 && (double)maxLayerSize / totalClasses > 0.8) { var dominantLayer = layerDistribution.First(kvp => kvp.Value == maxLayerSize); analysis.AntiPatterns.Add(new AntiPattern { PatternName = "Monolithic Layer", PatternType = "Architecture", Description = $"{dominantLayer.Key} layer contains {dominantLayer.Value} of {totalClasses} classes ({(double)dominantLayer.Value / totalClasses:P})", Location = "Project Structure", Severity = "High", Impact = "Poor separation of concerns, difficult to scale and maintain", Recommendation = "Distribute classes across appropriate architectural layers", RefactoringEffort = 15 }); } // Check for missing abstraction layers if (analysis.ProjectStructure.Layers.Count < 3) { analysis.AntiPatterns.Add(new AntiPattern { PatternName = "Insufficient Layering", PatternType = "Architecture", Description = $"Only {analysis.ProjectStructure.Layers.Count} architectural layers detected", Location = "Project Structure", Severity = "Medium", Impact = "Poor separation of concerns, tight coupling", Recommendation = "Introduce proper architectural layers (e.g., Presentation, Business, Data)", RefactoringEffort = 20 }); } } // Helper methods for analysis private string GetFullClassName(ClassDeclarationSyntax cls, string filePath) { var namespaceName = GetNamespace(cls); return $"{namespaceName}.{cls.Identifier.ValueText}"; } private string GetNamespace(SyntaxNode node) { var namespaceDecl = node.Ancestors().OfType().FirstOrDefault(); return namespaceDecl?.Name.ToString() ?? "Global"; } private List ExtractClassDependencies(ClassDeclarationSyntax cls) { var dependencies = new HashSet(); // Extract from base types if (cls.BaseList != null) { foreach (var baseType in cls.BaseList.Types) { dependencies.Add(baseType.Type?.ToString() ?? string.Empty); } } // Extract from field/property types var fieldDeclarations = cls.DescendantNodes().OfType(); foreach (var field in fieldDeclarations) { dependencies.Add(field.Declaration.Type?.ToString() ?? string.Empty); } var propertyDeclarations = cls.DescendantNodes().OfType(); foreach (var property in propertyDeclarations) { dependencies.Add(property.Type?.ToString() ?? string.Empty); } // Extract from method parameters and return types var methodDeclarations = cls.DescendantNodes().OfType(); foreach (var method in methodDeclarations) { dependencies.Add(method.ReturnType?.ToString() ?? string.Empty); foreach (var parameter in method.ParameterList.Parameters) { dependencies.Add(parameter.Type?.ToString() ?? string.Empty); } } return dependencies.Where(d => !string.IsNullOrEmpty(d) && d != "void").ToList(); } private List ExtractMethodInfo(ClassDeclarationSyntax cls) { var methods = new List(); var methodDeclarations = cls.DescendantNodes().OfType(); foreach (var method in methodDeclarations) { methods.Add(new MethodInfo { Name = method.Identifier.ValueText, IsPublic = method.Modifiers.Any(m => m.IsKind(SyntaxKind.PublicKeyword)), IsPrivate = method.Modifiers.Any(m => m.IsKind(SyntaxKind.PrivateKeyword)), IsStatic = method.Modifiers.Any(m => m.IsKind(SyntaxKind.StaticKeyword)), ReturnType = method.ReturnType?.ToString() ?? string.Empty, ParameterCount = method.ParameterList.Parameters.Count, LineNumber = method.GetLocation().GetLineSpan().StartLinePosition.Line + 1, LinesOfCode = method.GetLocation().GetLineSpan().EndLinePosition.Line - method.GetLocation().GetLineSpan().StartLinePosition.Line + 1 }); } return methods; } private List ExtractPropertyInfo(ClassDeclarationSyntax cls) { var properties = new List(); var propertyDeclarations = cls.DescendantNodes().OfType(); foreach (var property in propertyDeclarations) { properties.Add(new PropertyInfo { Name = property.Identifier.ValueText, Type = property.Type?.ToString() ?? string.Empty, IsPublic = property.Modifiers.Any(m => m.IsKind(SyntaxKind.PublicKeyword)), IsPrivate = property.Modifiers.Any(m => m.IsKind(SyntaxKind.PrivateKeyword)), LineNumber = property.GetLocation().GetLineSpan().StartLinePosition.Line + 1, HasGetter = property.AccessorList?.Accessors.Any(a => a.IsKind(SyntaxKind.GetAccessorDeclaration)) ?? false, HasSetter = property.AccessorList?.Accessors.Any(a => a.IsKind(SyntaxKind.SetAccessorDeclaration)) ?? false }); } return properties; } // Naming convention helpers private bool IsPascalCase(string name) { if (string.IsNullOrEmpty(name)) return false; return char.IsUpper(name[0]) && !name.Contains('_') && !name.Contains('-'); } private string ToPascalCase(string name) { if (string.IsNullOrEmpty(name)) return name; return char.ToUpper(name[0]) + name.Substring(1); } private bool IsGenericName(string name) { var genericNames = new[] { "Data", "Info", "Object", "Item", "Element", "Thing", "Stuff", "Manager", "Helper", "Util" }; return genericNames.Any(gn => name.Equals(gn, StringComparison.OrdinalIgnoreCase)); } private bool StartsWithVerb(string methodName) { var verbs = new[] { "Get", "Set", "Add", "Remove", "Delete", "Update", "Create", "Build", "Make", "Do", "Execute", "Process", "Handle", "Manage", "Calculate", "Compute", "Parse", "Format", "Convert", "Transform", "Validate", "Check", "Is", "Has", "Can", "Should", "Will" }; return verbs.Any(verb => methodName.StartsWith(verb, StringComparison.OrdinalIgnoreCase)); } private bool IsPropertyAccessor(string methodName) { return methodName.StartsWith("get_") || methodName.StartsWith("set_") || methodName.Equals("ToString") || methodName.Equals("GetHashCode") || methodName.Equals("Equals"); } // Analysis result helpers private int CountCriticalIssues(ArchitectureAnalysis analysis) { return analysis.LayerViolations.Count(v => v.Severity == "High") + analysis.CircularDependencies.Count(c => c.Severity == "High") + analysis.AntiPatterns.Count(a => a.Severity == "High"); } private string GetArchitectureHealth(int score) { return score switch { >= 80 => "Excellent", >= 60 => "Good", >= 40 => "Fair", >= 20 => "Poor", _ => "Critical" }; } private List GetTopArchitectureRecommendations(ArchitectureAnalysis analysis) { var recommendations = new List(); // High severity issues first var highSeverityViolations = analysis.LayerViolations.Where(v => v.Severity == "High").Take(3); foreach (var violation in highSeverityViolations) { recommendations.Add($"Fix layer violation: {violation.Recommendation}"); } var criticalAntiPatterns = analysis.AntiPatterns.Where(a => a.Severity == "High").Take(2); foreach (var pattern in criticalAntiPatterns) { recommendations.Add($"Address {pattern.PatternName}: {pattern.Recommendation}"); } if (analysis.CircularDependencies.Any()) { recommendations.Add("Break circular dependencies using interfaces or event-driven patterns"); } if (!recommendations.Any()) { recommendations.Add("Architecture is well-structured. Consider minor naming improvements."); } return recommendations.Take(5).ToList(); } private string GetComplianceLevel(ArchitectureAnalysis analysis, string pattern) { var totalIssues = analysis.LayerViolations.Count + analysis.CircularDependencies.Count + analysis.AntiPatterns.Count(a => a.Severity != "Low"); return totalIssues switch { 0 => "Fully Compliant", <= 3 => "Mostly Compliant", <= 8 => "Partially Compliant", _ => "Non-Compliant" }; } private bool GetBoolParameter(IReadOnlyDictionary parameters, string key, bool defaultValue) { return parameters.TryGetValue(key, out var value) ? Convert.ToBoolean(value) : defaultValue; } } // Supporting data structures public class ArchitectureAnalysis { public string ProjectPath { get; set; } = string.Empty; public DateTime AnalysisDate { get; set; } public string DetectedPattern { get; set; } = string.Empty; public ProjectStructure ProjectStructure { get; set; } = new(); public List LayerViolations { get; set; } = new(); public List CircularDependencies { get; set; } = new(); public List NamingViolations { get; set; } = new(); public List AntiPatterns { get; set; } = new(); } public class ProjectStructure { public int TotalProjects { get; set; } public int TotalNamespaces { get; set; } public int TotalClasses { get; set; } public List Layers { get; set; } = new(); public List AllClasses { get; set; } = new(); } public class ArchitectureLayer { public string Name { get; set; } = string.Empty; public string Type { get; set; } = string.Empty; public List Projects { get; set; } = new(); public List Classes { get; set; } = new(); } public class ClassInfo { public string Name { get; set; } = string.Empty; public string FullName { get; set; } = string.Empty; public string Namespace { get; set; } = string.Empty; public string FilePath { get; set; } = string.Empty; public bool IsPublic { get; set; } public bool IsAbstract { get; set; } public bool IsStatic { get; set; } public List BaseTypes { get; set; } = new(); public List Dependencies { get; set; } = new(); public List Methods { get; set; } = new(); public List Properties { get; set; } = new(); } public class MethodInfo { public string Name { get; set; } = string.Empty; public bool IsPublic { get; set; } public bool IsPrivate { get; set; } public bool IsStatic { get; set; } public string ReturnType { get; set; } = string.Empty; public int ParameterCount { get; set; } public int LineNumber { get; set; } public int LinesOfCode { get; set; } } public class PropertyInfo { public string Name { get; set; } = string.Empty; public string Type { get; set; } = string.Empty; public bool IsPublic { get; set; } public bool IsPrivate { get; set; } public int LineNumber { get; set; } public bool HasGetter { get; set; } public bool HasSetter { get; set; } } public class LayerViolation { public string ViolationType { get; set; } = string.Empty; public string SourceLayer { get; set; } = string.Empty; public string TargetLayer { get; set; } = string.Empty; public string SourceClass { get; set; } = string.Empty; public string TargetClass { get; set; } = string.Empty; public string FilePath { get; set; } = string.Empty; public int LineNumber { get; set; } public string Severity { get; set; } = string.Empty; public string Description { get; set; } = string.Empty; public string Recommendation { get; set; } = string.Empty; } public class CircularDependency { public string DependencyType { get; set; } = string.Empty; public string DependencyChain { get; set; } = string.Empty; public string Severity { get; set; } = string.Empty; public string Description { get; set; } = string.Empty; public string Recommendation { get; set; } = string.Empty; } public class NamingViolation { public string ViolationType { get; set; } = string.Empty; public string ElementName { get; set; } = string.Empty; public string ElementType { get; set; } = string.Empty; public string CurrentNaming { get; set; } = string.Empty; public string ExpectedNaming { get; set; } = string.Empty; public string FilePath { get; set; } = string.Empty; public int LineNumber { get; set; } public string Severity { get; set; } = string.Empty; public string Recommendation { get; set; } = string.Empty; } public class AntiPattern { public string PatternName { get; set; } = string.Empty; public string PatternType { get; set; } = string.Empty; public string Description { get; set; } = string.Empty; public string Location { get; set; } = string.Empty; public string Severity { get; set; } = string.Empty; public string Impact { get; set; } = string.Empty; public string Recommendation { get; set; } = string.Empty; public int RefactoringEffort { get; set; } } }