MarketAlly.AIPlugin.Extensions/MarketAlly.AIPlugin.Analysis/WarningsAnalysisPlugin.cs

268 lines
8.0 KiB
C#
Executable File

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<string, Type> SupportedParameters => new Dictionary<string, Type>
{
["solutionPath"] = typeof(string),
["phase"] = typeof(string),
["maxAttempts"] = typeof(int),
["applyFixes"] = typeof(bool)
};
public async Task<AIPluginResult> ExecuteAsync(IReadOnlyDictionary<string, object> 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<object> 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<object>().ToList();
}
catch (Exception ex)
{
Console.WriteLine($"Warning: Warnings analysis failed: {ex.Message}");
}
return analysisResult;
}
private async Task<CompilationResult> 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<CompilationDiagnostic> ParseWarningsFromOutput(string output)
{
var warnings = new List<CompilationDiagnostic>();
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<string, int> GroupWarningsByType(List<CompilationDiagnostic> warnings)
{
return warnings.GroupBy(w => w.Code)
.ToDictionary(g => g.Key, g => g.Count());
}
private List<string> GetFilesWithWarnings(List<CompilationDiagnostic> warnings)
{
return warnings.Select(w => w.File)
.Where(f => !string.IsNullOrEmpty(f))
.Distinct()
.ToList();
}
private async Task<int> AttemptWarningFixes(List<CompilationDiagnostic> 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<bool> 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<string, int> WarningsByType { get; set; } = new();
public List<string> ProcessedFiles { get; set; } = new();
public int FixesApplied { get; set; }
public int FailedFixes { get; set; }
public string Summary { get; set; } = string.Empty;
public List<object> Details { get; set; } = new();
}
}