mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-05-07 00:39:00 +08:00
fix(source-generators): 收口PR269剩余review问题
- 修复 Cqrs handler registry 对 Roslyn error type 的直接引用,改走安全的运行时类型查找 - 补充 SchemaConfigGenerator 根 type 非字符串诊断回归与 Cqrs 未解析类型回归测试 - 更新 analyzer-warning-reduction 的 RP-024 跟踪与验证记录
This commit is contained in:
parent
df68cdfd82
commit
4ef9406ee9
@ -253,6 +253,11 @@ public sealed partial class CqrsHandlerRegistryGenerator
|
|||||||
|
|
||||||
private static bool CanReferenceFromGeneratedRegistry(Compilation compilation, ITypeSymbol type)
|
private static bool CanReferenceFromGeneratedRegistry(Compilation compilation, ITypeSymbol type)
|
||||||
{
|
{
|
||||||
|
// Roslyn error symbols stringify to unresolved type names; emitting them via typeof(...) would turn
|
||||||
|
// an existing user-code error into a second generator-produced compile error instead of falling back.
|
||||||
|
if (type.TypeKind == TypeKind.Error)
|
||||||
|
return false;
|
||||||
|
|
||||||
switch (type)
|
switch (type)
|
||||||
{
|
{
|
||||||
case IArrayTypeSymbol arrayType:
|
case IArrayTypeSymbol arrayType:
|
||||||
@ -274,7 +279,7 @@ public sealed partial class CqrsHandlerRegistryGenerator
|
|||||||
case ITypeParameterSymbol:
|
case ITypeParameterSymbol:
|
||||||
return false;
|
return false;
|
||||||
default:
|
default:
|
||||||
// Treat other Roslyn type kinds, such as dynamic or unresolved error types, as referenceable for now.
|
// Treat other Roslyn type kinds, such as dynamic, as referenceable for now.
|
||||||
// If a real-world case proves unsafe, tighten this branch instead of broadening the named-type path above.
|
// If a real-world case proves unsafe, tighten this branch instead of broadening the named-type path above.
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -221,6 +221,7 @@ public sealed class SchemaConfigGenerator : IIncrementalGenerator
|
|||||||
private static bool TryValidateSchemaRoot(string filePath, JsonElement root, out Diagnostic? diagnostic)
|
private static bool TryValidateSchemaRoot(string filePath, JsonElement root, out Diagnostic? diagnostic)
|
||||||
{
|
{
|
||||||
if (!root.TryGetProperty("type", out var rootTypeElement) ||
|
if (!root.TryGetProperty("type", out var rootTypeElement) ||
|
||||||
|
rootTypeElement.ValueKind != JsonValueKind.String ||
|
||||||
!IsSchemaType(rootTypeElement.GetString() ?? string.Empty, "object"))
|
!IsSchemaType(rootTypeElement.GetString() ?? string.Empty, "object"))
|
||||||
{
|
{
|
||||||
diagnostic = Diagnostic.Create(
|
diagnostic = Diagnostic.Create(
|
||||||
|
|||||||
@ -72,6 +72,45 @@ public class SchemaConfigGeneratorTests
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 验证根节点 <c>type</c> 元数据不是字符串时,会返回根对象约束诊断,而不是抛出 JSON 访问异常。
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void Run_Should_Report_Diagnostic_When_Root_Type_Metadata_Is_Not_A_String()
|
||||||
|
{
|
||||||
|
const string source = """
|
||||||
|
namespace TestApp
|
||||||
|
{
|
||||||
|
public sealed class Dummy
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
|
||||||
|
const string schema = """
|
||||||
|
{
|
||||||
|
"type": 123,
|
||||||
|
"required": ["id"],
|
||||||
|
"properties": {
|
||||||
|
"id": { "type": "integer" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
|
||||||
|
var result = SchemaGeneratorTestDriver.Run(
|
||||||
|
source,
|
||||||
|
("monster.schema.json", schema));
|
||||||
|
|
||||||
|
var diagnostic = result.Results.Single().Diagnostics.Single();
|
||||||
|
|
||||||
|
Assert.Multiple(() =>
|
||||||
|
{
|
||||||
|
Assert.That(diagnostic.Id, Is.EqualTo("GF_ConfigSchema_002"));
|
||||||
|
Assert.That(diagnostic.Severity, Is.EqualTo(DiagnosticSeverity.Error));
|
||||||
|
Assert.That(diagnostic.GetMessage(), Does.Contain("monster.schema.json"));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 验证 schema 文件名若生成无效根类型标识符时,会在生成前产生命名明确的诊断。
|
/// 验证 schema 文件名若生成无效根类型标识符时,会在生成前产生命名明确的诊断。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@ -1375,6 +1375,103 @@ public class CqrsHandlerRegistryGeneratorTests
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 验证 handler 合同里出现未解析错误类型时,生成器会改为运行时精确查找该类型,
|
||||||
|
/// 而不会把无效类型名直接写进生成代码中的 <c>typeof(...)</c>。
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void Emits_Runtime_Type_Lookup_When_Handler_Contract_Contains_Unresolved_Error_Types()
|
||||||
|
{
|
||||||
|
const string source = """
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Microsoft.Extensions.DependencyInjection
|
||||||
|
{
|
||||||
|
public interface IServiceCollection { }
|
||||||
|
|
||||||
|
public static class ServiceCollectionServiceExtensions
|
||||||
|
{
|
||||||
|
public static void AddTransient(IServiceCollection services, Type serviceType, Type implementationType) { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace GFramework.Core.Abstractions.Logging
|
||||||
|
{
|
||||||
|
public interface ILogger
|
||||||
|
{
|
||||||
|
void Debug(string msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace GFramework.Cqrs.Abstractions.Cqrs
|
||||||
|
{
|
||||||
|
public interface IRequest<TResponse> { }
|
||||||
|
public interface INotification { }
|
||||||
|
public interface IStreamRequest<TResponse> { }
|
||||||
|
|
||||||
|
public interface IRequestHandler<in TRequest, TResponse> where TRequest : IRequest<TResponse> { }
|
||||||
|
public interface INotificationHandler<in TNotification> where TNotification : INotification { }
|
||||||
|
public interface IStreamRequestHandler<in TRequest, out TResponse> where TRequest : IStreamRequest<TResponse> { }
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace GFramework.Cqrs
|
||||||
|
{
|
||||||
|
public interface ICqrsHandlerRegistry
|
||||||
|
{
|
||||||
|
void Register(Microsoft.Extensions.DependencyInjection.IServiceCollection services, GFramework.Core.Abstractions.Logging.ILogger logger);
|
||||||
|
}
|
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
|
||||||
|
public sealed class CqrsHandlerRegistryAttribute : Attribute
|
||||||
|
{
|
||||||
|
public CqrsHandlerRegistryAttribute(Type registryType) { }
|
||||||
|
}
|
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Assembly)]
|
||||||
|
public sealed class CqrsReflectionFallbackAttribute : Attribute
|
||||||
|
{
|
||||||
|
public CqrsReflectionFallbackAttribute(params string[] fallbackHandlerTypeNames) { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace TestApp
|
||||||
|
{
|
||||||
|
using GFramework.Cqrs.Abstractions.Cqrs;
|
||||||
|
|
||||||
|
public sealed record BrokenRequest() : IRequest<MissingResponse>;
|
||||||
|
|
||||||
|
public sealed class BrokenHandler : IRequestHandler<BrokenRequest, MissingResponse>
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
|
||||||
|
var execution = ExecuteGenerator(source);
|
||||||
|
var inputCompilationErrors = execution.InputCompilationDiagnostics
|
||||||
|
.Where(static diagnostic => diagnostic.Severity == DiagnosticSeverity.Error)
|
||||||
|
.ToArray();
|
||||||
|
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(inputCompilationErrors.Select(static diagnostic => diagnostic.Id), Does.Contain("CS0246"));
|
||||||
|
Assert.That(generatedCompilationErrors, Is.Empty);
|
||||||
|
Assert.That(generatorErrors, Is.Empty);
|
||||||
|
Assert.That(execution.GeneratedSources, Has.Length.EqualTo(1));
|
||||||
|
Assert.That(
|
||||||
|
execution.GeneratedSources[0].content,
|
||||||
|
Does.Contain("registryAssembly.GetType(\"MissingResponse\", throwOnError: false, ignoreCase: false);"));
|
||||||
|
Assert.That(
|
||||||
|
execution.GeneratedSources[0].content,
|
||||||
|
Does.Contain("internal sealed class __GFrameworkGeneratedCqrsHandlerRegistry"));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 验证当 fallback metadata 仍然必需且 runtime 提供了承载契约时,
|
/// 验证当 fallback metadata 仍然必需且 runtime 提供了承载契约时,
|
||||||
/// 生成器会继续产出注册器并发射程序集级 <c>CqrsReflectionFallbackAttribute</c>。
|
/// 生成器会继续产出注册器并发射程序集级 <c>CqrsReflectionFallbackAttribute</c>。
|
||||||
|
|||||||
@ -7,8 +7,8 @@
|
|||||||
|
|
||||||
## 当前恢复点
|
## 当前恢复点
|
||||||
|
|
||||||
- 恢复点编号:`ANALYZER-WARNING-REDUCTION-RP-023`
|
- 恢复点编号:`ANALYZER-WARNING-REDUCTION-RP-024`
|
||||||
- 当前阶段:`Phase 23`
|
- 当前阶段:`Phase 24`
|
||||||
- 当前焦点:
|
- 当前焦点:
|
||||||
- 已完成 `GFramework.Core` 当前 `MA0016` / `MA0002` / `MA0015` / `MA0077` 低风险收口批次
|
- 已完成 `GFramework.Core` 当前 `MA0016` / `MA0002` / `MA0015` / `MA0077` 低风险收口批次
|
||||||
- 已复核 `net10.0` 下的 `MA0158` 基线:`GFramework.Core` / `GFramework.Cqrs` 当前共有 `16` 个 object lock
|
- 已复核 `net10.0` 下的 `MA0158` 基线:`GFramework.Core` / `GFramework.Cqrs` 当前共有 `16` 个 object lock
|
||||||
@ -25,6 +25,8 @@
|
|||||||
`FilterConfiguration` 的公共 API 兼容形状,并将 analyzer 兼容性处理收敛到局部 pragma
|
`FilterConfiguration` 的公共 API 兼容形状,并将 analyzer 兼容性处理收敛到局部 pragma
|
||||||
- 已完成当前 PR #269 第三轮 follow-up:继续收口 `SchemaConfigGenerator` 的根类型标识符校验与 XML 文档转义,
|
- 已完成当前 PR #269 第三轮 follow-up:继续收口 `SchemaConfigGenerator` 的根类型标识符校验与 XML 文档转义,
|
||||||
并补齐 `LoggingConfigurationTests`、`CollectionExtensionsTests`、`Cqrs` helper 抽取与 `ai-plan` 命令文本修正
|
并补齐 `LoggingConfigurationTests`、`CollectionExtensionsTests`、`Cqrs` helper 抽取与 `ai-plan` 命令文本修正
|
||||||
|
- 已完成当前 PR #269 第四轮 follow-up:将 `CqrsHandlerRegistryGenerator` 的 Roslyn error type 直接引用改为
|
||||||
|
运行时精确查找路径,并为 `SchemaConfigGenerator` 补上根 `type` 非字符串时的防御与回归测试
|
||||||
- `CoroutineScheduler` 的 tag/group 字典已显式使用 `StringComparer.Ordinal`,保持既有区分大小写语义
|
- `CoroutineScheduler` 的 tag/group 字典已显式使用 `StringComparer.Ordinal`,保持既有区分大小写语义
|
||||||
- `EasyEvents.AddEvent<T>()` 的重复注册路径已恢复为 `ArgumentException`,以保持既有异常契约
|
- `EasyEvents.AddEvent<T>()` 的重复注册路径已恢复为 `ArgumentException`,以保持既有异常契约
|
||||||
- `Option<T>` 已声明 `IEquatable<Option<T>>`,与已有强类型 `Equals(Option<T>)` 契约对齐
|
- `Option<T>` 已声明 `IEquatable<Option<T>>`,与已有强类型 `Equals(Option<T>)` 契约对齐
|
||||||
@ -59,6 +61,8 @@
|
|||||||
- 已完成当前 PR #269 的 review follow-up:收口 `ContextAwareGenerator` 的字段命名冲突 / 锁内读取契约、
|
- 已完成当前 PR #269 的 review follow-up:收口 `ContextAwareGenerator` 的字段命名冲突 / 锁内读取契约、
|
||||||
`CqrsHandlerRegistryGenerator` 的运行时类型 null 防御与超大文件拆分、`SchemaConfigGenerator` 的取消语义,
|
`CqrsHandlerRegistryGenerator` 的运行时类型 null 防御与超大文件拆分、`SchemaConfigGenerator` 的取消语义,
|
||||||
并恢复 `EasyEvents` / `CollectionExtensions` / logging 配置模型的公共 API 兼容形状
|
并恢复 `EasyEvents` / `CollectionExtensions` / logging 配置模型的公共 API 兼容形状
|
||||||
|
- 已完成当前 PR #269 的第四轮 review follow-up:确认 5 个 latest-head 未解决线程中仅剩 2 个本地仍成立,
|
||||||
|
已分别在 `CqrsHandlerRegistryGenerator` 与 `SchemaConfigGenerator` 中收口,并补齐定向 generator regression tests
|
||||||
- 已完成 `GFramework.Game.SourceGenerators` 中 `SchemaConfigGenerator` 的第一批 `MA0051` 收口;warnings-only 基线剩余 `9` 条
|
- 已完成 `GFramework.Game.SourceGenerators` 中 `SchemaConfigGenerator` 的第一批 `MA0051` 收口;warnings-only 基线剩余 `9` 条
|
||||||
`MA0051`
|
`MA0051`
|
||||||
|
|
||||||
@ -110,6 +114,9 @@
|
|||||||
- `RP-023` 继续复核 PR #269 剩余 nitpick/outside-diff 项,确认仍成立的项集中在 `SchemaConfigGenerator` 根类型名校验、
|
- `RP-023` 继续复核 PR #269 剩余 nitpick/outside-diff 项,确认仍成立的项集中在 `SchemaConfigGenerator` 根类型名校验、
|
||||||
aggregate registration comparer XML 文档转义、logging / collection 反射测试补强,以及跟踪文档中的
|
aggregate registration comparer XML 文档转义、logging / collection 反射测试补强,以及跟踪文档中的
|
||||||
`RestoreFallbackFolders=""` 可复制性问题
|
`RestoreFallbackFolders=""` 可复制性问题
|
||||||
|
- `RP-024` 使用 `$gframework-pr-review` 继续复核 PR #269 latest-head unresolved threads,确认 `EasyEvents` 异常契约、
|
||||||
|
`SchemaConfigGenerator` 取消传播与 `ContextAwareGenerator` 快照冲突线程均已在本地收口,仅剩 `Cqrs` error type
|
||||||
|
直接引用与根 schema `type` 非字符串防御仍成立;现已补齐实现与回归测试
|
||||||
- 当前工作树分支 `fix/analyzer-warning-reduction-batch` 已在 `ai-plan/public/README.md` 建立 topic 映射
|
- 当前工作树分支 `fix/analyzer-warning-reduction-batch` 已在 `ai-plan/public/README.md` 建立 topic 映射
|
||||||
|
|
||||||
## 当前风险
|
## 当前风险
|
||||||
|
|||||||
@ -1,5 +1,37 @@
|
|||||||
# Analyzer Warning Reduction 追踪
|
# Analyzer Warning Reduction 追踪
|
||||||
|
|
||||||
|
## 2026-04-22 — RP-024
|
||||||
|
|
||||||
|
### 阶段:PR #269 第四轮 review follow-up 收口(RP-024)
|
||||||
|
|
||||||
|
- 启动复核:
|
||||||
|
- 延续 `$gframework-pr-review` 对 PR #269 latest-head unresolved threads 的复核,重点核对最新 5 个未解决线程是否仍与当前
|
||||||
|
worktree 一致
|
||||||
|
- 本地确认 `EasyEvents` 异常契约、`SchemaConfigGenerator` 取消传播与 `ContextAwareGenerator` 字段冲突线程已是陈旧信号,
|
||||||
|
真正仍成立的仅剩 `CqrsHandlerRegistryGenerator` 的 Roslyn error type 直接引用,以及根 schema `type` 非字符串时的
|
||||||
|
`GetString()` 防御
|
||||||
|
- 决策:
|
||||||
|
- `CqrsHandlerRegistryGenerator` 保持现有“优先精确重建、必要时退回运行时查找”的设计,不引入新的程序集级 fallback 契约分支;
|
||||||
|
只在 `CanReferenceFromGeneratedRegistry(...)` 中显式拒绝 `TypeKind.Error`,让未解析类型走已有运行时查找路径
|
||||||
|
- `SchemaConfigGenerator` 继续沿用现有 `GF_ConfigSchema_002` 诊断,不新增诊断 ID;仅在根对象校验入口补上
|
||||||
|
`JsonValueKind.String` 前置判断
|
||||||
|
- 实施调整:
|
||||||
|
- 为 `CqrsHandlerRegistryGenerator.RuntimeTypeReferences` 增加 `TypeKind.Error` 防御,避免把未解析类型写成生成代码里的
|
||||||
|
`typeof(...)`
|
||||||
|
- 为 `SchemaConfigGeneratorTests` 补上根 `type` 为数字时返回 `GF_ConfigSchema_002` 的回归测试
|
||||||
|
- 为 `CqrsHandlerRegistryGeneratorTests` 补上未解析 error type 会改走运行时 `GetType(...)` 精确查找的回归测试
|
||||||
|
- 验证结果:
|
||||||
|
- `dotnet build GFramework.Cqrs.SourceGenerators/GFramework.Cqrs.SourceGenerators.csproj -c Release --no-restore -p:RestoreFallbackFolders="" -v minimal`
|
||||||
|
- 结果:`0 Warning(s)`,`0 Error(s)`
|
||||||
|
- `dotnet build GFramework.Game.SourceGenerators/GFramework.Game.SourceGenerators.csproj -c Release --no-restore -p:RestoreFallbackFolders="" -v minimal`
|
||||||
|
- 结果:通过;仍保留既有 `9` 条 `SchemaConfigGenerator.cs` `MA0051`
|
||||||
|
- `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --no-restore --filter "FullyQualifiedName~SchemaConfigGeneratorTests.Run_Should_Report_Diagnostic_When_Root_Type_Metadata_Is_Not_A_String|FullyQualifiedName~CqrsHandlerRegistryGeneratorTests.Emits_Runtime_Type_Lookup_When_Handler_Contract_Contains_Unresolved_Error_Types" -m:1 -p:RestoreFallbackFolders="" -v minimal`
|
||||||
|
- 结果:`2 Passed`,`0 Failed`
|
||||||
|
- 说明:测试命令需在无沙箱环境下运行,因为当前 test host 在沙箱内创建本地 socket 会收到 `Permission denied`
|
||||||
|
- 下一步建议:
|
||||||
|
- 若继续压缩 PR #269 的 review backlog,可再次抓取最新 unresolved threads,确认 GitHub 上仅剩陈旧线程后再决定是否继续代码改动
|
||||||
|
- 若回到 analyzer 主线,继续推进 `SchemaConfigGenerator.cs` 剩余 `MA0051`
|
||||||
|
|
||||||
## 2026-04-22 — RP-023
|
## 2026-04-22 — RP-023
|
||||||
|
|
||||||
### 阶段:PR #269 第三轮 review follow-up 收口(RP-023)
|
### 阶段:PR #269 第三轮 review follow-up 收口(RP-023)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user