using MarketAlly.AIPlugin; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Formatting; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; namespace MarketAlly.AIPlugin.Refactoring.Plugins { [AIPlugin("CodeRefactoring", "Performs actual code refactoring operations like method extraction, class splitting, and code simplification")] public class CodeRefactoringPlugin : IAIPlugin { [AIParameter("Full path to the file to refactor", required: true)] public string FilePath { get; set; } [AIParameter("Refactoring operations to perform (comma-separated): extract-methods, split-classes, simplify-conditionals, remove-duplicates", required: true)] public string Operations { get; set; } [AIParameter("Apply changes to file", required: false)] public bool ApplyChanges { get; set; } = false; [AIParameter("Maximum method length before extraction", required: false)] public int MaxMethodLength { get; set; } = 20; [AIParameter("Maximum class size before splitting", required: false)] public int MaxClassSize { get; set; } = 500; [AIParameter("Minimum complexity for method extraction", required: false)] public int MinComplexityForExtraction { get; set; } = 8; public IReadOnlyDictionary SupportedParameters => new Dictionary { ["filePath"] = typeof(string), ["filepath"] = typeof(string), ["operations"] = typeof(string), ["applyChanges"] = typeof(bool), ["applychanges"] = typeof(bool), ["maxMethodLength"] = typeof(int), ["maxmethodlength"] = typeof(int), ["maxClassSize"] = typeof(int), ["maxclasssize"] = typeof(int), ["minComplexityForExtraction"] = typeof(int), ["mincomplexityforextraction"] = typeof(int) }; public async Task ExecuteAsync(IReadOnlyDictionary parameters) { try { // Extract parameters string filePath = GetParameterValue(parameters, "filePath", "filepath")?.ToString(); string operations = GetParameterValue(parameters, "operations")?.ToString(); bool applyChanges = GetBoolParameter(parameters, "applyChanges", "applychanges", false); int maxMethodLength = GetIntParameter(parameters, "maxMethodLength", "maxmethodlength", 20); int maxClassSize = GetIntParameter(parameters, "maxClassSize", "maxclasssize", 500); int minComplexity = GetIntParameter(parameters, "minComplexityForExtraction", "mincomplexityforextraction", 8); if (!File.Exists(filePath)) { return new AIPluginResult(new FileNotFoundException($"File not found: {filePath}"), "File not found"); } if (string.IsNullOrEmpty(operations)) { return new AIPluginResult(new ArgumentException("Operations parameter is required"), "Missing operations"); } var sourceCode = await File.ReadAllTextAsync(filePath); var syntaxTree = CSharpSyntaxTree.ParseText(sourceCode); var root = syntaxTree.GetRoot(); var operationList = operations.Split(',', StringSplitOptions.RemoveEmptyEntries) .Select(op => op.Trim().ToLower()).ToList(); var refactoringResult = new RefactoringResult { FilePath = filePath, OriginalContent = sourceCode, Operations = new List() }; var modifiedRoot = root; // Execute refactoring operations foreach (var operation in operationList) { switch (operation) { case "extract-methods": modifiedRoot = await ExtractMethods(modifiedRoot, refactoringResult, maxMethodLength, minComplexity); break; case "split-classes": modifiedRoot = await SplitLargeClasses(modifiedRoot, refactoringResult, maxClassSize); break; case "simplify-conditionals": modifiedRoot = await SimplifyConditionals(modifiedRoot, refactoringResult); break; case "remove-duplicates": modifiedRoot = await RemoveDuplicateCode(modifiedRoot, refactoringResult); break; case "introduce-parameter-objects": modifiedRoot = await IntroduceParameterObjects(modifiedRoot, refactoringResult); break; default: refactoringResult.Operations.Add(new RefactoringOperation { Type = operation, Success = false, Message = $"Unknown operation: {operation}" }); break; } } var workspace = new AdhocWorkspace(); modifiedRoot = Formatter.Format(modifiedRoot, workspace); refactoringResult.RefactoredContent = modifiedRoot.ToFullString(); // Apply changes if requested if (applyChanges && refactoringResult.Operations.Any(op => op.Success)) { await SafeFileOperations.ApplyChangesWithRetry(filePath, refactoringResult); } var summary = GenerateRefactoringSummary(refactoringResult); return new AIPluginResult(new { Message = $"Refactoring completed: {refactoringResult.Operations.Count(op => op.Success)} operations successful", FilePath = filePath, ChangesApplied = applyChanges, Summary = summary, DetailedResult = refactoringResult, Timestamp = DateTime.UtcNow }); } catch (Exception ex) { return new AIPluginResult(ex, $"Code refactoring failed: {ex.Message}"); } } private async Task ExtractMethods(SyntaxNode root, RefactoringResult result, int maxLength, int minComplexity) { var operation = new RefactoringOperation { Type = "extract-methods", ExtractedMethods = new List() }; var modifiedRoot = root; try { var methods = root.DescendantNodes().OfType().ToList(); foreach (var method in methods) { var lineCount = method.GetText().Lines.Count; var complexity = CalculateComplexity(method); if (lineCount > maxLength && complexity >= minComplexity) { var extractionCandidates = FindExtractionCandidates(method); foreach (var candidate in extractionCandidates) { var (newMethod, extractedMethod) = CreateExtractedMethod(method, candidate); // Replace the original method with the refactored version modifiedRoot = modifiedRoot.ReplaceNode( modifiedRoot.DescendantNodes().OfType() .First(m => m.Identifier.ValueText == method.Identifier.ValueText), newMethod); // Add the extracted method to the class var containingClass = method.Ancestors().OfType().First(); var updatedClass = containingClass.AddMembers(extractedMethod); modifiedRoot = modifiedRoot.ReplaceNode( modifiedRoot.DescendantNodes().OfType() .First(c => c.Identifier.ValueText == containingClass.Identifier.ValueText), updatedClass); operation.ExtractedMethods.Add(new ExtractedMethod { OriginalMethodName = method.Identifier.ValueText, ExtractedMethodName = extractedMethod.Identifier.ValueText, LinesExtracted = candidate.Statements.Count, Reason = $"Extracted {candidate.Statements.Count} lines to reduce method complexity" }); } } } operation.Success = operation.ExtractedMethods.Any(); operation.Message = $"Extracted {operation.ExtractedMethods.Count} methods"; } catch (Exception ex) { operation.Success = false; operation.Message = $"Method extraction failed: {ex.Message}"; } result.Operations.Add(operation); return await Task.FromResult(modifiedRoot); } private async Task SplitLargeClasses(SyntaxNode root, RefactoringResult result, int maxSize) { var operation = new RefactoringOperation { Type = "split-classes", SplitClasses = new List() }; var modifiedRoot = root; try { var classes = root.DescendantNodes().OfType().ToList(); foreach (var cls in classes) { var lineCount = cls.GetText().Lines.Count; var methodCount = cls.Members.OfType().Count(); if (lineCount > maxSize || methodCount > 20) { var splitSuggestion = AnalyzeClassForSplitting(cls); if (splitSuggestion.ShouldSplit) { // Create partial class structure var partialClasses = CreatePartialClasses(cls, splitSuggestion); operation.SplitClasses.Add(new SplitClass { OriginalClassName = cls.Identifier.ValueText, PartialClassNames = partialClasses.Select(pc => pc.Identifier.ValueText).ToList(), Reason = splitSuggestion.Reason, LineCount = lineCount, MethodCount = methodCount }); } } } operation.Success = operation.SplitClasses.Any(); operation.Message = $"Analyzed {operation.SplitClasses.Count} classes for splitting"; } catch (Exception ex) { operation.Success = false; operation.Message = $"Class splitting failed: {ex.Message}"; } result.Operations.Add(operation); return await Task.FromResult(modifiedRoot); } private async Task SimplifyConditionals(SyntaxNode root, RefactoringResult result) { var operation = new RefactoringOperation { Type = "simplify-conditionals", SimplifiedConditionals = new List() }; var modifiedRoot = root; try { var ifStatements = root.DescendantNodes().OfType().ToList(); foreach (var ifStatement in ifStatements) { // Simplify redundant else statements if (ifStatement.Else != null && IsRedundantElse(ifStatement)) { var simplifiedIf = ifStatement.WithElse(null); modifiedRoot = modifiedRoot.ReplaceNode(ifStatement, simplifiedIf); operation.SimplifiedConditionals.Add($"Removed redundant else at line {ifStatement.GetLocation().GetLineSpan().StartLinePosition.Line + 1}"); } // Simplify boolean comparisons if (ifStatement.Condition is BinaryExpressionSyntax binaryExpr) { var simplified = SimplifyBooleanExpression(binaryExpr); if (simplified != null) { var newIf = ifStatement.WithCondition(simplified); modifiedRoot = modifiedRoot.ReplaceNode(ifStatement, newIf); operation.SimplifiedConditionals.Add($"Simplified boolean expression at line {ifStatement.GetLocation().GetLineSpan().StartLinePosition.Line + 1}"); } } } operation.Success = operation.SimplifiedConditionals.Any(); operation.Message = $"Simplified {operation.SimplifiedConditionals.Count} conditionals"; } catch (Exception ex) { operation.Success = false; operation.Message = $"Conditional simplification failed: {ex.Message}"; } result.Operations.Add(operation); return await Task.FromResult(modifiedRoot); } private async Task RemoveDuplicateCode(SyntaxNode root, RefactoringResult result) { var operation = new RefactoringOperation { Type = "remove-duplicates", DuplicatesRemoved = new List() }; var modifiedRoot = root; try { // Find duplicate string literals var stringLiterals = root.DescendantNodes().OfType() .Where(l => l.Token.IsKind(SyntaxKind.StringLiteralToken)) .GroupBy(l => l.Token.ValueText) .Where(g => g.Count() > 2 && g.Key.Length > 5) .ToList(); foreach (var group in stringLiterals) { // Suggest extracting to constant operation.DuplicatesRemoved.Add($"Found {group.Count()} duplicate string literals: '{group.Key}' - consider extracting to constant"); } // Find duplicate code blocks (simplified) var blocks = root.DescendantNodes().OfType() .Where(b => b.Statements.Count > 3) .ToList(); var duplicateBlocks = FindSimilarBlocks(blocks); foreach (var duplicate in duplicateBlocks) { operation.DuplicatesRemoved.Add($"Found similar code blocks - consider extracting to method"); } operation.Success = operation.DuplicatesRemoved.Any(); operation.Message = $"Found {operation.DuplicatesRemoved.Count} potential duplicates"; } catch (Exception ex) { operation.Success = false; operation.Message = $"Duplicate removal failed: {ex.Message}"; } result.Operations.Add(operation); return await Task.FromResult(modifiedRoot); } private async Task IntroduceParameterObjects(SyntaxNode root, RefactoringResult result) { var operation = new RefactoringOperation { Type = "introduce-parameter-objects", ParameterObjects = new List() }; var modifiedRoot = root; try { var methods = root.DescendantNodes().OfType() .Where(m => m.ParameterList.Parameters.Count > 4) .ToList(); foreach (var method in methods) { var parameterGroups = AnalyzeParametersForGrouping(method); foreach (var group in parameterGroups) { if (group.Parameters.Count > 2) { operation.ParameterObjects.Add( $"Method '{method.Identifier.ValueText}' could benefit from parameter object for: {string.Join(", ", group.Parameters.Select(p => p.Identifier.ValueText))}" ); } } } operation.Success = operation.ParameterObjects.Any(); operation.Message = $"Found {operation.ParameterObjects.Count} parameter object opportunities"; } catch (Exception ex) { operation.Success = false; operation.Message = $"Parameter object analysis failed: {ex.Message}"; } result.Operations.Add(operation); return await Task.FromResult(modifiedRoot); } // Helper methods for refactoring operations private int CalculateComplexity(MethodDeclarationSyntax method) { int complexity = 1; 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.SwitchStatement) || node.IsKind(SyntaxKind.CatchClause)); return complexity + decisionNodes.Count(); } private List FindExtractionCandidates(MethodDeclarationSyntax method) { var candidates = new List(); if (method.Body != null) { var statements = method.Body.Statements; // Look for consecutive statements that can be extracted for (int i = 0; i < statements.Count - 2; i++) { var candidateStatements = new List(); // Group 3-5 consecutive statements for (int j = i; j < Math.Min(i + 5, statements.Count); j++) { candidateStatements.Add(statements[j]); } if (candidateStatements.Count >= 3 && IsGoodExtractionCandidate(candidateStatements)) { candidates.Add(new ExtractionCandidate { Statements = candidateStatements, StartIndex = i, Reason = "Consecutive statements with clear purpose" }); i += candidateStatements.Count - 1; // Skip ahead } } } return candidates; } private bool IsGoodExtractionCandidate(List statements) { // Simple heuristic: avoid extracting if it contains too many local variable references var localVarReferences = statements .SelectMany(s => s.DescendantNodes().OfType()) .Select(i => i.Identifier.ValueText) .Distinct() .Count(); return localVarReferences <= 3; // Don't extract if too many dependencies } private (MethodDeclarationSyntax newMethod, MethodDeclarationSyntax extractedMethod) CreateExtractedMethod( MethodDeclarationSyntax originalMethod, ExtractionCandidate candidate) { // Create extracted method name var extractedMethodName = $"{originalMethod.Identifier.ValueText}Helper{candidate.StartIndex + 1}"; // Create the extracted method var extractedMethod = SyntaxFactory.MethodDeclaration( SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.VoidKeyword)), extractedMethodName) .AddModifiers(SyntaxFactory.Token(SyntaxKind.PrivateKeyword)) .WithBody(SyntaxFactory.Block(candidate.Statements)) .WithLeadingTrivia(SyntaxFactory.Comment("// Extracted method to reduce complexity")); // Create method call var methodCall = SyntaxFactory.ExpressionStatement( SyntaxFactory.InvocationExpression( SyntaxFactory.IdentifierName(extractedMethodName))); // Replace statements in original method var newStatements = originalMethod.Body.Statements.ToList(); newStatements.RemoveRange(candidate.StartIndex, candidate.Statements.Count); newStatements.Insert(candidate.StartIndex, methodCall); var newMethod = originalMethod.WithBody(SyntaxFactory.Block(newStatements)); return (newMethod, extractedMethod); } private ClassSplitAnalysis AnalyzeClassForSplitting(ClassDeclarationSyntax cls) { var methods = cls.Members.OfType().ToList(); var properties = cls.Members.OfType().ToList(); // Simple heuristic: if class has both data operations and UI operations, suggest split var hasDataMethods = methods.Any(m => m.Identifier.ValueText.Contains("Save") || m.Identifier.ValueText.Contains("Load") || m.Identifier.ValueText.Contains("Update")); var hasUIMethods = methods.Any(m => m.Identifier.ValueText.Contains("Display") || m.Identifier.ValueText.Contains("Show") || m.Identifier.ValueText.Contains("Render")); return new ClassSplitAnalysis { ShouldSplit = hasDataMethods && hasUIMethods, Reason = hasDataMethods && hasUIMethods ? "Class mixes data operations with UI operations - consider separating concerns" : "Class is large but has cohesive responsibilities" }; } private List CreatePartialClasses(ClassDeclarationSyntax cls, ClassSplitAnalysis analysis) { // This would create actual partial classes - simplified for this example var partialClasses = new List(); // Create data operations partial class var dataClass = SyntaxFactory.ClassDeclaration($"{cls.Identifier.ValueText}Data") .AddModifiers(SyntaxFactory.Token(SyntaxKind.PartialKeyword)) .WithLeadingTrivia(SyntaxFactory.Comment("// Partial class for data operations")); partialClasses.Add(dataClass); return partialClasses; } private bool IsRedundantElse(IfStatementSyntax ifStatement) { var statement = ifStatement.Statement; if (statement is BlockSyntax block) { var lastStatement = block.Statements.LastOrDefault(); return lastStatement is ReturnStatementSyntax || lastStatement is ThrowStatementSyntax || lastStatement is BreakStatementSyntax || lastStatement is ContinueStatementSyntax; } return statement is ReturnStatementSyntax || statement is ThrowStatementSyntax || statement is BreakStatementSyntax || statement is ContinueStatementSyntax; } private ExpressionSyntax SimplifyBooleanExpression(BinaryExpressionSyntax binaryExpr) { // Simplify comparisons like "x == true" to "x" if (binaryExpr.IsKind(SyntaxKind.EqualsExpression)) { if (binaryExpr.Right is LiteralExpressionSyntax literal && literal.Token.IsKind(SyntaxKind.TrueKeyword)) { return binaryExpr.Left; } if (binaryExpr.Left is LiteralExpressionSyntax leftLiteral && leftLiteral.Token.IsKind(SyntaxKind.TrueKeyword)) { return binaryExpr.Right; } } // Simplify "x == false" to "!x" if (binaryExpr.IsKind(SyntaxKind.EqualsExpression)) { if (binaryExpr.Right is LiteralExpressionSyntax literal && literal.Token.IsKind(SyntaxKind.FalseKeyword)) { return SyntaxFactory.PrefixUnaryExpression( SyntaxKind.LogicalNotExpression, binaryExpr.Left); } } return null; // No simplification needed } private List FindSimilarBlocks(List blocks) { var similarities = new List(); var blockHashes = new Dictionary>(); var similarityCache = new Dictionary<(int, int), double>(); // Pre-compute hashes to avoid O(n²) comparisons for (int i = 0; i < blocks.Count; i++) { var hash = ComputeBlockHash(blocks[i]); if (!blockHashes.ContainsKey(hash)) blockHashes[hash] = new List<(BlockSyntax, int)>(); blockHashes[hash].Add((blocks[i], i)); } // Only compare blocks with similar hashes and cache results foreach (var hashGroup in blockHashes.Values.Where(g => g.Count > 1)) { // Early exit if group is too large to avoid performance issues if (hashGroup.Count > 10) { similarities.Add($"Found {hashGroup.Count} blocks with similar structure (too many to analyze individually)"); continue; } for (int i = 0; i < hashGroup.Count; i++) { for (int j = i + 1; j < hashGroup.Count; j++) { var key = (hashGroup[i].index, hashGroup[j].index); if (!similarityCache.TryGetValue(key, out var similarity)) { similarity = CalculateBlockSimilarity(hashGroup[i].block, hashGroup[j].block); similarityCache[key] = similarity; } if (similarity > 0.7) { similarities.Add($"Blocks at lines {hashGroup[i].block.GetLocation().GetLineSpan().StartLinePosition.Line + 1} and {hashGroup[j].block.GetLocation().GetLineSpan().StartLinePosition.Line + 1} are {similarity:P0} similar"); } } } } return similarities; } private int ComputeBlockHash(BlockSyntax block) { // Simple hash based on statement count and types var hash = block.Statements.Count; foreach (var stmt in block.Statements.Take(3)) // Only first few statements for performance { hash = hash * 31 + stmt.GetType().GetHashCode(); } return hash; } private double CalculateBlockSimilarity(BlockSyntax block1, BlockSyntax block2) { if (block1.Statements.Count != block2.Statements.Count) return 0.0; int similarStatements = 0; for (int i = 0; i < block1.Statements.Count; i++) { var stmt1 = block1.Statements[i].ToString().Trim(); var stmt2 = block2.Statements[i].ToString().Trim(); // Simple text similarity check if (stmt1.Equals(stmt2, StringComparison.OrdinalIgnoreCase)) { similarStatements++; } } return (double)similarStatements / block1.Statements.Count; } private List AnalyzeParametersForGrouping(MethodDeclarationSyntax method) { var groups = new List(); var parameters = method.ParameterList.Parameters.ToList(); // Group parameters by type or naming pattern var typeGroups = parameters.GroupBy(p => p.Type.ToString()).Where(g => g.Count() > 1); foreach (var group in typeGroups) { groups.Add(new ParameterGroup { GroupName = $"{group.Key}Parameters", Parameters = group.ToList() }); } return groups; } private object GenerateRefactoringSummary(RefactoringResult result) { var successfulOps = result.Operations.Where(op => op.Success).ToList(); return new { TotalOperations = result.Operations.Count, SuccessfulOperations = successfulOps.Count, FailedOperations = result.Operations.Count - successfulOps.Count, MethodsExtracted = successfulOps.Sum(op => op.ExtractedMethods?.Count ?? 0), ClassesSplit = successfulOps.Sum(op => op.SplitClasses?.Count ?? 0), ConditionalsSimplified = successfulOps.Sum(op => op.SimplifiedConditionals?.Count ?? 0), DuplicatesFound = successfulOps.Sum(op => op.DuplicatesRemoved?.Count ?? 0), ParameterObjectOpportunities = successfulOps.Sum(op => op.ParameterObjects?.Count ?? 0), ChangesApplied = result.ChangesApplied }; } // Helper methods for parameter extraction private object GetParameterValue(IReadOnlyDictionary parameters, params string[] keys) { foreach (var key in keys) { if (parameters.TryGetValue(key, out var value)) return value; } return null; } private bool GetBoolParameter(IReadOnlyDictionary parameters, string key1, string key2, bool defaultValue = false) { var value = GetParameterValue(parameters, key1, key2); return value != null ? Convert.ToBoolean(value) : defaultValue; } private int GetIntParameter(IReadOnlyDictionary parameters, string key1, string key2, int defaultValue = 0) { var value = GetParameterValue(parameters, key1, key2); return value != null ? Convert.ToInt32(value) : defaultValue; } } // Supporting classes for code refactoring public class RefactoringResult { public string FilePath { get; set; } public string OriginalContent { get; set; } public string RefactoredContent { get; set; } public List Operations { get; set; } = new List(); public bool ChangesApplied { get; set; } public string BackupPath { get; set; } } public class RefactoringOperation { public string Type { get; set; } public bool Success { get; set; } public string Message { get; set; } public List ExtractedMethods { get; set; } = new List(); public List SplitClasses { get; set; } = new List(); public List SimplifiedConditionals { get; set; } = new List(); public List DuplicatesRemoved { get; set; } = new List(); public List ParameterObjects { get; set; } = new List(); } public class ExtractedMethod { public string OriginalMethodName { get; set; } public string ExtractedMethodName { get; set; } public int LinesExtracted { get; set; } public string Reason { get; set; } } public class SplitClass { public string OriginalClassName { get; set; } public List PartialClassNames { get; set; } = new List(); public string Reason { get; set; } public int LineCount { get; set; } public int MethodCount { get; set; } } public class ExtractionCandidate { public List Statements { get; set; } = new List(); public int StartIndex { get; set; } public string Reason { get; set; } } public class ClassSplitAnalysis { public bool ShouldSplit { get; set; } public string Reason { get; set; } } public class ParameterGroup { public string GroupName { get; set; } public List Parameters { get; set; } = new List(); } // Extension methods for safe file operations public static class SafeFileOperations { public static async Task ApplyChangesWithRetry(string filePath, RefactoringResult result, int maxRetries = 3) { for (int attempt = 0; attempt < maxRetries; attempt++) { try { var backupPath = $"{filePath}.{DateTime.Now:yyyyMMdd_HHmmss}.bak"; // Create backup with file locking using (var sourceStream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read)) using (var backupStream = new FileStream(backupPath, FileMode.Create, FileAccess.Write, FileShare.None)) { await sourceStream.CopyToAsync(backupStream); } // Write new content with exclusive access using (var fileStream = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.None)) using (var writer = new StreamWriter(fileStream)) { await writer.WriteAsync(result.RefactoredContent); } result.BackupPath = backupPath; result.ChangesApplied = true; return; } catch (IOException ex) when (attempt < maxRetries - 1) { // Wait before retry on file access issues await Task.Delay(100 * (attempt + 1)); continue; } } throw new InvalidOperationException($"Failed to apply changes to {filePath} after {maxRetries} attempts"); } } }