使用 `Roslyn` 分析器和修复器 对异步方法规范化返回Async结尾
之前写过一篇使用修复器帮助添加头部注释文本的功能,今天使用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
使用 `Roslyn` 分析器和修复器 对异步方法规范化返回Async结尾的更多相关文章
- 建立标准编码规则(三)-CodeFixProvider 给代码分析器增加修复建议
给代码分析器增加修复建议 既然代码分析器,向代码编写者提出了错误或警告,那么有没有可能向代码编写者提交有效的改进建议? 相对于 DiagnosticAnalyzer,代码修复继承与 CodeFixPr ...
- .Net 异步方法, await async 使用
最近朋友问起await 和 async第一次听说这个await ,就查了一下这个await使用在于 异步方法async 中,中文意思就是等待,经过一系列的百度参考简单的明白了这个东西的意思, 异步 ...
- SpringMvc Controller请求链接忽略大小写(包含拦截器)及@ResponseBody返回String中文乱码处理
SpringMvc Controller请求链接忽略大小写(包含拦截器)及@ResponseBody返回String中文乱码处理... @RequestMapping(value = "/t ...
- vue props 下有验证器 validator 验证数据返回true false后,false给default值
vue props 下有验证器 validator 验证数据返回true false后,false给default值 props: { type: { validator (value) { retu ...
- Extjs的数据读取器store和后台返回类型简单解析
工作中用到了Extjs,从后台获取数据的时候,用到了extjs自己的Ext.data.store方法,然后封装了ExtGridReturn方法, 目的:前台用到Ext.data.store读取从后台传 ...
- struts2拦截器实现session超时返回登录页面(iframe下跳转到其父页面)
需求:session超时时,返回登录页面,由于页面嵌套在iframe下,因此要跳转到登录页面的父页面,但是首页,登录页面等不需要进行跳转 实现: java文件:SessionIterceptor.ja ...
- 【踩坑】.NET异步方法不标记async,Task<int> 返回值 return default问题
在.NET中,返回类型为 Task<T> 的方法并不一定要标记为 async.这是因为 async 关键字只是用来告诉编译器该方法中包含异步操作,并且可以使用 await 和其他异步特 ...
- WebApi 通过拦截器设置特定的返回格式
public class ActionFilter : ActionFilterAttribute { /// <summary> /// Action执行之后由MVC框架调用 /// & ...
- C# 4 中使用迭代器的等待任务
介绍 可能你已经阅读 C#5 关于 async 和 await 关键字以及它们如何帮助简化异步编程的,可惜的是在升级VS2010后短短两年时间,任然没有准备好升级到VS2012,在VS2010和C#4 ...
- .NET 7 RC 2 发布,倒计时一个月发布正式版
微软2022-10-22 发布了 .NET 7 RC 2,下一站是.NET 7正式发布,就在下个月Net Conf 2022(11月8日)期间正式发布. 经过长达一年时间的开发,.NET 7 规划的所 ...
随机推荐
- Swift开发基础03-函数
定义 形参默认是let,也只能是let func sum(v1: Int, v2: Int) -> Int { return v1 + v2 } sum(v1: 10, v2: 20) // 无 ...
- CentOS之yum安装JDK
1.查看云端目前支持安装的jdk版本 [root@localhost ~]# yum search java|grep jdk ldapjdk-javadoc.noarch : Javadoc for ...
- [rCore学习笔记 016]实现应用程序
写在前面 本随笔是非常菜的菜鸡写的.如有问题请及时提出. 可以联系:1160712160@qq.com GitHhub:https://github.com/WindDevil (目前啥也没有 设计方 ...
- C# EPPlus帮助类(EPPlusExcelHelper)
public class EPPlusExcelHelper : IDisposable { public ExcelPackage ExcelPackage { get; private set; ...
- [rCore学习笔记 017]实现批处理操作系统
写在前面 本随笔是非常菜的菜鸡写的.如有问题请及时提出. 可以联系:1160712160@qq.com GitHhub:https://github.com/WindDevil (目前啥也没有 本章目 ...
- gitbook 入门教程之比较代码块差异 diff 插件
在 markdown 文档中显示代码之间的差异的 Gitbook 插件 English | 中文 主页 Github : https://snowdreams1006.github.io/gitboo ...
- Linux 中 Crontab 执行时的环境变量问题(allure命令不执行)
前几天做了UI自动化脚本部署linux服务器,但是放下脚本的allure命令不执行(生成allure报告和启动allure服务的命令不执行),然后就各种找问题,一开始怀疑是allure的环境变量问题, ...
- golang轻量级的代码复制粘贴检查器 cpd
golang轻量级的代码复制粘贴检查器 cpd 项目地址: https://github.com/dengjiawen8955/copy-paste-detector 快速开始 clone git c ...
- Codeforces Round 947 (Div. 1 + Div. 2) A~H
Codeforces Round 947 (Div. 1 + Div. 2) A 模拟. B 最小的 \(a\) 肯定作为 \(i\).对于不被 \(i\) 整除的,最小的那个作为 \(j\),判断是 ...
- jmeter forEach循环获取response参数值进行接口请求
jmeter forEach循环获取response参数值进行接口请求 注意: 一,ForEach控制器 输入变量前缀:输入正则表达式变量的引用名称即可 Start index for loop(ex ...