using MarketAlly.AIPlugin; using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Text.Json; using System.Threading.Tasks; namespace MarketAlly.AIPlugin.Analysis.Plugins { [AIPlugin("WarningsAnalysis", "Analyzes and attempts to fix compiler warnings in .NET solutions")] public class WarningsAnalysisPlugin : IAIPlugin { [AIParameter("Solution path to analyze", required: true)] public string SolutionPath { get; set; } = string.Empty; [AIParameter("Analysis phase (initial/final)", required: false)] public string Phase { get; set; } = "initial"; [AIParameter("Maximum attempts per warning", required: false)] public int MaxAttempts { get; set; } = 3; [AIParameter("Apply fixes automatically", required: false)] public bool ApplyFixes { get; set; } = false; public IReadOnlyDictionary SupportedParameters => new Dictionary { ["solutionPath"] = typeof(string), ["phase"] = typeof(string), ["maxAttempts"] = typeof(int), ["applyFixes"] = typeof(bool) }; public async Task ExecuteAsync(IReadOnlyDictionary parameters) { try { var solutionPath = parameters["solutionPath"].ToString(); var phase = parameters.GetValueOrDefault("phase", "initial").ToString(); var maxAttempts = Convert.ToInt32(parameters.GetValueOrDefault("maxAttempts", 3)); var applyFixes = Convert.ToBoolean(parameters.GetValueOrDefault("applyFixes", false)); var result = await AnalyzeWarningsAsync(solutionPath ?? string.Empty, phase ?? "initial", maxAttempts, applyFixes); return new AIPluginResult(result, $"Warnings analysis completed for {phase} phase"); } catch (Exception ex) { return new AIPluginResult(ex, $"Warnings analysis failed: {ex.Message}"); } } private async Task AnalyzeWarningsAsync(string solutionPath, string phase, int maxAttempts, bool applyFixes) { var analysisResult = new WarningAnalysisResult { Phase = phase, SolutionPath = solutionPath, Timestamp = DateTime.UtcNow, }; try { var buildResult = await RunBuildAnalysis(solutionPath); analysisResult.TotalWarnings = buildResult.WarningCount; analysisResult.WarningsByType = GroupWarningsByType(buildResult.Warnings); analysisResult.ProcessedFiles = GetFilesWithWarnings(buildResult.Warnings); analysisResult.FixesApplied = applyFixes ? await AttemptWarningFixes(buildResult.Warnings, maxAttempts) : 0; analysisResult.Summary = $"{phase} warnings analysis: {buildResult.WarningCount} warnings found"; analysisResult.Details = buildResult.Warnings.Take(10).Select(w => new { w.Code, w.Message, w.File, w.Line, Type = w.Type }).Cast().ToList(); } catch (Exception ex) { Console.WriteLine($"Warning: Warnings analysis failed: {ex.Message}"); } return analysisResult; } private async Task RunBuildAnalysis(string solutionPath) { var processInfo = new ProcessStartInfo { FileName = "dotnet", Arguments = $"build \"{solutionPath}\" --verbosity normal --nologo", RedirectStandardOutput = true, RedirectStandardError = true, UseShellExecute = false, CreateNoWindow = true }; using var process = Process.Start(processInfo); if (process == null) { throw new InvalidOperationException("Failed to start dotnet build process"); } var output = await process.StandardOutput.ReadToEndAsync(); var error = await process.StandardError.ReadToEndAsync(); await process.WaitForExitAsync(); var result = new CompilationResult { ExitCode = process.ExitCode, BuildOutput = output, BuildErrors = error, Status = process.ExitCode == 0 ? CompilationStatus.Success : CompilationStatus.Failed }; // Parse warnings from output result.Warnings = ParseWarningsFromOutput(output + error); result.WarningCount = result.Warnings.Count; return result; } private List ParseWarningsFromOutput(string output) { var warnings = new List(); var lines = output.Split('\n', StringSplitOptions.RemoveEmptyEntries); foreach (var line in lines) { if (line.Contains("warning", StringComparison.OrdinalIgnoreCase)) { var warning = TryParseWarningLine(line.Trim()); if (warning != null) { warnings.Add(warning); } } } return warnings; } private CompilationDiagnostic? TryParseWarningLine(string line) { var patterns = new[] { @"(.+?)\((\d+),(\d+)\):\s*warning\s+([A-Z]+\d+):\s*(.+)", @"warning\s+([A-Z]+\d+):\s*(.+)\s*\[(.+?)\]" }; foreach (var pattern in patterns) { var match = System.Text.RegularExpressions.Regex.Match(line, pattern, System.Text.RegularExpressions.RegexOptions.IgnoreCase); if (match.Success) { if (match.Groups.Count >= 5 && !string.IsNullOrEmpty(match.Groups[1].Value)) { return new CompilationDiagnostic { File = Path.GetFileName(match.Groups[1].Value), Line = int.TryParse(match.Groups[2].Value, out var parsedLine) ? parsedLine : 0, Column = int.TryParse(match.Groups[3].Value, out var parsedCol) ? parsedCol : 0, Code = match.Groups[4].Value, Message = match.Groups[5].Value.Trim(), Type = "warning" }; } else if (match.Groups.Count >= 3) { return new CompilationDiagnostic { Code = match.Groups[1].Value, Message = match.Groups[2].Value.Trim(), File = match.Groups.Count > 3 ? Path.GetFileName(match.Groups[3].Value) : "Unknown", Type = "warning" }; } } } return null; } private Dictionary GroupWarningsByType(List warnings) { return warnings.GroupBy(w => w.Code) .ToDictionary(g => g.Key, g => g.Count()); } private List GetFilesWithWarnings(List warnings) { return warnings.Select(w => w.File) .Where(f => !string.IsNullOrEmpty(f)) .Distinct() .ToList(); } private async Task AttemptWarningFixes(List warnings, int maxAttempts) { var fixesApplied = 0; // Group warnings by file for efficient processing var warningsByFile = warnings.GroupBy(w => w.File); foreach (var fileGroup in warningsByFile.Take(5)) // Limit to 5 files { var fileName = fileGroup.Key; if (string.IsNullOrEmpty(fileName) || fileName == "Unknown") continue; // Apply simple fixes for common warnings foreach (var warning in fileGroup.Take(3)) // Limit warnings per file { if (await TryApplySimpleFix(warning)) { fixesApplied++; } } } return fixesApplied; } private async Task TryApplySimpleFix(CompilationDiagnostic warning) { // This is a simplified implementation // In practice, you'd implement specific fixes for different warning types switch (warning.Code?.ToUpper()) { case "CS0168": // Variable declared but never used case "CS0219": // Variable assigned but never used // Could remove unused variables break; case "CS0162": // Unreachable code // Could remove unreachable code break; case "CS1998": // Async method lacks 'await' // Could remove async modifier if appropriate break; } // For demo purposes, just return true occasionally await Task.Delay(10); return new Random().Next(100) < 30; // 30% success rate } } class WarningAnalysisResult { public string Phase { get; set; } = string.Empty; public string SolutionPath { get; set; } = string.Empty; public DateTime Timestamp { get; set; } public int TotalWarnings { get; set; } public Dictionary WarningsByType { get; set; } = new(); public List ProcessedFiles { get; set; } = new(); public int FixesApplied { get; set; } public int FailedFixes { get; set; } public string Summary { get; set; } = string.Empty; public List Details { get; set; } = new(); } }