1319 lines
44 KiB
C#
Executable File
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; }
|
|
}
|
|
} |