之前写过一篇使用修复器帮助添加头部注释文本的功能,今天使用Roslyn的代码修复器对异步返回方法规范化的功能
首先需要实现分析器,使用RegisterSyntaxNodeAction
,分析所有SyntaxKind.MethodDeclaration
的语法类型,
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class AsyncMethodNameAnalyzer : DiagnosticAnalyzer
{
public const string DiagnosticId = "GEN051";
private static readonly LocalizableString Title = "将异步方法名改为以Async结尾";
private static readonly LocalizableString MessageFormat = "将异步方法名改为以Async结尾";
private static readonly LocalizableString Description = "将异步方法名改为以Async结尾.";
private const string Category = "Documentation";
private static readonly DiagnosticDescriptor Rule = new(
DiagnosticId, Title, MessageFormat, Category,
DiagnosticSeverity.Warning, isEnabledByDefault: true, description: Description);
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => [Rule];
public override void Initialize(AnalysisContext context)
{
if (context is null)
return;
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
context.EnableConcurrentExecution();
context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.MethodDeclaration);
}
// 异步方法名称应该以Async结尾
private const string AsyncSuffix = "Async";
private static void AnalyzeNode(SyntaxNodeAnalysisContext context)
{
var methodDeclaration = (MethodDeclarationSyntax)context.Node;
//如果方法包含async修饰的情况
if (methodDeclaration.Modifiers.Any(SyntaxKind.AsyncKeyword))
{
if (!methodDeclaration.Identifier.Text.EndsWith(AsyncSuffix, StringComparison.OrdinalIgnoreCase))
{
var diagnostic = Diagnostic.Create(Rule, methodDeclaration.Identifier.GetLocation(), methodDeclaration.Identifier.Text);
context.ReportDiagnostic(diagnostic);
}
}
var returnType = methodDeclaration.ReturnType;
//如果返回类型为Task或者Task<T>,或者ValueTask<T>,ValueTask 则方法名应该以Async结尾
// 判断返回类型是否为 Task 或 ValueTask
if (returnType is IdentifierNameSyntax identifierName)
{
if (identifierName.Identifier.Text == "Task" || identifierName.Identifier.Text == "ValueTask")
{
if (!methodDeclaration.Identifier.Text.EndsWith(AsyncSuffix, StringComparison.OrdinalIgnoreCase))
{
var diagnostic = Diagnostic.Create(Rule, methodDeclaration.Identifier.GetLocation(), methodDeclaration.Identifier.Text);
context.ReportDiagnostic(diagnostic);
}
}
}
else if (returnType is GenericNameSyntax genericName && (genericName.Identifier.Text == "Task" || genericName.Identifier.Text == "ValueTask"))
{
if (!methodDeclaration.Identifier.Text.EndsWith(AsyncSuffix, StringComparison.OrdinalIgnoreCase))
{
var diagnostic = Diagnostic.Create(Rule, methodDeclaration.Identifier.GetLocation(), methodDeclaration.Identifier.Text);
context.ReportDiagnostic(diagnostic);
}
}
}
}
在 Initialize
方法中,注册了一个语法节点操作来处理 MethodDeclarationSyntax
节点,主要是考虑方法是否async
关键字标注,或者返回的类型是否是Task
或者ValueTask
,如果这些条件满足则判断方法名称MethodDeclaration.Identifier
是否为Async
结尾.如果存在这样的问题 那么创建一个诊断并报告。
[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(AsyncMethodNameCodeFixProvider))]
[Shared]
internal class AsyncMethodNameCodeFixProvider : CodeFixProvider
{
public override ImmutableArray<string> FixableDiagnosticIds => [AsyncMethodNameAnalyzer.DiagnosticId];
public sealed override FixAllProvider GetFixAllProvider() =>
WellKnownFixAllProviders.BatchFixer;
private const string Title = "将异步方法名改为以Async结尾";
public override Task RegisterCodeFixesAsync(CodeFixContext context)
{
var diagnostic = context.Diagnostics[0];
var diagnosticSpan = diagnostic.Location.SourceSpan;
context.RegisterCodeFix(
CodeAction.Create(
title: Title,
createChangedDocument:
c =>
FixDocumentAsync(context.Document, diagnostic, c),
equivalenceKey: Title),
diagnostic);
return Task.CompletedTask;
}
private const string AsyncSuffix = "Async";
private static async Task<Document> FixDocumentAsync(Document document, Diagnostic diagnostic, CancellationToken c)
{
var root = await document.GetSyntaxRootAsync(c).ConfigureAwait(false);
if (root == null)
return document;
var node = root.FindNode(diagnostic.Location.SourceSpan);
var methodDeclaration = (MethodDeclarationSyntax)node;
var newName = $"{methodDeclaration.Identifier.Text}{AsyncSuffix}";
var newRoot = root.ReplaceNode(methodDeclaration, methodDeclaration.WithIdentifier(SyntaxFactory.Identifier(newName)));
return document.WithSyntaxRoot(newRoot);
}
}
修复器的实现就更加简单了,我们通过诊断信息和Document
就可以定位到有问题的方法本身,然后使用WithIdentifier
和SyntaxFactory.Identifier
将方法名称修正为正确的值 并返回修复的Document即可!
最后我们看一下效果
编译时返回的警告信息
编辑文档是产生的提示信息
点击提示即可修复代码问题
其实这个属于代码习惯或者代码风格的问题,个人是比较推荐异步方法还是加上后缀的,毕竟有了这个规范我们一看方法就能知道这是一个异步方法 😃
最后你可以使用我发布的nuget包体验:
dotnet add package Biwen.AutoClassGen
源代码我发布到了GitHub,欢迎star! https://github.com/vipwan/Biwen.AutoClassGen