之前写过两篇关于Roslyn源生成器生成源代码的用例,今天使用Roslyn的代码修复器CodeFixProvider实现一个cs文件头部注释的功能,

代码修复器会同时涉及到CodeFixProviderDiagnosticAnalyzer,

实现FileHeaderAnalyzer

首先我们知道修复器的先决条件是分析器,比如这里,如果要对代码添加头部注释,那么分析器必须要给出对应的分析提醒:

我们首先实现实现名为FileHeaderAnalyzer的分析器:

[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class FileHeaderAnalyzer : DiagnosticAnalyzer
{
public const string DiagnosticId = "GEN050";
private static readonly LocalizableString Title = "文件缺少头部信息";
private static readonly LocalizableString MessageFormat = "文件缺少头部信息";
private static readonly LocalizableString Description = "每个文件应包含头部信息.";
private const string Category = "Document"; 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.RegisterSyntaxTreeAction(AnalyzeSyntaxTree);
} private static void AnalyzeSyntaxTree(SyntaxTreeAnalysisContext context)
{
var root = context.Tree.GetRoot(context.CancellationToken);
var firstToken = root.GetFirstToken(); // 检查文件是否以注释开头
var hasHeaderComment = firstToken.LeadingTrivia.Any(trivia => trivia.IsKind(SyntaxKind.SingleLineCommentTrivia) || trivia.IsKind(SyntaxKind.MultiLineCommentTrivia)); if (!hasHeaderComment)
{
var diagnostic = Diagnostic.Create(Rule, Location.Create(context.Tree, TextSpan.FromBounds(0, 0)));
context.ReportDiagnostic(diagnostic);
}
}
}

FileHeaderAnalyzer分析器的原理很简单,需要重载几个方法,重点是Initialize方法,这里的RegisterSyntaxTreeAction即核心代码,SyntaxTreeAnalysisContext对象取到当前源代码的SyntaxNode根节点,然后判断TA的第一个SyntaxToken是否为注释行(SyntaxKind.SingleLineCommentTrivia|SyntaxKind.MultiLineCommentTrivia)

如果不为注释行,那么就通知分析器!

实现了上面的代码我们看一下效果:

并且编译的时候分析器将会在错误面板中显示警告清单:

实现CodeFixProvider

分析器完成了,现在我们就来实现名为AddFileHeaderCodeFixProvider的修复器,

