MarketAlly.AIPlugin.Extensions/MarketAlly.AIPlugin.Refacto.../BatchRefactorPlugin.cs

698 lines
20 KiB
C#
Executable File

using MarketAlly.AIPlugin;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
namespace MarketAlly.AIPlugin.Refactoring.Plugins
{
[AIPlugin("BatchRefactor", "Orchestrates multiple refactoring operations across files and projects")]
public class BatchRefactorPlugin : IAIPlugin
{
[AIParameter("Root directory to process", required: true)]
public string RootDirectory { get; set; }
[AIParameter("File pattern to match (e.g., *.cs)", required: false)]
public string FilePattern { get; set; } = "*.cs";
[AIParameter("Refactoring operations to perform (comma-separated)", required: true)]
public string Operations { get; set; }
[AIParameter("Configuration file path for refactoring rules", required: false)]
public string ConfigPath { get; set; }
[AIParameter("Maximum concurrent operations", required: false)]
public int MaxConcurrency { get; set; } = 3;
[AIParameter("Apply changes to files", required: false)]
public bool ApplyChanges { get; set; } = false;
[AIParameter("Create detailed operation log", required: false)]
public bool DetailedLogging { get; set; } = true;
[AIParameter("Stop on first error", required: false)]
public bool StopOnError { get; set; } = false;
public IReadOnlyDictionary<string, Type> SupportedParameters => new Dictionary<string, Type>
{
["rootDirectory"] = typeof(string),
["rootdirectory"] = typeof(string), // Allow lowercase
["filePattern"] = typeof(string),
["filepattern"] = typeof(string), // Allow lowercase
["operations"] = typeof(string),
["configPath"] = typeof(string),
["configpath"] = typeof(string), // Allow lowercase
["maxConcurrency"] = typeof(int),
["maxconcurrency"] = typeof(int), // Allow lowercase
["applyChanges"] = typeof(bool),
["applychanges"] = typeof(bool), // Allow lowercase
["detailedLogging"] = typeof(bool),
["detailedlogging"] = typeof(bool), // Allow lowercase
["stopOnError"] = typeof(bool),
["stoponerror"] = typeof(bool) // Allow lowercase
};
public async Task<AIPluginResult> ExecuteAsync(IReadOnlyDictionary<string, object> parameters)
{
try
{
// Extract parameters with case-insensitive handling
string rootDirectory = GetParameterValue(parameters, "rootDirectory", "rootdirectory")?.ToString();
string filePattern = GetParameterValue(parameters, "filePattern", "filepattern")?.ToString() ?? "*.cs";
string operations = GetParameterValue(parameters, "operations")?.ToString();
string configPath = GetParameterValue(parameters, "configPath", "configpath")?.ToString();
int maxConcurrency = GetIntParameter(parameters, "maxConcurrency", "maxconcurrency", 3);
bool applyChanges = GetBoolParameter(parameters, "applyChanges", "applychanges", false);
bool detailedLogging = GetBoolParameter(parameters, "detailedLogging", "detailedlogging", true);
bool stopOnError = GetBoolParameter(parameters, "stopOnError", "stoponerror", false);
// Validate inputs
if (!Directory.Exists(rootDirectory))
{
return new AIPluginResult(
new DirectoryNotFoundException($"Directory not found: {rootDirectory}"),
"Invalid root directory"
);
}
if (string.IsNullOrEmpty(operations))
{
return new AIPluginResult(
new ArgumentException("Operations parameter is required"),
"Missing operations parameter"
);
}
// Load configuration
if (configPath == null)
configPath = rootDirectory;
var config = await LoadConfiguration(configPath);
// Parse operations
var operationList = operations.Split(',', StringSplitOptions.RemoveEmptyEntries)
.Select(op => op.Trim().ToLower())
.ToList();
// Discover files to process
var filesToProcess = DiscoverFiles(rootDirectory, filePattern, config);
if (!filesToProcess.Any())
{
return new AIPluginResult(new
{
Message = "No files found matching the criteria",
RootDirectory = rootDirectory,
FilePattern = filePattern,
FilesProcessed = 0
});
}
// Execute batch refactoring
var batchResult = await ExecuteBatchRefactoring(
filesToProcess,
operationList,
config,
maxConcurrency,
applyChanges,
detailedLogging,
stopOnError
);
// Generate summary
var summary = GenerateBatchSummary(batchResult, operationList, applyChanges);
return new AIPluginResult(new
{
Message = $"Batch refactoring completed: {batchResult.TotalFiles} files processed",
RootDirectory = rootDirectory,
FilePattern = filePattern,
Operations = operationList,
ChangesApplied = applyChanges,
Summary = summary,
DetailedResults = batchResult,
Timestamp = DateTime.UtcNow
});
}
catch (Exception ex)
{
return new AIPluginResult(ex, $"Batch refactoring failed: {ex.Message}");
}
}
private async Task<RefactoringConfiguration> LoadConfiguration(string configPath)
{
var config = new RefactoringConfiguration();
if (!string.IsNullOrEmpty(configPath) && File.Exists(configPath))
{
try
{
var configJson = await File.ReadAllTextAsync(configPath);
config = JsonSerializer.Deserialize<RefactoringConfiguration>(configJson, new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true
});
}
catch (Exception ex)
{
// Use default configuration if loading fails
config = new RefactoringConfiguration();
config.Errors.Add($"Failed to load configuration: {ex.Message}");
}
} else
{
config.RootDirectory = configPath;
}
// Set defaults if not specified
if (config.ExcludedFiles == null || !config.ExcludedFiles.Any())
config.ExcludedFiles = new List<string> { "*.Designer.cs", "*.generated.cs", "AssemblyInfo.cs" };
if (config.ExcludedDirectories == null || !config.ExcludedDirectories.Any())
config.ExcludedDirectories = new List<string> { "bin", "obj", ".git", ".vs", "packages", "node_modules" };
return config;
}
private List<string> DiscoverFiles(string rootDirectory, string filePattern, RefactoringConfiguration config)
{
var files = Directory.GetFiles(rootDirectory, filePattern, SearchOption.AllDirectories);
return files.Where(file => !ShouldExcludeFile(file, config)).ToList();
}
private bool ShouldExcludeFile(string filePath, RefactoringConfiguration config)
{
var fileName = Path.GetFileName(filePath);
var relativePath = Path.GetRelativePath(config.RootDirectory ?? "", filePath);
// Check excluded file patterns
foreach (var pattern in config.ExcludedFiles)
{
if (MatchesPattern(fileName, pattern))
return true;
}
// Check excluded directories
foreach (var excludedDir in config.ExcludedDirectories)
{
if (relativePath.Contains(excludedDir, StringComparison.OrdinalIgnoreCase))
return true;
}
// Check excluded patterns
foreach (var pattern in config.ExcludedPatterns)
{
if (MatchesPattern(relativePath, pattern))
return true;
}
return false;
}
private bool MatchesPattern(string input, string pattern)
{
// Simple pattern matching with * wildcard support
if (pattern.Contains('*'))
{
var regexPattern = pattern.Replace("*", ".*");
return System.Text.RegularExpressions.Regex.IsMatch(input, regexPattern, System.Text.RegularExpressions.RegexOptions.IgnoreCase);
}
return input.Equals(pattern, StringComparison.OrdinalIgnoreCase);
}
private async Task<BatchRefactoringResult> ExecuteBatchRefactoring(
List<string> files,
List<string> operations,
RefactoringConfiguration config,
int maxConcurrency,
bool applyChanges,
bool detailedLogging,
bool stopOnError)
{
var result = new BatchRefactoringResult
{
TotalFiles = files.Count,
StartTime = DateTime.UtcNow,
FileResults = new List<BatchFileRefactoringResult>()
};
var semaphore = new SemaphoreSlim(maxConcurrency, maxConcurrency);
var cancellationTokenSource = new CancellationTokenSource();
var tasks = files.Select(async file =>
{
await semaphore.WaitAsync(cancellationTokenSource.Token);
try
{
var fileResult = await ProcessSingleFile(file, operations, config, applyChanges, detailedLogging, cancellationTokenSource.Token);
lock (result)
{
result.FileResults.Add(fileResult);
if (fileResult.Success)
{
result.SuccessfulFiles++;
}
else
{
result.FailedFiles++;
if (stopOnError)
{
cancellationTokenSource.Cancel();
}
}
}
return fileResult;
}
finally
{
semaphore.Release();
}
});
try
{
await Task.WhenAll(tasks);
}
catch (OperationCanceledException)
{
result.StoppedOnError = true;
}
result.EndTime = DateTime.UtcNow;
result.TotalDuration = result.EndTime - result.StartTime;
return result;
}
private async Task<BatchFileRefactoringResult> ProcessSingleFile(
string filePath,
List<string> operations,
RefactoringConfiguration config,
bool applyChanges,
bool detailedLogging,
CancellationToken cancellationToken)
{
var fileResult = new BatchFileRefactoringResult
{
FilePath = filePath,
FileName = Path.GetFileName(filePath),
StartTime = DateTime.UtcNow,
OperationResults = new List<OperationResult>()
};
try
{
foreach (var operation in operations)
{
cancellationToken.ThrowIfCancellationRequested();
var operationResult = await ExecuteOperation(filePath, operation, config, applyChanges, detailedLogging);
fileResult.OperationResults.Add(operationResult);
if (!operationResult.Success && config.StopOnFirstError)
{
break;
}
}
fileResult.Success = fileResult.OperationResults.All(or => or.Success);
fileResult.TotalOperations = fileResult.OperationResults.Count;
fileResult.SuccessfulOperations = fileResult.OperationResults.Count(or => or.Success);
}
catch (Exception ex)
{
fileResult.Success = false;
fileResult.Error = ex.Message;
}
finally
{
fileResult.EndTime = DateTime.UtcNow;
fileResult.Duration = fileResult.EndTime - fileResult.StartTime;
}
return fileResult;
}
private async Task<OperationResult> ExecuteOperation(
string filePath,
string operation,
RefactoringConfiguration config,
bool applyChanges,
bool detailedLogging)
{
var result = new OperationResult
{
OperationType = operation,
StartTime = DateTime.UtcNow
};
try
{
switch (operation.ToLower())
{
case "codeanalysis":
case "code-analysis":
result = await ExecuteCodeAnalysis(filePath, result, applyChanges);
break;
case "documentation":
case "add-documentation":
result = await ExecuteDocumentation(filePath, result, applyChanges);
break;
case "formatting":
case "format-code":
result = await ExecuteCodeFormatting(filePath, result, applyChanges);
break;
case "naming":
case "naming-conventions":
result = await ExecuteNamingConventions(filePath, result, applyChanges);
break;
case "cleanup":
case "code-cleanup":
result = await ExecuteCodeCleanup(filePath, result, applyChanges);
break;
default:
result.Success = false;
result.Error = $"Unknown operation: {operation}";
break;
}
}
catch (Exception ex)
{
result.Success = false;
result.Error = ex.Message;
}
finally
{
result.EndTime = DateTime.UtcNow;
result.Duration = result.EndTime - result.StartTime;
}
return result;
}
private async Task<OperationResult> ExecuteCodeAnalysis(string filePath, OperationResult result, bool applyChanges)
{
try
{
var analysisPlugin = new CodeAnalysisPlugin();
var parameters = new Dictionary<string, object>
{
["path"] = filePath,
["analysisDepth"] = "detailed",
["includeComplexity"] = true,
["includeCodeSmells"] = true,
["includeSuggestions"] = true
};
var pluginResult = await analysisPlugin.ExecuteAsync(parameters);
result.Success = pluginResult.Success;
result.Details = pluginResult.Data;
result.Message = pluginResult.Message;
if (!pluginResult.Success)
{
result.Error = pluginResult.Message;
}
return result;
}
catch (Exception ex)
{
result.Success = false;
result.Error = ex.Message;
return result;
}
}
private async Task<OperationResult> ExecuteDocumentation(string filePath, OperationResult result, bool applyChanges)
{
try
{
var docPlugin = new EnhancedDocumentationGeneratorPlugin();
var parameters = new Dictionary<string, object>
{
["filePath"] = filePath,
["style"] = "intelligent",
["includeExamples"] = false,
["includeSeeAlso"] = false,
["applyChanges"] = applyChanges
};
var pluginResult = await docPlugin.ExecuteAsync(parameters);
result.Success = pluginResult.Success;
result.Details = pluginResult.Data;
result.Message = pluginResult.Message;
if (!pluginResult.Success)
{
result.Error = pluginResult.Message;
}
return result;
}
catch (Exception ex)
{
result.Success = false;
result.Error = ex.Message;
return result;
}
}
private async Task<OperationResult> ExecuteCodeFormatting(string filePath, OperationResult result, bool applyChanges)
{
try
{
var formatPlugin = new CodeFormatterPlugin();
var parameters = new Dictionary<string, object>
{
["path"] = filePath,
["formattingStyle"] = "microsoft",
["fixIndentation"] = true,
["organizeUsings"] = true,
["removeUnnecessary"] = true,
["applyChanges"] = applyChanges
};
var pluginResult = await formatPlugin.ExecuteAsync(parameters);
result.Success = pluginResult.Success;
result.Details = pluginResult.Data;
result.Message = pluginResult.Message;
if (!pluginResult.Success)
{
result.Error = pluginResult.Message;
}
return result;
}
catch (Exception ex)
{
result.Success = false;
result.Error = ex.Message;
return result;
}
}
private async Task<OperationResult> ExecuteNamingConventions(string filePath, OperationResult result, bool applyChanges)
{
try
{
var namingPlugin = new NamingConventionPlugin();
var parameters = new Dictionary<string, object>
{
["filePath"] = filePath,
["convention"] = "pascal",
["checkMeaningfulness"] = true,
["aiSuggestions"] = false, // Disable AI suggestions for batch processing
["applyChanges"] = applyChanges
};
var pluginResult = await namingPlugin.ExecuteAsync(parameters);
result.Success = pluginResult.Success;
result.Details = pluginResult.Data;
result.Message = pluginResult.Message;
if (!pluginResult.Success)
{
result.Error = pluginResult.Message;
}
return result;
}
catch (Exception ex)
{
result.Success = false;
result.Error = ex.Message;
return result;
}
}
private async Task<OperationResult> ExecuteCodeCleanup(string filePath, OperationResult result, bool applyChanges)
{
try
{
// Execute multiple cleanup operations
var cleanupOperations = new[] { "formatting", "documentation" };
var cleanupResults = new List<object>();
foreach (var cleanup in cleanupOperations)
{
var cleanupResult = await ExecuteOperation(filePath, cleanup, new RefactoringConfiguration(), applyChanges, false);
cleanupResults.Add(new
{
Operation = cleanup,
Success = cleanupResult.Success,
Message = cleanupResult.Message
});
}
result.Success = cleanupResults.Cast<dynamic>().All(r => r.Success);
result.Details = new { CleanupOperations = cleanupResults };
result.Message = $"Executed {cleanupOperations.Length} cleanup operations";
return result;
}
catch (Exception ex)
{
result.Success = false;
result.Error = ex.Message;
return result;
}
}
private object GenerateBatchSummary(BatchRefactoringResult batchResult, List<string> operations, bool changesApplied)
{
var operationStats = batchResult.FileResults
.SelectMany(fr => fr.OperationResults)
.GroupBy(or => or.OperationType)
.ToDictionary(g => g.Key, g => new
{
Total = g.Count(),
Successful = g.Count(or => or.Success),
Failed = g.Count(or => !or.Success),
AverageDuration = g.Average(or => or.Duration.TotalMilliseconds)
});
var fileTypeStats = batchResult.FileResults
.GroupBy(fr => Path.GetExtension(fr.FilePath))
.ToDictionary(g => g.Key, g => new
{
Count = g.Count(),
Successful = g.Count(fr => fr.Success)
});
return new
{
TotalFiles = batchResult.TotalFiles,
SuccessfulFiles = batchResult.SuccessfulFiles,
FailedFiles = batchResult.FailedFiles,
TotalDuration = batchResult.TotalDuration,
AverageFileProcessingTime = batchResult.FileResults.Any()
? TimeSpan.FromMilliseconds(batchResult.FileResults.Average(fr => fr.Duration.TotalMilliseconds))
: TimeSpan.Zero,
ChangesApplied = changesApplied,
StoppedOnError = batchResult.StoppedOnError,
OperationStatistics = operationStats,
FileTypeStatistics = fileTypeStats,
TopErrors = batchResult.FileResults
.Where(fr => !fr.Success)
.GroupBy(fr => fr.Error)
.OrderByDescending(g => g.Count())
.Take(5)
.Select(g => new { Error = g.Key, Count = g.Count() })
.ToList()
};
}
// 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 batch refactoring
public class RefactoringConfiguration
{
public string RootDirectory { get; set; }
public List<string> ExcludedFiles { get; set; } = new List<string>();
public List<string> ExcludedDirectories { get; set; } = new List<string>();
public List<string> ExcludedPatterns { get; set; } = new List<string>();
public bool StopOnFirstError { get; set; } = false;
public Dictionary<string, object> OperationSettings { get; set; } = new Dictionary<string, object>();
public List<string> Errors { get; set; } = new List<string>();
}
public class BatchRefactoringResult
{
public int TotalFiles { get; set; }
public int SuccessfulFiles { get; set; }
public int FailedFiles { get; set; }
public DateTime StartTime { get; set; }
public DateTime EndTime { get; set; }
public TimeSpan TotalDuration { get; set; }
public bool StoppedOnError { get; set; }
public List<BatchFileRefactoringResult> FileResults { get; set; } = new List<BatchFileRefactoringResult>();
}
public class BatchFileRefactoringResult
{
public string FilePath { get; set; }
public string FileName { get; set; }
public bool Success { get; set; }
public string Error { get; set; }
public int TotalOperations { get; set; }
public int SuccessfulOperations { get; set; }
public DateTime StartTime { get; set; }
public DateTime EndTime { get; set; }
public TimeSpan Duration { get; set; }
public List<OperationResult> OperationResults { get; set; } = new List<OperationResult>();
}
public class OperationResult
{
public string OperationType { get; set; }
public bool Success { get; set; }
public string Message { get; set; }
public string Error { get; set; }
public object Details { get; set; }
public DateTime StartTime { get; set; }
public DateTime EndTime { get; set; }
public TimeSpan Duration { get; set; }
}
}