当前位置: 首页 > news >正文

IIncrementalGenerator 如何在源代码生成器单元测试提供 AnalyzerConfigOptionsProvider 选项

本文是 为 IIncrementalGenerator 增量 Source Generator 源代码生成项目添加单元测试 的后续。在上文介绍了如何给增量 Source Generator 源代码生成项目添加单元测试,本文将在此基础上,告诉大家如何提供 AnalyzerConfigOptionsProvider 选项

先来看看一个简单的源代码生成器的例子,以下的代码将根据配置的 FooProperty 属性决定生成的代码内容

[Generator(LanguageNames.CSharp)]
public class IncrementalGenerator : IIncrementalGenerator
{public void Initialize(IncrementalGeneratorInitializationContext context){IncrementalValueProvider<string> configurationProvider = context.AnalyzerConfigOptionsProvider.Select((t, _) =>{var globalOptions = t.GlobalOptions;if (globalOptions.TryGetValue("build_property.FooProperty", out var property)){return property;}return null;});context.RegisterSourceOutput(configurationProvider, (productionContext, configurationProperty) =>{productionContext.AddSource("GeneratedCode.cs",$$"""using System;namespace LurlelnarkallChijurjeaqelba{public static class GeneratedCode{public static void Print(){Console.WriteLine("配置的属性 {{configurationProperty}}");}}}""");});}
}

注: 为了让我的博客引擎开森,以上代码部分花括号被我替换为了全角花括号。大家在使用的时候需要将全角花括号替换为半角花括号

以上的 build_property.FooProperty 就是获取某个属性配置的写法,正常来说是需要依靠 CompilerVisibleProperty 指定具体的属性名才能被源代码生成器访问到的,详细请参阅 IIncrementalGenerator 增量 Source Generator 生成代码入门 读取 csproj 项目文件的属性配置

在正式项目里面大概的写法如下:

  <PropertyGroup><FooProperty>lindexi is doubi</FooProperty></PropertyGroup><ItemGroup><CompilerVisibleProperty Include="FooProperty" /></ItemGroup>

以上代码既可以写到 csproj 项目文件里面,也可以被放在带在 NuGet 包里的 props 文件里面。如果对此机制感觉到陌生,还请参阅 dotnet 源代码生成器分析器入门

单元测试里面,需要使用 CSharpGeneratorDriver.Create 的重载方法传入 AnalyzerConfigOptionsProvider 类型参数

由于 AnalyzerConfigOptionsProvider 是抽象的,我添加了如下代码用于辅助测试

internal class TestAnalyzerConfigOptionsProvider : AnalyzerConfigOptionsProvider
{public TestAnalyzerConfigOptionsProvider(Dictionary<string, string> configOptions){var testAnalyzerConfigOptions = new TestAnalyzerConfigOptions(configOptions);GlobalOptions = testAnalyzerConfigOptions;}public override AnalyzerConfigOptions GetOptions(SyntaxTree tree){return GlobalOptions;}public override AnalyzerConfigOptions GetOptions(AdditionalText textFile){return GlobalOptions;}public override AnalyzerConfigOptions GlobalOptions { get; }
}internal class TestAnalyzerConfigOptions : AnalyzerConfigOptions
{public TestAnalyzerConfigOptions(Dictionary<string, string> configOptions){_configOptions = configOptions;}private readonly Dictionary<string, string> _configOptions;public override bool TryGetValue(string key, [NotNullWhen(true)] out string? value){return _configOptions.TryGetValue(key, out value);}
}

通过 TestAnalyzerConfigOptionsProvider 辅助代码,可以使用字典表示将要注入到测试里面的属性,代码如下

        // 创建 AnalyzerConfigOptionsvar configOptions = new Dictionary<string, string>{["build_property.FooProperty"] = "Test",};var analyzerConfigOptionsProvider = new TestAnalyzerConfigOptionsProvider(configOptions);

configOptions 传入到 CSharpGeneratorDriver.Create 方法里面,代码如下

        var compilation = CreateCompilation(...);var generator = new IncrementalGenerator();GeneratorDriver driver = CSharpGeneratorDriver.Create([generator.AsSourceGenerator()], optionsProvider: analyzerConfigOptionsProvider);

这里需要让 IncrementalGenerator 通过 AsSourceGenerator 扩展方法转换为 ISourceGenerator 类型,才能传入此重载方法里面

完成以上步骤之后,就可以调用 GeneratorDriver.RunGenerators 开始执行源代码生成器

        driver = driver.RunGenerators(compilation);

以下是示例的单元测试


