MarketAlly.AIPlugin.Extensions/MarketAlly.AIPlugin.Analysis/CompilationValidator.cs

333 lines
9.8 KiB
C#
Executable File

// CompilationValidator.cs and supporting classes
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace MarketAlly.AIPlugin.Analysis.Plugins
{
public class CompilationValidator
{
public async Task<CompilationResult> ValidateCompilationAsync(string solutionPath)
{
var result = new CompilationResult
{
StartTime = DateTime.UtcNow,
SolutionPath = solutionPath
};
try
{
// Use dotnet build to validate compilation
var processInfo = new ProcessStartInfo
{
FileName = "dotnet",
Arguments = $"build \"{solutionPath}\" --verbosity quiet --nologo",
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
CreateNoWindow = true
};
using var process = Process.Start(processInfo) ?? throw new InvalidOperationException("Failed to start build process");
var output = await process.StandardOutput.ReadToEndAsync();
var error = await process.StandardError.ReadToEndAsync();
await process.WaitForExitAsync();
result.ExitCode = process.ExitCode;
result.BuildOutput = output;
result.BuildErrors = error;
// Parse compilation results
result.Status = process.ExitCode == 0 ? CompilationStatus.Success : CompilationStatus.Failed;
result.ErrorCount = CountErrors(output + error);
result.WarningCount = CountWarnings(output + error);
// Extract specific errors and warnings
result.Errors = ExtractDiagnostics(output + error, "error");
result.Warnings = ExtractDiagnostics(output + error, "warning");
}
catch (Exception ex)
{
result.Status = CompilationStatus.Failed;
result.ErrorMessage = ex.Message;
}
finally
{
result.EndTime = DateTime.UtcNow;
result.Duration = result.EndTime - result.StartTime;
}
return result;
}
private int CountErrors(string buildOutput)
{
return Regex.Matches(buildOutput, @"error\s+[A-Z]+\d+:", RegexOptions.IgnoreCase).Count;
}
private int CountWarnings(string buildOutput)
{
return Regex.Matches(buildOutput, @"warning\s+[A-Z]+\d+:", RegexOptions.IgnoreCase).Count;
}
private List<CompilationDiagnostic> ExtractDiagnostics(string buildOutput, string type)
{
var diagnostics = new List<CompilationDiagnostic>();
var pattern = $@"{type}\s+([A-Z]+\d+):\s*(.+?)\s+\[(.+?)\]";
var matches = Regex.Matches(buildOutput, pattern, RegexOptions.IgnoreCase | RegexOptions.Multiline);
foreach (Match match in matches.Take(10)) // Limit to 10 diagnostics
{
var diagnostic = new CompilationDiagnostic
{
Code = match.Groups[1].Value,
Message = match.Groups[2].Value.Trim(),
File = ExtractFileName(match.Groups[3].Value),
Type = type
};
// Try to extract line/column from message
ExtractLineColumn(diagnostic);
diagnostics.Add(diagnostic);
}
return diagnostics;
}
private string ExtractFileName(string filePath)
{
try
{
return Path.GetFileName(filePath);
}
catch
{
return filePath;
}
}
private void ExtractLineColumn(CompilationDiagnostic diagnostic)
{
// Try to extract line/column from message like "Program.cs(10,5)"
var lineColMatch = Regex.Match(diagnostic.Message, @"\((\d+),(\d+)\)");
if (lineColMatch.Success)
{
if (int.TryParse(lineColMatch.Groups[1].Value, out var line))
diagnostic.Line = line;
if (int.TryParse(lineColMatch.Groups[2].Value, out var column))
diagnostic.Column = column;
}
}
}
public class CompilationDiagnostic
{
public string Code { get; set; } = string.Empty;
public string Message { get; set; } = string.Empty;
public string File { get; set; } = string.Empty;
public int Line { get; set; }
public int Column { get; set; }
public string Type { get; set; } = string.Empty;
public string Severity { get; set; } = string.Empty;
public string Category { get; set; } = string.Empty;
public string HelpLink { get; set; } = string.Empty;
public bool IsWarningAsError { get; set; }
}
// Alternative implementation using MSBuild APIs for more detailed analysis
public class AdvancedCompilationValidator
{
public async Task<CompilationResult> ValidateWithMSBuildAsync(string solutionPath)
{
var result = new CompilationResult
{
StartTime = DateTime.UtcNow,
SolutionPath = solutionPath
};
try
{
// This would require Microsoft.Build packages
// Keeping the simpler dotnet build approach for now
// but providing structure for future enhancement
result = await new CompilationValidator().ValidateCompilationAsync(solutionPath);
}
catch (Exception ex)
{
result.Status = CompilationStatus.Failed;
result.ErrorMessage = ex.Message;
}
finally
{
result.EndTime = DateTime.UtcNow;
result.Duration = result.EndTime - result.StartTime;
}
return result;
}
}
// Compilation analysis extensions
public static class CompilationResultExtensions
{
public static CompilationResult WithPreviousErrorCount(this CompilationResult result, int previousCount)
{
result.PreviousErrorCount = previousCount;
return result;
}
public static bool ImprovedFrom(this CompilationResult result, CompilationResult previous)
{
return result.ErrorCount < previous.ErrorCount ||
(result.ErrorCount == previous.ErrorCount && result.WarningCount < previous.WarningCount);
}
public static bool IsSuccessful(this CompilationResult result)
{
return result.Status == CompilationStatus.Success || result.Status == CompilationStatus.Warning;
}
public static bool HasErrors(this CompilationResult result)
{
return result.ErrorCount > 0;
}
public static bool HasWarnings(this CompilationResult result)
{
return result.WarningCount > 0;
}
public static double GetErrorReduction(this CompilationResult result)
{
if (!result.PreviousErrorCount.HasValue || result.PreviousErrorCount.Value == 0)
return 0.0;
return (double)(result.PreviousErrorCount.Value - result.ErrorCount) / result.PreviousErrorCount.Value;
}
public static string GetSummary(this CompilationResult result)
{
return $"{result.Status} - {result.ErrorCount} errors, {result.WarningCount} warnings ({result.Duration.TotalSeconds:F1}s)";
}
public static List<CompilationDiagnostic> GetCriticalIssues(this CompilationResult result)
{
return result.Errors.Where(e => IsCriticalError(e.Code)).ToList();
}
private static bool IsCriticalError(string errorCode)
{
// Define critical error codes that should be prioritized
var criticalCodes = new[]
{
"CS0103", // Name does not exist
"CS0246", // Type or namespace not found
"CS0029", // Cannot implicitly convert
"CS1002", // Syntax error
"CS1513", // } expected
};
return criticalCodes.Contains(errorCode);
}
}
// Compilation metrics for learning analysis
public class CompilationMetrics
{
public DateTime Timestamp { get; set; } = DateTime.UtcNow;
public string SolutionPath { get; set; } = string.Empty;
public CompilationStatus Status { get; set; }
public int ErrorCount { get; set; }
public int WarningCount { get; set; }
public TimeSpan BuildDuration { get; set; }
public Dictionary<string, int> ErrorsByType { get; set; } = new();
public Dictionary<string, int> WarningsByType { get; set; } = new();
public List<string> ModifiedFiles { get; set; } = new();
public string BuildConfiguration { get; set; } = "Debug";
public string TargetFramework { get; set; } = string.Empty;
public static CompilationMetrics FromResult(CompilationResult result, List<string>? modifiedFiles = null)
{
var metrics = new CompilationMetrics
{
SolutionPath = result.SolutionPath,
Status = result.Status,
ErrorCount = result.ErrorCount,
WarningCount = result.WarningCount,
BuildDuration = result.Duration,
ModifiedFiles = modifiedFiles ?? new List<string>()
};
// Group errors and warnings by type
metrics.ErrorsByType = result.Errors
.GroupBy(e => e.Code)
.ToDictionary(g => g.Key, g => g.Count());
metrics.WarningsByType = result.Warnings
.GroupBy(w => w.Code)
.ToDictionary(g => g.Key, g => g.Count());
return metrics;
}
}
// Build output parser for enhanced diagnostics
public class BuildOutputParser
{
public static List<CompilationDiagnostic> ParseMSBuildOutput(string buildOutput)
{
var diagnostics = new List<CompilationDiagnostic>();
var lines = buildOutput.Split('\n', StringSplitOptions.RemoveEmptyEntries);
foreach (var line in lines)
{
var diagnostic = TryParseDiagnosticLine(line.Trim());
if (diagnostic != null)
{
diagnostics.Add(diagnostic);
}
}
return diagnostics;
}
private static CompilationDiagnostic? TryParseDiagnosticLine(string line)
{
// MSBuild diagnostic format: File(line,column): error/warning CODE: Message
var pattern = @"^(.+?)\((\d+),(\d+)\):\s+(error|warning)\s+([A-Z]+\d+):\s+(.+)$";
var match = Regex.Match(line, pattern, RegexOptions.IgnoreCase);
if (!match.Success)
return null;
return new CompilationDiagnostic
{
File = Path.GetFileName(match.Groups[1].Value),
Line = int.Parse(match.Groups[2].Value),
Column = int.Parse(match.Groups[3].Value),
Type = match.Groups[4].Value.ToLower(),
Code = match.Groups[5].Value,
Message = match.Groups[6].Value.Trim(),
Severity = DetermineSeverity(match.Groups[4].Value, match.Groups[5].Value)
};
}
private static string DetermineSeverity(string type, string code)
{
if (type.Equals("error", StringComparison.OrdinalIgnoreCase))
return "Error";
// Some warnings are more critical than others
var highPriorityWarnings = new[] { "CS0162", "CS0219", "CS0414" };
if (highPriorityWarnings.Contains(code))
return "High";
return "Normal";
}
}
}