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.Refactoring.Plugins { [AIPlugin("CodeAnalysis", "Analyzes code structure, complexity metrics, and suggests refactoring improvements")] public class CodeAnalysisPlugin : IAIPlugin { [AIParameter("Full path to the file or directory to analyze", required: true)] public string Path { get; set; } [AIParameter("Analysis depth: basic, detailed, comprehensive", required: false)] public string AnalysisDepth { get; set; } = "detailed"; [AIParameter("Include complexity metrics in analysis", required: false)] public bool IncludeComplexity { get; set; } = true; [AIParameter("Include code smell detection", required: false)] public bool IncludeCodeSmells { get; set; } = true; [AIParameter("Include refactoring suggestions", required: false)] public bool IncludeSuggestions { get; set; } = true; public IReadOnlyDictionary SupportedParameters => new Dictionary { ["path"] = typeof(string), ["analysisDepth"] = typeof(string), ["includeComplexity"] = typeof(bool), ["includeCodeSmells"] = typeof(bool), ["includeSuggestions"] = typeof(bool) }; public async Task ExecuteAsync(IReadOnlyDictionary parameters) { try { // Extract parameters string path = parameters["path"].ToString(); string analysisDepth = parameters.TryGetValue("analysisDepth", out var depthObj) ? depthObj.ToString().ToLower() : "detailed"; bool includeComplexity = parameters.TryGetValue("includeComplexity", out var complexityObj) ? Convert.ToBoolean(complexityObj) : true; bool includeCodeSmells = parameters.TryGetValue("includeCodeSmells", out var smellsObj) ? Convert.ToBoolean(smellsObj) : true; bool includeSuggestions = parameters.TryGetValue("includeSuggestions", out var suggestionsObj) ? Convert.ToBoolean(suggestionsObj) : true; // Validate path if (!File.Exists(path) && !Directory.Exists(path)) { return new AIPluginResult( new FileNotFoundException($"Path not found: {path}"), "Invalid path" ); } var analysisResults = new List(); if (File.Exists(path)) { // Analyze single file var result = await AnalyzeFileAsync(path, analysisDepth, includeComplexity, includeCodeSmells, includeSuggestions); if (result != null) analysisResults.Add(result); } else { // Analyze directory var csharpFiles = Directory.GetFiles(path, "*.cs", SearchOption.AllDirectories) .Where(f => !ShouldExcludeFile(f)) .ToList(); foreach (var file in csharpFiles) { var result = await AnalyzeFileAsync(file, analysisDepth, includeComplexity, includeCodeSmells, includeSuggestions); if (result != null) analysisResults.Add(result); } } // Generate summary var summary = GenerateAnalysisSummary(analysisResults, analysisDepth); return new AIPluginResult(new { Message = $"Code analysis completed for {analysisResults.Count} file(s)", Path = path, AnalysisDepth = analysisDepth, Summary = summary, DetailedResults = analysisResults, Timestamp = DateTime.UtcNow }); } catch (Exception ex) { return new AIPluginResult(ex, $"Code analysis failed: {ex.Message}"); } } private async Task AnalyzeFileAsync(string filePath, string analysisDepth, bool includeComplexity, bool includeCodeSmells, bool includeSuggestions) { try { var sourceCode = await File.ReadAllTextAsync(filePath); var syntaxTree = CSharpSyntaxTree.ParseText(sourceCode); var root = syntaxTree.GetRoot(); var result = new CodeAnalysisResult { FilePath = filePath, FileName = System.IO.Path.GetFileName(filePath), LinesOfCode = sourceCode.Split('\n').Length, Timestamp = DateTime.UtcNow }; // Basic structure analysis await AnalyzeStructure(result, root); // Complexity analysis if (includeComplexity) { await AnalyzeComplexity(result, root); } // Code smell detection if (includeCodeSmells) { await DetectCodeSmells(result, root, sourceCode); } // Generate suggestions if (includeSuggestions) { await GenerateRefactoringSuggestions(result, root, analysisDepth); } return result; } catch (Exception ex) { return new CodeAnalysisResult { FilePath = filePath, FileName = System.IO.Path.GetFileName(filePath), Error = ex.Message, Timestamp = DateTime.UtcNow }; } } private async Task AnalyzeStructure(CodeAnalysisResult result, SyntaxNode root) { var structure = new CodeStructure(); // Count different types of declarations structure.Classes = root.DescendantNodes().OfType().Count(); structure.Interfaces = root.DescendantNodes().OfType().Count(); structure.Methods = root.DescendantNodes().OfType().Count(); structure.Properties = root.DescendantNodes().OfType().Count(); structure.Fields = root.DescendantNodes().OfType().Count(); // Analyze using statements structure.UsingStatements = root.DescendantNodes().OfType().Count(); // Analyze namespaces var namespaces = root.DescendantNodes().OfType() .Select(n => n.Name.ToString()) .Distinct() .ToList(); structure.Namespaces = namespaces; result.Structure = structure; await Task.CompletedTask; } private async Task AnalyzeComplexity(CodeAnalysisResult result, SyntaxNode root) { var complexity = new ComplexityMetrics(); var methods = root.DescendantNodes().OfType(); foreach (var method in methods) { var methodComplexity = CalculateCyclomaticComplexity(method); var cognitiveComplexity = CalculateCognitiveComplexity(method); complexity.Methods.Add(new MethodComplexity { MethodName = method.Identifier.ValueText, CyclomaticComplexity = methodComplexity, CognitiveComplexity = cognitiveComplexity, LineCount = method.GetText().Lines.Count, ParameterCount = method.ParameterList.Parameters.Count }); } complexity.AverageCyclomaticComplexity = complexity.Methods.Any() ? complexity.Methods.Average(m => m.CyclomaticComplexity) : 0; complexity.AverageCognitiveComplexity = complexity.Methods.Any() ? complexity.Methods.Average(m => m.CognitiveComplexity) : 0; complexity.MaxComplexity = complexity.Methods.Any() ? complexity.Methods.Max(m => m.CyclomaticComplexity) : 0; result.Complexity = complexity; await Task.CompletedTask; } private async Task DetectCodeSmells(CodeAnalysisResult result, SyntaxNode root, string sourceCode) { var codeSmells = new List(); // God Class detection var classes = root.DescendantNodes().OfType(); foreach (var cls in classes) { var methodCount = cls.DescendantNodes().OfType().Count(); var lineCount = cls.GetText().Lines.Count; if (methodCount > 20 || lineCount > 500) { codeSmells.Add(new CodeSmell { Type = "God Class", Severity = "High", Description = $"Class '{cls.Identifier.ValueText}' is too large ({methodCount} methods, {lineCount} lines)", Location = $"Line {cls.GetLocation().GetLineSpan().StartLinePosition.Line + 1}", Suggestion = "Consider splitting this class into smaller, more focused classes" }); } } // Long Method detection var methods = root.DescendantNodes().OfType(); foreach (var method in methods) { var lineCount = method.GetText().Lines.Count; if (lineCount > 50) { codeSmells.Add(new CodeSmell { Type = "Long Method", Severity = "Medium", Description = $"Method '{method.Identifier.ValueText}' is too long ({lineCount} lines)", Location = $"Line {method.GetLocation().GetLineSpan().StartLinePosition.Line + 1}", Suggestion = "Consider extracting parts of this method into smaller methods" }); } } // Long Parameter List detection foreach (var method in methods) { var paramCount = method.ParameterList.Parameters.Count; if (paramCount > 5) { codeSmells.Add(new CodeSmell { Type = "Long Parameter List", Severity = "Medium", Description = $"Method '{method.Identifier.ValueText}' has too many parameters ({paramCount})", Location = $"Line {method.GetLocation().GetLineSpan().StartLinePosition.Line + 1}", Suggestion = "Consider grouping parameters into a class or using method overloads" }); } } // Duplicate Code detection (simplified) var stringLiterals = root.DescendantNodes().OfType() .Where(l => l.Token.IsKind(SyntaxKind.StringLiteralToken)) .GroupBy(l => l.Token.ValueText) .Where(g => g.Count() > 3 && g.Key.Length > 10) .ToList(); foreach (var group in stringLiterals) { codeSmells.Add(new CodeSmell { Type = "Duplicate String Literals", Severity = "Low", Description = $"String literal '{group.Key}' appears {group.Count()} times", Location = "Multiple locations", Suggestion = "Consider extracting this string to a constant" }); } result.CodeSmells = codeSmells; await Task.CompletedTask; } private async Task GenerateRefactoringSuggestions(CodeAnalysisResult result, SyntaxNode root, string analysisDepth) { var suggestions = new List(); // Extract Method suggestions var methods = root.DescendantNodes().OfType(); foreach (var method in methods) { var complexity = CalculateCyclomaticComplexity(method); if (complexity > 10) { suggestions.Add(new RefactoringSuggestion { Type = "Extract Method", Priority = "High", Description = $"Method '{method.Identifier.ValueText}' has high complexity ({complexity})", Location = $"Line {method.GetLocation().GetLineSpan().StartLinePosition.Line + 1}", Recommendation = "Consider extracting complex logic into separate methods", EstimatedEffort = "Medium" }); } } // Extract Class suggestions var classes = root.DescendantNodes().OfType(); foreach (var cls in classes) { var methodCount = cls.DescendantNodes().OfType().Count(); if (methodCount > 15) { suggestions.Add(new RefactoringSuggestion { Type = "Extract Class", Priority = "High", Description = $"Class '{cls.Identifier.ValueText}' has too many responsibilities ({methodCount} methods)", Location = $"Line {cls.GetLocation().GetLineSpan().StartLinePosition.Line + 1}", Recommendation = "Consider splitting into multiple classes with single responsibilities", EstimatedEffort = "High" }); } } // Introduce Parameter Object suggestions foreach (var method in methods) { var paramCount = method.ParameterList.Parameters.Count; if (paramCount > 4) { suggestions.Add(new RefactoringSuggestion { Type = "Introduce Parameter Object", Priority = "Medium", Description = $"Method '{method.Identifier.ValueText}' has many parameters ({paramCount})", Location = $"Line {method.GetLocation().GetLineSpan().StartLinePosition.Line + 1}", Recommendation = "Consider grouping related parameters into a class", EstimatedEffort = "Low" }); } } // Documentation suggestions var undocumentedPublicMembers = root.DescendantNodes() .Where(n => IsPublicMember(n) && !HasDocumentation(n)) .Take(10) // Limit suggestions .ToList(); if (undocumentedPublicMembers.Any()) { suggestions.Add(new RefactoringSuggestion { Type = "Add Documentation", Priority = "Low", Description = $"{undocumentedPublicMembers.Count} public members lack XML documentation", Location = "Multiple locations", Recommendation = "Add XML documentation to improve code maintainability", EstimatedEffort = "Low" }); } result.Suggestions = suggestions; await Task.CompletedTask; } private int CalculateCyclomaticComplexity(MethodDeclarationSyntax method) { int complexity = 1; // Base complexity // Count decision points var decisionNodes = method.DescendantNodes().Where(node => node.IsKind(SyntaxKind.IfStatement) || node.IsKind(SyntaxKind.WhileStatement) || node.IsKind(SyntaxKind.ForStatement) || node.IsKind(SyntaxKind.ForEachStatement) || node.IsKind(SyntaxKind.DoStatement) || node.IsKind(SyntaxKind.SwitchStatement) || node.IsKind(SyntaxKind.CaseSwitchLabel) || node.IsKind(SyntaxKind.CatchClause) || node.IsKind(SyntaxKind.ConditionalExpression) ); complexity += decisionNodes.Count(); // Count logical operators var logicalOperators = method.DescendantTokens().Where(token => token.IsKind(SyntaxKind.AmpersandAmpersandToken) || token.IsKind(SyntaxKind.BarBarToken) ); complexity += logicalOperators.Count(); return complexity; } private int CalculateCognitiveComplexity(MethodDeclarationSyntax method) { // Simplified cognitive complexity calculation // In practice, this would need more sophisticated nesting depth tracking int complexity = 0; int nestingLevel = 0; foreach (var node in method.DescendantNodes()) { switch (node.Kind()) { case SyntaxKind.IfStatement: case SyntaxKind.WhileStatement: case SyntaxKind.ForStatement: case SyntaxKind.ForEachStatement: case SyntaxKind.DoStatement: complexity += 1 + nestingLevel; break; case SyntaxKind.SwitchStatement: complexity += 1 + nestingLevel; break; case SyntaxKind.CatchClause: complexity += 1 + nestingLevel; break; } // Track nesting (simplified) if (node.IsKind(SyntaxKind.Block)) { nestingLevel++; } } return complexity; } private bool IsPublicMember(SyntaxNode node) { if (node is MemberDeclarationSyntax member) { return member.Modifiers.Any(m => m.IsKind(SyntaxKind.PublicKeyword)); } return false; } private bool HasDocumentation(SyntaxNode node) { return node.GetLeadingTrivia() .Any(trivia => trivia.IsKind(SyntaxKind.SingleLineDocumentationCommentTrivia) || trivia.IsKind(SyntaxKind.MultiLineDocumentationCommentTrivia)); } private bool ShouldExcludeFile(string filePath) { var fileName = System.IO.Path.GetFileName(filePath); var excludePatterns = new[] { ".Designer.cs", ".generated.cs", "AssemblyInfo.cs", "GlobalAssemblyInfo.cs" }; return excludePatterns.Any(pattern => fileName.EndsWith(pattern, StringComparison.OrdinalIgnoreCase)); } private object GenerateAnalysisSummary(List results, string analysisDepth) { if (!results.Any()) { return new { Message = "No files analyzed" }; } var totalFiles = results.Count; var totalLinesOfCode = results.Sum(r => r.LinesOfCode); var totalClasses = results.Sum(r => r.Structure?.Classes ?? 0); var totalMethods = results.Sum(r => r.Structure?.Methods ?? 0); var averageComplexity = results .Where(r => r.Complexity?.AverageCyclomaticComplexity > 0) .Average(r => r.Complexity?.AverageCyclomaticComplexity ?? 0); var topIssues = results .SelectMany(r => r.CodeSmells ?? new List()) .GroupBy(cs => cs.Type) .OrderByDescending(g => g.Count()) .Take(5) .Select(g => new { Type = g.Key, Count = g.Count() }) .ToList(); var topSuggestions = results .SelectMany(r => r.Suggestions ?? new List()) .GroupBy(s => s.Type) .OrderByDescending(g => g.Count()) .Take(5) .Select(g => new { Type = g.Key, Count = g.Count() }) .ToList(); return new { FilesAnalyzed = totalFiles, TotalLinesOfCode = totalLinesOfCode, TotalClasses = totalClasses, TotalMethods = totalMethods, AverageComplexity = Math.Round(averageComplexity, 2), TopCodeSmells = topIssues, TopRefactoringSuggestions = topSuggestions, QualityScore = CalculateQualityScore(results), AnalysisDepth = analysisDepth }; } private double CalculateQualityScore(List results) { if (!results.Any()) return 0; double score = 100.0; // Penalize high complexity var avgComplexity = results .Where(r => r.Complexity?.AverageCyclomaticComplexity > 0) .Average(r => r.Complexity?.AverageCyclomaticComplexity ?? 0); if (avgComplexity > 10) score -= (avgComplexity - 10) * 2; // Penalize code smells var totalSmells = results.Sum(r => r.CodeSmells?.Count ?? 0); var totalMethods = results.Sum(r => r.Structure?.Methods ?? 1); var smellRatio = (double)totalSmells / totalMethods; score -= smellRatio * 20; return Math.Max(0, Math.Min(100, Math.Round(score, 1))); } } // Supporting classes for code analysis public class CodeAnalysisResult { public string FilePath { get; set; } public string FileName { get; set; } public int LinesOfCode { get; set; } public CodeStructure Structure { get; set; } public ComplexityMetrics Complexity { get; set; } public List CodeSmells { get; set; } = new List(); public List Suggestions { get; set; } = new List(); public string Error { get; set; } public DateTime Timestamp { get; set; } } public class CodeStructure { public int Classes { get; set; } public int Interfaces { get; set; } public int Methods { get; set; } public int Properties { get; set; } public int Fields { get; set; } public int UsingStatements { get; set; } public List Namespaces { get; set; } = new List(); } public class ComplexityMetrics { public List Methods { get; set; } = new List(); public double AverageCyclomaticComplexity { get; set; } public double AverageCognitiveComplexity { get; set; } public int MaxComplexity { get; set; } } public class MethodComplexity { public string MethodName { get; set; } public int CyclomaticComplexity { get; set; } public int CognitiveComplexity { get; set; } public int LineCount { get; set; } public int ParameterCount { get; set; } } public class CodeSmell { public string Type { get; set; } public string Severity { get; set; } public string Description { get; set; } public string Location { get; set; } public string Suggestion { get; set; } } public class RefactoringSuggestion { public string Type { get; set; } public string Priority { get; set; } public string Description { get; set; } public string Location { get; set; } public string Recommendation { get; set; } public string EstimatedEffort { get; set; } } }