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.Threading.Tasks; namespace MarketAlly.AIPlugin.Analysis.Plugins { [AIPlugin("ComplexityAnalyzer", "Measures cyclomatic and cognitive complexity with refactoring suggestions")] public class ComplexityAnalyzerPlugin : IAIPlugin { [AIParameter("Full path to the file or directory to analyze", required: true)] public string Path { get; set; } = string.Empty; [AIParameter("Calculate cyclomatic complexity", required: false)] public bool CalculateCyclomatic { get; set; } = true; [AIParameter("Calculate cognitive complexity", required: false)] public bool CalculateCognitive { get; set; } = true; [AIParameter("Maximum acceptable cyclomatic complexity", required: false)] public int MaxCyclomaticComplexity { get; set; } = 10; [AIParameter("Maximum acceptable cognitive complexity", required: false)] public int MaxCognitiveComplexity { get; set; } = 15; [AIParameter("Generate complexity reduction suggestions", required: false)] public bool GenerateSuggestions { get; set; } = true; [AIParameter("Include method-level complexity breakdown", required: false)] public bool IncludeMethodBreakdown { get; set; } = true; public IReadOnlyDictionary SupportedParameters => new Dictionary { ["path"] = typeof(string), ["calculateCyclomatic"] = typeof(bool), ["calculateCognitive"] = typeof(bool), ["maxCyclomaticComplexity"] = typeof(int), ["maxCognitiveComplexity"] = typeof(int), ["generateSuggestions"] = typeof(bool), ["includeMethodBreakdown"] = typeof(bool) }; public async Task ExecuteAsync(IReadOnlyDictionary parameters) { try { // Extract parameters string path = parameters["path"]?.ToString() ?? string.Empty; bool calculateCyclomatic = GetBoolParameter(parameters, "calculateCyclomatic", true); bool calculateCognitive = GetBoolParameter(parameters, "calculateCognitive", true); int maxCyclomatic = GetIntParameter(parameters, "maxCyclomaticComplexity", 10); int maxCognitive = GetIntParameter(parameters, "maxCognitiveComplexity", 15); bool generateSuggestions = GetBoolParameter(parameters, "generateSuggestions", true); bool includeMethodBreakdown = GetBoolParameter(parameters, "includeMethodBreakdown", true); // Validate path if (!File.Exists(path) && !Directory.Exists(path)) { return new AIPluginResult( new FileNotFoundException($"Path not found: {path}"), "Path not found" ); } // Get files to analyze var filesToAnalyze = GetFilesToAnalyze(path); if (!filesToAnalyze.Any()) { return new AIPluginResult( new InvalidOperationException("No C# files found to analyze"), "No files found" ); } // Analyze complexity for each file var fileResults = new List(); var overallMetrics = new ComplexityMetrics(); var highComplexityMethods = new List(); var violations = new List(); foreach (string filePath in filesToAnalyze) { var fileResult = await AnalyzeFileComplexity( filePath, calculateCyclomatic, calculateCognitive, maxCyclomatic, maxCognitive, includeMethodBreakdown); fileResults.Add(fileResult); overallMetrics.Add(fileResult.Metrics); highComplexityMethods.AddRange(fileResult.HighComplexityMethods); violations.AddRange(fileResult.Violations); } // Generate suggestions if requested var suggestions = new List(); if (generateSuggestions) { suggestions = GenerateComplexityReductionSuggestions(highComplexityMethods, violations); } // Calculate overall complexity score (0-100, higher is better) int overallScore = CalculateOverallComplexityScore(overallMetrics, maxCyclomatic, maxCognitive); var result = new { Path = path, FilesAnalyzed = filesToAnalyze.Count, CyclomaticComplexity = calculateCyclomatic ? new { Average = overallMetrics.AverageCyclomaticComplexity, Maximum = overallMetrics.MaxCyclomaticComplexity, Total = overallMetrics.TotalCyclomaticComplexity, MethodsAboveThreshold = overallMetrics.CyclomaticViolations } : null, CognitiveComplexity = calculateCognitive ? new { Average = overallMetrics.AverageCognitiveComplexity, Maximum = overallMetrics.MaxCognitiveComplexity, Total = overallMetrics.TotalCognitiveComplexity, MethodsAboveThreshold = overallMetrics.CognitiveViolations } : null, HighComplexityMethods = highComplexityMethods.Take(10).Select(m => new { m.MethodName, m.ClassName, m.FilePath, m.LineNumber, m.CyclomaticComplexity, m.CognitiveComplexity, m.ParameterCount, m.LinesOfCode }).ToList(), ComplexityViolations = violations.Select(v => new { v.MethodName, v.ClassName, v.FilePath, v.LineNumber, v.ViolationType, v.ActualValue, v.ThresholdValue, v.Severity }).ToList(), ReductionSuggestions = suggestions, MethodBreakdown = includeMethodBreakdown ? fileResults.SelectMany(f => f.MethodDetails).ToList() : null, OverallComplexityScore = overallScore, Summary = new { TotalMethods = overallMetrics.TotalMethods, HighComplexityMethods = highComplexityMethods.Count, TotalViolations = violations.Count, AverageMethodComplexity = Math.Round(overallMetrics.AverageCyclomaticComplexity, 2), RecommendedActions = violations.Count > 0 ? "Refactor high-complexity methods" : "Complexity within acceptable limits" } }; return new AIPluginResult(result, $"Complexity analysis completed for {filesToAnalyze.Count} files"); } catch (Exception ex) { return new AIPluginResult(ex, "Failed to analyze complexity"); } } private async Task AnalyzeFileComplexity( string filePath, bool calculateCyclomatic, bool calculateCognitive, int maxCyclomatic, int maxCognitive, bool includeMethodBreakdown) { var sourceCode = await File.ReadAllTextAsync(filePath); var syntaxTree = CSharpSyntaxTree.ParseText(sourceCode, path: filePath); var root = await syntaxTree.GetRootAsync(); var methods = root.DescendantNodes().OfType().ToList(); var constructors = root.DescendantNodes().OfType().ToList(); var properties = root.DescendantNodes().OfType() .Where(p => p.AccessorList?.Accessors.Any(a => a.Body != null || a.ExpressionBody != null) == true) .ToList(); var result = new FileComplexityResult { FilePath = filePath, FileName = System.IO.Path.GetFileName(filePath), Metrics = new ComplexityMetrics(), HighComplexityMethods = new List(), Violations = new List(), MethodDetails = new List() }; // Analyze methods foreach (var method in methods) { var methodInfo = AnalyzeMethod(method, filePath, calculateCyclomatic, calculateCognitive); result.Metrics.Add(methodInfo); if (methodInfo.CyclomaticComplexity > maxCyclomatic || methodInfo.CognitiveComplexity > maxCognitive) { result.HighComplexityMethods.Add(methodInfo); } // Check for violations if (calculateCyclomatic && methodInfo.CyclomaticComplexity > maxCyclomatic) { result.Violations.Add(new ComplexityViolation { MethodName = methodInfo.MethodName, ClassName = methodInfo.ClassName, FilePath = filePath, LineNumber = methodInfo.LineNumber, ViolationType = "CyclomaticComplexity", ActualValue = methodInfo.CyclomaticComplexity, ThresholdValue = maxCyclomatic, Severity = methodInfo.CyclomaticComplexity > maxCyclomatic * 1.5 ? "High" : "Medium" }); } if (calculateCognitive && methodInfo.CognitiveComplexity > maxCognitive) { result.Violations.Add(new ComplexityViolation { MethodName = methodInfo.MethodName, ClassName = methodInfo.ClassName, FilePath = filePath, LineNumber = methodInfo.LineNumber, ViolationType = "CognitiveComplexity", ActualValue = methodInfo.CognitiveComplexity, ThresholdValue = maxCognitive, Severity = methodInfo.CognitiveComplexity > maxCognitive * 1.5 ? "High" : "Medium" }); } if (includeMethodBreakdown) { result.MethodDetails.Add(new { methodInfo.MethodName, methodInfo.ClassName, methodInfo.CyclomaticComplexity, methodInfo.CognitiveComplexity, methodInfo.ParameterCount, methodInfo.LinesOfCode, methodInfo.LineNumber }); } } // Analyze constructors foreach (var constructor in constructors) { var methodInfo = AnalyzeConstructor(constructor, filePath, calculateCyclomatic, calculateCognitive); result.Metrics.Add(methodInfo); if (methodInfo.CyclomaticComplexity > maxCyclomatic || methodInfo.CognitiveComplexity > maxCognitive) { result.HighComplexityMethods.Add(methodInfo); } } return result; } private MethodComplexityInfo AnalyzeMethod(MethodDeclarationSyntax method, string filePath, bool calculateCyclomatic, bool calculateCognitive) { var className = GetContainingClassName(method); var lineNumber = method.GetLocation().GetLineSpan().StartLinePosition.Line + 1; var info = new MethodComplexityInfo { MethodName = method.Identifier.ValueText, ClassName = className, FilePath = filePath, LineNumber = lineNumber, ParameterCount = method.ParameterList.Parameters.Count, LinesOfCode = CalculateLinesOfCode(method) }; if (calculateCyclomatic) { info.CyclomaticComplexity = CalculateCyclomaticComplexity(method); } if (calculateCognitive) { info.CognitiveComplexity = CalculateCognitiveComplexity(method); } return info; } private MethodComplexityInfo AnalyzeConstructor(ConstructorDeclarationSyntax constructor, string filePath, bool calculateCyclomatic, bool calculateCognitive) { var className = GetContainingClassName(constructor); var lineNumber = constructor.GetLocation().GetLineSpan().StartLinePosition.Line + 1; var info = new MethodComplexityInfo { MethodName = $"{className} (constructor)", ClassName = className, FilePath = filePath, LineNumber = lineNumber, ParameterCount = constructor.ParameterList.Parameters.Count, LinesOfCode = CalculateLinesOfCode(constructor) }; if (calculateCyclomatic) { info.CyclomaticComplexity = CalculateCyclomaticComplexity(constructor); } if (calculateCognitive) { info.CognitiveComplexity = CalculateCognitiveComplexity(constructor); } return info; } private int CalculateCyclomaticComplexity(SyntaxNode node) { int complexity = 1; // Base complexity var descendants = node.DescendantNodes(); // Decision points that increase complexity complexity += descendants.OfType().Count(); complexity += descendants.OfType().Count(); complexity += descendants.OfType().Count(); complexity += descendants.OfType().Count(); complexity += descendants.OfType().Count(); complexity += descendants.OfType().Count(); complexity += descendants.OfType().Count(); complexity += descendants.OfType().Count(); // Ternary operator complexity += descendants.OfType().Count(); // Case statements in switch foreach (var switchStmt in descendants.OfType()) { complexity += switchStmt.Sections.Count - 1; // Subtract 1 because switch already counted } // Switch expression arms foreach (var switchExpr in descendants.OfType()) { complexity += switchExpr.Arms.Count - 1; // Subtract 1 because switch already counted } // Logical operators (&& and ||) var binaryExpressions = descendants.OfType(); foreach (var expr in binaryExpressions) { if (expr.OperatorToken.IsKind(SyntaxKind.AmpersandAmpersandToken) || expr.OperatorToken.IsKind(SyntaxKind.BarBarToken)) { complexity++; } } return complexity; } private int CalculateCognitiveComplexity(SyntaxNode node) { var calculator = new CognitiveComplexityCalculator(); return calculator.Calculate(node); } private int CalculateLinesOfCode(SyntaxNode node) { var span = node.GetLocation().GetLineSpan(); return span.EndLinePosition.Line - span.StartLinePosition.Line + 1; } private string GetContainingClassName(SyntaxNode node) { var classDeclaration = node.Ancestors().OfType().FirstOrDefault(); if (classDeclaration != null) { return classDeclaration.Identifier.ValueText; } var structDeclaration = node.Ancestors().OfType().FirstOrDefault(); if (structDeclaration != null) { return structDeclaration.Identifier.ValueText; } return "Unknown"; } private List GetFilesToAnalyze(string path) { var files = new List(); if (File.Exists(path)) { if (path.EndsWith(".cs", StringComparison.OrdinalIgnoreCase)) { files.Add(path); } } else if (Directory.Exists(path)) { files.AddRange(Directory.GetFiles(path, "*.cs", SearchOption.AllDirectories)); } return files; } private List GenerateComplexityReductionSuggestions( List highComplexityMethods, List violations) { var suggestions = new List(); if (!highComplexityMethods.Any()) { suggestions.Add("✅ All methods have acceptable complexity levels."); return suggestions; } // General suggestions suggestions.Add("🔧 Consider the following complexity reduction strategies:"); // Method extraction suggestions var methodsWithHighCyclomatic = highComplexityMethods.Where(m => m.CyclomaticComplexity > 15).ToList(); if (methodsWithHighCyclomatic.Any()) { suggestions.Add($"📝 Extract smaller methods from {methodsWithHighCyclomatic.Count} method(s) with high cyclomatic complexity (>15)"); suggestions.Add(" • Break down large conditional blocks into separate methods"); suggestions.Add(" • Extract loop bodies into dedicated methods"); suggestions.Add(" • Use early returns to reduce nesting levels"); } // Parameter count suggestions var methodsWithManyParams = highComplexityMethods.Where(m => m.ParameterCount > 5).ToList(); if (methodsWithManyParams.Any()) { suggestions.Add($"📦 Reduce parameter count for {methodsWithManyParams.Count} method(s) with >5 parameters"); suggestions.Add(" • Consider using parameter objects or DTOs"); suggestions.Add(" • Use builder pattern for complex object creation"); } // Large method suggestions var largeMethods = highComplexityMethods.Where(m => m.LinesOfCode > 50).ToList(); if (largeMethods.Any()) { suggestions.Add($"📏 Break down {largeMethods.Count} large method(s) (>50 lines)"); suggestions.Add(" • Apply Single Responsibility Principle"); suggestions.Add(" • Extract helper methods for specific tasks"); } // Specific method suggestions var topComplexMethods = highComplexityMethods .OrderByDescending(m => m.CyclomaticComplexity + m.CognitiveComplexity) .Take(3); suggestions.Add("🎯 Priority refactoring targets:"); foreach (var method in topComplexMethods) { suggestions.Add($" • {method.ClassName}.{method.MethodName} " + $"(Cyclomatic: {method.CyclomaticComplexity}, Cognitive: {method.CognitiveComplexity})"); } return suggestions; } private int CalculateOverallComplexityScore(ComplexityMetrics metrics, int maxCyclomatic, int maxCognitive) { if (metrics.TotalMethods == 0) return 100; // Calculate percentage of methods within acceptable limits var cyclomaticScore = metrics.TotalMethods > 0 ? (double)(metrics.TotalMethods - metrics.CyclomaticViolations) / metrics.TotalMethods * 100 : 100; var cognitiveScore = metrics.TotalMethods > 0 ? (double)(metrics.TotalMethods - metrics.CognitiveViolations) / metrics.TotalMethods * 100 : 100; // Weighted average (cognitive complexity is slightly more important) var overallScore = (cyclomaticScore * 0.4 + cognitiveScore * 0.6); // Penalty for extremely high complexity methods var avgComplexity = metrics.AverageCyclomaticComplexity; if (avgComplexity > maxCyclomatic * 2) { overallScore *= 0.7; // 30% penalty } else if (avgComplexity > maxCyclomatic * 1.5) { overallScore *= 0.85; // 15% penalty } return Math.Max(0, Math.Min(100, (int)Math.Round(overallScore))); } private bool GetBoolParameter(IReadOnlyDictionary parameters, string key, bool defaultValue) { return parameters.TryGetValue(key, out var value) ? Convert.ToBoolean(value) : defaultValue; } private int GetIntParameter(IReadOnlyDictionary parameters, string key, int defaultValue) { return parameters.TryGetValue(key, out var value) ? Convert.ToInt32(value) : defaultValue; } } // Supporting classes for complexity analysis public class ComplexityMetrics { public int TotalMethods { get; set; } public int TotalCyclomaticComplexity { get; set; } public int TotalCognitiveComplexity { get; set; } public int MaxCyclomaticComplexity { get; set; } public int MaxCognitiveComplexity { get; set; } public int CyclomaticViolations { get; set; } public int CognitiveViolations { get; set; } public double AverageCyclomaticComplexity => TotalMethods > 0 ? (double)TotalCyclomaticComplexity / TotalMethods : 0; public double AverageCognitiveComplexity => TotalMethods > 0 ? (double)TotalCognitiveComplexity / TotalMethods : 0; public void Add(MethodComplexityInfo method) { TotalMethods++; TotalCyclomaticComplexity += method.CyclomaticComplexity; TotalCognitiveComplexity += method.CognitiveComplexity; if (method.CyclomaticComplexity > MaxCyclomaticComplexity) MaxCyclomaticComplexity = method.CyclomaticComplexity; if (method.CognitiveComplexity > MaxCognitiveComplexity) MaxCognitiveComplexity = method.CognitiveComplexity; } public void Add(ComplexityMetrics other) { TotalMethods += other.TotalMethods; TotalCyclomaticComplexity += other.TotalCyclomaticComplexity; TotalCognitiveComplexity += other.TotalCognitiveComplexity; if (other.MaxCyclomaticComplexity > MaxCyclomaticComplexity) MaxCyclomaticComplexity = other.MaxCyclomaticComplexity; if (other.MaxCognitiveComplexity > MaxCognitiveComplexity) MaxCognitiveComplexity = other.MaxCognitiveComplexity; CyclomaticViolations += other.CyclomaticViolations; CognitiveViolations += other.CognitiveViolations; } } public class MethodComplexityInfo { public string MethodName { get; set; } = string.Empty; public string ClassName { get; set; } = string.Empty; public string FilePath { get; set; } = string.Empty; public int LineNumber { get; set; } public int CyclomaticComplexity { get; set; } public int CognitiveComplexity { get; set; } public int ParameterCount { get; set; } public int LinesOfCode { get; set; } } public class ComplexityViolation { public string MethodName { get; set; } = string.Empty; public string ClassName { get; set; } = string.Empty; public string FilePath { get; set; } = string.Empty; public int LineNumber { get; set; } public string ViolationType { get; set; } = string.Empty; public int ActualValue { get; set; } public int ThresholdValue { get; set; } public string Severity { get; set; } = string.Empty; } public class FileComplexityResult { public string FilePath { get; set; } = string.Empty; public string FileName { get; set; } = string.Empty; public ComplexityMetrics Metrics { get; set; } = new(); public List HighComplexityMethods { get; set; } = new(); public List Violations { get; set; } = new(); public List MethodDetails { get; set; } = new(); } // Cognitive Complexity Calculator (implements the cognitive complexity algorithm) public class CognitiveComplexityCalculator { private int _complexity; private int _nestingLevel; public int Calculate(SyntaxNode node) { _complexity = 0; _nestingLevel = 0; Visit(node); return _complexity; } private void Visit(SyntaxNode node) { switch (node) { case IfStatementSyntax _: case WhileStatementSyntax _: case ForStatementSyntax _: case ForEachStatementSyntax _: case DoStatementSyntax _: _complexity += 1 + _nestingLevel; _nestingLevel++; VisitChildren(node); _nestingLevel--; break; case SwitchStatementSyntax switchStmt: _complexity += 1 + _nestingLevel; _nestingLevel++; VisitChildren(node); _nestingLevel--; break; case ConditionalExpressionSyntax _: _complexity += 1 + _nestingLevel; VisitChildren(node); break; case CatchClauseSyntax _: _complexity += 1 + _nestingLevel; _nestingLevel++; VisitChildren(node); _nestingLevel--; break; case BinaryExpressionSyntax binary when binary.OperatorToken.IsKind(SyntaxKind.AmpersandAmpersandToken) || binary.OperatorToken.IsKind(SyntaxKind.BarBarToken): _complexity += 1; VisitChildren(node); break; default: VisitChildren(node); break; } } private void VisitChildren(SyntaxNode node) { foreach (var child in node.ChildNodes()) { Visit(child); } } } }