我目前正在编写一些 Roslyn 分析器,其中一些必须使用来自多个类的信息(通常位于不同的文件中)才能完成其工作。
例如,我需要一个分析器,检查一个类是否实现了一个接口。如果是这种情况,分析器将搜索“HandleAsync”方法并检查返回类型。返回类型应该是通用的,如果是这种情况,分析器会尝试检查通用类(例如 ValueTask 的 T)是否具有特定属性。
我尝试了不同的方法,但结果总是相同的:它在我的测试中有效,因为这些类位于同一个文件中。在实际应用中,分析仪不起作用。
public override void Initialize(AnalysisContext context)
{
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
context.EnableConcurrentExecution();
context.RegisterCompilationAction(CheckForGenericReturnType);
}
private void CheckForGenericReturnType(CompilationAnalysisContext context)
{
var compilation = context.Compilation;
foreach (var syntaxTree in compilation.SyntaxTrees)
{
var semanticModel = compilation.GetSemanticModel(syntaxTree);
var root = syntaxTree.GetRoot();
var classDeclarations = root.DescendantNodes().OfType<ClassDeclarationSyntax>();
foreach (var classDeclaration in classDeclarations)
{
var classSymbol = semanticModel.GetDeclaredSymbol(classDeclaration) as INamedTypeSymbol;
var name = semanticModel.GetDeclaredSymbol(classDeclaration).BaseType.Name;
if (!name.Equals("SomeInterface"))
{
continue;
}
var methodSymbol = classSymbol.GetMembers().OfType<IMethodSymbol>().FirstOrDefault(method => method.Name == "HandleAsync");
if (methodSymbol == null)
{
continue;
}
var returnType = methodSymbol.ReturnType;
if (returnType != null)
{
if (returnType is INamedTypeSymbol namedTypeSymbol && namedTypeSymbol.IsGenericType)
{
var genericClassName = namedTypeSymbol.TypeArguments.FirstOrDefault();
if (genericClassName != null)
{
// search for classDefinition of the generic class, than check if attribute exists.
CheckForGenericClass(context, genericClassName);
}
}
}
}
}
}
private void CheckForGenericClass(CompilationAnalysisContext context, ITypeSymbol className)
{
var compilation = context.Compilation;
foreach (var syntaxTree in compilation.SyntaxTrees)
{
var semanticModel = compilation.GetSemanticModel(syntaxTree);
var root = syntaxTree.GetRoot();
var classDeclarations = root.DescendantNodes().OfType<ClassDeclarationSyntax>();
foreach (var classDeclaration in classDeclarations)
{
var classSymbol = semanticModel.GetDeclaredSymbol(classDeclaration) as INamedTypeSymbol;
var name = semanticModel.GetDeclaredSymbol(classDeclaration).Name;
if (!name.Equals(className.Name))
{
continue;
}
var hasAttribute = classSymbol.GetAttributes().Any(attribute => attribute.AttributeClass.Name == "TestAttribute");
if (!hasAttribute)
{
var diagnostic = Diagnostic.Create(Rule, classSymbol.Locations[0], classSymbol.Name);
context.ReportDiagnostic(diagnostic);
}
return;
}
}
}
这里的问题是,
RegisterCompilationAction
是用于语法分析的,你需要注册一个用于语义分析的动作(如RegisterSyntaxNodeAction
)来获取真实类型符号。
例如:
context.RegisterSyntaxNodeAction(CheckForGenericReturnType,
SyntaxKind.MethodDeclaration);
这会注册每个方法声明要执行的动作,你可以从上下文中获取一个
IMethodSymbol
,然后你可以检查它的名称、接口等
private void CheckForGenericReturnType(SyntaxNodeAnalysisContext context)
{
var symbol = context.SemanticModel.GetDeclaredSymbol(context.Node);
if (symbol is IMethodSymbol methodSymbol &&
symbol.Name == "HandleAsync" &&
symbol.ContainingType.Interfaces.Any(i => i.Name == "SomeInterface") &&
methodSymbol.ReturnType is INamedTypeSymbol namedReturnType &&
namedReturnType.IsGenericType &&
namedReturnType.TypeArguments.First()
.GetAttributes().First()
.AttributeClass.Name == "TestAttribute"
...