feat: 解析器增强 (C#/Java) + 不可转换检测优化
增强内容: 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 <monkeycode-ai@chaitin.com>
This commit is contained in:
@@ -8,18 +8,13 @@ using CodePlay.Core.Interfaces;
|
||||
namespace CodePlay.Core.Parsers;
|
||||
|
||||
/// <summary>
|
||||
/// C# 语法解析器
|
||||
/// C# 语法解析器 (增强版)
|
||||
/// 支持泛型、特性、LINQ、async/await、模式匹配等高级特性
|
||||
/// </summary>
|
||||
public class CSharpParser : BaseParser
|
||||
{
|
||||
/// <summary>
|
||||
/// 支持的语言类型
|
||||
/// </summary>
|
||||
public override LanguageType SupportedLanguage => LanguageType.CSharp;
|
||||
|
||||
/// <summary>
|
||||
/// 解析 C# 源代码
|
||||
/// </summary>
|
||||
public override Task<Interfaces.SyntaxTree> 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<string, object?>()
|
||||
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<string, object?> ExtractMetadataFromNode(Microsoft.CodeAnalysis.SyntaxNode node)
|
||||
{
|
||||
var metadata = new Dictionary<string, object?>();
|
||||
|
||||
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<UsingDirectiveSyntax>()
|
||||
.Select(u => u.Name?.ToString() ?? "")
|
||||
.Where(u => !string.IsNullOrEmpty(u));
|
||||
|
||||
tree.Root.Metadata["Usings"] = usings.ToList();
|
||||
|
||||
// 检测 async/await 使用
|
||||
var asyncMethods = root.DescendantNodes()
|
||||
.OfType<MethodDeclarationSyntax>()
|
||||
.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<InvocationExpressionSyntax>()
|
||||
.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<TypeParameterListSyntax>()
|
||||
.SelectMany(tp => tp.Parameters)
|
||||
.Select(p => p.Identifier.Text)
|
||||
.Distinct()
|
||||
.ToList();
|
||||
|
||||
tree.Root.Metadata["Generics"] = generics;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user