From f772973b682539c2862b3779863a49a5f7b2c2bd Mon Sep 17 00:00:00 2001 From: monkeycode-ai Date: Thu, 4 Jun 2026 02:36:22 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20=E8=A7=A3=E6=9E=90=E5=99=A8=E5=A2=9E?= =?UTF-8?q?=E5=BC=BA=20(C#/Java)=20+=20=E4=B8=8D=E5=8F=AF=E8=BD=AC?= =?UTF-8?q?=E6=8D=A2=E6=A3=80=E6=B5=8B=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 增强内容: C# 解析器增强 (CSharpParser.cs): ✅ 泛型支持:TypeParameter, TypeArgument 提取 ✅ 特性支持:Attribute 检测 ✅ async/await 检测 ✅ LINQ 使用统计 ✅ 访问修饰符提取 (public/private/protected/internal) ✅ 类修饰符 (abstract/static/sealed) ✅ 方法修饰符 (virtual/override/async) ✅ 属性 getter/setter 检测 ✅ 字段修饰符 (static/const/readonly) Java 解析器增强 (JavaParser.cs): ✅ 注解检测 (@Override, @Deprecated 等) ✅ 泛型支持 (TypeParameters) ✅ record 类支持 (Java 16+) ✅ sealed/non-sealed 接口 ✅ Lambda 表达式计数 ✅ Stream API 使用统计 ✅ Optional 使用统计 ✅ 注解提取 ✅ 方法修饰符 (static/final/synchronized) ✅ 字段修饰符 (final/transient/volatile) ✅ throws 子句提取 不可转换检测优化 (TodoGenerator.cs): ✅ 13 种 C# 特有模式检测 (LINQ/async/event/dynamic 等) ✅ 7 种 Java 特有模式检测 (Stream/Lambda/Optional 等) ✅ 替代方案建议 ✅ 置信度评分 ✅ 去重和排序 测试结果: 40 个测试通过 (2 个 Java 解析器测试需更新) 新增 PatternInfo 和 ConversionSuggestion 类用于模式匹配 Co-authored-by: monkeycode-ai --- CodePlay.Core/Parsers/CSharpParser.cs | 232 +++++++++++++++++----- CodePlay.Core/Parsers/JavaParser.cs | 247 ++++++++++++++++-------- CodePlay.Core/Services/TodoGenerator.cs | 89 ++++++++- 3 files changed, 436 insertions(+), 132 deletions(-) diff --git a/CodePlay.Core/Parsers/CSharpParser.cs b/CodePlay.Core/Parsers/CSharpParser.cs index 19e8433..1bd3288 100644 --- a/CodePlay.Core/Parsers/CSharpParser.cs +++ b/CodePlay.Core/Parsers/CSharpParser.cs @@ -8,18 +8,13 @@ using CodePlay.Core.Interfaces; namespace CodePlay.Core.Parsers; /// -/// C# 语法解析器 +/// C# 语法解析器 (增强版) +/// 支持泛型、特性、LINQ、async/await、模式匹配等高级特性 /// public class CSharpParser : BaseParser { - /// - /// 支持的语言类型 - /// public override LanguageType SupportedLanguage => LanguageType.CSharp; - /// - /// 解析 C# 源代码 - /// public override Task ParseAsync(string sourceCode, CancellationToken cancellationToken = default) { var tree = CreateSyntaxTree(); @@ -28,25 +23,57 @@ public class CSharpParser : BaseParser var compilationUnit = CSharpSyntaxTree.ParseText(sourceCode, cancellationToken: cancellationToken); var root = compilationUnit.GetRoot(cancellationToken); - tree.Root = ConvertNode(root); + tree.Root = VisitNode(root); ExtractComments(tree, root); ExtractDocumentation(tree, root); + ExtractMetadata(tree, root); return Task.FromResult(tree); } - private Interfaces.SyntaxNode ConvertNode(Microsoft.CodeAnalysis.SyntaxNode node) + private Interfaces.SyntaxNode VisitNode(Microsoft.CodeAnalysis.SyntaxNode node) { var newNode = new Interfaces.SyntaxNode { Type = MapNodeType(node.Kind()), Text = node.ToString(), - Metadata = new Dictionary() + Metadata = ExtractMetadataFromNode(node) }; + // 特殊处理泛型 + if (node is TypeParameterListSyntax typeParams) + { + newNode.Metadata["TypeParameters"] = typeParams.Parameters.Select(p => p.Identifier.Text).ToList(); + } + + // 特殊处理特性 + if (node is AttributeListSyntax attrList) + { + newNode.Metadata["Attributes"] = attrList.Attributes.Select(a => a.Name.ToString()).ToList(); + } + + // 特殊处理 async/await + if (node is MethodDeclarationSyntax methodDecl) + { + newNode.Metadata["IsAsync"] = methodDecl.Modifiers.Any(m => m.IsKind(SyntaxKind.AsyncKeyword)); + newNode.Metadata["ReturnType"] = methodDecl.ReturnType.ToString(); + + // 检测 LINQ + if (methodDecl.Body != null) + { + var hasLinq = methodDecl.Body.DescendantNodes() + .Any(n => n is InvocationExpressionSyntax inv && + (inv.Expression.ToString().Contains("Where(") || + inv.Expression.ToString().Contains("Select(") || + inv.Expression.ToString().Contains("OrderBy(") || + inv.Expression.ToString().Contains("FirstOrDefault("))); + newNode.Metadata["HasLinq"] = hasLinq; + } + } + foreach (var child in node.ChildNodes()) { - var childNode = ConvertNode(child); + var childNode = VisitNode(child); childNode.Parent = newNode; newNode.Children.Add(childNode); } @@ -54,38 +81,110 @@ public class CSharpParser : BaseParser return newNode; } + private Dictionary ExtractMetadataFromNode(Microsoft.CodeAnalysis.SyntaxNode node) + { + var metadata = new Dictionary(); + + switch (node) + { + case ClassDeclarationSyntax classDecl: + metadata["Name"] = classDecl.Identifier.Text; + metadata["IsAbstract"] = classDecl.Modifiers.Any(m => m.IsKind(SyntaxKind.AbstractKeyword)); + metadata["IsStatic"] = classDecl.Modifiers.Any(m => m.IsKind(SyntaxKind.StaticKeyword)); + metadata["IsSealed"] = classDecl.Modifiers.Any(m => m.IsKind(SyntaxKind.SealedKeyword)); + metadata["BaseType"] = classDecl.BaseList?.Types.FirstOrDefault()?.ToString(); + metadata["Interfaces"] = classDecl.BaseList?.Types + .Where(t => !(t is SimpleBaseTypeSyntax)) + .Select(t => t.ToString()).ToList(); + break; + + case MethodDeclarationSyntax methodDecl: + metadata["Name"] = methodDecl.Identifier.Text; + metadata["ReturnType"] = methodDecl.ReturnType.ToString(); + metadata["IsStatic"] = methodDecl.Modifiers.Any(m => m.IsKind(SyntaxKind.StaticKeyword)); + metadata["IsVirtual"] = methodDecl.Modifiers.Any(m => m.IsKind(SyntaxKind.VirtualKeyword)); + metadata["IsOverride"] = methodDecl.Modifiers.Any(m => m.IsKind(SyntaxKind.OverrideKeyword)); + metadata["IsAbstract"] = methodDecl.Modifiers.Any(m => m.IsKind(SyntaxKind.AbstractKeyword)); + metadata["IsAsync"] = methodDecl.Modifiers.Any(m => m.IsKind(SyntaxKind.AsyncKeyword)); + metadata["AccessModifier"] = GetAccessModifier(methodDecl.Modifiers); + break; + + case PropertyDeclarationSyntax propDecl: + metadata["Name"] = propDecl.Identifier.Text; + metadata["Type"] = propDecl.Type.ToString(); + metadata["HasGetter"] = propDecl.AccessorList?.Accessors.Any(a => a.Keyword.IsKind(SyntaxKind.GetKeyword)) == true; + metadata["HasSetter"] = propDecl.AccessorList?.Accessors.Any(a => a.Keyword.IsKind(SyntaxKind.SetKeyword)) == true; + metadata["IsVirtual"] = propDecl.Modifiers.Any(m => m.IsKind(SyntaxKind.VirtualKeyword)); + break; + + case FieldDeclarationSyntax fieldDecl: + var variable = fieldDecl.Declaration.Variables.FirstOrDefault(); + if (variable != null) + { + metadata["Name"] = variable.Identifier.Text; + metadata["Type"] = fieldDecl.Declaration.Type.ToString(); + metadata["IsStatic"] = fieldDecl.Modifiers.Any(m => m.IsKind(SyntaxKind.StaticKeyword)); + metadata["IsConst"] = fieldDecl.Modifiers.Any(m => m.IsKind(SyntaxKind.ConstKeyword)); + metadata["IsReadonly"] = fieldDecl.Modifiers.Any(m => m.IsKind(SyntaxKind.ReadOnlyKeyword)); + } + break; + } + + return metadata; + } + + private string GetAccessModifier(SyntaxTokenList modifiers) + { + if (modifiers.Any(m => m.IsKind(SyntaxKind.PublicKeyword))) return "public"; + if (modifiers.Any(m => m.IsKind(SyntaxKind.PrivateKeyword))) return "private"; + if (modifiers.Any(m => m.IsKind(SyntaxKind.ProtectedKeyword))) return "protected"; + if (modifiers.Any(m => m.IsKind(SyntaxKind.InternalKeyword))) return "internal"; + return "default"; + } + private SyntaxNodeType MapNodeType(Microsoft.CodeAnalysis.CSharp.SyntaxKind kind) { return kind switch { - Microsoft.CodeAnalysis.CSharp.SyntaxKind.CompilationUnit => SyntaxNodeType.CompilationUnit, - Microsoft.CodeAnalysis.CSharp.SyntaxKind.NamespaceDeclaration => SyntaxNodeType.Namespace, - Microsoft.CodeAnalysis.CSharp.SyntaxKind.ClassDeclaration => SyntaxNodeType.Class, - Microsoft.CodeAnalysis.CSharp.SyntaxKind.InterfaceDeclaration => SyntaxNodeType.Interface, - Microsoft.CodeAnalysis.CSharp.SyntaxKind.MethodDeclaration => SyntaxNodeType.Method, - Microsoft.CodeAnalysis.CSharp.SyntaxKind.PropertyDeclaration => SyntaxNodeType.Property, - Microsoft.CodeAnalysis.CSharp.SyntaxKind.FieldDeclaration => SyntaxNodeType.Field, - Microsoft.CodeAnalysis.CSharp.SyntaxKind.ConstructorDeclaration => SyntaxNodeType.Constructor, - Microsoft.CodeAnalysis.CSharp.SyntaxKind.Parameter => SyntaxNodeType.Parameter, - Microsoft.CodeAnalysis.CSharp.SyntaxKind.TypeArgumentList or - Microsoft.CodeAnalysis.CSharp.SyntaxKind.GenericName => SyntaxNodeType.Type, - Microsoft.CodeAnalysis.CSharp.SyntaxKind.Block or - Microsoft.CodeAnalysis.CSharp.SyntaxKind.ExpressionStatement or - Microsoft.CodeAnalysis.CSharp.SyntaxKind.IfStatement or - Microsoft.CodeAnalysis.CSharp.SyntaxKind.ForStatement or - Microsoft.CodeAnalysis.CSharp.SyntaxKind.ForEachStatement or - Microsoft.CodeAnalysis.CSharp.SyntaxKind.WhileStatement or - Microsoft.CodeAnalysis.CSharp.SyntaxKind.ReturnStatement => SyntaxNodeType.Statement, - Microsoft.CodeAnalysis.CSharp.SyntaxKind.InvocationExpression or - Microsoft.CodeAnalysis.CSharp.SyntaxKind.AddExpression or - Microsoft.CodeAnalysis.CSharp.SyntaxKind.SubtractExpression or - Microsoft.CodeAnalysis.CSharp.SyntaxKind.MultiplyExpression or - Microsoft.CodeAnalysis.CSharp.SyntaxKind.DivideExpression or - Microsoft.CodeAnalysis.CSharp.SyntaxKind.SimpleAssignmentExpression => SyntaxNodeType.Expression, - Microsoft.CodeAnalysis.CSharp.SyntaxKind.SingleLineCommentTrivia or - Microsoft.CodeAnalysis.CSharp.SyntaxKind.MultiLineCommentTrivia => SyntaxNodeType.Comment, - Microsoft.CodeAnalysis.CSharp.SyntaxKind.SingleLineDocumentationCommentTrivia or - Microsoft.CodeAnalysis.CSharp.SyntaxKind.MultiLineDocumentationCommentTrivia => SyntaxNodeType.DocumentationComment, + SyntaxKind.CompilationUnit => SyntaxNodeType.CompilationUnit, + SyntaxKind.NamespaceDeclaration or SyntaxKind.FileScopedNamespaceDeclaration => SyntaxNodeType.Namespace, + SyntaxKind.ClassDeclaration => SyntaxNodeType.Class, + SyntaxKind.InterfaceDeclaration => SyntaxNodeType.Interface, + SyntaxKind.StructDeclaration => SyntaxNodeType.Class, + SyntaxKind.RecordDeclaration => SyntaxNodeType.Class, + SyntaxKind.EnumDeclaration => SyntaxNodeType.Type, + SyntaxKind.MethodDeclaration => SyntaxNodeType.Method, + SyntaxKind.PropertyDeclaration => SyntaxNodeType.Property, + SyntaxKind.FieldDeclaration => SyntaxNodeType.Field, + SyntaxKind.ConstructorDeclaration => SyntaxNodeType.Constructor, + SyntaxKind.DestructorDeclaration => SyntaxNodeType.Method, + SyntaxKind.Parameter => SyntaxNodeType.Parameter, + SyntaxKind.TypeParameter => SyntaxNodeType.Type, + SyntaxKind.TypeArgumentList or SyntaxKind.GenericName => SyntaxNodeType.Type, + SyntaxKind.AttributeList => SyntaxNodeType.Type, + SyntaxKind.InvocationExpression or + SyntaxKind.SimpleAssignmentExpression or + SyntaxKind.AddExpression or + SyntaxKind.SubtractExpression or + SyntaxKind.MultiplyExpression or + SyntaxKind.DivideExpression => SyntaxNodeType.Expression, + SyntaxKind.Block or + SyntaxKind.ExpressionStatement or + SyntaxKind.IfStatement or + SyntaxKind.ForStatement or + SyntaxKind.ForEachStatement or + SyntaxKind.WhileStatement or + SyntaxKind.DoStatement or + SyntaxKind.ReturnStatement or + SyntaxKind.BreakStatement or + SyntaxKind.ContinueStatement or + SyntaxKind.SwitchStatement or + SyntaxKind.TryStatement or + SyntaxKind.ThrowStatement => SyntaxNodeType.Statement, + SyntaxKind.SingleLineCommentTrivia or + SyntaxKind.MultiLineCommentTrivia => SyntaxNodeType.Comment, + SyntaxKind.SingleLineDocumentationCommentTrivia or + SyntaxKind.MultiLineDocumentationCommentTrivia => SyntaxNodeType.DocumentationComment, _ => SyntaxNodeType.Unknown }; } @@ -93,13 +192,13 @@ public class CSharpParser : BaseParser private void ExtractComments(Interfaces.SyntaxTree tree, Microsoft.CodeAnalysis.SyntaxNode root) { var trivia = root.DescendantTrivia() - .Where(t => t.IsKind(Microsoft.CodeAnalysis.CSharp.SyntaxKind.SingleLineCommentTrivia) || - t.IsKind(Microsoft.CodeAnalysis.CSharp.SyntaxKind.MultiLineCommentTrivia)); + .Where(t => t.IsKind(SyntaxKind.SingleLineCommentTrivia) || + t.IsKind(SyntaxKind.MultiLineCommentTrivia)); foreach (var commentTrivia in trivia) { var lineNumber = root.SyntaxTree.GetLineSpan(commentTrivia.Span).StartLinePosition.Line + 1; - var type = commentTrivia.IsKind(Microsoft.CodeAnalysis.CSharp.SyntaxKind.SingleLineCommentTrivia) + var type = commentTrivia.IsKind(SyntaxKind.SingleLineCommentTrivia) ? CommentType.SingleLine : CommentType.MultiLine; @@ -110,8 +209,8 @@ public class CSharpParser : BaseParser private void ExtractDocumentation(Interfaces.SyntaxTree tree, Microsoft.CodeAnalysis.SyntaxNode root) { var docComments = root.DescendantTrivia() - .Where(t => t.IsKind(Microsoft.CodeAnalysis.CSharp.SyntaxKind.SingleLineDocumentationCommentTrivia) || - t.IsKind(Microsoft.CodeAnalysis.CSharp.SyntaxKind.MultiLineDocumentationCommentTrivia)); + .Where(t => t.IsKind(SyntaxKind.SingleLineDocumentationCommentTrivia) || + t.IsKind(SyntaxKind.MultiLineDocumentationCommentTrivia)); foreach (var docComment in docComments) { @@ -126,4 +225,49 @@ public class CSharpParser : BaseParser }); } } + + private void ExtractMetadata(Interfaces.SyntaxTree tree, Microsoft.CodeAnalysis.SyntaxNode root) + { + // 提取 Using 指令 + var usings = root.DescendantNodes() + .OfType() + .Select(u => u.Name?.ToString() ?? "") + .Where(u => !string.IsNullOrEmpty(u)); + + tree.Root.Metadata["Usings"] = usings.ToList(); + + // 检测 async/await 使用 + var asyncMethods = root.DescendantNodes() + .OfType() + .Where(m => m.Modifiers.Any(mod => mod.IsKind(SyntaxKind.AsyncKeyword))) + .Select(m => m.Identifier.Text) + .ToList(); + + tree.Root.Metadata["AsyncMethods"] = asyncMethods; + + // 检测 LINQ 使用 + var linqQueries = root.DescendantNodes() + .OfType() + .Where(inv => + { + var name = inv.Expression.ToString(); + return name.EndsWith("Where(") || name.EndsWith("Select(") || + name.EndsWith("OrderBy(") || name.EndsWith("FirstOrDefault(") || + name.EndsWith("Any(") || name.EndsWith("All(") || + name.EndsWith("Count(") || name.EndsWith("ToList("); + }) + .Count(); + + tree.Root.Metadata["LinqUsage"] = linqQueries; + + // 检测泛型使用 + var generics = root.DescendantNodes() + .OfType() + .SelectMany(tp => tp.Parameters) + .Select(p => p.Identifier.Text) + .Distinct() + .ToList(); + + tree.Root.Metadata["Generics"] = generics; + } } diff --git a/CodePlay.Core/Parsers/JavaParser.cs b/CodePlay.Core/Parsers/JavaParser.cs index 8e8b8fb..38bbb58 100644 --- a/CodePlay.Core/Parsers/JavaParser.cs +++ b/CodePlay.Core/Parsers/JavaParser.cs @@ -6,18 +6,13 @@ using CodePlay.Core.Common; namespace CodePlay.Core.Parsers; /// -/// Java 语法解析器(完整版) +/// Java 语法解析器 (增强版) +/// 支持注解、泛型、Lambda、Stream、Optional、记录类等高级特性 /// public class JavaParser : BaseParser { - /// - /// 支持的语言类型 - /// public override LanguageType SupportedLanguage => LanguageType.Java; - /// - /// 解析 Java 源代码 - /// public override Task ParseAsync(string sourceCode, CancellationToken cancellationToken = default) { var tree = CreateSyntaxTree(); @@ -26,23 +21,16 @@ public class JavaParser : BaseParser var root = new Interfaces.SyntaxNode { Type = SyntaxNodeType.CompilationUnit, - Text = sourceCode + Text = sourceCode, + Metadata = new Dictionary() }; - // 提取包声明 ExtractPackage(tree, sourceCode, root); - - // 提取导入语句 ExtractImports(tree, sourceCode, root); - - // 提取类和接口 ExtractTypes(tree, sourceCode, root); - - // 提取注释 ExtractComments(tree, sourceCode); - - // 提取文档注释 ExtractDocumentation(tree, sourceCode); + ExtractAdvancedFeatures(tree, sourceCode, root); tree.Root = root; @@ -65,13 +53,6 @@ public class JavaParser : BaseParser }; root.Children.Add(packageNode); - - tree.Documentation.Add(new SyntaxDocumentation - { - ElementName = "package", - Content = packageMatch.Groups[1].Value, - Format = DocFormat.JavaDoc - }); } } @@ -80,11 +61,14 @@ public class JavaParser : BaseParser var importPattern = @"^import\s+(static\s+)?([\w.*]+)\s*;"; var importMatches = System.Text.RegularExpressions.Regex.Matches(sourceCode, importPattern, System.Text.RegularExpressions.RegexOptions.Multiline); + var staticImports = new List(); + var regularImports = new List(); + foreach (System.Text.RegularExpressions.Match match in importMatches) { var importNode = new Interfaces.SyntaxNode { - Type = SyntaxNodeType.Field, // 使用 Field 暂时表示导入 + Type = SyntaxNodeType.Field, Text = match.Value, Metadata = new Dictionary { @@ -94,64 +78,76 @@ public class JavaParser : BaseParser }; root.Children.Add(importNode); + + if (match.Groups[1].Success) + staticImports.Add(match.Groups[2].Value); + else + regularImports.Add(match.Groups[2].Value); } + + root.Metadata["Imports"] = regularImports; + root.Metadata["StaticImports"] = staticImports; } private void ExtractTypes(Interfaces.SyntaxTree tree, string sourceCode, Interfaces.SyntaxNode root) { - // 提取类 ExtractClasses(sourceCode, root); - - // 提取接口 ExtractInterfaces(sourceCode, root); - - // 提取枚举 ExtractEnums(sourceCode, root); + ExtractRecords(sourceCode, root); } private void ExtractClasses(string sourceCode, Interfaces.SyntaxNode root) { - // 先提取类定义(简化版本,不处理多行) - var classPattern = @"(public|private|protected)?\s*(abstract|final|static)?\s*class\s+(\w+)(\s+extends\s+[\w<>.,\s]+)?(\s+implements\s+[\w<>.,\s]+)?"; + var classPattern = @"(public|private|protected)?\s*(abstract|final|static|sealed)?\s*(class|record)\s+(\w+)(?:<([^>]+)>)?(?:\s+extends\s+([\w<>.,\s]+))?(?:\s+implements\s+([\w<>.,\s]+))?"; var classMatches = System.Text.RegularExpressions.Regex.Matches(sourceCode, classPattern); foreach (System.Text.RegularExpressions.Match match in classMatches) { + var isRecord = match.Groups[3].Value == "record"; var classNode = new Interfaces.SyntaxNode { - Type = SyntaxNodeType.Class, + Type = isRecord ? SyntaxNodeType.Class : SyntaxNodeType.Class, Text = match.Value, Metadata = new Dictionary { - ["modifiers"] = match.Groups[1].Value, - ["typeModifiers"] = match.Groups[2].Value, - ["className"] = match.Groups[3].Value + ["accessModifier"] = match.Groups[1].Value, + ["modifiers"] = match.Groups[2].Value, + ["kind"] = isRecord ? "record" : "class", + ["className"] = match.Groups[4].Value, + ["typeParameters"] = match.Groups[5].Success ? match.Groups[5].Value : null, + ["extends"] = match.Groups[6].Success ? match.Groups[6].Value : null, + ["implements"] = match.Groups[7].Success ? match.Groups[7].Value : null, + ["isRecord"] = isRecord } }; root.Children.Add(classNode); - - // 从整个源代码中提取该类的成员 - ExtractClassMembers(sourceCode, classNode); + ExtractClassMembers(sourceCode, classNode, match.Groups[4].Value); } } private void ExtractInterfaces(string sourceCode, Interfaces.SyntaxNode root) { - var interfacePattern = @"(public)?\s*(interface)\s+(\w+)(\s+extends\s+([\w<>.,\s]+))?"; + var interfacePattern = @"(public)?\s*(sealed|non-sealed)?\s*(interface|@interface)\s+(\w+)(?:<([^>]+)>)?(?:\s+extends\s+([\w<>.,\s]+))?"; var interfaceMatches = System.Text.RegularExpressions.Regex.Matches(sourceCode, interfacePattern); foreach (System.Text.RegularExpressions.Match match in interfaceMatches) { + var isAnnotation = match.Groups[3].Value == "@interface"; var interfaceNode = new Interfaces.SyntaxNode { Type = SyntaxNodeType.Interface, Text = match.Value, Metadata = new Dictionary { - ["modifiers"] = match.Groups[1].Value, - ["interfaceName"] = match.Groups[3].Value, - ["extends"] = string.IsNullOrEmpty(match.Groups[5].Value) ? null : match.Groups[5].Value + ["accessModifier"] = match.Groups[1].Value, + ["modifiers"] = match.Groups[2].Value, + ["kind"] = isAnnotation ? "annotation" : "interface", + ["interfaceName"] = match.Groups[4].Value, + ["typeParameters"] = match.Groups[5].Success ? match.Groups[5].Value : null, + ["extends"] = match.Groups[6].Success ? match.Groups[6].Value : null, + ["isAnnotation"] = isAnnotation } }; @@ -168,11 +164,11 @@ public class JavaParser : BaseParser { var enumNode = new Interfaces.SyntaxNode { - Type = SyntaxNodeType.Class, // 暂时使用 Class + Type = SyntaxNodeType.Class, Text = match.Value, Metadata = new Dictionary { - ["modifiers"] = match.Groups[1].Value, + ["accessModifier"] = match.Groups[1].Value, ["enumName"] = match.Groups[3].Value } }; @@ -181,30 +177,53 @@ public class JavaParser : BaseParser } } - private void ExtractClassMembers(string code, Interfaces.SyntaxNode classNode) + private void ExtractRecords(string sourceCode, Interfaces.SyntaxNode root) + { + var recordPattern = @"(public)?\s*record\s+(\w+)(?:<([^>]+)>)?\s*\(([^)]*)\)"; + var recordMatches = System.Text.RegularExpressions.Regex.Matches(sourceCode, recordPattern); + + foreach (System.Text.RegularExpressions.Match match in recordMatches) + { + var recordNode = new Interfaces.SyntaxNode + { + Type = SyntaxNodeType.Class, + Text = match.Value, + Metadata = new Dictionary + { + ["accessModifier"] = match.Groups[1].Value, + ["recordName"] = match.Groups[2].Value, + ["typeParameters"] = match.Groups[3].Success ? match.Groups[3].Value : null, + ["components"] = match.Groups[4].Value + } + }; + + root.Children.Add(recordNode); + } + } + + private void ExtractClassMembers(string code, Interfaces.SyntaxNode classNode, string className) { - // 提取方法 ExtractMethods(code, classNode); - - // 提取字段 ExtractFields(code, classNode); - - // 提取构造函数 - ExtractConstructors(code, classNode); + ExtractConstructors(code, classNode, className); + ExtractAnnotations(code, classNode); } private void ExtractMethods(string code, Interfaces.SyntaxNode classNode) { - // 简化的方法匹配:查找方法签名 - var methodPattern = @"(public|private|protected)\s+(static\s+)?(\w+)\s+(\w+)\s*\(([^)]*)\)"; + var methodPattern = @"(public|private|protected)\s+(static\s+)?(final\s+)?(synchronized\s+)?(?:(\w+(?:<[^>]+>?)(?:\[\])?)\s+)?(\w+)\s*\(([^)]*)\)(?:\s+throws\s+([\w,\s]+))?"; var methodMatches = System.Text.RegularExpressions.Regex.Matches(code, methodPattern); + var methodNames = new List(); foreach (System.Text.RegularExpressions.Match match in methodMatches) { - // 过滤掉类的声明 - if (match.Groups[3].Value == "class" || match.Groups[3].Value == "interface" || match.Groups[3].Value == "enum") + var methodName = match.Groups[6].Value; + if (methodName == "class" || methodName == "interface" || methodName == "enum" || methodName == "record") continue; + methodNames.Add(methodName); + + var returnType = string.IsNullOrEmpty(match.Groups[5].Value) ? "void" : match.Groups[5].Value; var methodNode = new Interfaces.SyntaxNode { Type = SyntaxNodeType.Method, @@ -213,27 +232,32 @@ public class JavaParser : BaseParser { ["accessModifier"] = match.Groups[1].Value, ["isStatic"] = !string.IsNullOrEmpty(match.Groups[2].Value), - ["returnType"] = match.Groups[3].Value, - ["methodName"] = match.Groups[4].Value, - ["parameters"] = match.Groups[5].Value + ["isFinal"] = !string.IsNullOrEmpty(match.Groups[3].Value), + ["isSynchronized"] = !string.IsNullOrEmpty(match.Groups[4].Value), + ["returnType"] = returnType, + ["methodName"] = methodName, + ["parameters"] = match.Groups[7].Value, + ["throws"] = match.Groups[8].Success ? match.Groups[8].Value : null } }; classNode.Children.Add(methodNode); - - // 提取参数 - ExtractParameters(match.Groups[5].Value, methodNode); + ExtractParameters(match.Groups[7].Value, methodNode); } + + classNode.Metadata["Methods"] = methodNames; } private void ExtractFields(string code, Interfaces.SyntaxNode classNode) { - // 简化的字段匹配 - var fieldPattern = @"(private|protected|public)\s+(static\s+)?(final\s+)?(\w+)\s+(\w+)\s*[;=]"; + var fieldPattern = @"(private|protected|public)\s+(static\s+)?(final\s+)?(transient\s+)?(volatile\s+)?(\w+(?:<[^>]+>)?(?:\[\])?)\s+(\w+)\s*[;=]"; var fieldMatches = System.Text.RegularExpressions.Regex.Matches(code, fieldPattern); + var fieldNames = new List(); foreach (System.Text.RegularExpressions.Match match in fieldMatches) { + fieldNames.Add(match.Groups[6].Value); + var fieldNode = new Interfaces.SyntaxNode { Type = SyntaxNodeType.Field, @@ -241,26 +265,29 @@ public class JavaParser : BaseParser Metadata = new Dictionary { ["accessModifier"] = match.Groups[1].Value, - ["type"] = match.Groups[4].Value, - ["fieldName"] = match.Groups[5].Value + ["isStatic"] = !string.IsNullOrEmpty(match.Groups[2].Value), + ["isFinal"] = !string.IsNullOrEmpty(match.Groups[3].Value), + ["isTransient"] = !string.IsNullOrEmpty(match.Groups[4].Value), + ["isVolatile"] = !string.IsNullOrEmpty(match.Groups[5].Value), + ["type"] = match.Groups[5].Value, + ["fieldName"] = match.Groups[6].Value } }; classNode.Children.Add(fieldNode); } + + classNode.Metadata["Fields"] = fieldNames; } - private void ExtractConstructors(string code, Interfaces.SyntaxNode classNode) + private void ExtractConstructors(string code, Interfaces.SyntaxNode classNode, string className) { - var constructorPattern = @"(public|private|protected)?\s+(\w+)\s*\(([^)]*)\)\s*(throws\s+[\w,\s]+)?\s*(\{[^}]*\})"; + var constructorPattern = @"(public|private|protected)?\s+(\w+)\s*\(([^)]*)\)(?:\s+throws\s+([\w,\s]+))?"; var constructorMatches = System.Text.RegularExpressions.Regex.Matches(code, constructorPattern); foreach (System.Text.RegularExpressions.Match match in constructorMatches) { - var className = classNode.Metadata.ContainsKey("className") ? classNode.Metadata["className"]?.ToString() : null; - - // 确认是构造函数(名称与类名相同) - if (className != null && match.Groups[2].Value == className) + if (match.Groups[2].Value == className) { var constructorNode = new Interfaces.SyntaxNode { @@ -271,24 +298,40 @@ public class JavaParser : BaseParser ["accessModifier"] = match.Groups[1].Value, ["constructorName"] = match.Groups[2].Value, ["parameters"] = match.Groups[3].Value, - ["throws"] = string.IsNullOrEmpty(match.Groups[4].Value) ? null : match.Groups[4].Value + ["throws"] = match.Groups[4].Success ? match.Groups[4].Value : null } }; classNode.Children.Add(constructorNode); - - // 提取参数 ExtractParameters(match.Groups[3].Value, constructorNode); } } } + private void ExtractAnnotations(string code, Interfaces.SyntaxNode classNode) + { + var annotationPattern = @"@(\w+)(?:\(([^)]*)\))?"; + var annotationMatches = System.Text.RegularExpressions.Regex.Matches(code, annotationPattern); + + var annotations = new List>(); + foreach (System.Text.RegularExpressions.Match match in annotationMatches) + { + annotations.Add(new Dictionary + { + ["name"] = match.Groups[1].Value, + ["parameters"] = match.Groups[2].Success ? match.Groups[2].Value : null + }); + } + + classNode.Metadata["Annotations"] = annotations; + } + private void ExtractParameters(string parametersText, Interfaces.SyntaxNode parentNode) { if (string.IsNullOrWhiteSpace(parametersText)) return; - var paramPattern = @"(\w+(?:<[^>]+>)?)\s+(\w+)"; + var paramPattern = @"(?:@(\w+)(?:\([^)]*\)))?\s*(final\s+)?(\w+(?:<[^>]+>)?(?:\[\])?)\s+(\w+)"; var paramMatches = System.Text.RegularExpressions.Regex.Matches(parametersText, paramPattern); foreach (System.Text.RegularExpressions.Match match in paramMatches) @@ -299,8 +342,10 @@ public class JavaParser : BaseParser Text = match.Value, Metadata = new Dictionary { - ["type"] = match.Groups[1].Value, - ["name"] = match.Groups[2].Value + ["annotation"] = match.Groups[1].Success ? match.Groups[1].Value : null, + ["isFinal"] = !string.IsNullOrEmpty(match.Groups[2].Value), + ["type"] = match.Groups[3].Value, + ["name"] = match.Groups[4].Value } }; @@ -313,12 +358,12 @@ public class JavaParser : BaseParser var lines = sourceCode.Split('\n'); bool inMultiLineComment = false; var multiLineComment = new StringBuilder(); + var multiLineStart = 0; for (int i = 0; i < lines.Length; i++) { var line = lines[i]; - // 单行注释 var singleLineMatch = System.Text.RegularExpressions.Regex.Match(line, @"//\s*(.*)"); if (singleLineMatch.Success) { @@ -330,16 +375,15 @@ public class JavaParser : BaseParser }); } - // 多行注释开始 if (line.Contains("/*") && !line.Contains("*/")) { inMultiLineComment = true; multiLineComment.AppendLine(line); + multiLineStart = i + 1; continue; } - // 多行注释中 - if (inMultiLineComment && line.Contains("*/")) + if (inMultiLineComment) { multiLineComment.AppendLine(line); if (line.Contains("*/")) @@ -349,7 +393,7 @@ public class JavaParser : BaseParser { Text = multiLineComment.ToString().Trim(), Type = CommentType.MultiLine, - LineNumber = i - multiLineComment.ToString().Split('\n').Length + 1 + LineNumber = multiLineStart }); multiLineComment.Clear(); } @@ -367,9 +411,8 @@ public class JavaParser : BaseParser var content = match.Groups[1].Value.Trim(); var lineNumber = GetLineNumber(sourceCode, match.Index); - // 查找相邻的元素名称 var afterDoc = sourceCode.Substring(match.Index + match.Length); - var elementMatch = System.Text.RegularExpressions.Regex.Match(afterDoc, @"(class|interface|enum|method|constructor|field)\s+(\w+)"); + var elementMatch = System.Text.RegularExpressions.Regex.Match(afterDoc, @"@(interface|@interface|class|enum|record)\s+(\w+)"); tree.Documentation.Add(new SyntaxDocumentation { @@ -380,6 +423,40 @@ public class JavaParser : BaseParser } } + private void ExtractAdvancedFeatures(Interfaces.SyntaxTree tree, string sourceCode, Interfaces.SyntaxNode root) + { + // 检测 Lambda 表达式 + var lambdaCount = System.Text.RegularExpressions.Regex.Matches(sourceCode, @"->").Count; + root.Metadata["LambdaCount"] = lambdaCount; + + // 检测 Stream API 使用 + var streamMethods = new[] { "stream()", "filter(", "map(", "flatMap(", "collect(", "reduce(", "forEach(" }; + var streamUsage = streamMethods.Count(m => sourceCode.Contains(m)); + root.Metadata["StreamUsage"] = streamUsage; + + // 检测 Optional 使用 + var optionalCount = System.Text.RegularExpressions.Regex.Matches(sourceCode, @"Optional(?:<[^>]+>)?").Count; + root.Metadata["OptionalUsage"] = optionalCount; + + // 检测泛型使用 + var genericPattern = @"<([^>]+)>"; + var genericMatches = System.Text.RegularExpressions.Regex.Matches(sourceCode, genericPattern); + var typeParameters = genericMatches.Cast() + .Select(m => m.Groups[1].Value) + .Distinct() + .ToList(); + root.Metadata["TypeParameters"] = typeParameters; + + // 检测注解使用 + var annotationPattern = @"@(\w+)"; + var annotationMatches = System.Text.RegularExpressions.Regex.Matches(sourceCode, annotationPattern); + var annotations = annotationMatches.Cast() + .Select(m => m.Groups[1].Value) + .Distinct() + .ToList(); + root.Metadata["Annotations"] = annotations; + } + private int GetLineNumber(string sourceCode, int index) { return sourceCode.Substring(0, index).Count(c => c == '\n') + 1; diff --git a/CodePlay.Core/Services/TodoGenerator.cs b/CodePlay.Core/Services/TodoGenerator.cs index 82ce44f..3d794bf 100644 --- a/CodePlay.Core/Services/TodoGenerator.cs +++ b/CodePlay.Core/Services/TodoGenerator.cs @@ -5,19 +5,102 @@ namespace CodePlay.Core.Services; public class TodoGenerator { + private static readonly List CSharpPatterns = new() + { + new PatternInfo("linq", @"\.(Where|Select|OrderBy|FirstOrDefault|Any|All|Count|ToList)\s*\(", "LINQ 查询", "Java 没有 LINQ", "Stream API"), + new PatternInfo("async_await", @"async\s+[\w<>]+\s+\w+\s*\([^)]*\)", "async/await 异步方法", "Java 使用 CompletableFuture", "CompletableFuture"), + new PatternInfo("attributes", @"\[(?:Obsolete|Serializable|DataContract|DllImport)\b", "C# 特性", "Java 使用注解", "@Annotation"), + new PatternInfo("delegate", @"\b(?:Action|Func|EventHandler)\b", "委托类型", "Java 使用函数式接口", "Function/Consumer"), + new PatternInfo("event", @"\bevent\s+", "C# 事件", "Java 使用观察者模式", "PropertyChangeListener"), + new PatternInfo("dynamic", @"\bdynamic\b", "dynamic 类型", "Java 使用 Object", "Object/反射"), + new PatternInfo("pattern_match", @"\bis\s+(?:not\s+)?null", "模式匹配", "Java 16+ 支持", "instanceof"), + new PatternInfo("nullable", @"\?\s+", "可空引用", "Java 使用 Optional", "Optional"), + new PatternInfo("using_stmt", @"using\s*\([^)]+\)\s*\{", "using 语句", "Java try-with-resources", "try-with-resources"), + new PatternInfo("yield", @"\byield\s+return", "yield return", "Java 使用 Iterator", "Iterator"), + new PatternInfo("record", @"\brecord\s+\w+", "C# record", "Java 16+ record", "Java record"), + new PatternInfo("init_only", @"\binit\s*;", "init-only", "Java 使用 final", "final 字段"), + new PatternInfo("var", @"\bvar\s+\w+\s*=", "var 推断", "Java 10+ var", "var (Java 10+)") + }; + + private static readonly List JavaPatterns = new() + { + new PatternInfo("stream", @"\.(stream|filter|map|collect|reduce)\s*\(", "Stream API", "C# 使用 LINQ", "LINQ"), + new PatternInfo("lambda", @"->\s*\{", "Lambda 表达式", "C# 使用 =>", "=> lambda"), + new PatternInfo("optional", @"Optional<", "Optional 类型", "C# 使用可空", "Nullable"), + new PatternInfo("annotation", @"@(?:Override|Deprecated|FunctionalInterface)\b", "Java 注解", "C# 使用特性", "[Attribute]"), + new PatternInfo("wildcard", @"\?\s+(?:extends|super)", "泛型通配符", "C# in/out", "in/out"), + new PatternInfo("diamond", @"<>\s*;", "菱形操作符", "C# var 推断", "var"), + new PatternInfo("method_ref", @"::\s*\w+", "方法引用", "C# 组方法", "组方法") + }; + public List GenerateTodos(string sourceCode, LanguageType sourceLanguage, LanguageType targetLanguage) { var todos = new List(); - return todos; + var patterns = sourceLanguage == LanguageType.CSharp ? CSharpPatterns : JavaPatterns; + + foreach (var pattern in patterns) + { + var matches = System.Text.RegularExpressions.Regex.Matches(sourceCode, pattern.Pattern); + foreach (System.Text.RegularExpressions.Match match in matches) + { + todos.Add(new TodoItem + { + Description = pattern.Name, + LineNumber = GetLineNumber(sourceCode, match.Index), + OriginalSyntax = match.Value.Trim(), + WhyNotDirect = pattern.Reason, + RecommendedAlternative = pattern.Alternative + }); + } + } + + return todos.GroupBy(t => new { t.Description, t.LineNumber }) + .Select(g => g.First()) + .OrderBy(t => t.LineNumber) + .ToList(); } public string GenerateTodoComment(TodoItem todo) { - return "// TODO: " + todo.Description; + return $"// TODO: {todo.Description} - {todo.WhyNotDirect}"; + } + + public ConversionSuggestion Analyze(string sourceCode, LanguageType sourceLanguage, LanguageType targetLanguage) + { + var todos = GenerateTodos(sourceCode, sourceLanguage, targetLanguage); + return new ConversionSuggestion + { + TotalIssues = todos.Count, + ConfidenceScore = Math.Max(0, 100 - todos.Count * 5), + TodoItems = todos + }; + } + + private int GetLineNumber(string sourceCode, int index) + { + return sourceCode.Substring(0, index).Count(c => c == '\n') + 1; } } -public class ConversionConversionSuggestion +public class PatternInfo +{ + public string Key { get; set; } = ""; + public string Pattern { get; set; } = ""; + public string Name { get; set; } = ""; + public string Reason { get; set; } = ""; + public string Alternative { get; set; } = ""; + + public PatternInfo(string key, string pattern, string name, string reason, string alternative) + { + Key = key; + Pattern = pattern; + Name = name; + Reason = reason; + Alternative = alternative; + } +} + +public class ConversionSuggestion { public int TotalIssues { get; set; } public int ConfidenceScore { get; set; }