[TestClass]
public class IncrementalGeneratorTest
{[TestMethod]public void Test(){var testCode ="""using System;namespace LurlelnarkallChijurjeaqelba;""";var compilation = CreateCompilation(testCode);var generator = new IncrementalGenerator();// 创建 AnalyzerConfigOptionsvar configOptions = new Dictionary<string, string>{["build_property.FooProperty"] = "Test",};var analyzerConfigOptionsProvider = new TestAnalyzerConfigOptionsProvider(configOptions);GeneratorDriver driver = CSharpGeneratorDriver.Create([generator.AsSourceGenerator()], optionsProvider: analyzerConfigOptionsProvider);driver = driver.RunGenerators(compilation);var runResult = driver.GetRunResult();Assert.HasCount(1, runResult.GeneratedTrees);foreach (var generatedTree in runResult.GeneratedTrees){var generatedCode = generatedTree.ToString();Debug.WriteLine(generatedCode);if (generatedTree.FilePath.EndsWith("GeneratedCode.cs")){var expected ="""using System;namespace LurlelnarkallChijurjeaqelba{public static class GeneratedCode{public static void Print(){Console.WriteLine("配置的属性 Test");}}}""";// 防止拉取 git 时出现的 \r\n 不匹配问题。能够解决一些拉取 git 的奇怪的坑,也就是在我电脑上跑的好好的,但为什么在你电脑上就炸了expected = expected.Replace("\r\n", "\n");Assert.AreEqual(expected, generatedCode.Replace("\r\n", "\n"));}}}private static CSharpCompilation CreateCompilation(string source)=> CSharpCompilation.Create("compilation",new[] { CSharpSyntaxTree.ParseText(source, path: "Foo.cs") },new MetadataReference[]{// 如果缺少引用,那将会导致单元测试有些符号无法寻找正确,从而导致解析失败// 在这里添加你自己的依赖库的引用}// 加上整个 dotnet 的基础库.Concat(MetadataReferenceProvider.GetDotNetMetadataReferenceList()),new CSharpCompilationOptions(OutputKind.ConsoleApplication));
}internal class TestAnalyzerConfigOptionsProvider : AnalyzerConfigOptionsProvider
{public TestAnalyzerConfigOptionsProvider(Dictionary<string, string> configOptions){var testAnalyzerConfigOptions = new TestAnalyzerConfigOptions(configOptions);GlobalOptions = testAnalyzerConfigOptions;}public override AnalyzerConfigOptions GetOptions(SyntaxTree tree){return GlobalOptions;}public override AnalyzerConfigOptions GetOptions(AdditionalText textFile){return GlobalOptions;}public override AnalyzerConfigOptions GlobalOptions { get; }
}internal class TestAnalyzerConfigOptions : AnalyzerConfigOptions
{public TestAnalyzerConfigOptions(Dictionary<string, string> configOptions){_configOptions = configOptions;}private readonly Dictionary<string, string> _configOptions;public override bool TryGetValue(string key, [NotNullWhen(true)] out string? value){return _configOptions.TryGetValue(key, out value);}
}internal static class MetadataReferenceProvider
{public static IReadOnlyList<MetadataReference> GetDotNetMetadataReferenceList(){if (_cacheList is not null){return _cacheList;}var metadataReferenceList = new List<MetadataReference>();var assembly = Assembly.Load("System.Runtime");foreach (var file in Directory.GetFiles(Path.GetDirectoryName(assembly.Location)!, "*.dll")){try{metadataReferenceList.Add(MetadataReference.CreateFromFile(file));}catch{// 忽略}}_cacheList = metadataReferenceList;return _cacheList;}private static IReadOnlyList<MetadataReference>? _cacheList;
}

本文的代码放在 github 和 gitee 上,可以使用如下命令行拉取代码。我整个代码仓库比较庞大,使用以下命令行可以进行部分拉取,拉取速度比较快

先创建一个空文件夹,接着使用命令行 cd 命令进入此空文件夹,在命令行里面输入以下代码,即可获取到本文的代码

git init
git remote add origin https://gitee.com/lindexi/lindexi_gd.git
git pull origin 28cf0fb972be64983e4e5eb91d9dd62930f2d49d

以上使用的是国内的 gitee 的源,如果 gitee 不能访问,请替换为 github 的源。请在命令行继续输入以下代码,将 gitee 源换成 github 源进行拉取代码。如果依然拉取不到代码,可以发邮件向我要代码

git remote remove origin
git remote add origin https://github.com/lindexi/lindexi_gd.git
git pull origin 28cf0fb972be64983e4e5eb91d9dd62930f2d49d

获取代码之后,进入 Roslyn/LurlelnarkallChijurjeaqelba 文件夹,即可获取到源代码

http://www.jsqmd.com/news/52179/

相关文章:

  • 降ai率免费工具推荐:提升内容原创性的实用选择
  • Ai元人文构想理论体系——初级阶段愿景
  • PeachPie 1.1.13 发布支持最新PHP 8.5.0
  • [1141] Swich Control Key and Command Key in Macbook
  • 读社会工程:防范钓鱼欺诈(卷3)03保护课程
  • 2025年下半年公交候车厅品牌推荐前十指南
  • 2025年下半年电子站牌品牌口碑推荐榜单
  • 2025年下半年公交候车厅品牌选购终极指南:十大优质供应商推荐
  • 2025年下半年电子站牌品牌综合推荐指南:十大优质供应商盘点
  • 高性能AI股票预测分析报告 - 2025年11月27日 - 06:28:00
  • 高性能AI股票预测分析报告 - 2025年11月27日
  • 增强AI股票预测分析报告 - 2025年11月27日
  • AI股票预测分析报告 - 2025年11月27日
  • Bazzite:专为游戏打造的即用型操作系统
  • 大盘风险控制策略分析报告 - 2025年11月27日
  • 使用spaCy构建可定制NLP管道
  • ant-design中a-table获取多分页中选择、全选问题处理
  • ant-design中a-table前端分页
  • hyx_蓝桥杯C++学习_系列二
  • 【Java学习】【Java基础】--第3篇:初学模板方法模式和策略模式
  • 节奏
  • mysql常用到的面试问题
  • 充电模块厂家哪家好,2025充电模块厂家权威榜单
  • 电源模块厂家哪家好,2025电源模块厂家公司盘点
  • 2025杭州办公室保洁哪家好?权威排行
  • 杭州公司日常保洁哪家好?2025杭州保洁公司精选榜单
  • 2025杭州商务楼保洁公司推荐综合实力榜单
  • 2025杭州保安公司综合实力榜单
  • 杭州保洁外包服务哪家好?2025精选杭州园区保洁公司榜单
  • 2025杭州专业物业管理公司权威排行