feat: CodePlay 第二阶段优化 - 转换质量与特性完善
核心修复: - 修复 LinqToStreamConverter 13 个正则双反斜杠转义错误 (87→0 失败) - 修复 InheritanceConverter 接口判断逻辑 (纯 I 前缀父类→implements) - 修复 PropertyConverter init-only 属性组索引 新增转换器 (C# 8-13 特性): - NullCoalescingConverter: ??、?.、??= 运算符转换 - SwitchExpressionConverter: switch 表达式→if-else 链 - PrimaryConstructorConverter: 主构造函数→传统构造函数 增强: - LinqToStreamConverter 新增 FirstOrDefault(predicate)、OrderByDescending、TakeWhile、SkipWhile、Reverse 等 - AutoFixEngine 3 轮自动修复: 轮1 导入、轮2 类型映射、轮3 API 调用/语法错误 - NamingConverter: PascalCase→camelCase 命名转换 - DetectUnconvertibleSyntax: LINQ/async/record/init/var/switch/primary ctor 问题记录 - XML Doc→JavaDoc 格式转换与注释保留 新增测试: - CSharpToJavaEdgeCaseTests: 16 个边界测试 - CSharpToJavaSemanticEquivalenceTests: 15 个语义等价性测试 - 从 164 增加到 179 总测试 (168 通过, 0 失败) 新增文件: - Pipeline/Converters/NullCoalescingConverter.cs - Pipeline/Converters/SwitchExpressionConverter.cs - Pipeline/Converters/PrimaryConstructorConverter.cs - Converters/CSharpToCppStrategy.cs + CppCodeGenerator.cs - Tests/Semantics/CSharpToJavaSemanticEquivalenceTests.cs - Tests/CSharpAdvancedFeaturesTests.cs + CSharp13FeatureTests.cs Co-authored-by: monkeycode-ai <monkeycode-ai@chaitin.com>
This commit is contained in:
@@ -1,84 +1,106 @@
|
||||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using CodePlay.Core.Interfaces;
|
||||
using CodePlay.Core.Models;
|
||||
using CodePlay.Core.Common;
|
||||
using CodePlay.Core.Pipeline;
|
||||
using CodePlay.Core.Pipeline.Converters;
|
||||
|
||||
namespace CodePlay.Core.Converters;
|
||||
|
||||
/// <summary>
|
||||
/// C# 到 Java 转换策略
|
||||
/// 命名风格转换器 - C# PascalCase → Java camelCase
|
||||
/// </summary>
|
||||
public class NamingConverter
|
||||
{
|
||||
/// <summary>
|
||||
/// 将 C# 命名转换为 Java 风格
|
||||
/// PascalCase → camelCase (方法和变量名)
|
||||
/// PascalCase → PascalCase (类名保持不变)
|
||||
/// </summary>
|
||||
public string ConvertMethodName(string name)
|
||||
{
|
||||
if (string.IsNullOrEmpty(name) || char.IsLower(name[0]))
|
||||
return name;
|
||||
return char.ToLowerInvariant(name[0]) + name.Substring(1);
|
||||
}
|
||||
|
||||
public string ConvertFieldName(string name)
|
||||
{
|
||||
if (string.IsNullOrEmpty(name) || char.IsLower(name[0]))
|
||||
return name;
|
||||
return char.ToLowerInvariant(name[0]) + name.Substring(1);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// C# 到 Java 转换策略 - 使用管道模式,每条转换规则独立
|
||||
/// </summary>
|
||||
public class CSharpToJavaStrategy : IConversionStrategy
|
||||
{
|
||||
/// <summary>
|
||||
/// 源语言
|
||||
/// </summary>
|
||||
public LanguageType SourceLanguage => LanguageType.CSharp;
|
||||
|
||||
/// <summary>
|
||||
/// 目标语言
|
||||
/// </summary>
|
||||
public LanguageType TargetLanguage => LanguageType.Java;
|
||||
|
||||
private readonly List<TypeMapping> _typeMappings = new();
|
||||
private readonly ConversionPipeline _pipeline;
|
||||
private readonly ITypeMapper _typeMapper;
|
||||
private readonly NamingConverter _namingConverter;
|
||||
|
||||
public CSharpToJavaStrategy()
|
||||
{
|
||||
InitializeTypeMappings();
|
||||
}
|
||||
|
||||
private void InitializeTypeMappings()
|
||||
{
|
||||
_typeMappings.AddRange(new[]
|
||||
{
|
||||
new TypeMapping("System.String", "java.lang.String"),
|
||||
new TypeMapping("System.Int32", "int"),
|
||||
new TypeMapping("System.Int64", "long"),
|
||||
new TypeMapping("System.Boolean", "boolean"),
|
||||
new TypeMapping("System.Double", "double"),
|
||||
new TypeMapping("System.Single", "float"),
|
||||
new TypeMapping("System.Object", "Object"),
|
||||
new TypeMapping("System.Collections.Generic.List", "java.util.ArrayList"),
|
||||
new TypeMapping("System.Collections.Generic.Dictionary", "java.util.HashMap"),
|
||||
new TypeMapping("System.Collections.Generic.IEnumerable", "java.util.stream.Stream"),
|
||||
new TypeMapping("System.Array", "java.util.Arrays"),
|
||||
new TypeMapping("System.Console", "System.out"),
|
||||
new TypeMapping("System.DateTime", "java.time.LocalDateTime"),
|
||||
new TypeMapping("System.TimeSpan", "java.time.Duration"),
|
||||
new TypeMapping("System.Exception", "Exception"),
|
||||
new TypeMapping("System.ArgumentException", "IllegalArgumentException"),
|
||||
new TypeMapping("System.InvalidOperationException", "IllegalStateException"),
|
||||
new TypeMapping("System.NullReferenceException", "NullPointerException"),
|
||||
new TypeMapping("System.Threading.Tasks.Task", "java.util.concurrent.CompletableFuture"),
|
||||
});
|
||||
_pipeline = CreatePipeline();
|
||||
_typeMapper = new CSharpJavaTypeMapper();
|
||||
_namingConverter = new NamingConverter();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 映射类型
|
||||
/// 创建转换管道 - 按优先级顺序注册所有转换器
|
||||
/// </summary>
|
||||
public string MapType(string sourceType)
|
||||
private ConversionPipeline CreatePipeline()
|
||||
{
|
||||
var mapping = _typeMappings.FirstOrDefault(m => sourceType.Contains(m.SourceType));
|
||||
return mapping?.TargetType ?? sourceType
|
||||
.Replace("var ", "Object ")
|
||||
.Replace("public ", "public ")
|
||||
.Replace("private ", "private ")
|
||||
.Replace("protected ", "protected ");
|
||||
var pipeline = new ConversionPipeline();
|
||||
|
||||
// 按优先级顺序注册转换器 (数值越小优先级越高)
|
||||
// 5-15: 结构级转换 (Record)
|
||||
pipeline.Register(new RecordConverter());
|
||||
|
||||
// 20-35: 主构造函数
|
||||
pipeline.Register(new PrimaryConstructorConverter());
|
||||
|
||||
// 10-30: 类型映射
|
||||
pipeline.Register(new NullableTypeConverter());
|
||||
pipeline.Register(new PrimitiveTypeConverter());
|
||||
pipeline.Register(new CollectionTypeConverter());
|
||||
|
||||
// 40-55: 结构处理 + switch 表达式
|
||||
pipeline.Register(new InheritanceConverter());
|
||||
pipeline.Register(new NullCoalescingConverter());
|
||||
pipeline.Register(new ModifierRemover());
|
||||
pipeline.Register(new SwitchExpressionConverter());
|
||||
|
||||
// 50-70: 语法转换
|
||||
pipeline.Register(new LambdaConverter());
|
||||
pipeline.Register(new PatternMatchingConverter());
|
||||
pipeline.Register(new PropertyConverter());
|
||||
|
||||
// 80-100: API 映射
|
||||
pipeline.Register(new LinqToStreamConverter());
|
||||
pipeline.Register(new AsyncConverter());
|
||||
pipeline.Register(new RangeIndexConverter());
|
||||
|
||||
// 110+: 输出处理
|
||||
pipeline.Register(new ConsoleConverter());
|
||||
|
||||
return pipeline;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 转换语法节点
|
||||
/// </summary>
|
||||
public Interfaces.SyntaxNode ConvertNode(Interfaces.SyntaxNode node, ConversionContext context)
|
||||
public SyntaxNode ConvertNode(SyntaxNode node, ConversionContext context)
|
||||
{
|
||||
var newNode = new Interfaces.SyntaxNode
|
||||
var newNode = new SyntaxNode
|
||||
{
|
||||
Type = node.Type,
|
||||
Text = ConvertText(node.Text, context),
|
||||
Metadata = new Dictionary<string, object?>(node.Metadata),
|
||||
Parent = node.Parent,
|
||||
Children = new List<Interfaces.SyntaxNode>(),
|
||||
IsUnconvertible = node.IsUnconvertible,
|
||||
TodoDescription = node.TodoDescription
|
||||
Text = ConvertText(node.Text ?? "", context),
|
||||
Children = new List<SyntaxNode>()
|
||||
};
|
||||
|
||||
foreach (var child in node.Children)
|
||||
@@ -91,59 +113,276 @@ public class CSharpToJavaStrategy : IConversionStrategy
|
||||
return newNode;
|
||||
}
|
||||
|
||||
public string MapType(string sourceType)
|
||||
{
|
||||
return _typeMapper.MapType(sourceType);
|
||||
}
|
||||
|
||||
private string ConvertText(string text, ConversionContext context)
|
||||
{
|
||||
var result = text;
|
||||
if (string.IsNullOrWhiteSpace(text)) return text;
|
||||
|
||||
// 命名空间转 package
|
||||
if (result.StartsWith("namespace "))
|
||||
var sw = Stopwatch.StartNew();
|
||||
|
||||
var packageName = "";
|
||||
var imports = new HashSet<string>();
|
||||
var codeLines = new List<string>();
|
||||
var docStrings = new List<(int LineIndex, string Content)>();
|
||||
|
||||
var lines = text.Split('\n').ToList();
|
||||
|
||||
for (int i = 0; i < lines.Count; i++)
|
||||
{
|
||||
result = result.Replace("namespace ", "package ")
|
||||
.Replace("{", ";");
|
||||
var line = lines[i];
|
||||
var processedLine = line.Trim();
|
||||
if (string.IsNullOrWhiteSpace(processedLine)) continue;
|
||||
|
||||
// XML 文档注释 (/// <summary> ... </summary>)
|
||||
if (processedLine.StartsWith("///"))
|
||||
{
|
||||
if (context.Options?.KeepDocStrings == true)
|
||||
{
|
||||
docStrings.Add((codeLines.Count, processedLine));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// 单行注释 (//)
|
||||
if (processedLine.StartsWith("//"))
|
||||
{
|
||||
if (context.Options?.KeepComments == true)
|
||||
{
|
||||
codeLines.Add(processedLine);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// 多行注释 (/* ... */)
|
||||
if (processedLine.StartsWith("/*") || processedLine.StartsWith("*") || processedLine.StartsWith("*/"))
|
||||
{
|
||||
if (context.Options?.KeepComments == true)
|
||||
{
|
||||
codeLines.Add(processedLine);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// File-scoped namespace
|
||||
if (processedLine.StartsWith("namespace ") && !processedLine.Contains("{"))
|
||||
{
|
||||
var nsMatch = Regex.Match(processedLine, @"namespace\s+([\w.]+)");
|
||||
if (nsMatch.Success) packageName = nsMatch.Groups[1].Value;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Braced namespace
|
||||
if (processedLine.StartsWith("namespace ") && processedLine.Contains("{"))
|
||||
{
|
||||
var nsMatch = Regex.Match(processedLine, @"namespace\s+([\w.]+)");
|
||||
if (nsMatch.Success) packageName = nsMatch.Groups[1].Value;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip namespace block braces
|
||||
if (processedLine == "{" || processedLine == "}") continue;
|
||||
|
||||
// Using statements
|
||||
if (processedLine.StartsWith("using "))
|
||||
{
|
||||
var usingMatch = Regex.Match(processedLine, @"using\s+([\w.]+);");
|
||||
if (usingMatch.Success) imports.Add($"import {usingMatch.Groups[1].Value};");
|
||||
continue;
|
||||
}
|
||||
|
||||
// 检测不可转换语法并记录 issue
|
||||
DetectUnconvertibleSyntax(processedLine, context);
|
||||
|
||||
// 应用转换管道
|
||||
var convertedLine = _pipeline.Execute(processedLine, context);
|
||||
|
||||
// 在转换后的代码前插入文档注释
|
||||
if (docStrings.Count > 0 && docStrings[0].LineIndex == codeLines.Count)
|
||||
{
|
||||
var pendingDocs = docStrings.Where(d => d.LineIndex == codeLines.Count).ToList();
|
||||
foreach (var doc in pendingDocs)
|
||||
{
|
||||
// 将 XML Doc 转换为 JavaDoc 格式
|
||||
var javadoc = ConvertXmlDocToJavadoc(doc.Content);
|
||||
codeLines.Add(javadoc);
|
||||
}
|
||||
docStrings.RemoveAll(d => d.LineIndex == codeLines.Count - pendingDocs.Count);
|
||||
}
|
||||
|
||||
codeLines.Add(convertedLine);
|
||||
}
|
||||
|
||||
// using 转 import
|
||||
if (result.StartsWith("using "))
|
||||
sw.Stop();
|
||||
|
||||
// 生成输出
|
||||
var output = new StringBuilder();
|
||||
if (!string.IsNullOrEmpty(packageName)) output.AppendLine($"package {packageName};");
|
||||
foreach (var imp in imports.OrderBy(i => i)) output.AppendLine(imp);
|
||||
if (imports.Count > 0 || !string.IsNullOrEmpty(packageName)) output.AppendLine();
|
||||
foreach (var line in codeLines)
|
||||
{
|
||||
result = result.Replace("using ", "import ")
|
||||
.Replace(";", ";");
|
||||
if (!string.IsNullOrWhiteSpace(line)) output.AppendLine(line);
|
||||
}
|
||||
|
||||
// 类型映射
|
||||
foreach (var mapping in _typeMappings)
|
||||
return output.ToString().TrimEnd();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将 C# XML 文档注释转换为 JavaDoc 格式
|
||||
/// </summary>
|
||||
private string ConvertXmlDocToJavadoc(string xmlDocLine)
|
||||
{
|
||||
return xmlDocLine
|
||||
.Replace("///", " *")
|
||||
.Replace("<summary>", "")
|
||||
.Replace("</summary>", "")
|
||||
.Replace("<param name=", "@param ")
|
||||
.Replace("<returns>", "@return")
|
||||
.Replace("</returns>", "")
|
||||
.Replace("<exception cref=", "@throws ")
|
||||
.Replace("</exception>", "")
|
||||
.Trim();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检测不可直接转换的 C# 语法
|
||||
/// </summary>
|
||||
private static void DetectUnconvertibleSyntax(string line, ConversionContext context)
|
||||
{
|
||||
// 检测 LINQ 链式调用 - 需要转换为 Stream API
|
||||
if (Regex.IsMatch(line, @"\.where\s*\(.*\)\s*\.select\s*\(", RegexOptions.IgnoreCase) ||
|
||||
Regex.IsMatch(line, @"\.orderby\s*\(") ||
|
||||
Regex.IsMatch(line, @"\.groupby\s*\("))
|
||||
{
|
||||
result = result.Replace(mapping.SourceType, mapping.TargetType);
|
||||
context.Issues.Add(new ConversionIssue
|
||||
{
|
||||
Type = "UnconvertibleSyntax",
|
||||
Severity = "Low",
|
||||
Description = "LINQ method chain detected - auto-converted to Stream API",
|
||||
Suggestion = "Verify Stream API equivalence manually",
|
||||
SourceSyntax = line.Trim()
|
||||
});
|
||||
}
|
||||
|
||||
// 检测 async/await - 需要转换为 CompletableFuture 或回调
|
||||
if (Regex.IsMatch(line, @"\basync\b|\bawait\b"))
|
||||
{
|
||||
context.Issues.Add(new ConversionIssue
|
||||
{
|
||||
Type = "UnconvertibleSyntax",
|
||||
Severity = "Medium",
|
||||
Description = "async/await pattern detected, converted to synchronous call",
|
||||
Suggestion = "Consider using CompletableFuture or Executor for async operations",
|
||||
SourceSyntax = line.Trim()
|
||||
});
|
||||
}
|
||||
|
||||
// 检测 record - 需确认转换正确性
|
||||
if (Regex.IsMatch(line, @"\brecord\s+\w+"))
|
||||
{
|
||||
context.Issues.Add(new ConversionIssue
|
||||
{
|
||||
Type = "UnconvertibleSyntax",
|
||||
Severity = "Low",
|
||||
Description = "Record type converted to class",
|
||||
Suggestion = "Consider adding @Value annotation for immutability (Lombok)",
|
||||
SourceSyntax = line.Trim()
|
||||
});
|
||||
}
|
||||
|
||||
// 检测 init-only 属性 - 语义丢失警告
|
||||
if (Regex.IsMatch(line, @"get;\s*init;"))
|
||||
{
|
||||
context.Issues.Add(new ConversionIssue
|
||||
{
|
||||
Type = "UnconvertibleSyntax",
|
||||
Severity = "Low",
|
||||
Description = "Init-only property: immutability is lost in Java setter",
|
||||
Suggestion = "Remove the setter or mark the field as @ReadOnly",
|
||||
SourceSyntax = line.Trim()
|
||||
});
|
||||
}
|
||||
|
||||
// 检测 var 隐式类型 - 警告
|
||||
if (Regex.IsMatch(line, @"\bvar\s+\w+\s*="))
|
||||
{
|
||||
context.Issues.Add(new ConversionIssue
|
||||
{
|
||||
Type = "UnconvertibleSyntax",
|
||||
Severity = "Low",
|
||||
Description = "Implicit type 'var' mapped to Object",
|
||||
Suggestion = "Use explicit type declaration",
|
||||
SourceSyntax = line.Trim()
|
||||
});
|
||||
}
|
||||
|
||||
// 检测 switch 表达式 - 需验证转换正确性
|
||||
if (Regex.IsMatch(line, @"\bswitch\s*{"))
|
||||
{
|
||||
context.Issues.Add(new ConversionIssue
|
||||
{
|
||||
Type = "UnconvertibleSyntax",
|
||||
Severity = "Low",
|
||||
Description = "Switch expression converted to if-else chain",
|
||||
Suggestion = "Verify logic equivalence manually",
|
||||
SourceSyntax = line.Trim()
|
||||
});
|
||||
}
|
||||
|
||||
// 检测主构造函数 - 需验证
|
||||
if (Regex.IsMatch(line, @"class\s+\w+\s*\(\s*\w+\s+\w+"))
|
||||
{
|
||||
context.Issues.Add(new ConversionIssue
|
||||
{
|
||||
Type = "UnconvertibleSyntax",
|
||||
Severity = "Low",
|
||||
Description = "Primary constructor converted to traditional constructor",
|
||||
Suggestion = "Verify field assignments in generated constructor",
|
||||
SourceSyntax = line.Trim()
|
||||
});
|
||||
}
|
||||
|
||||
// C# 特定语法处理
|
||||
result = result.Replace("base.", "super.")
|
||||
.Replace("this.", "this.")
|
||||
.Replace("null", "null")
|
||||
.Replace("true", "true")
|
||||
.Replace("false", "false");
|
||||
|
||||
// 属性转方法
|
||||
result = System.Text.RegularExpressions.Regex.Replace(
|
||||
result,
|
||||
@"public\s+(\w+)\s+(\w+)\s*\{\s*get;\s*set;\s*\}",
|
||||
"private $1 $2;\n public $1 get$2() { return $2; }\n public void set$2($1 value) { this.$2 = value; }"
|
||||
);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 类型映射
|
||||
/// C# 到 Java 类型映射器
|
||||
/// </summary>
|
||||
public class TypeMapping
|
||||
public class CSharpJavaTypeMapper : ITypeMapper
|
||||
{
|
||||
public string SourceType { get; set; }
|
||||
public string TargetType { get; set; }
|
||||
|
||||
public TypeMapping(string source, string target)
|
||||
private readonly Dictionary<string, string> _mappings = new()
|
||||
{
|
||||
SourceType = source;
|
||||
TargetType = target;
|
||||
{ "string", "String" },
|
||||
{ "int", "Integer" },
|
||||
{ "long", "Long" },
|
||||
{ "float", "Float" },
|
||||
{ "double", "Double" },
|
||||
{ "bool", "Boolean" },
|
||||
{ "byte", "Byte" },
|
||||
{ "char", "Character" },
|
||||
{ "short", "Short" },
|
||||
{ "void", "void" },
|
||||
{ "var", "Object" },
|
||||
{ "object", "Object" },
|
||||
};
|
||||
|
||||
public string MapType(string sourceType)
|
||||
{
|
||||
return _mappings.TryGetValue(sourceType, out var target) ? target : sourceType;
|
||||
}
|
||||
|
||||
public string MapGenericType(string sourceType)
|
||||
{
|
||||
var match = Regex.Match(sourceType, @"(\w+)<(.+)>");
|
||||
if (match.Success)
|
||||
{
|
||||
var outer = MapType(match.Groups[1].Value);
|
||||
var inner = match.Groups[2].Value;
|
||||
return $"{outer}<{inner}>";
|
||||
}
|
||||
return MapType(sourceType);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user