799 lines
26 KiB
C#
Executable File
799 lines
26 KiB
C#
Executable File
using MarketAlly.AIPlugin;
|
|
using LibGit2Sharp;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Text.Json;
|
|
using System.Text.RegularExpressions;
|
|
using System.Threading.Tasks;
|
|
|
|
namespace MarketAlly.AIPlugin.Refactoring.Plugins
|
|
{
|
|
[AIPlugin("SolutionRefactoring", "Processes entire solutions with Git integration, respecting .gitignore and creating safe refactoring branches")]
|
|
public class SolutionRefactoringPlugin : IAIPlugin
|
|
{
|
|
[AIParameter("Path to solution directory (containing .sln file)", required: true)]
|
|
public string SolutionPath { get; set; }
|
|
|
|
[AIParameter("Refactoring operations to perform", required: true)]
|
|
public string Operations { get; set; }
|
|
|
|
[AIParameter("Create new Git branch for refactoring", required: false)]
|
|
public bool CreateBranch { get; set; } = true;
|
|
|
|
[AIParameter("Branch name for refactoring (auto-generated if empty)", required: false)]
|
|
public string BranchName { get; set; }
|
|
|
|
[AIParameter("Apply changes or preview only", required: false)]
|
|
public bool ApplyChanges { get; set; } = false;
|
|
|
|
[AIParameter("Respect .gitignore file", required: false)]
|
|
public bool RespectGitIgnore { get; set; } = true;
|
|
|
|
[AIParameter("Maximum files to process per project", required: false)]
|
|
public int MaxFilesPerProject { get; set; } = 100;
|
|
|
|
[AIParameter("Skip projects matching patterns (comma-separated)", required: false)]
|
|
public string SkipProjects { get; set; } = "*.Test,*.Tests,*.UnitTest";
|
|
|
|
public IReadOnlyDictionary<string, Type> SupportedParameters => new Dictionary<string, Type>
|
|
{
|
|
["solutionPath"] = typeof(string),
|
|
["solutionpath"] = typeof(string),
|
|
["operations"] = typeof(string),
|
|
["createBranch"] = typeof(bool),
|
|
["createbranch"] = typeof(bool),
|
|
["branchName"] = typeof(string),
|
|
["branchname"] = typeof(string),
|
|
["applyChanges"] = typeof(bool),
|
|
["applychanges"] = typeof(bool),
|
|
["respectGitIgnore"] = typeof(bool),
|
|
["respectgitignore"] = typeof(bool),
|
|
["maxFilesPerProject"] = typeof(int),
|
|
["maxfilesperproject"] = typeof(int),
|
|
["skipProjects"] = typeof(string),
|
|
["skipprojects"] = typeof(string)
|
|
};
|
|
|
|
public async Task<AIPluginResult> ExecuteAsync(IReadOnlyDictionary<string, object> parameters)
|
|
{
|
|
try
|
|
{
|
|
// Extract parameters
|
|
string solutionPath = GetParameterValue(parameters, "solutionPath", "solutionpath")?.ToString();
|
|
string operations = GetParameterValue(parameters, "operations")?.ToString();
|
|
bool createBranch = GetBoolParameter(parameters, "createBranch", "createbranch", true);
|
|
string branchName = GetParameterValue(parameters, "branchName", "branchname")?.ToString();
|
|
bool applyChanges = GetBoolParameter(parameters, "applyChanges", "applychanges", false);
|
|
bool respectGitIgnore = GetBoolParameter(parameters, "respectGitIgnore", "respectgitignore", true);
|
|
int maxFilesPerProject = GetIntParameter(parameters, "maxFilesPerProject", "maxfilesperproject", 100);
|
|
string skipProjects = GetParameterValue(parameters, "skipProjects", "skipprojects")?.ToString() ?? "*.Test,*.Tests,*.UnitTest";
|
|
|
|
// Validate solution path
|
|
if (!Directory.Exists(solutionPath))
|
|
{
|
|
return new AIPluginResult(new DirectoryNotFoundException($"Solution directory not found: {solutionPath}"), "Invalid solution path");
|
|
}
|
|
|
|
var solutionResult = new SolutionRefactoringResult
|
|
{
|
|
SolutionPath = solutionPath,
|
|
StartTime = DateTime.UtcNow
|
|
};
|
|
|
|
// Discover solution structure
|
|
await DiscoverSolutionStructure(solutionResult, solutionPath, skipProjects);
|
|
|
|
// Setup Git integration
|
|
var gitManager = new GitRefactoringManager(solutionPath);
|
|
if (createBranch && gitManager.IsGitRepository)
|
|
{
|
|
branchName = branchName ?? GenerateBranchName(operations);
|
|
solutionResult.GitInfo = await gitManager.CreateRefactoringBranch(branchName, applyChanges);
|
|
}
|
|
|
|
// Process .gitignore
|
|
var gitIgnoreRules = new List<string>();
|
|
if (respectGitIgnore)
|
|
{
|
|
gitIgnoreRules = LoadGitIgnoreRules(solutionPath);
|
|
solutionResult.GitIgnoreRules = gitIgnoreRules;
|
|
}
|
|
|
|
// Process each project
|
|
foreach (var project in solutionResult.Projects)
|
|
{
|
|
try
|
|
{
|
|
await ProcessProject(project, operations, gitIgnoreRules, maxFilesPerProject, applyChanges);
|
|
solutionResult.SuccessfulProjects++;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
project.Error = ex.Message;
|
|
solutionResult.FailedProjects++;
|
|
}
|
|
}
|
|
|
|
solutionResult.EndTime = DateTime.UtcNow;
|
|
solutionResult.TotalDuration = solutionResult.EndTime - solutionResult.StartTime;
|
|
|
|
// Generate comprehensive summary
|
|
var summary = GenerateSolutionSummary(solutionResult);
|
|
|
|
return new AIPluginResult(new
|
|
{
|
|
Message = $"Solution refactoring completed: {solutionResult.Projects.Count} projects processed",
|
|
SolutionPath = solutionPath,
|
|
GitBranch = solutionResult.GitInfo?.NewBranchName,
|
|
ChangesApplied = applyChanges,
|
|
Summary = summary,
|
|
DetailedResult = solutionResult,
|
|
GitCommands = GenerateGitCommands(solutionResult),
|
|
Timestamp = DateTime.UtcNow
|
|
});
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return new AIPluginResult(ex, $"Solution refactoring failed: {ex.Message}");
|
|
}
|
|
}
|
|
private async Task DiscoverSolutionStructure(SolutionRefactoringResult result, string solutionPath, string skipPatterns)
|
|
{
|
|
// Find .sln files
|
|
var solutionFiles = Directory.GetFiles(solutionPath, "*.sln", SearchOption.TopDirectoryOnly);
|
|
if (solutionFiles.Any())
|
|
{
|
|
result.SolutionFile = solutionFiles.First();
|
|
result.SolutionName = Path.GetFileNameWithoutExtension(result.SolutionFile);
|
|
|
|
// NEW: Parse .sln file to understand logical project structure
|
|
await ParseSolutionFileForProjectTypes(result, result.SolutionFile);
|
|
}
|
|
|
|
// Find all .csproj files
|
|
var projectFiles = Directory.GetFiles(solutionPath, "*.csproj", SearchOption.AllDirectories);
|
|
var skipPatternList = skipPatterns.Split(',', StringSplitOptions.RemoveEmptyEntries)
|
|
.Select(p => p.Trim()).ToList();
|
|
|
|
foreach (var projectFile in projectFiles)
|
|
{
|
|
var projectName = Path.GetFileNameWithoutExtension(projectFile);
|
|
|
|
// Skip test projects and other excluded patterns
|
|
if (ShouldSkipProject(projectName, skipPatternList))
|
|
{
|
|
result.SkippedProjects.Add(new SkippedProject
|
|
{
|
|
Name = projectName,
|
|
Path = projectFile,
|
|
Reason = "Matches skip pattern"
|
|
});
|
|
continue;
|
|
}
|
|
|
|
var project = new ProjectRefactoringInfo
|
|
{
|
|
Name = projectName,
|
|
ProjectFilePath = projectFile,
|
|
ProjectDirectory = Path.GetDirectoryName(projectFile),
|
|
CSharpFiles = new List<string>()
|
|
};
|
|
|
|
// NEW: Analyze project file for MAUI and other special properties
|
|
await AnalyzeProjectFileProperties(project, projectFile);
|
|
|
|
// Discover C# files in project with MAUI-aware filtering
|
|
var csFiles = Directory.GetFiles(project.ProjectDirectory, "*.cs", SearchOption.AllDirectories);
|
|
|
|
// NEW: Apply MAUI-aware file filtering
|
|
var filteredFiles = csFiles.Where(file => !ShouldExcludeFileForProject(file, project, solutionPath)).ToList();
|
|
|
|
project.CSharpFiles.AddRange(filteredFiles);
|
|
project.TotalFiles = csFiles.Length;
|
|
project.ProcessableFiles = filteredFiles.Count; // NEW: Track how many we'll actually process
|
|
|
|
// NEW: Add MAUI-specific analysis if this is a MAUI project
|
|
if (project.IsMauiProject)
|
|
{
|
|
await AnalyzeMauiProjectStructure(project);
|
|
}
|
|
|
|
result.Projects.Add(project);
|
|
}
|
|
|
|
await Task.CompletedTask;
|
|
}
|
|
|
|
// NEW: Add these supporting methods to your class:
|
|
|
|
private async Task ParseSolutionFileForProjectTypes(SolutionRefactoringResult result, string solutionFile)
|
|
{
|
|
try
|
|
{
|
|
var solutionContent = await File.ReadAllTextAsync(solutionFile);
|
|
var lines = solutionContent.Split('\n');
|
|
|
|
foreach (var line in lines)
|
|
{
|
|
// Parse project entries from .sln file
|
|
var projectMatch = System.Text.RegularExpressions.Regex.Match(line,
|
|
@"Project\(""([^""]+)""\)\s*=\s*""([^""]+)"",\s*""([^""]+)"",\s*""([^""]+)""");
|
|
|
|
if (projectMatch.Success)
|
|
{
|
|
var projectTypeGuid = projectMatch.Groups[1].Value;
|
|
var projectName = projectMatch.Groups[2].Value;
|
|
var projectPath = projectMatch.Groups[3].Value;
|
|
|
|
// Store solution-level project info for later correlation
|
|
result.SolutionProjectEntries.Add(new SolutionProjectEntry
|
|
{
|
|
Name = projectName,
|
|
RelativePath = projectPath,
|
|
ProjectTypeGuid = projectTypeGuid,
|
|
IsVirtualProject = DetermineIfVirtualProject(projectTypeGuid)
|
|
});
|
|
}
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.WriteLine($"[WARNING] Could not parse solution file: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
private async Task AnalyzeProjectFileProperties(ProjectRefactoringInfo project, string projectFile)
|
|
{
|
|
try
|
|
{
|
|
var projectContent = await File.ReadAllTextAsync(projectFile);
|
|
|
|
// Check for MAUI project
|
|
project.IsMauiProject = projectContent.Contains("<UseMaui>true</UseMaui>", StringComparison.OrdinalIgnoreCase);
|
|
|
|
// Extract target frameworks
|
|
var targetFrameworksMatch = System.Text.RegularExpressions.Regex.Match(projectContent,
|
|
@"<TargetFrameworks?>(.*?)</TargetFrameworks?>", System.Text.RegularExpressions.RegexOptions.IgnoreCase);
|
|
|
|
if (targetFrameworksMatch.Success)
|
|
{
|
|
project.TargetFrameworks = targetFrameworksMatch.Groups[1].Value
|
|
.Split(';', StringSplitOptions.RemoveEmptyEntries)
|
|
.Select(tf => tf.Trim())
|
|
.ToList();
|
|
}
|
|
|
|
// Check for other project types
|
|
if (projectContent.Contains("<PackageReference Include=\"Microsoft.NET.Test.Sdk\"", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
project.IsTestProject = true;
|
|
}
|
|
|
|
if (project.IsMauiProject)
|
|
{
|
|
Console.WriteLine($"[MAUI] Detected MAUI project: {project.Name}");
|
|
Console.WriteLine($" Target Frameworks: {string.Join(", ", project.TargetFrameworks)}");
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.WriteLine($"[WARNING] Could not analyze project file {projectFile}: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
private bool ShouldExcludeFileForProject(string filePath, ProjectRefactoringInfo project, string solutionBasePath)
|
|
{
|
|
var fileName = Path.GetFileName(filePath);
|
|
var relativePath = Path.GetRelativePath(solutionBasePath, filePath);
|
|
|
|
// Standard exclusions (same as before)
|
|
var standardExclusions = new[]
|
|
{
|
|
".Designer.cs", ".generated.cs", ".g.cs", "AssemblyInfo.cs",
|
|
"GlobalAssemblyInfo.cs", "Reference.cs", "TemporaryGeneratedFile",
|
|
".AssemblyAttributes.cs"
|
|
};
|
|
|
|
if (standardExclusions.Any(pattern => fileName.Contains(pattern, StringComparison.OrdinalIgnoreCase)))
|
|
return true;
|
|
|
|
// MAUI-specific exclusions
|
|
if (project.IsMauiProject)
|
|
{
|
|
// Always skip these MAUI infrastructure files
|
|
var mauiInfrastructureFiles = new[]
|
|
{
|
|
"MauiProgram.cs", "App.xaml.cs", "AppShell.xaml.cs"
|
|
};
|
|
|
|
if (mauiInfrastructureFiles.Any(file => fileName.Equals(file, StringComparison.OrdinalIgnoreCase)))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// Skip small platform-specific files (but not large ones that might need refactoring)
|
|
if (IsInMauiPlatformFolder(filePath))
|
|
{
|
|
try
|
|
{
|
|
var content = File.ReadAllText(filePath);
|
|
var lineCount = content.Split('\n').Length;
|
|
|
|
// Skip small platform files (< 50 lines), but process large ones
|
|
if (lineCount < 50)
|
|
{
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
Console.WriteLine($"[MAUI] Large platform file detected: {fileName} ({lineCount} lines) - will analyze carefully");
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
// If we can't read the file, don't exclude it
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private bool IsInMauiPlatformFolder(string filePath)
|
|
{
|
|
var pathParts = filePath.Split(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
|
|
return pathParts.Any(part =>
|
|
part.Equals("Platforms", StringComparison.OrdinalIgnoreCase) ||
|
|
part.Equals("Platform", StringComparison.OrdinalIgnoreCase) ||
|
|
part.Equals("Android", StringComparison.OrdinalIgnoreCase) ||
|
|
part.Equals("iOS", StringComparison.OrdinalIgnoreCase) ||
|
|
part.Equals("MacCatalyst", StringComparison.OrdinalIgnoreCase) ||
|
|
part.Equals("Windows", StringComparison.OrdinalIgnoreCase) ||
|
|
part.Equals("Tizen", StringComparison.OrdinalIgnoreCase));
|
|
}
|
|
|
|
private async Task AnalyzeMauiProjectStructure(ProjectRefactoringInfo project)
|
|
{
|
|
project.MauiAnalysis = new MauiProjectAnalysis();
|
|
|
|
// Check for platform folders
|
|
var projectDir = project.ProjectDirectory;
|
|
var platformsDir = Path.Combine(projectDir, "Platforms");
|
|
|
|
if (Directory.Exists(platformsDir))
|
|
{
|
|
project.MauiAnalysis.HasPlatformsFolder = true;
|
|
|
|
var platformDirs = Directory.GetDirectories(platformsDir);
|
|
foreach (var platformDir in platformDirs)
|
|
{
|
|
var platformName = Path.GetFileName(platformDir);
|
|
var platformFiles = Directory.GetFiles(platformDir, "*.cs", SearchOption.AllDirectories);
|
|
project.MauiAnalysis.PlatformSpecificFiles[platformName] = platformFiles.ToList();
|
|
}
|
|
}
|
|
|
|
// Generate MAUI-specific recommendations
|
|
var recommendations = new List<string>();
|
|
|
|
if (project.TargetFrameworks.Count > 4)
|
|
{
|
|
recommendations.Add("Consider reducing target frameworks - too many platforms increase complexity");
|
|
}
|
|
|
|
var sharedBusinessLogicFiles = project.CSharpFiles
|
|
.Where(f => !IsInMauiPlatformFolder(f))
|
|
.Where(f => f.Contains("ViewModel") || f.Contains("Service") || f.Contains("Model"))
|
|
.ToList();
|
|
|
|
if (sharedBusinessLogicFiles.Count > 20)
|
|
{
|
|
recommendations.Add("Large number of shared business logic files - consider organizing into feature folders");
|
|
}
|
|
|
|
project.MauiAnalysis.RefactoringRecommendations = recommendations;
|
|
|
|
await Task.CompletedTask;
|
|
}
|
|
|
|
private bool DetermineIfVirtualProject(string projectTypeGuid)
|
|
{
|
|
// Solution folders and other virtual project types
|
|
return projectTypeGuid.Equals("{2150E333-8FDC-42A3-9474-1A3956D46DE8}", StringComparison.OrdinalIgnoreCase);
|
|
}
|
|
|
|
private bool ShouldSkipProject(string projectName, List<string> skipPatterns)
|
|
{
|
|
return skipPatterns.Any(pattern =>
|
|
{
|
|
var regexPattern = pattern.Replace("*", ".*");
|
|
return Regex.IsMatch(projectName, regexPattern, RegexOptions.IgnoreCase);
|
|
});
|
|
}
|
|
|
|
private List<string> LoadGitIgnoreRules(string solutionPath)
|
|
{
|
|
var gitIgnoreFile = Path.Combine(solutionPath, ".gitignore");
|
|
var rules = new List<string>();
|
|
|
|
if (File.Exists(gitIgnoreFile))
|
|
{
|
|
var lines = File.ReadAllLines(gitIgnoreFile);
|
|
foreach (var line in lines)
|
|
{
|
|
var trimmed = line.Trim();
|
|
if (!string.IsNullOrEmpty(trimmed) && !trimmed.StartsWith("#"))
|
|
{
|
|
rules.Add(trimmed);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Add common .NET ignore patterns if not present
|
|
var commonPatterns = new[] { "bin/", "obj/", "*.user", "*.suo", ".vs/", "packages/" };
|
|
foreach (var pattern in commonPatterns)
|
|
{
|
|
if (!rules.Contains(pattern))
|
|
{
|
|
rules.Add(pattern);
|
|
}
|
|
}
|
|
|
|
return rules;
|
|
}
|
|
|
|
private bool IsFileIgnored(string filePath, string basePath, List<string> gitIgnoreRules)
|
|
{
|
|
var relativePath = Path.GetRelativePath(basePath, filePath).Replace('\\', '/');
|
|
|
|
foreach (var rule in gitIgnoreRules)
|
|
{
|
|
if (string.IsNullOrEmpty(rule)) continue;
|
|
|
|
var pattern = rule;
|
|
if (pattern.EndsWith("/"))
|
|
{
|
|
// Directory pattern
|
|
if (relativePath.StartsWith(pattern.TrimEnd('/'), StringComparison.OrdinalIgnoreCase))
|
|
return true;
|
|
}
|
|
else if (pattern.Contains("*"))
|
|
{
|
|
// Wildcard pattern
|
|
var regexPattern = "^" + Regex.Escape(pattern).Replace("\\*", ".*") + "$";
|
|
if (Regex.IsMatch(relativePath, regexPattern, RegexOptions.IgnoreCase))
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
// Exact match
|
|
if (relativePath.Equals(pattern, StringComparison.OrdinalIgnoreCase) ||
|
|
relativePath.EndsWith("/" + pattern, StringComparison.OrdinalIgnoreCase))
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private async Task ProcessProject(ProjectRefactoringInfo project, string operations, List<string> gitIgnoreRules, int maxFiles, bool applyChanges)
|
|
{
|
|
project.StartTime = DateTime.UtcNow;
|
|
|
|
// Filter files based on .gitignore and other exclusions
|
|
var filteredFiles = project.CSharpFiles
|
|
.Where(file => !IsFileIgnored(file, project.ProjectDirectory, gitIgnoreRules))
|
|
.Where(file => !ShouldExcludeFile(file))
|
|
.Take(maxFiles)
|
|
.ToList();
|
|
|
|
project.FilesToProcess = filteredFiles.Count;
|
|
project.FilesSkipped = project.TotalFiles - project.FilesToProcess;
|
|
|
|
// Process each file with the refactoring plugins
|
|
var codeRefactoringPlugin = new CodeRefactoringPlugin();
|
|
var codeAnalysisPlugin = new CodeAnalysisPlugin();
|
|
var documentationPlugin = new EnhancedDocumentationGeneratorPlugin();
|
|
|
|
foreach (var file in filteredFiles)
|
|
{
|
|
try
|
|
{
|
|
var fileResult = new FileRefactoringResult
|
|
{
|
|
FilePath = file,
|
|
FileName = Path.GetFileName(file),
|
|
StartTime = DateTime.UtcNow
|
|
};
|
|
|
|
// Run analysis first
|
|
var analysisParams = new Dictionary<string, object>
|
|
{
|
|
["path"] = file,
|
|
["analysisDepth"] = "detailed",
|
|
["includeComplexity"] = true,
|
|
["includeCodeSmells"] = true,
|
|
["includeSuggestions"] = true
|
|
};
|
|
|
|
var analysisResult = await codeAnalysisPlugin.ExecuteAsync(analysisParams);
|
|
fileResult.AnalysisResult = analysisResult;
|
|
|
|
// Run refactoring if analysis succeeded
|
|
if (analysisResult.Success)
|
|
{
|
|
var refactorParams = new Dictionary<string, object>
|
|
{
|
|
["filePath"] = file,
|
|
["operations"] = operations,
|
|
["applyChanges"] = applyChanges,
|
|
["maxMethodLength"] = 25,
|
|
["maxClassSize"] = 400,
|
|
["minComplexityForExtraction"] = 6
|
|
};
|
|
|
|
var refactorResult = await codeRefactoringPlugin.ExecuteAsync(refactorParams);
|
|
fileResult.RefactoringResult = refactorResult;
|
|
|
|
if (refactorResult.Success && applyChanges)
|
|
{
|
|
project.FilesModified++;
|
|
}
|
|
}
|
|
|
|
// Add documentation if requested
|
|
if (operations.Contains("documentation"))
|
|
{
|
|
var docParams = new Dictionary<string, object>
|
|
{
|
|
["filePath"] = file,
|
|
["style"] = "intelligent",
|
|
["applyChanges"] = applyChanges
|
|
};
|
|
|
|
var docResult = await documentationPlugin.ExecuteAsync(docParams);
|
|
fileResult.DocumentationResult = docResult;
|
|
}
|
|
|
|
fileResult.EndTime = DateTime.UtcNow;
|
|
fileResult.Success = analysisResult.Success;
|
|
project.FileResults.Add(fileResult);
|
|
project.FilesProcessed++;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
project.FileResults.Add(new FileRefactoringResult
|
|
{
|
|
FilePath = file,
|
|
FileName = Path.GetFileName(file),
|
|
Success = false,
|
|
Error = ex.Message
|
|
});
|
|
}
|
|
}
|
|
|
|
project.EndTime = DateTime.UtcNow;
|
|
project.Duration = project.EndTime - project.StartTime;
|
|
project.Success = project.FileResults.Any() && project.FileResults.All(f => f.Success);
|
|
}
|
|
|
|
private bool ShouldExcludeFile(string filePath)
|
|
{
|
|
var fileName = Path.GetFileName(filePath);
|
|
var excludePatterns = new[]
|
|
{
|
|
".Designer.cs", ".generated.cs", ".g.cs", "AssemblyInfo.cs",
|
|
"GlobalAssemblyInfo.cs", "TemporaryGeneratedFile_", ".AssemblyAttributes.cs",
|
|
"Reference.cs", "References.cs"
|
|
};
|
|
|
|
return excludePatterns.Any(pattern => fileName.Contains(pattern, StringComparison.OrdinalIgnoreCase));
|
|
}
|
|
|
|
private string GenerateBranchName(string operations)
|
|
{
|
|
var timestamp = DateTime.Now.ToString("yyyyMMdd-HHmm");
|
|
var operationSummary = operations.Split(',').FirstOrDefault()?.Trim() ?? "refactor";
|
|
return $"refactor/{operationSummary}-{timestamp}";
|
|
}
|
|
|
|
private object GenerateSolutionSummary(SolutionRefactoringResult result)
|
|
{
|
|
var totalFiles = result.Projects.Sum(p => p.FilesToProcess);
|
|
var totalModified = result.Projects.Sum(p => p.FilesModified);
|
|
var totalSkipped = result.Projects.Sum(p => p.FilesSkipped);
|
|
|
|
return new
|
|
{
|
|
SolutionName = result.SolutionName,
|
|
TotalProjects = result.Projects.Count,
|
|
SuccessfulProjects = result.SuccessfulProjects,
|
|
FailedProjects = result.FailedProjects,
|
|
SkippedProjects = result.SkippedProjects.Count,
|
|
TotalFiles = totalFiles,
|
|
FilesModified = totalModified,
|
|
FilesSkipped = totalSkipped,
|
|
ProcessingTime = result.TotalDuration,
|
|
GitBranch = result.GitInfo?.NewBranchName,
|
|
TopIssuesFound = GetTopIssues(result),
|
|
ProjectSummaries = result.Projects.Select(p => new
|
|
{
|
|
p.Name,
|
|
p.FilesToProcess,
|
|
p.FilesModified,
|
|
p.Success,
|
|
Duration = p.Duration.TotalSeconds
|
|
}).ToList()
|
|
};
|
|
}
|
|
|
|
private object GetTopIssues(SolutionRefactoringResult result)
|
|
{
|
|
// Aggregate issues across all files
|
|
var allIssues = new List<string>();
|
|
|
|
foreach (var project in result.Projects)
|
|
{
|
|
foreach (var file in project.FileResults)
|
|
{
|
|
// Extract issues from analysis results
|
|
// This would need to parse the actual result data
|
|
if (file.AnalysisResult?.Success == true)
|
|
{
|
|
// Add logic to extract code smells and suggestions
|
|
}
|
|
}
|
|
}
|
|
|
|
return new
|
|
{
|
|
TotalIssues = allIssues.Count,
|
|
TopIssueTypes = allIssues.GroupBy(i => i).OrderByDescending(g => g.Count()).Take(5)
|
|
.Select(g => new { Type = g.Key, Count = g.Count() }).ToList()
|
|
};
|
|
}
|
|
|
|
private object GenerateGitCommands(SolutionRefactoringResult result)
|
|
{
|
|
var commands = new List<string>();
|
|
|
|
if (result.GitInfo != null)
|
|
{
|
|
commands.Add($"# Current branch: {result.GitInfo.NewBranchName}");
|
|
commands.Add("# To review changes:");
|
|
commands.Add("git diff HEAD~1");
|
|
commands.Add("git status");
|
|
commands.Add("");
|
|
commands.Add("# To commit changes:");
|
|
commands.Add($"git add .");
|
|
commands.Add($"git commit -m \"Automated refactoring: {string.Join(", ", result.GitInfo.OperationsPerformed)}\"");
|
|
commands.Add("");
|
|
commands.Add("# To merge back to main:");
|
|
commands.Add($"git checkout {result.GitInfo.OriginalBranch}");
|
|
commands.Add($"git merge {result.GitInfo.NewBranchName}");
|
|
commands.Add("");
|
|
commands.Add("# To discard changes (if needed):");
|
|
commands.Add($"git checkout {result.GitInfo.OriginalBranch}");
|
|
commands.Add($"git branch -D {result.GitInfo.NewBranchName}");
|
|
}
|
|
|
|
return commands;
|
|
}
|
|
|
|
// 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 solution processing
|
|
public class SolutionRefactoringResult
|
|
{
|
|
public string SolutionPath { get; set; }
|
|
public string SolutionFile { get; set; }
|
|
public string SolutionName { get; set; }
|
|
public List<ProjectRefactoringInfo> Projects { get; set; } = new List<ProjectRefactoringInfo>();
|
|
public List<SkippedProject> SkippedProjects { get; set; } = new List<SkippedProject>();
|
|
public List<string> GitIgnoreRules { get; set; } = new List<string>();
|
|
public GitRefactoringInfo GitInfo { get; set; }
|
|
public int SuccessfulProjects { get; set; }
|
|
public int FailedProjects { get; set; }
|
|
public DateTime StartTime { get; set; }
|
|
public DateTime EndTime { get; set; }
|
|
public TimeSpan TotalDuration { get; set; }
|
|
public List<SolutionProjectEntry> SolutionProjectEntries { get; set; } = new List<SolutionProjectEntry>();
|
|
}
|
|
|
|
public class ProjectRefactoringInfo
|
|
{
|
|
public string Name { get; set; }
|
|
public string ProjectFilePath { get; set; }
|
|
public string ProjectDirectory { get; set; }
|
|
public List<string> CSharpFiles { get; set; } = new List<string>();
|
|
public int TotalFiles { get; set; }
|
|
public int FilesToProcess { get; set; }
|
|
public int FilesProcessed { get; set; }
|
|
public int FilesModified { get; set; }
|
|
public int FilesSkipped { get; set; }
|
|
public bool Success { get; set; }
|
|
public string Error { get; set; }
|
|
public DateTime StartTime { get; set; }
|
|
public DateTime EndTime { get; set; }
|
|
public TimeSpan Duration { get; set; }
|
|
public List<FileRefactoringResult> FileResults { get; set; } = new List<FileRefactoringResult>();
|
|
|
|
public bool IsMauiProject { get; set; }
|
|
public bool IsTestProject { get; set; }
|
|
public List<string> TargetFrameworks { get; set; } = new List<string>();
|
|
public int ProcessableFiles { get; set; }
|
|
public MauiProjectAnalysis MauiAnalysis { get; set; }
|
|
}
|
|
|
|
public class SolutionProjectEntry
|
|
{
|
|
public string Name { get; set; } = string.Empty;
|
|
public string RelativePath { get; set; } = string.Empty;
|
|
public string ProjectTypeGuid { get; set; } = string.Empty;
|
|
public bool IsVirtualProject { get; set; }
|
|
}
|
|
|
|
public class MauiProjectAnalysis
|
|
{
|
|
public bool HasPlatformsFolder { get; set; }
|
|
public Dictionary<string, List<string>> PlatformSpecificFiles { get; set; } = new Dictionary<string, List<string>>();
|
|
public List<string> RefactoringRecommendations { get; set; } = new List<string>();
|
|
}
|
|
|
|
public class FileRefactoringResult
|
|
{
|
|
public string FilePath { get; set; }
|
|
public string FileName { get; set; }
|
|
public bool Success { get; set; }
|
|
public string Error { get; set; }
|
|
public DateTime StartTime { get; set; }
|
|
public DateTime EndTime { get; set; }
|
|
public AIPluginResult AnalysisResult { get; set; }
|
|
public AIPluginResult RefactoringResult { get; set; }
|
|
public AIPluginResult DocumentationResult { get; set; }
|
|
}
|
|
|
|
public class SkippedProject
|
|
{
|
|
public string Name { get; set; }
|
|
public string Path { get; set; }
|
|
public string Reason { get; set; }
|
|
}
|
|
|
|
public class GitRefactoringInfo
|
|
{
|
|
public string RepositoryPath { get; set; }
|
|
public string OriginalBranch { get; set; }
|
|
public string OriginalCommit { get; set; }
|
|
public string NewBranchName { get; set; }
|
|
public bool BranchCreated { get; set; }
|
|
public bool Success { get; set; }
|
|
public string Error { get; set; }
|
|
public DateTime CreatedAt { get; set; }
|
|
public List<string> OperationsPerformed { get; set; } = new List<string>();
|
|
}
|
|
} |