我想制作一个分析器,它会为代码中某些属性成员的每次出现抛出一条消息(severity = info)。这样可以模仿[Obsolete(...)]
的行为,但只会抛出一条消息。
属性定义类似于
public class ThrowsMessageAttribute : Attribute
{
// ...
}
我想要发送消息的成员将被归因于它:
public class Foo
{
[ThrowsMessage]
public void Bar() { }
}
对于我在代码中使用的每个Bar()
,我现在会在错误列表的消息选项卡中找到一个条目。
我的出发点是一个空的DiagnosticAnalyzer类:
[DiagnosticAnalyzer(LanguageNames.CSharp)]
internal class MyDiagnosticAnalyzer : DiagnosticAnalyzer
{
private static readonly DiagnosticDescriptor Descriptor =
new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, Category, DiagnosticSeverity.Info, true, Description, HelpLink);
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } = ImmutableArray.Create(Descriptor);
public override void Initialize(AnalysisContext context)
{
// how to go on from here?
}
}
拥有AnalysisContext
我该如何继续前进?我需要在ordner中实现的逻辑是什么,以找到以明显方式归属的所有符号引用?
也许我完全走错了路,解决这个问题不应该通过分析仪完成。还有哪些其他选择?
修改
根据@Tamás的建议,我几乎使用以下代码开始工作:
public override void Initialize(AnalysisContext context)
{
context.RegisterSemanticModelAction(Analyze);
}
private static void Analyze(SemanticModelAnalysisContext context)
{
var semanticModel = context.SemanticModel;
var step2 = GetSymbolsOfAttributedMethods(semanticModel, "ThrowsMessage");
Step3(context, list2, semanticModel);
}
private static List<ISymbol> GetSymbolsOfAttributedMethods(SemanticModel semanticModel, string attributeName)
{
var methodDeclarations = semanticModel.SyntaxTree.GetRoot().DescendantNodes().OfType<MethodDeclarationSyntax>();
var symbolList = new List<ISymbol>();
foreach (var declaration in methodDeclarations)
{
foreach (var attributeList in declaration.AttributeLists)
{
if (attributeList.Attributes.Any(a => (a.Name as IdentifierNameSyntax)?.Identifier.Text == attributeName))
{
symbolList.Add(semanticModel.GetDeclaredSymbol(declaration));
break;
}
}
}
return symbolList;
}
private static void Step3(SemanticModelAnalysisContext context, List<ISymbol> attributedSymbols, SemanticModel semanticModel)
{
var invocationExpressions = semanticModel.SyntaxTree.GetRoot().DescendantNodes().OfType<InvocationExpressionSyntax>();
foreach (var invocation in invocationExpressions)
{
var symbol = semanticModel.GetSymbolInfo(invocation).Symbol;
if (attributedSymbols.Contains(symbol))
{
var l = Location.Create(context.SemanticModel.SyntaxTree, invocation.FullSpan);
context.ReportDiagnostic(Diagnostic.Create(Rule, l));
}
}
}
这可以按预期工作,但我报告诊断的位置还不是很正确,因为它不仅是调用而且是尾随空格。这是为什么?
答案 0 :(得分:1)
以下是我要采取的路线:
使用SemanticModelAction
context.RegisterSemanticModelAction
使用您的特殊属性查找方法的MethodDeclaration
并获取方法的符号。这看起来像这样:
private List<ISymbol> GetSymbolsOfAttributedMethods(string attributeName)
{
var methodDeclarations = semanticModel.SyntaxTree.GetRoot().DescendantNodes().OfType<MethodDeclarationSyntax>();
var symbolList = new List<ISymbol>();
foreach (var declaration in methodDeclarations)
{
foreach (var attributeList in declaration.AttributeLists)
{
if (attributeList.Attributes.Any(a => (a.Name as IdentifierNameSyntax)?.Identifier.Text == attributeName))
{
symbolList.Add(semanticModel.GetDeclaredSymbol(declaration));
break;
}
}
}
return symbolList;
}
semanticModel
可以从您注册的操作的上下文中获取。
浏览所有InvocationExpression
(以与我们对methodDeclarations
类似的方式获取它们,加载其符号(确保在此使用GetSymbolInfo(invocation).Symbol
而不是{ {1}}正如我们之前所做的那样。)
将步骤3中的符号与步骤2中的符号进行比较,如果调用符号属于具有特殊属性的符号,则将其与GetDeclaredSymbol
进行比较。
修改强>
关于您的修改,原因是您使用的是FullSpan
。
此节点在字符中的绝对跨度,包括其前导和尾随琐事。
使用Span
或使用invocation.GetLocation()
而忘记完全创建ReportDiagnostic
对象。
Roslyn reference非常彻底,所以它通常是个好看的地方。不要忘记Syntax Visualizer,这是另一种可以让你的生活更轻松100倍的工具。