/// <summary>
/// 自动给文件添加头部注释
/// </summary>
[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(AddFileHeaderCodeFixProvider))]
[Shared]
public class AddFileHeaderCodeFixProvider : CodeFixProvider
{
private const string Title = "添加文件头部信息";
//约定模板文件的名称
private const string ConfigFileName = "Biwen.AutoClassGen.Comment";
private const string VarPrefix = "$";//变量前缀
//如果模板不存在的时候的默认注释文本
private const string DefaultComment = """
// Licensed to the {Product} under one or more agreements.
// The {Product} licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
"""; #region regex private const RegexOptions ROptions = RegexOptions.Compiled | RegexOptions.Singleline;
private static readonly Regex VersionRegex = new(@"<Version>(.*?)</Version>", ROptions);
private static readonly Regex CopyrightRegex = new(@"<Copyright>(.*?)</Copyright>", ROptions);
private static readonly Regex CompanyRegex = new(@"<Company>(.*?)</Company>", ROptions);
private static readonly Regex DescriptionRegex = new(@"<Description>(.*?)</Description>", ROptions);
private static readonly Regex AuthorsRegex = new(@"<Authors>(.*?)</Authors>", ROptions);
private static readonly Regex ProductRegex = new(@"<Product>(.*?)</Product>", ROptions);
private static readonly Regex TargetFrameworkRegex = new(@"<TargetFramework>(.*?)</TargetFramework>", ROptions);
private static readonly Regex TargetFrameworksRegex = new(@"<TargetFrameworks>(.*?)</TargetFrameworks>", ROptions);
private static readonly Regex ImportRegex = new(@"<Import Project=""(.*?)""", ROptions); #endregion public sealed override ImmutableArray<string> FixableDiagnosticIds
{
//重写FixableDiagnosticIds,返回分析器的报告Id,表示当前修复器能修复的对应Id
get { return [FileHeaderAnalyzer.DiagnosticId]; }
} public sealed override FixAllProvider GetFixAllProvider()
{
return WellKnownFixAllProviders.BatchFixer;
} 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, diagnosticSpan, c),
equivalenceKey: Title),
diagnostic); return Task.CompletedTask;
} private static async Task<Document> FixDocumentAsync(Document document, TextSpan span, CancellationToken ct)
{
var root = await document.GetSyntaxRootAsync(ct).ConfigureAwait(false); //从项目配置中获取文件头部信息
var projFilePath = document.Project.FilePath ?? "C:\\test.csproj";//单元测试时没有文件路径,因此使用默认路径 var projectDirectory = Path.GetDirectoryName(projFilePath);
var configFilePath = Path.Combine(projectDirectory, ConfigFileName); var comment = DefaultComment; string? copyright = "MIT";
string? author = Environment.UserName;
string? company = string.Empty;
string? description = string.Empty;
string? title = document.Project.Name;
string? version = document.Project.Version.ToString();
string? product = document.Project.AssemblyName;
string? file = Path.GetFileName(document.FilePath);
string? targetFramework = string.Empty;
#pragma warning disable CA1305 // 指定 IFormatProvider
string? date = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
#pragma warning restore CA1305 // 指定 IFormatProvider if (File.Exists(configFilePath))
{
comment = File.ReadAllText(configFilePath, System.Text.Encoding.UTF8);
} #region 查找程序集元数据 // 加载项目文件:
var text = File.ReadAllText(projFilePath, System.Text.Encoding.UTF8);
// 载入Import的文件,例如 : <Import Project="..\Version.props" />
// 使用正则表达式匹配Project:
var importMatchs = ImportRegex.Matches(text);
foreach (Match importMatch in importMatchs)
{
var importFile = Path.Combine(projectDirectory, importMatch.Groups[1].Value);
if (File.Exists(importFile))
{
text += File.ReadAllText(importFile);
}
} //存在变量引用的情况,需要解析
string RawVal(string old, string @default)
{
if (old == null)
return @default; //当取得的版本号为变量引用:$(Version)的时候,需要再次解析
if (version.StartsWith(VarPrefix, StringComparison.Ordinal))
{
var varName = old.Substring(2, old.Length - 3);
var varMatch = new Regex($@"<{varName}>(.*?)</{varName}>", RegexOptions.Singleline).Match(text);
if (varMatch.Success)
{
return varMatch.Groups[1].Value;
}
//未找到变量引用,返回默
return @default;
}
return old;
} var versionMatch = VersionRegex.Match(text);
var copyrightMath = CopyrightRegex.Match(text);
var companyMatch = CompanyRegex.Match(text);
var descriptionMatch = DescriptionRegex.Match(text);
var authorsMatch = AuthorsRegex.Match(text);
var productMatch = ProductRegex.Match(text);
var targetFrameworkMatch = TargetFrameworkRegex.Match(text);
var targetFrameworksMatch = TargetFrameworksRegex.Match(text); if (versionMatch.Success)
{
version = RawVal(versionMatch.Groups[1].Value, version);
}
if (copyrightMath.Success)
{
copyright = RawVal(copyrightMath.Groups[1].Value, copyright);
}
if (companyMatch.Success)
{
company = RawVal(companyMatch.Groups[1].Value, company);
}
if (descriptionMatch.Success)
{
description = RawVal(descriptionMatch.Groups[1].Value, description);
}
if (authorsMatch.Success)
{
author = RawVal(authorsMatch.Groups[1].Value, author);
}
if (productMatch.Success)
{
product = RawVal(productMatch.Groups[1].Value, product);
}
if (targetFrameworkMatch.Success)
{
targetFramework = RawVal(targetFrameworkMatch.Groups[1].Value, targetFramework);
}
if (targetFrameworksMatch.Success)
{
targetFramework = RawVal(targetFrameworksMatch.Groups[1].Value, targetFramework);
} #endregion //使用正则表达式替换
comment = Regex.Replace(comment, @"\{(?<key>[^}]+)\}", m =>
{
var key = m.Groups["key"].Value;
return key switch
{
"Product" => product,
"Title" => title,
"Version" => version,
"Date" => date,
"Author" => author,
"Company" => company,
"Copyright" => copyright,
"File" => file,
"Description" => description,
"TargetFramework" => targetFramework,
_ => m.Value,
};
}, RegexOptions.Singleline); var headerComment = SyntaxFactory.Comment(comment + Environment.NewLine);
var newRoot = root?.WithLeadingTrivia(headerComment);
if (newRoot == null)
{
return document;
}
var newDocument = document.WithSyntaxRoot(newRoot); return newDocument;
}
}

