之前写过一篇使用修复器帮助添加头部注释文本的功能,今天使用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就可以定位到有问题的方法本身,然后使用WithIdentifierSyntaxFactory.Identifier将方法名称修正为正确的值 并返回修复的Document即可!

预览效果

最后我们看一下效果

编译时返回的警告信息



编辑文档是产生的提示信息



点击提示即可修复代码问题

最后

其实这个属于代码习惯或者代码风格的问题,个人是比较推荐异步方法还是加上后缀的,毕竟有了这个规范我们一看方法就能知道这是一个异步方法

最后你可以使用我发布的nuget包体验:

dotnet add package Biwen.AutoClassGen

源代码我发布到了GitHub,欢迎star! https://github.com/vipwan/Biwen.AutoClassGen

使用 `Roslyn` 分析器和修复器 对异步方法规范化返回Async结尾的更多相关文章

  1. 建立标准编码规则(三)-CodeFixProvider 给代码分析器增加修复建议

    给代码分析器增加修复建议 既然代码分析器,向代码编写者提出了错误或警告,那么有没有可能向代码编写者提交有效的改进建议? 相对于 DiagnosticAnalyzer,代码修复继承与 CodeFixPr ...

  2. .Net 异步方法, await async 使用

    最近朋友问起await  和 async第一次听说这个await ,就查了一下这个await使用在于 异步方法async 中,中文意思就是等待,经过一系列的百度参考简单的明白了这个东西的意思,  异步 ...

  3. SpringMvc Controller请求链接忽略大小写(包含拦截器)及@ResponseBody返回String中文乱码处理

    SpringMvc Controller请求链接忽略大小写(包含拦截器)及@ResponseBody返回String中文乱码处理... @RequestMapping(value = "/t ...

  4. vue props 下有验证器 validator 验证数据返回true false后,false给default值

    vue props 下有验证器 validator 验证数据返回true false后,false给default值 props: { type: { validator (value) { retu ...

  5. Extjs的数据读取器store和后台返回类型简单解析

    工作中用到了Extjs,从后台获取数据的时候,用到了extjs自己的Ext.data.store方法,然后封装了ExtGridReturn方法, 目的:前台用到Ext.data.store读取从后台传 ...

  6. struts2拦截器实现session超时返回登录页面(iframe下跳转到其父页面)

    需求:session超时时,返回登录页面,由于页面嵌套在iframe下,因此要跳转到登录页面的父页面,但是首页,登录页面等不需要进行跳转 实现: java文件:SessionIterceptor.ja ...

  7. 【踩坑】.NET异步方法不标记async,Task<int> 返回值 return default问题

    ​ 在.NET中,返回类型为 Task<T> 的方法并不一定要标记为 async.这是因为 async 关键字只是用来告诉编译器该方法中包含异步操作,并且可以使用 await 和其他异步特 ...

  8. WebApi 通过拦截器设置特定的返回格式

    public class ActionFilter : ActionFilterAttribute { /// <summary> /// Action执行之后由MVC框架调用 /// & ...

  9. C# 4 中使用迭代器的等待任务

    介绍 可能你已经阅读 C#5 关于 async 和 await 关键字以及它们如何帮助简化异步编程的,可惜的是在升级VS2010后短短两年时间,任然没有准备好升级到VS2012,在VS2010和C#4 ...

  10. .NET 7 RC 2 发布,倒计时一个月发布正式版

    微软2022-10-22 发布了 .NET 7 RC 2,下一站是.NET 7正式发布,就在下个月Net Conf 2022(11月8日)期间正式发布. 经过长达一年时间的开发,.NET 7 规划的所 ...

随机推荐

  1. Swift开发基础03-函数

    定义 形参默认是let,也只能是let func sum(v1: Int, v2: Int) -> Int { return v1 + v2 } sum(v1: 10, v2: 20) // 无 ...

  2. CentOS之yum安装JDK

    1.查看云端目前支持安装的jdk版本 [root@localhost ~]# yum search java|grep jdk ldapjdk-javadoc.noarch : Javadoc for ...

  3. [rCore学习笔记 016]实现应用程序

    写在前面 本随笔是非常菜的菜鸡写的.如有问题请及时提出. 可以联系:1160712160@qq.com GitHhub:https://github.com/WindDevil (目前啥也没有 设计方 ...

  4. C# EPPlus帮助类(EPPlusExcelHelper)

    public class EPPlusExcelHelper : IDisposable { public ExcelPackage ExcelPackage { get; private set; ...

  5. [rCore学习笔记 017]实现批处理操作系统

    写在前面 本随笔是非常菜的菜鸡写的.如有问题请及时提出. 可以联系:1160712160@qq.com GitHhub:https://github.com/WindDevil (目前啥也没有 本章目 ...

  6. gitbook 入门教程之比较代码块差异 diff 插件

    在 markdown 文档中显示代码之间的差异的 Gitbook 插件 English | 中文 主页 Github : https://snowdreams1006.github.io/gitboo ...

  7. Linux 中 Crontab 执行时的环境变量问题(allure命令不执行)

    前几天做了UI自动化脚本部署linux服务器,但是放下脚本的allure命令不执行(生成allure报告和启动allure服务的命令不执行),然后就各种找问题,一开始怀疑是allure的环境变量问题, ...

  8. golang轻量级的代码复制粘贴检查器 cpd

    golang轻量级的代码复制粘贴检查器 cpd 项目地址: https://github.com/dengjiawen8955/copy-paste-detector 快速开始 clone git c ...

  9. Codeforces Round 947 (Div. 1 + Div. 2) A~H

    Codeforces Round 947 (Div. 1 + Div. 2) A 模拟. B 最小的 \(a\) 肯定作为 \(i\).对于不被 \(i\) 整除的,最小的那个作为 \(j\),判断是 ...

  10. jmeter forEach循环获取response参数值进行接口请求

    jmeter forEach循环获取response参数值进行接口请求 注意: 一,ForEach控制器 输入变量前缀:输入正则表达式变量的引用名称即可 Start index for loop(ex ...