f772973b68
增强内容: 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>
274 lines
12 KiB
C#
274 lines
12 KiB
C#
using Microsoft.CodeAnalysis;
|
|
using Microsoft.CodeAnalysis.CSharp;
|
|
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
|
using CodePlay.Core.Models;
|
|
using CodePlay.Core.Common;
|
|
using CodePlay.Core.Interfaces;
|
|
|
|
namespace CodePlay.Core.Parsers;
|
|
|
|
/// <summary>
|
|
/// C# 语法解析器 (增强版)
|
|
/// 支持泛型、特性、LINQ、async/await、模式匹配等高级特性
|
|
/// </summary>
|
|
public class CSharpParser : BaseParser
|
|
{
|
|
public override LanguageType SupportedLanguage => LanguageType.CSharp;
|
|
|
|
public override Task<Interfaces.SyntaxTree> ParseAsync(string sourceCode, CancellationToken cancellationToken = default)
|
|
{
|
|
var tree = CreateSyntaxTree();
|
|
tree.SourceCode = sourceCode;
|
|
|
|
var compilationUnit = CSharpSyntaxTree.ParseText(sourceCode, cancellationToken: cancellationToken);
|
|
var root = compilationUnit.GetRoot(cancellationToken);
|
|
|
|
tree.Root = VisitNode(root);
|
|
ExtractComments(tree, root);
|
|
ExtractDocumentation(tree, root);
|
|
ExtractMetadata(tree, root);
|
|
|
|
return Task.FromResult(tree);
|
|
}
|
|
|
|
private Interfaces.SyntaxNode VisitNode(Microsoft.CodeAnalysis.SyntaxNode node)
|
|
{
|
|
var newNode = new Interfaces.SyntaxNode
|
|
{
|
|
Type = MapNodeType(node.Kind()),
|
|
Text = node.ToString(),
|
|
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 = VisitNode(child);
|
|
childNode.Parent = newNode;
|
|
newNode.Children.Add(childNode);
|
|
}
|
|
|
|
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
|
|
{
|
|
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
|
|
};
|
|
}
|
|
|
|
private void ExtractComments(Interfaces.SyntaxTree tree, Microsoft.CodeAnalysis.SyntaxNode root)
|
|
{
|
|
var trivia = root.DescendantTrivia()
|
|
.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(SyntaxKind.SingleLineCommentTrivia)
|
|
? CommentType.SingleLine
|
|
: CommentType.MultiLine;
|
|
|
|
AddComment(tree, commentTrivia.ToString(), type, lineNumber);
|
|
}
|
|
}
|
|
|
|
private void ExtractDocumentation(Interfaces.SyntaxTree tree, Microsoft.CodeAnalysis.SyntaxNode root)
|
|
{
|
|
var docComments = root.DescendantTrivia()
|
|
.Where(t => t.IsKind(SyntaxKind.SingleLineDocumentationCommentTrivia) ||
|
|
t.IsKind(SyntaxKind.MultiLineDocumentationCommentTrivia));
|
|
|
|
foreach (var docComment in docComments)
|
|
{
|
|
var node = root.FindNode(docComment.Span);
|
|
var lineNumber = root.SyntaxTree.GetLineSpan(docComment.Span).StartLinePosition.Line + 1;
|
|
|
|
tree.Documentation.Add(new SyntaxDocumentation
|
|
{
|
|
ElementName = node.Parent?.ToString() ?? "Unknown",
|
|
Content = docComment.ToString(),
|
|
Format = DocFormat.XmlDoc
|
|
});
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|