333 lines
9.8 KiB
C#
Executable File
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";
|
|
}
|
|
}
|
|
} |