之前写过两篇关于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. 树莓派4B-MAX9814麦克风模块

    树莓派4B-MAX9814麦克风模块 硬件需求 树莓派 MAX9814模块 杜邦线 MAX9814模块 电子特性 实验电路板 实验电路局部 典型工作特性 引角接线 代码展示 import RPi.GP ...

  2. 建立Model

    直接使用Sequelize虽然可以,但是存在一些问题. 团队开发时,有人喜欢自己加timestamp: var Pet = sequelize.define('pet', { id: { type: ...

  3. 关于ComfyUI的一些Tips

    关于ComfyUI的一些Tips 前言: 最近发的ComfyUI相关文章节奏不知道会不会很快,在创作的时候没有考虑很多,想着把自己的知识分享出去.后台也看到很多私信,有各种各样的问题,这是我欠缺考虑了 ...

  4. 转载 | win11右键菜单改为win10的bat命令(以及恢复方法bat)

    原文来自这里:https://blog.51cto.com/knifeedge/5340751 版权归:IT利刃出鞘 本质上就是写入注册表. 一.右键菜单改回Win10(展开) 1. 新建文件:win ...

  5. 基于Java+SpringBoot+Vue宠物咖啡馆平台设计和实现

    \n文末获取源码联系 感兴趣的可以先收藏起来,大家在毕设选题,项目以及论文编写等相关问题都可以给我加好友咨询 系统介绍: 随着信息技术在管理上越来越深入而广泛的应用,管理信息系统的实施在技术上已逐步成 ...

  6. PN转Modbus RTU模块连接ACS4QQ变频器通信

    一台完整的机器在出厂前由许多部件组成.但是,由于各种原因,这些组件来自不同的制造商,导致设备之间的通信协议存在差异.Modbus和Profinet代表两种不同的通信协议,Profinet通常用于较新的 ...

  7. Midnight Commander (MC)

    Midnight Commander GNU Midnight Commander 是一个可视化文件管理器,根据 GNU 通用公共许可证获得许可,因此有资格成为自由软件.它是一个功能丰富的全屏文本模式 ...

  8. DASCTF 2023六月挑战赛|二进制专项 PWN (下)

    DASCTF 2023六月挑战赛|二进制专项 PWN (下) 1.can_you_find_me 检查保护 意料之中 64位ida逆向 只有add,和del功能不能show 先看add吧 最多申请10 ...

  9. Jmeter二次开发函数 - 文本替换

    此篇文章将在Jmeter创建一个新函数,实现替换文本中的指定内容功能.效果图如下 1.eclipse项目创建步骤此处省略,可参考上一篇Jmeter二次开发函数之入门 2.新建class命名为" ...

  10. 对比python学julia(第三章:游戏编程)--(第二节)公主迎圣诞(4)

    4.  碰撞检测 .得分及生命 在第 4 个阶段,利用GameZero的碰撞检测功能,使公主角色能够接到雪花 .礼物或剪刀. 在"sdgz"项目目录中 ,把 version3.jl ...