代码修复器最重要的重载方法RegisterCodeFixesAsync,对象CodeFixContext包含项目和源代码以及对应分析器的信息:

比如:CodeFixContext.Document表示对应的源代码,CodeFixContext.Document.Project表示对应项目,CodeFixContext.Document.Project.FilePath就是代码中我需要的*.csproj的项目文件,

我们取到项目文件,那么我们就可以读取配置在项目文件中的信息,比如Company,Authors,Description,甚至上一篇我们提到的版本号等有用信息,当前我用的正则表达式,当然如果可以你也可以使用XPath,

然后取到的有用数据替换模板即可得到想要的注释代码片段了!

比如我的Comment模板文件Biwen.AutoClassGen.Comment

// Licensed to the {Product} under one or more agreements.
// The {Product} licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
// {Product} Author: 万雅虎 Github: https://github.com/vipwan
// {Description}
// Modify Date: {Date} {File}

替换后将会生成如下的代码:

// Licensed to the Biwen.QuickApi under one or more agreements.
// The Biwen.QuickApi licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
// Biwen.QuickApi Author: 万雅虎 Github: https://github.com/vipwan
// Biwen.QuickApi ,NET9+ MinimalApi CQRS
// Modify Date: 2024-09-07 15:22:42 Verb.cs

最后使用SyntaxFactory.Comment(comment)方法生成一个注释的SyntaxTrivia并附加到当前的根语法树上,最后返回这个新的Document即可!

大功告成,我们来看效果:

以上代码就完成了整个源生成步骤,最后你可以使用我发布的nuget包体验:

dotnet add package Biwen.AutoClassGen

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

https://github.com/vipwan/Biwen.AutoClassGen/blob/master/Biwen.AutoClassGen.Gen/CodeFixProviders/AddFileHeaderCodeFixProvider.cs

