diff --git a/GFramework.Cqrs.SourceGenerators/Cqrs/CqrsHandlerRegistryGenerator.SourceEmission.cs b/GFramework.Cqrs.SourceGenerators/Cqrs/CqrsHandlerRegistryGenerator.SourceEmission.cs index 3391e6ad..4db622cb 100644 --- a/GFramework.Cqrs.SourceGenerators/Cqrs/CqrsHandlerRegistryGenerator.SourceEmission.cs +++ b/GFramework.Cqrs.SourceGenerators/Cqrs/CqrsHandlerRegistryGenerator.SourceEmission.cs @@ -8,15 +8,12 @@ public sealed partial class CqrsHandlerRegistryGenerator /// /// 生成程序集级 CQRS handler 注册器源码。 /// - /// - /// 当前轮次的生成环境,用于决定 runtime 是否提供 CqrsReflectionFallbackAttribute 契约,以及是否需要在输出中发射对应的程序集级元数据。 - /// /// /// 已整理并排序的 handler 注册描述。方法会据此生成 CqrsHandlerRegistry.g.cs,其中包含直接注册、实现类型反射注册、精确运行时类型查找等分支。 /// /// /// 当前轮次选定的程序集级 reflection fallback 元数据发射策略。 - /// 调用方必须先确保:若该策略包含 fallback handlers,则 已声明支持对应的 fallback attribute 契约; + /// 调用方必须先确保:若该策略包含 fallback handlers,则当前 runtime 已声明支持对应的 fallback attribute 契约; /// 否则应在进入本方法前报告诊断并放弃生成,而不是输出会静默漏注册的半成品注册器。 /// /// 完整的注册器源代码文本。 @@ -28,13 +25,12 @@ public sealed partial class CqrsHandlerRegistryGenerator /// 该方法本身不报告诊断;“fallback 必需但 runtime 契约缺失”的错误由调用方在进入本方法前处理。 /// private static string GenerateSource( - GenerationEnvironment generationEnvironment, IReadOnlyList registrations, ReflectionFallbackEmissionSpec reflectionFallbackEmission) { var sourceShape = CreateGeneratedRegistrySourceShape(registrations); var builder = new StringBuilder(); - AppendGeneratedSourcePreamble(builder, generationEnvironment, reflectionFallbackEmission); + AppendGeneratedSourcePreamble(builder, reflectionFallbackEmission); AppendGeneratedRegistryType(builder, registrations, sourceShape); return builder.ToString(); } @@ -68,11 +64,9 @@ public sealed partial class CqrsHandlerRegistryGenerator /// 发射生成文件头、nullable 指令以及注册器所需的程序集级元数据特性。 /// /// 生成源码构造器。 - /// 当前轮次的生成环境。 /// 需要写入程序集级 reflection fallback 特性的元数据策略。 private static void AppendGeneratedSourcePreamble( StringBuilder builder, - GenerationEnvironment generationEnvironment, ReflectionFallbackEmissionSpec reflectionFallbackEmission) { builder.AppendLine("// "); @@ -102,10 +96,14 @@ public sealed partial class CqrsHandlerRegistryGenerator StringBuilder builder, ReflectionFallbackEmissionSpec reflectionFallbackEmission) { - foreach (var attributeEmission in reflectionFallbackEmission.Attributes) + for (var index = 0; index < reflectionFallbackEmission.Attributes.Length; index++) { - AppendReflectionFallbackAttribute(builder, attributeEmission); - builder.AppendLine(); + if (index > 0) + { + builder.AppendLine(); + } + + AppendReflectionFallbackAttribute(builder, reflectionFallbackEmission.Attributes[index]); } } diff --git a/GFramework.Cqrs.SourceGenerators/Cqrs/CqrsHandlerRegistryGenerator.cs b/GFramework.Cqrs.SourceGenerators/Cqrs/CqrsHandlerRegistryGenerator.cs index 688fbc9a..5ebe31b0 100644 --- a/GFramework.Cqrs.SourceGenerators/Cqrs/CqrsHandlerRegistryGenerator.cs +++ b/GFramework.Cqrs.SourceGenerators/Cqrs/CqrsHandlerRegistryGenerator.cs @@ -296,7 +296,7 @@ public sealed partial class CqrsHandlerRegistryGenerator : IIncrementalGenerator context.AddSource( HintName, - GenerateSource(generationEnvironment, registrations, reflectionFallbackEmission)); + GenerateSource(registrations, reflectionFallbackEmission)); } /// diff --git a/GFramework.Cqrs.Tests/Cqrs/ReflectionFallbackNotificationContainer.cs b/GFramework.Cqrs.Tests/Cqrs/ReflectionFallbackNotificationContainer.cs index c80465ce..d54f0dfd 100644 --- a/GFramework.Cqrs.Tests/Cqrs/ReflectionFallbackNotificationContainer.cs +++ b/GFramework.Cqrs.Tests/Cqrs/ReflectionFallbackNotificationContainer.cs @@ -13,6 +13,9 @@ internal sealed class ReflectionFallbackNotificationContainer /// /// 获取可被直接引用、适合通过 元数据补扫的处理器类型。 /// + /// + /// 可被生成注册器直接引用的 fallback 处理器类型,用于验证 runtime 会优先消费 元数据。 + /// public static Type DirectFallbackHandlerType => typeof(DirectFallbackGeneratedRegistryNotificationHandler); /// diff --git a/GFramework.SourceGenerators.Tests/Cqrs/CqrsHandlerRegistryGeneratorTests.cs b/GFramework.SourceGenerators.Tests/Cqrs/CqrsHandlerRegistryGeneratorTests.cs index 8b5b1b56..78441c5b 100644 --- a/GFramework.SourceGenerators.Tests/Cqrs/CqrsHandlerRegistryGeneratorTests.cs +++ b/GFramework.SourceGenerators.Tests/Cqrs/CqrsHandlerRegistryGeneratorTests.cs @@ -1876,6 +1876,7 @@ public class CqrsHandlerRegistryGeneratorTests var generatorErrors = execution.GeneratorDiagnostics .Where(static diagnostic => diagnostic.Severity == DiagnosticSeverity.Error) .ToArray(); + var generatedSource = execution.GeneratedSources[0].content; Assert.Multiple(() => { @@ -1885,13 +1886,24 @@ public class CqrsHandlerRegistryGeneratorTests Assert.That(execution.GeneratedSources, Has.Length.EqualTo(1)); Assert.That(execution.GeneratedSources[0].filename, Is.EqualTo("CqrsHandlerRegistry.g.cs")); Assert.That( - execution.GeneratedSources[0].content, + generatedSource, Does.Contain( "[assembly: global::GFramework.Cqrs.CqrsReflectionFallbackAttribute(typeof(global::TestApp.Container.AlphaHandler), typeof(global::TestApp.Container.BetaHandler))]")); Assert.That( - execution.GeneratedSources[0].content, + generatedSource, Does.Not.Contain( "[assembly: global::GFramework.Cqrs.CqrsReflectionFallbackAttribute(\"TestApp.Container+AlphaHandler\", \"TestApp.Container+BetaHandler\")]")); + Assert.That(generatedSource, Does.Not.Contain("CqrsReflectionFallbackAttribute()")); + Assert.That( + CountOccurrences( + generatedSource, + "[assembly: global::GFramework.Cqrs.CqrsReflectionFallbackAttribute"), + Is.EqualTo(1)); + Assert.That( + CountOccurrences( + generatedSource, + "[assembly: global::GFramework.Cqrs.CqrsReflectionFallbackAttribute(\""), + Is.Zero); }); } @@ -1915,6 +1927,7 @@ public class CqrsHandlerRegistryGeneratorTests var generatorErrors = execution.GeneratorDiagnostics .Where(static diagnostic => diagnostic.Severity == DiagnosticSeverity.Error) .ToArray(); + var generatedSource = execution.GeneratedSources[0].content; Assert.Multiple(() => { @@ -1924,13 +1937,32 @@ public class CqrsHandlerRegistryGeneratorTests Assert.That(execution.GeneratedSources, Has.Length.EqualTo(1)); Assert.That(execution.GeneratedSources[0].filename, Is.EqualTo("CqrsHandlerRegistry.g.cs")); Assert.That( - execution.GeneratedSources[0].content, + generatedSource, Does.Contain( "[assembly: global::GFramework.Cqrs.CqrsReflectionFallbackAttribute(typeof(global::TestApp.Container.AlphaHandler))]")); Assert.That( - execution.GeneratedSources[0].content, + generatedSource, Does.Contain( "[assembly: global::GFramework.Cqrs.CqrsReflectionFallbackAttribute(\"TestApp.Container+BetaHandler\")]")); + Assert.That(generatedSource, Does.Not.Contain("CqrsReflectionFallbackAttribute()")); + Assert.That( + CountOccurrences( + generatedSource, + "[assembly: global::GFramework.Cqrs.CqrsReflectionFallbackAttribute"), + Is.EqualTo(2)); + Assert.That( + CountOccurrences( + generatedSource, + "[assembly: global::GFramework.Cqrs.CqrsReflectionFallbackAttribute(\""), + Is.EqualTo(1)); + Assert.That( + generatedSource, + Does.Contain( + "[assembly: global::GFramework.Cqrs.CqrsReflectionFallbackAttribute(typeof(global::TestApp.Container.AlphaHandler))]" + + Environment.NewLine + + "[assembly: global::GFramework.Cqrs.CqrsReflectionFallbackAttribute(\"TestApp.Container+BetaHandler\")]" + + Environment.NewLine + + "[assembly: global::GFramework.Cqrs.CqrsHandlerRegistryAttribute(typeof(global::GFramework.Generated.Cqrs.__GFrameworkGeneratedCqrsHandlerRegistry))]")); }); } @@ -1985,6 +2017,31 @@ public class CqrsHandlerRegistryGeneratorTests return execution.GeneratedSources[0].content; } + /// + /// 统计生成源码中某个固定片段的出现次数,用于锁定程序集级 fallback 特性的发射个数。 + /// + /// 待统计的完整生成源码。 + /// 需要计数的固定片段。 + /// 中出现的次数。 + private static int CountOccurrences(string text, string value) + { + if (string.IsNullOrEmpty(value)) + throw new ArgumentException("The search value must not be null or empty.", nameof(value)); + + var count = 0; + var startIndex = 0; + + while (true) + { + var nextIndex = text.IndexOf(value, startIndex, global::System.StringComparison.Ordinal); + if (nextIndex < 0) + return count; + + count++; + startIndex = nextIndex + value.Length; + } + } + /// /// 运行 CQRS handler registry generator,并返回生成输出及相关诊断。 /// diff --git a/ai-plan/public/cqrs-rewrite/todos/cqrs-rewrite-migration-tracking.md b/ai-plan/public/cqrs-rewrite/todos/cqrs-rewrite-migration-tracking.md index e6905ac8..64365852 100644 --- a/ai-plan/public/cqrs-rewrite/todos/cqrs-rewrite-migration-tracking.md +++ b/ai-plan/public/cqrs-rewrite/todos/cqrs-rewrite-migration-tracking.md @@ -66,6 +66,11 @@ CQRS 迁移与收敛。 - 当本轮 fallback 同时包含可直接引用与仅能按名称恢复的 handlers,且 runtime 同时支持 `Type[]`、`string[]` 和多实例特性时,生成器会拆分输出两段 fallback 元数据 - `GFramework.Cqrs.Tests` 已补充 mixed fallback metadata 回归,锁定 registrar 只对字符串条目执行定向 `Assembly.GetType(...)` - `GFramework.SourceGenerators.Tests` 已补充 mixed fallback emission 回归,锁定 generator 会输出两个程序集级 fallback 特性实例而不是整体退回字符串 +- `2026-04-29` 已重新执行 `$gframework-pr-review`: + - 当前分支对应 `PR #302`,状态为 `OPEN` + - latest reviewed commit 当前剩余 `3` 条 open AI review threads:`2` 条 Greptile、`1` 条 CodeRabbit + - 本地核对后确认 `dotnet-format` 仍只有 `Restore operation failed` 噪音,没有附带当前仍成立的文件级格式诊断 + - 已按 review triage 修正 generator source preamble 的多实例 fallback 特性排版、移除死参数,并补强 mixed/direct fallback 发射回归断言与 XML 文档 - 当前主线优先级: - generator 覆盖面继续扩大 - dispatch/invoker 反射占比继续下降 @@ -116,6 +121,18 @@ CQRS 迁移与收敛。 - `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --filter "FullyQualifiedName~CqrsHandlerRegistryGeneratorTests"` - 结果:通过 - 备注:`18/18` 测试通过;本轮覆盖 mixed fallback metadata 的双特性发射路径 +- `python3 .agents/skills/gframework-pr-review/scripts/fetch_current_pr_review.py --json-output /tmp/gframework-pr-review.json` + - 结果:通过 + - 备注:确认当前分支对应 `PR #302`;latest head review 仍有 `3` 条 open AI threads,其中 MegaLinter 仅报告 `dotnet-format` restore failure 噪音 +- `dotnet build GFramework.Cqrs.SourceGenerators/GFramework.Cqrs.SourceGenerators.csproj -c Release` + - 结果:通过 + - 备注:`0 warning / 0 error` +- `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --filter "FullyQualifiedName~CqrsHandlerRegistryGeneratorTests"` + - 结果:通过 + - 备注:`18/18` 测试通过;本轮直接覆盖 fallback preamble 排版与特性个数断言收紧 +- `dotnet test GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj -c Release --filter "FullyQualifiedName~GFramework.Cqrs.Tests.Cqrs.CqrsHandlerRegistrarTests"` + - 结果:通过 + - 备注:`13/13` 测试通过;本轮确认 mixed fallback metadata 的 registrar 消费路径未回归 ## 下一步 diff --git a/ai-plan/public/cqrs-rewrite/traces/cqrs-rewrite-migration-trace.md b/ai-plan/public/cqrs-rewrite/traces/cqrs-rewrite-migration-trace.md index 33aa44fa..cb008db1 100644 --- a/ai-plan/public/cqrs-rewrite/traces/cqrs-rewrite-migration-trace.md +++ b/ai-plan/public/cqrs-rewrite/traces/cqrs-rewrite-migration-trace.md @@ -32,6 +32,22 @@ - `13/13` passed - `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --filter "FullyQualifiedName~CqrsHandlerRegistryGeneratorTests"` - `18/18` passed +- 随后按 `$gframework-pr-review` 重新拉取当前分支 PR 审查数据: + - 当前 worktree `feat/cqrs-optimization` 已对应 `PR #302` + - latest head commit 仍有 `3` 条 open AI review threads:Greptile 指向 generator preamble 的死参数与多实例 fallback 特性空行,CodeRabbit 指向 mixed/direct fallback 测试断言过宽 + - MegaLinter 仍只暴露 `dotnet-format` 的 `Restore operation failed`,未给出本地仍成立的格式文件线索,因此按环境噪音处理 +- 本轮已继续收口 `RP-052` 的 follow-up: + - 在 `GFramework.Cqrs.SourceGenerators/Cqrs/CqrsHandlerRegistryGenerator.SourceEmission.cs` 中移除已不再参与判断的 `generationEnvironment` 透传参数 + - 调整多实例 fallback 特性发射时的换行策略,避免最后一个 fallback 特性与 `CqrsHandlerRegistryAttribute` 之间保留多余空行 + - 在 `GFramework.SourceGenerators.Tests/Cqrs/CqrsHandlerRegistryGeneratorTests.cs` 中补强 direct/mixed fallback 发射断言,锁定特性实例个数、拒绝空 marker,并确保 mixed 场景的程序集级 preamble 排版稳定 + - 在 `GFramework.Cqrs.Tests/Cqrs/ReflectionFallbackNotificationContainer.cs` 中为 `DirectFallbackHandlerType` 补齐 `` XML 文档 +- 本轮 review follow-up 验证已通过: + - `dotnet build GFramework.Cqrs.SourceGenerators/GFramework.Cqrs.SourceGenerators.csproj -c Release` + - `0 warning / 0 error` + - `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --filter "FullyQualifiedName~CqrsHandlerRegistryGeneratorTests"` + - `18/18` passed + - `dotnet test GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj -c Release --filter "FullyQualifiedName~GFramework.Cqrs.Tests.Cqrs.CqrsHandlerRegistrarTests"` + - `13/13` passed ## 2026-04-20