MarketAlly.AIPlugin.Extensions/MarketAlly.AIPlugin.Refacto.../CodeRefactoringPlugin.cs

804 lines
27 KiB
C#
Executable File

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<string, Type> SupportedParameters => new Dictionary<string, Type>
{
["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<AIPluginResult> ExecuteAsync(IReadOnlyDictionary<string, object> 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<RefactoringOperation>()
};
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<SyntaxNode> ExtractMethods(SyntaxNode root, RefactoringResult result, int maxLength, int minComplexity)
{
var operation = new RefactoringOperation { Type = "extract-methods", ExtractedMethods = new List<ExtractedMethod>() };
var modifiedRoot = root;
try
{
var methods = root.DescendantNodes().OfType<MethodDeclarationSyntax>().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<MethodDeclarationSyntax>()
.First(m => m.Identifier.ValueText == method.Identifier.ValueText),
newMethod);
// Add the extracted method to the class
var containingClass = method.Ancestors().OfType<ClassDeclarationSyntax>().First();
var updatedClass = containingClass.AddMembers(extractedMethod);
modifiedRoot = modifiedRoot.ReplaceNode(
modifiedRoot.DescendantNodes().OfType<ClassDeclarationSyntax>()
.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<SyntaxNode> SplitLargeClasses(SyntaxNode root, RefactoringResult result, int maxSize)
{
var operation = new RefactoringOperation { Type = "split-classes", SplitClasses = new List<SplitClass>() };
var modifiedRoot = root;
try
{
var classes = root.DescendantNodes().OfType<ClassDeclarationSyntax>().ToList();
foreach (var cls in classes)
{
var lineCount = cls.GetText().Lines.Count;
var methodCount = cls.Members.OfType<MethodDeclarationSyntax>().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<SyntaxNode> SimplifyConditionals(SyntaxNode root, RefactoringResult result)
{
var operation = new RefactoringOperation { Type = "simplify-conditionals", SimplifiedConditionals = new List<string>() };
var modifiedRoot = root;
try
{
var ifStatements = root.DescendantNodes().OfType<IfStatementSyntax>().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<SyntaxNode> RemoveDuplicateCode(SyntaxNode root, RefactoringResult result)
{
var operation = new RefactoringOperation { Type = "remove-duplicates", DuplicatesRemoved = new List<string>() };
var modifiedRoot = root;
try
{
// Find duplicate string literals
var stringLiterals = root.DescendantNodes().OfType<LiteralExpressionSyntax>()
.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<BlockSyntax>()
.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<SyntaxNode> IntroduceParameterObjects(SyntaxNode root, RefactoringResult result)
{
var operation = new RefactoringOperation { Type = "introduce-parameter-objects", ParameterObjects = new List<string>() };
var modifiedRoot = root;
try
{
var methods = root.DescendantNodes().OfType<MethodDeclarationSyntax>()
.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<ExtractionCandidate> FindExtractionCandidates(MethodDeclarationSyntax method)
{
var candidates = new List<ExtractionCandidate>();
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<StatementSyntax>();
// 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<StatementSyntax> statements)
{
// Simple heuristic: avoid extracting if it contains too many local variable references
var localVarReferences = statements
.SelectMany(s => s.DescendantNodes().OfType<IdentifierNameSyntax>())
.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<MethodDeclarationSyntax>().ToList();
var properties = cls.Members.OfType<PropertyDeclarationSyntax>().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<ClassDeclarationSyntax> CreatePartialClasses(ClassDeclarationSyntax cls, ClassSplitAnalysis analysis)
{
// This would create actual partial classes - simplified for this example
var partialClasses = new List<ClassDeclarationSyntax>();
// 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<string> FindSimilarBlocks(List<BlockSyntax> blocks)
{
var similarities = new List<string>();
var blockHashes = new Dictionary<int, List<(BlockSyntax block, int index)>>();
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<ParameterGroup> AnalyzeParametersForGrouping(MethodDeclarationSyntax method)
{
var groups = new List<ParameterGroup>();
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<string, object> parameters, params string[] keys)
{
foreach (var key in keys)
{
if (parameters.TryGetValue(key, out var value))
return value;
}
return null;
}
private bool GetBoolParameter(IReadOnlyDictionary<string, object> 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<string, object> 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<RefactoringOperation> Operations { get; set; } = new List<RefactoringOperation>();
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<ExtractedMethod> ExtractedMethods { get; set; } = new List<ExtractedMethod>();
public List<SplitClass> SplitClasses { get; set; } = new List<SplitClass>();
public List<string> SimplifiedConditionals { get; set; } = new List<string>();
public List<string> DuplicatesRemoved { get; set; } = new List<string>();
public List<string> ParameterObjects { get; set; } = new List<string>();
}
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<string> PartialClassNames { get; set; } = new List<string>();
public string Reason { get; set; }
public int LineCount { get; set; }
public int MethodCount { get; set; }
}
public class ExtractionCandidate
{
public List<StatementSyntax> Statements { get; set; } = new List<StatementSyntax>();
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<ParameterSyntax> Parameters { get; set; } = new List<ParameterSyntax>();
}
// 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");
}
}
}