From b7a476456a377c9dd7b9c56dba3ae71691aeadec Mon Sep 17 00:00:00 2001
From: GeWuYou <95328647+GeWuYou@users.noreply.github.com>
Date: Fri, 17 Apr 2026 12:58:34 +0800
Subject: [PATCH] =?UTF-8?q?feat(cqrs):=20=E4=B8=BACQRS=E5=A4=84=E7=90=86?=
=?UTF-8?q?=E5=99=A8=E6=B3=A8=E5=86=8C=E7=94=9F=E6=88=90=E5=99=A8=E6=B7=BB?=
=?UTF-8?q?=E5=8A=A0=E8=AF=A6=E7=BB=86=E6=96=87=E6=A1=A3=E6=B3=A8=E9=87=8A?=
=?UTF-8?q?=E5=B9=B6=E6=94=B9=E8=BF=9B=E6=B5=8B=E8=AF=95=E9=AA=8C=E8=AF=81?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
添加了完整的XML文档注释到Execute方法,详细说明了CQRS处理器注册生成器的执行流程、参数含义、实现逻辑和注意事项。同时改进了测试框架,在GeneratorExecutionResult中分离了生成代码的编译诊断,使测试能够更精确地验证生成代码的质量。
---
.../Cqrs/CqrsHandlerRegistryGenerator.cs | 36 ++++++++++++++++++-
.../Cqrs/CqrsHandlerRegistryGeneratorTests.cs | 26 ++++++++++++--
2 files changed, 58 insertions(+), 4 deletions(-)
diff --git a/GFramework.Cqrs.SourceGenerators/Cqrs/CqrsHandlerRegistryGenerator.cs b/GFramework.Cqrs.SourceGenerators/Cqrs/CqrsHandlerRegistryGenerator.cs
index 5be2c5ab..6421e0ca 100644
--- a/GFramework.Cqrs.SourceGenerators/Cqrs/CqrsHandlerRegistryGenerator.cs
+++ b/GFramework.Cqrs.SourceGenerators/Cqrs/CqrsHandlerRegistryGenerator.cs
@@ -155,7 +155,41 @@ public sealed class CqrsHandlerRegistryGenerator : IIncrementalGenerator
reflectionFallbackHandlerTypeMetadataName);
}
- private static void Execute(SourceProductionContext context, GenerationEnvironment generationEnvironment,
+ ///
+ /// 执行 CQRS handler registry 生成管线的最终发射阶段,负责将候选 handler 分析结果汇总为单个
+ /// CqrsHandlerRegistry.g.cs,并在需要时附带程序集级 reflection fallback 元数据。
+ ///
+ /// 用于报告诊断并发射生成源码的源生产上下文。
+ ///
+ /// 当前编译轮次可用的 runtime 合同快照。
+ /// 只有当 CQRS 注册器生成所需的基础契约齐备时,才允许继续生成;当存在
+ /// CqrsReflectionFallbackAttribute 时,才允许输出依赖 fallback 元数据恢复的注册结果。
+ ///
+ ///
+ /// 来自语法和语义分析阶段的 handler 候选结果。
+ /// 集合中可能包含 占位项,且同一实现类型可能因 partial 声明重复出现,后续会统一去重并聚合。
+ ///
+ ///
+ ///
+ /// 该方法负责发射两类生成结果:注册器类型本体,以及在静态类型信息不足时用于运行时补全注册的程序集级
+ /// CqrsReflectionFallbackAttribute 元数据。生成这些结果的目标是把可静态确定的 handler 注册尽量前移到编译期,
+ /// 从而减少运行时程序集扫描成本,同时保留对少数复杂类型形态的兼容回退路径。
+ ///
+ ///
+ /// 该阶段依赖两个语义前提:一是 runtime 已提供 CQRS 注册器生成所需的基础合同;二是只要存在任何 handler
+ /// 需要通过 reflection fallback 恢复,就必须同时存在承载该元数据的
+ /// CqrsReflectionFallbackAttribute。如果基础合同缺失,生成器会静默跳过本轮发射;如果候选集合去重后没有任何可注册
+ /// handler,也会直接跳过 AddSource,避免输出空注册器。
+ ///
+ ///
+ /// 当 fallback handler 元数据非空但 runtime 缺少 CqrsReflectionFallbackAttribute 时,
+ /// 该方法会报告 GF_Cqrs_001 并停止发射源码。这样可以避免生成一个表面可用、但会静默漏掉部分 handler 注册的半成品
+ /// registry。只有在静态注册结果与 fallback 契约同时成立时,才允许调用 AddSource。
+ ///
+ ///
+ private static void Execute(
+ SourceProductionContext context,
+ GenerationEnvironment generationEnvironment,
ImmutableArray candidates)
{
if (!generationEnvironment.GenerationEnabled)
diff --git a/GFramework.SourceGenerators.Tests/Cqrs/CqrsHandlerRegistryGeneratorTests.cs b/GFramework.SourceGenerators.Tests/Cqrs/CqrsHandlerRegistryGeneratorTests.cs
index 4b23a86a..6db73364 100644
--- a/GFramework.SourceGenerators.Tests/Cqrs/CqrsHandlerRegistryGeneratorTests.cs
+++ b/GFramework.SourceGenerators.Tests/Cqrs/CqrsHandlerRegistryGeneratorTests.cs
@@ -1244,6 +1244,9 @@ public class CqrsHandlerRegistryGeneratorTests
var execution = ExecuteGenerator(
source,
allowUnsafe: true);
+ var generatedCompilationErrors = execution.GeneratedCompilationDiagnostics
+ .Where(static diagnostic => diagnostic.Severity == DiagnosticSeverity.Error)
+ .ToArray();
var generatorErrors = execution.GeneratorDiagnostics
.Where(static diagnostic => diagnostic.Severity == DiagnosticSeverity.Error)
.ToArray();
@@ -1252,6 +1255,7 @@ public class CqrsHandlerRegistryGeneratorTests
Assert.Multiple(() =>
{
+ Assert.That(generatedCompilationErrors, Is.Empty);
Assert.That(execution.GeneratedSources, Is.Empty);
Assert.That(missingContractDiagnostic, Is.Not.Null);
Assert.That(
@@ -1355,12 +1359,16 @@ public class CqrsHandlerRegistryGeneratorTests
var execution = ExecuteGenerator(
source,
allowUnsafe: true);
+ var generatedCompilationErrors = execution.GeneratedCompilationDiagnostics
+ .Where(static diagnostic => diagnostic.Severity == DiagnosticSeverity.Error)
+ .ToArray();
var generatorErrors = execution.GeneratorDiagnostics
.Where(static diagnostic => diagnostic.Severity == DiagnosticSeverity.Error)
.ToArray();
Assert.Multiple(() =>
{
+ Assert.That(generatedCompilationErrors, Is.Empty);
Assert.That(generatorErrors, Is.Empty);
Assert.That(execution.GeneratedSources, Has.Length.EqualTo(1));
Assert.That(execution.GeneratedSources[0].filename, Is.EqualTo("CqrsHandlerRegistry.g.cs"));
@@ -1464,14 +1472,24 @@ public class CqrsHandlerRegistryGeneratorTests
var runResult = driver.GetRunResult();
Assert.That(runResult.Results, Has.Length.EqualTo(1));
+ var generatedSyntaxTrees = runResult.Results[0].GeneratedSources
+ .Select(static sourceResult => sourceResult.SyntaxTree)
+ .ToHashSet();
var generatedSources = runResult.Results[0].GeneratedSources
.Select(static sourceResult =>
(filename: sourceResult.HintName, content: sourceResult.SourceText.ToString()))
.ToArray();
+ var compilationDiagnostics = updatedCompilation.GetDiagnostics().ToArray();
+ var generatedCompilationDiagnostics = compilationDiagnostics
+ .Where(diagnostic =>
+ diagnostic.Location.SourceTree is not null &&
+ generatedSyntaxTrees.Contains(diagnostic.Location.SourceTree))
+ .ToArray();
return new GeneratorExecutionResult(
generatedSources,
generatorDiagnostics.ToArray(),
- updatedCompilation.GetDiagnostics().ToArray());
+ compilationDiagnostics,
+ generatedCompilationDiagnostics);
}
///
@@ -1479,9 +1497,11 @@ public class CqrsHandlerRegistryGeneratorTests
///
/// 本轮生成产生的源文件集合。
/// 生成器自身报告的诊断集合。
- /// 将生成结果并回编译后的编译诊断集合。
+ /// 将生成结果并回编译后的完整编译诊断集合。
+ /// 仅来自生成源文件的编译诊断集合。
private sealed record GeneratorExecutionResult(
(string filename, string content)[] GeneratedSources,
Diagnostic[] GeneratorDiagnostics,
- Diagnostic[] CompilationDiagnostics);
+ Diagnostic[] CompilationDiagnostics,
+ Diagnostic[] GeneratedCompilationDiagnostics);
}