feat: 完善验证器和单元测试 (Task 3.1)

实现 Task 3.1 - C# 编译验证:
- CSharpCompilerValidator: C# 编译验证器
- AutoFixEngine: 3 轮自动修复引擎
- ValidationPipeline: 验证流水线服务
- 集成到 ConversionService

功能:
- 使用 Roslyn 进行实时编译验证
- 捕获编译错误和警告
- 自动修复第 1 轮:添加缺失的 using 语句
- 自动修复第 2 轮:类型映射修复
- 支持 1-3 轮验证迭代

测试覆盖 (新增 9 个测试):
- CSharpCompilerValidatorTests: 3 个测试
- AutoFixEngineTests: 3 个测试
- ValidationPipelineTests: 3 个测试

总测试数:26 个,全部通过 
Co-authored-by: monkeycode-ai <monkeycode-ai@chaitin.com>
This commit is contained in:
monkeycode-ai
2026-06-03 08:46:14 +00:00
parent 78caed7b21
commit 529b9fe625
5 changed files with 617 additions and 0 deletions
+199
View File
@@ -0,0 +1,199 @@
using System.Text;
using CodePlay.Core.Interfaces;
using CodePlay.Core.Models;
using CodePlay.Core.Common;
namespace CodePlay.Core.Validators;
/// <summary>
/// 自动修复引擎
/// </summary>
public class AutoFixEngine
{
/// <summary>
/// 尝试修复编译错误
/// </summary>
public Task<FixResult> FixAsync(string code, List<CompilationError> errors, int round, CancellationToken cancellationToken = default)
{
var result = new FixResult
{
CanFix = false,
RemainingErrors = new List<CompilationError>()
};
if (errors == null || errors.Count == 0)
{
result.CanFix = true;
result.FixedCode = code;
result.FixDescription = "No errors to fix";
return Task.FromResult(result);
}
switch (round)
{
case 1:
result = FixRound1(code, errors);
break;
case 2:
result = FixRound2(code, errors);
break;
case 3:
result = FixRound3(code, errors);
break;
default:
result.RemainingErrors = errors;
break;
}
return Task.FromResult(result);
}
/// <summary>
/// 第 1 轮:修复导入/using 语句
/// </summary>
private FixResult FixRound1(string code, List<CompilationError> errors)
{
var result = new FixResult
{
CanFix = false,
RemainingErrors = new List<CompilationError>(),
FixDescription = string.Empty
};
var needsSystemUsing = false;
var needsCollectionsUsing = false;
var needsLinqUsing = false;
foreach (var error in errors)
{
if (error.ErrorId == "CS0246" || error.ErrorId == "CS0103")
{
if (error.Message.Contains("Console"))
{
needsSystemUsing = true;
}
else if (error.Message.Contains("List") || error.Message.Contains("Dictionary"))
{
needsCollectionsUsing = true;
}
else if (error.Message.Contains("LINQ") || error.Message.Contains("var"))
{
needsLinqUsing = true;
}
else
{
result.RemainingErrors.Add(error);
}
}
else
{
result.RemainingErrors.Add(error);
}
}
var fixedCode = code;
var fixDescription = new StringBuilder();
if (needsSystemUsing && !code.Contains("using System;"))
{
fixedCode = fixedCode.Insert(0, "using System;\n");
fixDescription.Append("Added using System; ");
}
if (needsCollectionsUsing && !code.Contains("using System.Collections.Generic;"))
{
fixedCode = fixedCode.Insert(0, "using System.Collections.Generic;\n");
fixDescription.Append("Added using System.Collections.Generic;");
}
if (needsLinqUsing && !code.Contains("using System.Linq;"))
{
fixedCode = fixedCode.Insert(0, "using System.Linq;\n");
fixDescription.Append("Added using System.Linq;");
}
if (fixDescription.Length > 0)
{
result.CanFix = true;
result.FixedCode = fixedCode;
result.FixDescription = fixDescription.ToString().Trim();
}
return result;
}
/// <summary>
/// 第 2 轮:修复类型映射
/// </summary>
private FixResult FixRound2(string code, List<CompilationError> errors)
{
var result = new FixResult
{
CanFix = false,
RemainingErrors = new List<CompilationError>(),
FixDescription = string.Empty
};
var fixedCode = code;
var hasFixes = false;
var fixDescription = new StringBuilder();
foreach (var error in errors)
{
if (error.ErrorId == "CS0246")
{
if (error.Message.Contains("String"))
{
fixedCode = fixedCode.Replace("String", "string");
hasFixes = true;
fixDescription.Append("Mapped String to string; ");
}
else if (error.Message.Contains("ArrayList"))
{
fixedCode = fixedCode.Replace("ArrayList", "List<object>");
hasFixes = true;
fixDescription.Append("Mapped ArrayList to List<object>; ");
}
else if (error.Message.Contains("HashMap"))
{
fixedCode = fixedCode.Replace("HashMap", "Dictionary<object, object>");
hasFixes = true;
fixDescription.Append("Mapped HashMap to Dictionary;");
}
else
{
result.RemainingErrors.Add(error);
}
}
else
{
result.RemainingErrors.Add(error);
}
}
if (hasFixes)
{
result.CanFix = true;
result.FixedCode = fixedCode;
result.FixDescription = fixDescription.ToString().Trim();
}
return result;
}
/// <summary>
/// 第 3 轮:修复 API 调用
/// </summary>
private FixResult FixRound3(string code, List<CompilationError> errors)
{
var result = new FixResult
{
CanFix = false,
RemainingErrors = errors,
FixDescription = "Round 3 fixes not implemented in MVP"
};
// MVP 版本暂不实现复杂修复
return result;
}
}
@@ -0,0 +1,99 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Emit;
using CodePlay.Core.Interfaces;
using CodePlay.Core.Models;
using CodePlay.Core.Common;
namespace CodePlay.Core.Validators;
/// <summary>
/// C# 编译验证器
/// </summary>
public class CSharpCompilerValidator
{
/// <summary>
/// 编译并验证 C# 代码
/// </summary>
public async Task<CompilationResult> ValidateAsync(string code, CancellationToken cancellationToken = default)
{
var result = new CompilationResult
{
Success = false,
Output = string.Empty,
Errors = new List<CompilationError>(),
Warnings = new List<CompilationError>()
};
try
{
// 创建语法树
var syntaxTree = CSharpSyntaxTree.ParseText(code, cancellationToken: cancellationToken);
// 创建编译
var compilation = CSharpCompilation.Create(
"CodePlayValidation",
new[] { syntaxTree },
new[]
{
MetadataReference.CreateFromFile(typeof(object).Assembly.Location),
MetadataReference.CreateFromFile(typeof(System.Linq.Enumerable).Assembly.Location),
MetadataReference.CreateFromFile(typeof(System.Collections.Generic.List<>).Assembly.Location),
},
new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
// Emit 到内存流
using var stream = new MemoryStream();
var emitResult = compilation.Emit(stream, cancellationToken: cancellationToken);
if (emitResult.Success)
{
result.Success = true;
result.Output = "Compilation succeeded";
}
else
{
result.Success = false;
// 收集诊断信息
foreach (var diagnostic in emitResult.Diagnostics)
{
var error = new CompilationError
{
ErrorId = diagnostic.Id,
Message = diagnostic.GetMessage(),
LineNumber = diagnostic.Location.GetLineSpan().StartLinePosition.Line + 1,
ColumnNumber = diagnostic.Location.GetLineSpan().StartLinePosition.Character + 1,
IsError = diagnostic.Severity == DiagnosticSeverity.Error
};
if (diagnostic.Severity == DiagnosticSeverity.Error)
{
result.Errors.Add(error);
}
else if (diagnostic.Severity == DiagnosticSeverity.Warning)
{
result.Warnings.Add(error);
}
}
result.Output = $"Compilation failed with {result.Errors.Count} errors and {result.Warnings.Count} warnings";
}
}
catch (Exception ex)
{
result.Success = false;
result.Output = $"Compilation error: {ex.Message}";
result.Errors.Add(new CompilationError
{
ErrorId = "EXCEPTION",
Message = ex.Message,
LineNumber = 0,
ColumnNumber = 0,
IsError = true
});
}
return await Task.FromResult(result);
}
}
@@ -0,0 +1,82 @@
using CodePlay.Core.Interfaces;
using CodePlay.Core.Models;
using CodePlay.Core.Common;
namespace CodePlay.Core.Validators;
/// <summary>
/// 验证流水线服务
/// </summary>
public class ValidationPipeline
{
private readonly CSharpCompilerValidator _validator;
private readonly AutoFixEngine _fixEngine;
public ValidationPipeline()
{
_validator = new CSharpCompilerValidator();
_fixEngine = new AutoFixEngine();
}
/// <summary>
/// 执行验证流水线
/// </summary>
public async Task<ValidationSummary> ValidateAsync(
string code,
LanguageType language,
int maxRounds = 3,
CancellationToken cancellationToken = default)
{
var summary = new ValidationSummary
{
Passed = false,
RoundsExecuted = 0,
NeedsManualReview = false,
CompilationErrors = new List<CompilationError>(),
ValidationLog = new List<string>()
};
summary.ValidationLog.Add($"Starting validation at {DateTime.UtcNow:HH:mm:ss.fff}");
for (int round = 1; round <= maxRounds; round++)
{
summary.RoundsExecuted = round;
summary.ValidationLog.Add($"Round {round} starting");
// 编译验证
var compileResult = await _validator.ValidateAsync(code, cancellationToken);
if (compileResult.Success)
{
summary.Passed = true;
summary.ValidationLog.Add($"Round {round}: Compilation succeeded");
break;
}
summary.CompilationErrors = compileResult.Errors;
summary.ValidationLog.Add($"Round {round}: Compilation failed with {compileResult.Errors.Count} errors");
// 尝试自动修复
var fixResult = await _fixEngine.FixAsync(code, compileResult.Errors, round, cancellationToken);
if (fixResult.CanFix)
{
code = fixResult.FixedCode!;
summary.ValidationLog.Add($"Round {round}: Auto-fix applied - {fixResult.FixDescription}");
}
else
{
summary.NeedsManualReview = true;
summary.ValidationLog.Add($"Round {round}: Cannot auto-fix, needs manual review");
break;
}
}
if (!summary.Passed)
{
summary.ValidationLog.Add($"Validation completed - Needs manual review");
}
return summary;
}
}