804 lines
27 KiB
C#
Executable File
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");
|
|
}
|
|
}
|
|
} |