我有一个 C# 代码分析器基类:
using System;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using CodeQuality.Shared;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Diagnostics;
namespace CodeQuality
{
internal abstract class CodeAnalyzerBase<TSyntaxNode> : DiagnosticAnalyzer
where TSyntaxNode : SyntaxNode
{
private readonly object contextLock = new object();
private SyntaxNodeAnalysisContext context;
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(this.Rule, DiagnosticDescriptors.AnalyzerExceptionRule);
/// <summary>The syntax kind to which you want to register the AnalyzeNode method</summary>
/// <value>The kind of the syntax.</value>
internal abstract SyntaxKind SyntaxKind { get; }
protected abstract DiagnosticDescriptor Rule { get; }
protected SyntaxNodeAnalysisContext Context
{
get
{
lock (this.contextLock)
{
return this.context;
}
}
set
{
lock (this.contextLock)
{
this.context = value;
}
}
}
public override void Initialize(AnalysisContext context)
{
if (!Debugger.IsAttached)
{
// we want concurrent execution, but it's annoying while actively debugging
context.EnableConcurrentExecution();
}
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics);
context.RegisterSyntaxNodeAction(this.AnalyzeNode, this.SyntaxKind);
}
internal void AnalyzeNode(SyntaxNodeAnalysisContext context)
{
try
{
this.Context = context;
if (this.Context.Node.SyntaxTree == null)
{
return;
}
if (!(this.Context.Node is TSyntaxNode node))
{
return;
}
this.AnalyzeNode(node);
}
catch (Exception e)
{
var location = this.Context.Node.GetLocation();
var lineSpan = location.GetLineSpan();
DebugHelper.LogException(
e,
lineSpan.Path,
lineSpan.StartLinePosition.Line,
$"Supported Rules: {string.Join(Environment.NewLine, this.SupportedDiagnostics.Select(r => r.Id))}");
var diagnostic = Diagnostic.Create(
DiagnosticDescriptors.AnalyzerExceptionRule,
location,
$"File: {lineSpan.Path}",
$"Line: {lineSpan.StartLinePosition.Line}",
$"Exception message: {e.Message}",
$"Inner Exception: {e.InnerException}",
$"Stack Trace: {e.StackTrace}");
this.Context.ReportDiagnostic(diagnostic);
}
}
internal abstract void AnalyzeNode(TSyntaxNode node);
}
}
我有这个基类的几个实现。本来我的父类AnalyzeNode方法中没有try catch,所以当分析器抛出异常时,就会导致被分析项目编译失败。这些分析器对我们的构建过程并非绝对重要,所以我不希望分析器错误阻止整个构建过程继续进行。考虑到这一点,我添加了 try/catch,这意味着创建一个编译器警告,其中包含引发错误的文件、行、消息等数据。如果我看到警告,我知道我需要查看在那个位置/用例并在分析器中处理它。
当我将 DLL 安装到我的解决方案的项目中时,具体的实现确实捕获了它们应该捕获的东西。但是有时会报如下错误:
AD0001 分析器“{此处的具体实现}”引发了类型为“System.ArgumentException”的异常,消息为“分析器不支持 ID 为‘{DiagnosticDescriptors.AnalyzerExceptionRule.Id}’的报告诊断”。
我试过调试,但我似乎从来没有在调试时碰到过 catch 块。此外,我在尝试报告警告诊断之前添加了一行来记录 SupportedDiagnostics,而 Supported 诊断正是我所期望的(该规则定义了每个具体类型和 DiagnosticDescriptors.AnalyzerExceptionRule)。但是,它在尝试报告时仍然会抛出错误。
这里有一个具体实现的简单例子:
// analyzes to ensure there are no empty catch blocks unless they contain comments
[DiagnosticAnalyzer(LanguageNames.CSharp)]
internal class EmptyCatchBlockAnalyzer : CodeAnalyzerBase<CatchClauseSyntax>
{
internal override SyntaxKind SyntaxKind => SyntaxKind.CatchClause;
protected override DiagnosticDescriptor Rule => DiagnosticDescriptors.EmptyCatchBlockRule;
internal override void AnalyzeNode(CatchClauseSyntax node)
{
if (node.Block == null)
{
return;
}
if (node.Block.Statements == null)
{
return;
}
if (!node.Block.Statements.Any() && !node.Block.DescendantTrivia().Any(trivia => trivia.IsKind(SyntaxKind.SingleLineCommentTrivia) || trivia.IsKind(SyntaxKind.MultiLineCommentTrivia)))
{
var diagnostic = Diagnostic.Create(this.Rule, node.CatchKeyword.GetLocation());
this.Context.ReportDiagnostic(diagnostic);
}
}
}
我在这些文件的各个区域做了很多记录,试图了解事情发生的时间。分析器都已正确构造和初始化(我认为),因为当时记录 SupportedDiagnostics 也显示了正确的代码。
基于基类中的代码,我预计具体类中
AnalyzeNoe
执行中的任何错误都会导致警告诊断。这是 catch 块中使用的诊断:
internal static DiagnosticDescriptor AnalyzerExceptionRule => new DiagnosticDescriptor(
"ID0000",
"Code analyzer exception",
$"An error occurred while attempting to run code analysis.",
DiagnosticCategories.Usage, // "Usage"
DiagnosticSeverity.Warning,
isEnabledByDefault: true);
这里有一个诊断来匹配上面的示例具体实现:
internal static DiagnosticDescriptor EmptyCatchBlockRule => new DiagnosticDescriptor(
"ID0050",
"Empty Catch Block",
"Code contains an empty catch block. Add code to handle the error or add a comment justifying the existence of this empty catch block.",
DiagnosticCategories.Syntax, // "Syntax"
DiagnosticSeverity.Error,
isEnabledByDefault: true);