MarketAlly.AIPlugin.Extensions/MarketAlly.AIPlugin.Analysis/ArchitectureValidatorPlugin.cs

1319 lines
44 KiB
C#
Executable File

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<string, Type> SupportedParameters => new Dictionary<string, Type>
{
["projectPath"] = typeof(string),
["architecturePattern"] = typeof(string),
["checkLayerBoundaries"] = typeof(bool),
["checkCircularDependencies"] = typeof(bool),
["validateNaming"] = typeof(bool),
["checkAntiPatterns"] = typeof(bool),
["generateDocumentation"] = typeof(bool)
};
public async Task<AIPluginResult> ExecuteAsync(IReadOnlyDictionary<string, object> 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<LayerViolation>(),
CircularDependencies = new List<CircularDependency>(),
NamingViolations = new List<NamingViolation>(),
AntiPatterns = new List<AntiPattern>(),
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<string>();
var classes = new List<ClassInfo>();
var layerMap = new Dictionary<string, ArchitectureLayer>();
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<NamespaceDeclarationSyntax>();
foreach (var ns in namespaceDeclarations)
{
namespaces.Add(ns.Name?.ToString() ?? string.Empty);
}
// Extract class information
var classDeclarations = root.DescendantNodes().OfType<ClassDeclarationSyntax>();
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<string>(),
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<string>();
var recursionStack = new HashSet<string>();
var path = new List<string>();
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<string>
{
"# 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<string> layers, List<string> 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<string> layers, List<string> 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<string> layers, List<string> 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<string> layers, List<string> 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<string> layers, List<string> 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<string, int> GetLayerHierarchy(string pattern)
{
return pattern.ToLowerInvariant() switch
{
"clean architecture" => new Dictionary<string, int>
{
["Domain"] = 1,
["Application"] = 2,
["Infrastructure"] = 3,
["Presentation"] = 4
},
"mvc" => new Dictionary<string, int>
{
["Domain"] = 1,
["Business"] = 2,
["Presentation"] = 3
},
"layered" => new Dictionary<string, int>
{
["Domain"] = 1,
["Business"] = 2,
["Data"] = 3,
["Presentation"] = 4
},
_ => new Dictionary<string, int>()
};
}
private bool IsLayerViolation(ArchitectureLayer sourceLayer, ArchitectureLayer targetLayer, Dictionary<string, int> 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<string, List<string>> BuildDependencyGraph(List<ClassInfo> classes)
{
var graph = new Dictionary<string, List<string>>();
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<string, List<string>> graph,
HashSet<string> visited, HashSet<string> recursionStack, List<string> 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<string, HashSet<string>>();
foreach (var ns in namespaces.Keys)
{
namespaceGraph[ns] = new HashSet<string>();
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<NamespaceDeclarationSyntax>().FirstOrDefault();
return namespaceDecl?.Name.ToString() ?? "Global";
}
private List<string> ExtractClassDependencies(ClassDeclarationSyntax cls)
{
var dependencies = new HashSet<string>();
// 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<FieldDeclarationSyntax>();
foreach (var field in fieldDeclarations)
{
dependencies.Add(field.Declaration.Type?.ToString() ?? string.Empty);
}
var propertyDeclarations = cls.DescendantNodes().OfType<PropertyDeclarationSyntax>();
foreach (var property in propertyDeclarations)
{
dependencies.Add(property.Type?.ToString() ?? string.Empty);
}
// Extract from method parameters and return types
var methodDeclarations = cls.DescendantNodes().OfType<MethodDeclarationSyntax>();
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<MethodInfo> ExtractMethodInfo(ClassDeclarationSyntax cls)
{
var methods = new List<MethodInfo>();
var methodDeclarations = cls.DescendantNodes().OfType<MethodDeclarationSyntax>();
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<PropertyInfo> ExtractPropertyInfo(ClassDeclarationSyntax cls)
{
var properties = new List<PropertyInfo>();
var propertyDeclarations = cls.DescendantNodes().OfType<PropertyDeclarationSyntax>();
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<string> GetTopArchitectureRecommendations(ArchitectureAnalysis analysis)
{
var recommendations = new List<string>();
// 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<string, object> 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<LayerViolation> LayerViolations { get; set; } = new();
public List<CircularDependency> CircularDependencies { get; set; } = new();
public List<NamingViolation> NamingViolations { get; set; } = new();
public List<AntiPattern> AntiPatterns { get; set; } = new();
}
public class ProjectStructure
{
public int TotalProjects { get; set; }
public int TotalNamespaces { get; set; }
public int TotalClasses { get; set; }
public List<ArchitectureLayer> Layers { get; set; } = new();
public List<ClassInfo> AllClasses { get; set; } = new();
}
public class ArchitectureLayer
{
public string Name { get; set; } = string.Empty;
public string Type { get; set; } = string.Empty;
public List<string> Projects { get; set; } = new();
public List<ClassInfo> 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<string> BaseTypes { get; set; } = new();
public List<string> Dependencies { get; set; } = new();
public List<MethodInfo> Methods { get; set; } = new();
public List<PropertyInfo> 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; }
}
}