使用 `Roslyn` 分析器和修复器对.cs源代码添加头部注释的更多相关文章

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

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

  2. asp.net 分析器错误消息: 文件.aspx.cs”不存在错误

    发布webapplication时后老是报告分析器错误消息: 文件.aspx.cs”不存在错误,差点抓狂,后来在网上搜到原因是: <%@ Page Language="C#" ...

  3. 周末学习笔记——day02(带参装饰器,wraps修改文档注释,三元表达式,列表字典推导式,迭代器,生成器,枚举对象,递归)

    一,复习 ''' 1.函数的参数:实参与形参 形参:定义函数()中出现的参数 实参:调用函数()中出现的参数 形参拿到实参的值,如果整体赋值(自己改变存放值的地址),实参不会改变,(可变类型)如果修改 ...

  4. 如何在Windows资源管理器右键菜单中 添加CMD

    我们在用windows时经常需要在某个目录下执行执行一些dos命令,通常我们会在开始菜单的运行下键入:cmd,开启dos命令窗口,然后在cd到目标操作目录,每次这样操作比较麻烦.下面介绍一种直接在资源 ...

  5. CS文件类头注释

    1.修改unity生成CS文件的模板(模板位置:Unity\Editor\Data\Resources\ScriptTemplates 文件名:81-C# Script-NewBehaviourScr ...

  6. duilib 修复CTreeViewUI控件动态添加子控件时,对是否显示判断不足的bug

    转载请说明出处,谢谢~~:http://blog.csdn.net/zhuhongshu/article/details/42264947 这个bug我在仿酷狗开发日志里提到过,不过后来发现修复的不够 ...

  7. Android 热修复 Tinker接入及源代码浅析

    本文已在我的公众号hongyangAndroid首发.转载请标明出处: http://blog.csdn.net/lmj623565791/article/details/54882693本文出自张鸿 ...

  8. [Python之路] 使用装饰器给Web框架添加路由功能(静态、动态、伪静态URL)

    一.观察以下代码 以下来自 Python实现简易HTTP服务器与MINI WEB框架(利用WSGI实现服务器与框架解耦) 中的mini_frame最后版本的代码: import time def in ...

  9. cs程序添加初始化加载

    this.Name = "mysirst"; this.Text = "车辆窗体程序"; this.Load += new System.EventHandle ...

  10. 配置你的Editor

    ![](http://oqefp0r4y.bkt.clouddn.com/editor_index.png) ### 说明1. 走一波配置流,莫等闲,高效快速开发,从自己的常用的工具开始2. 寻找舒适 ...

随机推荐

  1. Java 表达式执行引擎 jexl

    介绍 JEXL的全称是Java表达式语言(Java Expression Language),简单的说,它可以配合我们的Java程序运算一些简单的表达式. 具体可以识别哪些表达式? 包含最基本的加减乘 ...

  2. Spring Boot集成Mybatis分页插件pagehelper

    引入依赖 <!--分页插件开始--> <dependency> <groupId>com.github.pagehelper</groupId> < ...

  3. msgpack的使用

    1.引入包 <!--msgpack依赖--> <dependency> <groupId>org.msgpack</groupId> <artif ...

  4. nodejs,express设置允许跨域请求

    express设置允许跨域请求 //设置跨域访问 app.all("*", function (req, res, next) { //设置允许跨域的域名,*代表允许任意域名跨域 ...

  5. 洛谷P1095

    [NOIP2007 普及组] 守望者的逃离 题目背景 恶魔猎手尤迪安野心勃勃,他背叛了暗夜精灵,率领深藏在海底的娜迦族企图叛变. 题目描述 守望者在与尤迪安的交锋中遭遇了围杀,被困在一个荒芜的大岛上. ...

  6. element-plus如何隐藏el-row

    在 Element Plus 中,el-row 是用于布局的组件,如果你想要隐藏 el-row,你可以使用 CSS 的 display 属性将其设置为 none.以下是一个简单的示例: <tem ...

  7. JS实现复制粘贴图片

    最近在开发公司的可视化编辑器应用, 同事们提了一个需求, 即可以直接复制图片到编辑器中粘贴, 生成对应的图片组件. 因为传统的点击上传太麻烦, 得先把图片保存到本地, 然后再回到编辑器点击上传, 选择 ...

  8. JuiceFS 直连 NFS 新功能介绍,赋能 NAS 进行 AI 训练

    NAS 通过提供多用户网络数据存取服务,极大地简化了数据共享和管理.而 NFS 作为实现这种共享的一种主流协议,尽管广泛应用,但在处理复杂的 AI 训练场景时常常受限于其性能和一致性问题. Juice ...

  9. 安卓开发 StateListDrawable 应用

    基础部份     StateListDrawable 安卓开发中,如果要做一个按扭按下改变背景,或获取焦点改变背景,最简单的方法是利用将背景指向一个资源,然后果在资源中配置事件,总共分为三步, 1)  ...

  10. hadoop hive hbase flume sqoop基本操作

    top 里的id为cpu空闲度 如果wa为99.8就是负担太重.得停掉一些任务 cat /proc/cpuinfo 查看cpu信息 cat /proc/meminfo 查看内存信息 hadoop基础操 ...