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:
monkeycode-ai
2026-06-16 07:08:11 +00:00
parent f772973b68
commit db536cfb2c
70 changed files with 6999 additions and 2240 deletions
+321 -142
View File
@@ -1,97 +1,91 @@
using System.Text;
using System.Text.RegularExpressions;
using CodePlay.Core.Interfaces;
using CodePlay.Core.Models;
using CodePlay.Core.Common;
namespace CodePlay.Core.Converters;
/// <summary>
/// Java 到 C# 转换策略
/// </summary>
public class JavaToCSharpStrategy : IConversionStrategy
{
/// <summary>
/// 源语言
/// </summary>
public LanguageType SourceLanguage => LanguageType.Java;
/// <summary>
/// 目标语言
/// </summary>
public LanguageType TargetLanguage => LanguageType.CSharp;
private readonly List<TypeMapping> _typeMappings = new();
private readonly List<TypeMapping> _typeMappings;
public JavaToCSharpStrategy()
{
InitializeTypeMappings();
_typeMappings = InitializeTypeMappings();
}
private void InitializeTypeMappings()
private List<TypeMapping> InitializeTypeMappings()
{
_typeMappings.AddRange(new[]
return new List<TypeMapping>
{
new TypeMapping("java.lang.String", "string"),
new TypeMapping("java.lang.Object", "object"),
new TypeMapping("java.lang.Integer", "int"),
new TypeMapping("java.lang.Long", "long"),
new TypeMapping("java.lang.Boolean", "bool"),
new TypeMapping("java.lang.Double", "double"),
new TypeMapping("java.lang.Float", "float"),
new TypeMapping("java.util.ArrayList", "List"),
new TypeMapping("java.util.List", "IEnumerable"),
new TypeMapping("java.util.HashMap", "Dictionary"),
new TypeMapping("java.util.Map", "IDictionary"),
new TypeMapping("java.util.stream.Stream", "IEnumerable"),
new TypeMapping("java.util.Arrays", "Array"),
new TypeMapping("java.time.LocalDateTime", "DateTime"),
new TypeMapping("java.time.Duration", "TimeSpan"),
new TypeMapping("java.lang.Exception", "Exception"),
new TypeMapping("java.lang.IllegalArgumentException", "ArgumentException"),
new TypeMapping("java.lang.IllegalStateException", "InvalidOperationException"),
new TypeMapping("java.lang.NullPointerException", "NullReferenceException"),
new TypeMapping("java.util.concurrent.CompletableFuture", "Task"),
new TypeMapping("System.out.println", "Console.WriteLine"),
new TypeMapping("public static void main", "static void Main"),
});
// 基本类型
new("String", "string"),
new("java.lang.String", "string"),
new("Integer", "int"),
new("Long", "long"),
new("Float", "float"),
new("Double", "double"),
new("Boolean", "bool"),
new("Byte", "byte"),
new("Character", "char"),
new("Short", "short"),
new("Void", "void"),
// 集合类型
new("ArrayList<", "List<"),
new("LinkedList<", "LinkedList<"),
new("HashSet<", "HashSet<"),
new("TreeSet<", "SortedSet<"),
new("HashMap<", "Dictionary<"),
new("TreeMap<", "SortedDictionary<"),
new("ConcurrentHashMap<", "ConcurrentDictionary<"),
new("List<", "IList<"),
new("Map<", "IDictionary<"),
new("Set<", "ISet<"),
// 任务/异步
new("CompletableFuture<", "Task<"),
new("CompletableFuture<Void>", "Task"),
new("CompletableFuture", "Task"),
// 时间类型
new("LocalDateTime", "DateTime"),
new("LocalDate", "DateOnly"),
new("LocalTime", "TimeOnly"),
new("Duration", "TimeSpan"),
new("Period", "TimeSpan"),
new("Instant", "DateTime"),
new("ZoneId", "TimeZoneInfo"),
// 异常类型
new("IllegalArgumentException", "ArgumentException"),
new("IllegalStateException", "InvalidOperationException"),
new("NullPointerException", "ArgumentNullException"),
new("IOException", "IOException"),
new("RuntimeException", "Exception"),
new("Exception", "Exception"),
new("Throwable", "Exception"),
// 其他
new("StringBuilder", "StringBuilder"),
new("Object", "object"),
new("Class<", "Type"),
};
}
/// <summary>
/// 映射类型
/// </summary>
public string MapType(string sourceType)
public SyntaxNode ConvertNode(SyntaxNode node, ConversionContext context)
{
var result = sourceType;
foreach (var mapping in _typeMappings)
{
result = result.Replace(mapping.SourceType, mapping.TargetType);
}
// Java 到 C# 的特定转换
result = result
.Replace("var ", "var ")
.Replace("final ", "")
.Replace(".size()", ".Count")
.Replace(".length", ".Length")
.Replace(".equals(", ".Equals(")
.Replace(".toString()", ".ToString()")
.Replace(".hashCode()", ".GetHashCode()");
return result;
}
/// <summary>
/// 转换语法节点
/// </summary>
public Interfaces.SyntaxNode ConvertNode(Interfaces.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),
Text = ConvertText(node.Text ?? "", context),
Metadata = new Dictionary<string, object?>(node.Metadata ?? new Dictionary<string, object?>()),
Parent = node.Parent,
Children = new List<Interfaces.SyntaxNode>(),
Children = new List<SyntaxNode>(),
IsUnconvertible = node.IsUnconvertible,
TodoDescription = node.TodoDescription
};
@@ -103,109 +97,294 @@ public class JavaToCSharpStrategy : IConversionStrategy
newNode.Children.Add(convertedChild);
}
// 检测不可转换的语法
CheckUnconvertibleSyntax(node.Text, context);
return newNode;
}
public string MapType(string sourceType)
{
var result = sourceType;
foreach (var mapping in _typeMappings)
{
if (result.Contains(mapping.SourceType))
{
result = result.Replace(mapping.SourceType, mapping.TargetType);
}
}
return result;
}
private string ConvertText(string text, ConversionContext context)
{
if (string.IsNullOrWhiteSpace(text)) return text;
var result = text;
// package namespace
if (result.StartsWith("package "))
// 1. package -> namespace
result = Regex.Replace(result,
@"^package\s+([\w.]+)\s*;",
m => $"namespace {m.Groups[1].Value.Replace('_', '.')}");
// 2. import -> using
result = Regex.Replace(result,
@"^import\s+([\w.]+)\s*;",
m => $"using {m.Groups[1].Value};");
// 移除 Java 特有的静态导入
result = Regex.Replace(result,
@"^import\s+static\s+[\w.]+\s*;[\r\n]*",
"");
// 3. 添加常用的 using 语句
if (result.Contains("List<") || result.Contains("ArrayList"))
{
result = result.Replace("package ", "namespace ")
.Replace(";", " {");
if (!result.Contains("using System.Collections.Generic;"))
{
var insertIdx = result.IndexOf("using ");
if (insertIdx >= 0)
{
var endOfLine = result.IndexOf('\n', insertIdx);
result = result.Insert(endOfLine + 1, "using System.Collections.Generic;\n");
}
}
}
// import 转 using
if (result.StartsWith("import "))
// 4. 类型映射
foreach (var mapping in _typeMappings)
{
result = result.Replace("import ", "using ")
.Replace(";", ";");
result = Regex.Replace(result,
$@"\b{Regex.Escape(mapping.SourceType)}(?=<|\b)",
mapping.TargetType);
}
// 类型映射
result = MapType(result);
// 5. extends -> : (类继承)
result = Regex.Replace(result,
@"(class\s+\w+)\s+extends\s+(\w+)",
"$1 : $2");
// Java 特定语法处理
result = result
.Replace("super.", "base.")
.Replace("System.out.println", "Console.WriteLine")
.Replace("@Override", "[Override]")
.Replace("extends", ":")
.Replace("implements", ":");
// 6. implements -> : (接口实现)
result = Regex.Replace(result,
@"(class\s+\w+\s*(?::\s*\w+)?)\s+implements\s+",
"$1, ");
// 方法声明转换
result = System.Text.RegularExpressions.Regex.Replace(
result,
@"public\s+(\w+)\s+get(\w+)\(\)",
"public $1 Get$2 { get; }"
);
// 7. 移除 Java 注解
result = Regex.Replace(result,
@"@\w+(?:\([^)]*\))?\s*",
"");
result = System.Text.RegularExpressions.Regex.Replace(
result,
@"public\s+void\s+set(\w+)\(\1\s+\w+\)",
"public void Set$1 { set; }"
);
// 8. static import 处理
result = Regex.Replace(result,
@"import\s+static\s+([\w.]+)\s*;",
"// TODO: Convert static import: using static $1;");
// 9. System.out.println -> Console.WriteLine
result = Regex.Replace(result,
@"System\.out\.println\s*\(",
"Console.WriteLine(");
// 10. System.out.print -> Console.Write
result = Regex.Replace(result,
@"System\.out\.print\s*\(",
"Console.Write(");
// 11. super -> base
result = Regex.Replace(result,
@"\bsuper\b",
"base");
// 12. this -> this (保持不变)
// result = Regex.Replace(result, @"\bthis\b", "this");
// 13. null, true, false (Java 和 C# 相同,但确保小写)
result = result.Replace("null", "null")
.Replace("true", "true")
.Replace("false", "false");
// 14. getter/setter -> C# 属性
result = ConvertGettersSetters(result);
// 15. Lambda 表达式:(a, b) -> expr => (a, b) => expr
result = Regex.Replace(result,
@"(\w+)\s*->\s*",
"$1 => ");
result = Regex.Replace(result,
@"\(([\w,\s]+)\)\s*->\s*",
"($1) => ");
// 16. Stream API -> LINQ
result = ConvertStreamToLinq(result);
// 17. CompletableFuture -> Task
result = Regex.Replace(result,
@"CompletableFuture\.completedFuture\(",
"Task.FromResult(");
result = Regex.Replace(result,
@"CompletableFuture\.supplyAsync\(",
"Task.Run(");
result = Regex.Replace(result,
@"\.thenApply\(",
".ContinueWith(t => ");
result = Regex.Replace(result,
@"\.thenCompose\(",
".ContinueWith(");
result = Regex.Replace(result,
@"\.whenComplete\(",
".ContinueWith(");
// 18. throws -> 移除 (C# 不强制声明异常)
result = Regex.Replace(result,
@"\s*throws\s+\w+(?:\s*,\s*\w+)*",
"");
// 19. @Override -> [Obsolete] 或移除
result = Regex.Replace(result,
@"@Override\s*",
"// [Obsolete]\n");
// 20. final -> 不移除 (C# 没有等价物,但可作为注释保留)
result = Regex.Replace(result,
@"\bfinal\s+",
"// final\n");
// 21. 泛型通配符处理
result = Regex.Replace(result,
@"List<\? extends (\w+)>",
"IEnumerable<$1>");
result = Regex.Replace(result,
@"List<\? super (\w+)>",
"IList<$1>");
result = Regex.Replace(result,
@"List<\?>",
"IEnumerable");
// 22. 方法引用 -> Lambda
result = Regex.Replace(result,
@"(\w+)::(\w+)",
"x => x.$2");
return result;
}
private void CheckUnconvertibleSyntax(string text, ConversionContext context)
private string ConvertGettersSetters(string code)
{
// 检测 Stream API
if (text.Contains(".stream(") || text.Contains("Stream."))
// 处理 getter: public Type getName() { return name; }
code = Regex.Replace(code,
@"(public|private|protected)\s+(\w+)\s+get(\w+)\s*\(\s*\)\s*\{\s*return\s+(\w+)\s*;\s*\}",
m => {
var access = m.Groups[1].Value;
var type = m.Groups[2].Value;
var propName = Capitalize(m.Groups[4].Value);
var fieldName = m.Groups[4].Value;
return $"{access} {type} {propName} {{ get => {fieldName}; }}";
});
// 处理 setter: public void setName(Type name) { this.name = name; }
var setterPattern = @"(public|private|protected)\s+void\s+set(\w+)\s*\(\s*(\w+)\s+(\w+)\s*\)\s*\{\s*this\.(\w+)\s*=\s*(\w+)\s*;\s*\}";
var setters = Regex.Matches(code, setterPattern);
foreach (Match setter in setters)
{
context.Issues.Add(new ConversionIssue
{
Type = IssueType.UnconvertibleSyntax,
Description = "Java Stream API 需要转换为 LINQ",
OriginalCode = text,
Suggestion = "使用 LINQ 替代:stream().filter() → .Where(), stream().map() → .Select()"
});
var access = setter.Groups[1].Value;
var propName = Capitalize(setter.Groups[2].Value);
var type = setter.Groups[3].Value;
var paramName = setter.Groups[4].Value;
var fieldName = setter.Groups[5].Value;
context.TodoItems.Add(new TodoItem
// 查找对应的 getter 并组合成完整属性
var getterPattern = $@"({access})\s+{type}\s+{propName}\s*\{{\s*get\s*=>\s*{fieldName}\s*;\s*\}}";
var getterMatch = Regex.Match(code, getterPattern);
if (getterMatch.Success)
{
Description = "将 Stream API 转换为 LINQ",
OriginalSyntax = "Stream API",
WhyNotDirect = "Java Stream 和 LINQ 语法不同,需要手动调整",
RecommendedAlternative = "使用 .Where(), .Select(), .Aggregate() 等 LINQ 方法"
});
// 替换为完整属性
code = Regex.Replace(code, getterPattern, $"{access} {type} {propName} {{ get; set; }}");
// 移除 setter
code = Regex.Replace(code, setterPattern, "");
}
}
// 检测 CompletableFuture
if (text.Contains("CompletableFuture") || text.Contains("thenApply") || text.Contains("thenAccept"))
return code;
}
private string ConvertStreamToLinq(string code)
{
// Stream 方法映射
var mappings = new (string Java, string CSharp)[]
{
context.Issues.Add(new ConversionIssue
{
Type = IssueType.UnconvertibleSyntax,
Description = "CompletableFuture 需要转换为 async/await",
OriginalCode = text,
Suggestion = "使用 async/await 模式:completableFuture.thenApply() → await Task"
});
context.TodoItems.Add(new TodoItem
{
Description = "将 CompletableFuture 转换为 async/await",
OriginalSyntax = "CompletableFuture",
WhyNotDirect = "Java CompletableFuture 和 C# async/await 模式不同",
RecommendedAlternative = "使用 Task<T> 和 async/await 关键字"
});
(".stream()", ""), // C# 直接使用 IEnumerable
(".filter(", ".Where("),
(".map(", ".Select("),
(".flatMap(", ".SelectMany("),
(".anyMatch(", ".Any("),
(".allMatch(", ".All("),
(".noneMatch(", "!.Any("),
(".count()", ".Count()"),
(".sum()", ".Sum()"),
(".average()", ".Average()"),
(".max(", ".Max("),
(".min(", ".Min("),
(".findFirst().orElse(null)", ".FirstOrDefault()"),
(".findFirst().orElseThrow()", ".First()"),
(".findAny()", ".FirstOrDefault()"),
(".collect(Collectors.toList())", ".ToList()"),
(".collect(Collectors.toSet())", ".ToHashSet()"),
(".collect(Collectors.toMap(", ".ToDictionary("),
(".collect(Collectors.joining(", ".Aggregate("),
(".collect(Collectors.groupingBy(", ".GroupBy("),
(".collect(Collectors.partitioningBy(", ".GroupBy("),
(".skip(", ".Skip("),
(".limit(", ".Take("),
(".takeWhile(", ".TakeWhile("),
(".dropWhile(", ".SkipWhile("),
(".sorted(", ".OrderBy(x => x)"),
(".sorted(Comparator.", ".OrderBy("),
(".distinct(", ".Distinct("),
(".peek(", ".Select("), // C# 没有直接的 Peek
(".reduce(", ".Aggregate("),
(".forEach(", ".ToList().ForEach("),
(".toArray(", ".ToArray()"),
(".parallel()", ".AsParallel()"),
(".parallelStream()", ".AsParallel()"),
};
var result = code;
foreach (var (java, csharp) in mappings)
{
result = Regex.Replace(result, Regex.Escape(java), csharp);
}
// 检测接口默认方法
if (text.Contains("default ") && text.Contains(" interface "))
// 添加 LINQ using
if (result.Contains(".Where(") || result.Contains(".Select(") || result.Contains(".Any("))
{
context.Logs.Add(new TransformationLog
if (!result.Contains("using System.Linq;"))
{
Timestamp = DateTime.UtcNow,
Operation = "Warning",
Details = "Java 接口默认方法需要特殊处理",
Level = LogLevel.Warning
});
var insertIdx = result.IndexOf("using ");
if (insertIdx >= 0)
{
var endOfLine = result.IndexOf('\n', insertIdx);
result = result.Insert(endOfLine + 1, "using System.Linq;\n");
}
}
}
return result;
}
private string Capitalize(string s)
{
if (string.IsNullOrEmpty(s)) return s;
return char.ToUpperInvariant(s[0]) + s.Substring(1);
}
}
public class TypeMapping
{
public string SourceType { get; set; }
public string TargetType { get; set; }
public TypeMapping(string source, string target)
{
SourceType = source;
TargetType = target;
}
}