mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-05-07 00:39:00 +08:00
fix(source-generators): 收口PR269剩余review与构建规范
- 修复 SchemaConfigGenerator 的归一化字段名冲突诊断,并补充对应 generator 回归测试 - 修复 CqrsHandlerRegistryGenerator 对 dynamic 的运行时类型引用,避免生成非法 typeof(dynamic) - 更新 AGENTS 与 analyzer-warning-reduction 跟踪,明确受影响模块必须独立 build 并处理或显式报告 warning
This commit is contained in:
parent
4ef9406ee9
commit
050f4321c6
@ -31,6 +31,12 @@ All AI agents and contributors must follow these rules when writing, reviewing,
|
||||
- Every completed task MUST pass at least one build validation before it is considered done.
|
||||
- If the task changes multiple projects or shared abstractions, prefer a solution-level or affected-project
|
||||
`dotnet build ... -c Release`; otherwise use the smallest build command that still proves the result compiles.
|
||||
- When a task adds a feature or modifies code, contributors MUST run a Release build for every directly affected
|
||||
module/project instead of relying on an unrelated project or solution slice that does not actually compile the touched
|
||||
code.
|
||||
- Warnings reported by those affected-module builds are part of the task scope. Contributors MUST resolve the touched
|
||||
module's build warnings in the same change, or stop and explicitly report the exact warning IDs and blocker instead of
|
||||
deferring them to a separate long-lived cleanup branch by default.
|
||||
- If the required build passes and there are task-related staged or unstaged changes, contributors MUST create a Git
|
||||
commit automatically instead of leaving the task uncommitted, unless the user explicitly says not to commit.
|
||||
- Commit messages MUST use Conventional Commits format: `<type>(<scope>): <summary>`.
|
||||
|
||||
@ -81,6 +81,14 @@ public sealed partial class CqrsHandlerRegistryGenerator
|
||||
return false;
|
||||
}
|
||||
|
||||
// Roslyn models dynamic as a pseudo-type, but generated C# cannot emit typeof(dynamic).
|
||||
// Normalize it to the CLR runtime type so precise reflected registrations stay compilable.
|
||||
if (type.TypeKind == TypeKind.Dynamic)
|
||||
{
|
||||
runtimeTypeReference = RuntimeTypeReferenceSpec.FromDirectReference("global::System.Object");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (CanReferenceFromGeneratedRegistry(compilation, type))
|
||||
{
|
||||
runtimeTypeReference = RuntimeTypeReferenceSpec.FromDirectReference(
|
||||
@ -255,7 +263,7 @@ public sealed partial class CqrsHandlerRegistryGenerator
|
||||
{
|
||||
// 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)
|
||||
if (type.TypeKind is TypeKind.Error or TypeKind.Dynamic)
|
||||
return false;
|
||||
|
||||
switch (type)
|
||||
@ -279,8 +287,8 @@ public sealed partial class CqrsHandlerRegistryGenerator
|
||||
case ITypeParameterSymbol:
|
||||
return false;
|
||||
default:
|
||||
// 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.
|
||||
// Remaining Roslyn type kinds that reach this branch have already been normalized by earlier guards
|
||||
// and can continue through the direct-reference path without emitting fallback reflection code.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@ -18,3 +18,4 @@
|
||||
GF_ConfigSchema_011 | GFramework.SourceGenerators.Config | Error | ConfigSchemaDiagnostics
|
||||
GF_ConfigSchema_012 | GFramework.SourceGenerators.Config | Error | ConfigSchemaDiagnostics
|
||||
GF_ConfigSchema_013 | GFramework.SourceGenerators.Config | Error | ConfigSchemaDiagnostics
|
||||
GF_ConfigSchema_014 | GFramework.SourceGenerators.Config | Error | ConfigSchemaDiagnostics
|
||||
|
||||
@ -356,20 +356,16 @@ public sealed class SchemaConfigGenerator : IIncrementalGenerator
|
||||
}
|
||||
|
||||
var properties = new List<SchemaPropertySpec>();
|
||||
foreach (var property in propertiesElement.EnumerateObject())
|
||||
{
|
||||
var parsedProperty = ParseProperty(
|
||||
if (!TryParseObjectProperties(
|
||||
filePath,
|
||||
property,
|
||||
requiredProperties.Contains(property.Name),
|
||||
CombinePath(displayPath, property.Name),
|
||||
isDirectChildOfRoot: isRoot);
|
||||
if (parsedProperty.Diagnostic is not null)
|
||||
displayPath,
|
||||
isRoot,
|
||||
propertiesElement,
|
||||
requiredProperties,
|
||||
properties,
|
||||
out var propertyDiagnostic))
|
||||
{
|
||||
return ParsedObjectResult.FromDiagnostic(parsedProperty.Diagnostic);
|
||||
}
|
||||
|
||||
properties.Add(parsedProperty.Property!);
|
||||
return ParsedObjectResult.FromDiagnostic(propertyDiagnostic!);
|
||||
}
|
||||
|
||||
return ParsedObjectResult.FromObject(new SchemaObjectSpec(
|
||||
@ -381,6 +377,60 @@ public sealed class SchemaConfigGenerator : IIncrementalGenerator
|
||||
properties));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 解析对象 schema 的直接子属性,并在进入代码发射前阻止归一化后的属性名冲突落入生成输出。
|
||||
/// </summary>
|
||||
/// <param name="filePath">Schema 文件路径。</param>
|
||||
/// <param name="displayPath">当前对象的逻辑路径。</param>
|
||||
/// <param name="isRoot">当前对象是否为根对象。</param>
|
||||
/// <param name="propertiesElement">对象的 <c>properties</c> JSON 节点。</param>
|
||||
/// <param name="requiredProperties">当前对象声明的必填字段集合。</param>
|
||||
/// <param name="properties">成功时返回的已解析属性列表。</param>
|
||||
/// <param name="diagnostic">解析失败时返回的首个诊断。</param>
|
||||
/// <returns>当所有属性都可安全生成时返回 <see langword="true" />。</returns>
|
||||
private static bool TryParseObjectProperties(
|
||||
string filePath,
|
||||
string displayPath,
|
||||
bool isRoot,
|
||||
JsonElement propertiesElement,
|
||||
ISet<string> requiredProperties,
|
||||
ICollection<SchemaPropertySpec> properties,
|
||||
out Diagnostic? diagnostic)
|
||||
{
|
||||
var schemaKeyByGeneratedPropertyName = new Dictionary<string, string>(StringComparer.Ordinal);
|
||||
foreach (var property in propertiesElement.EnumerateObject())
|
||||
{
|
||||
var propertyDisplayPath = CombinePath(displayPath, property.Name);
|
||||
var parsedProperty = ParseProperty(
|
||||
filePath,
|
||||
property,
|
||||
requiredProperties.Contains(property.Name),
|
||||
propertyDisplayPath,
|
||||
isDirectChildOfRoot: isRoot);
|
||||
if (parsedProperty.Diagnostic is not null)
|
||||
{
|
||||
diagnostic = parsedProperty.Diagnostic;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!TryRegisterGeneratedPropertyName(
|
||||
filePath,
|
||||
propertyDisplayPath,
|
||||
property.Name,
|
||||
parsedProperty.Property!.PropertyName,
|
||||
schemaKeyByGeneratedPropertyName,
|
||||
out diagnostic))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
properties.Add(parsedProperty.Property!);
|
||||
}
|
||||
|
||||
diagnostic = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 解析单个 schema 属性定义。
|
||||
/// </summary>
|
||||
@ -4052,6 +4102,43 @@ public sealed class SchemaConfigGenerator : IIncrementalGenerator
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 记录同一对象节点内已分配的生成属性名,并在 schema key 归一化后发生冲突时返回明确诊断。
|
||||
/// 该校验会在生成器进入源码发射前阻止重复属性、查询方法与索引成员名落入后续编译阶段。
|
||||
/// </summary>
|
||||
/// <param name="filePath">Schema 文件路径。</param>
|
||||
/// <param name="displayPath">当前字段的逻辑路径。</param>
|
||||
/// <param name="schemaName">当前字段的原始 schema key。</param>
|
||||
/// <param name="propertyName">当前字段归一化后的 CLR 属性名。</param>
|
||||
/// <param name="schemaKeyByGeneratedPropertyName">同一对象内已分配的属性名与原始 schema key 对照表。</param>
|
||||
/// <param name="diagnostic">检测到冲突时返回的诊断。</param>
|
||||
/// <returns>当生成属性名在当前对象作用域内唯一时返回 <see langword="true" />。</returns>
|
||||
private static bool TryRegisterGeneratedPropertyName(
|
||||
string filePath,
|
||||
string displayPath,
|
||||
string schemaName,
|
||||
string propertyName,
|
||||
IDictionary<string, string> schemaKeyByGeneratedPropertyName,
|
||||
out Diagnostic? diagnostic)
|
||||
{
|
||||
if (!schemaKeyByGeneratedPropertyName.TryGetValue(propertyName, out var existingSchemaName))
|
||||
{
|
||||
schemaKeyByGeneratedPropertyName.Add(propertyName, schemaName);
|
||||
diagnostic = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
diagnostic = Diagnostic.Create(
|
||||
ConfigSchemaDiagnostics.DuplicateGeneratedIdentifier,
|
||||
CreateFileLocation(filePath),
|
||||
Path.GetFileName(filePath),
|
||||
displayPath,
|
||||
schemaName,
|
||||
propertyName,
|
||||
existingSchemaName);
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将 schema 文件名派生的根实体名验证为生成代码可直接使用的根类型标识符。
|
||||
/// 这里与属性名验证保持一致,避免文件名中的前导数字或其他非法字符把根配置类型/表类型生成为无效 C# 标识符。
|
||||
|
||||
@ -151,4 +151,15 @@ public static class ConfigSchemaDiagnostics
|
||||
SourceGeneratorsConfigCategory,
|
||||
DiagnosticSeverity.Error,
|
||||
true);
|
||||
|
||||
/// <summary>
|
||||
/// schema 字段名在标识符归一化后发生冲突。
|
||||
/// </summary>
|
||||
public static readonly DiagnosticDescriptor DuplicateGeneratedIdentifier = new(
|
||||
"GF_ConfigSchema_014",
|
||||
"Config schema property names collide after C# identifier normalization",
|
||||
"Property '{1}' in schema file '{0}' uses schema key '{2}', which generates duplicate C# identifier '{3}' already produced by schema key '{4}'",
|
||||
SourceGeneratorsConfigCategory,
|
||||
DiagnosticSeverity.Error,
|
||||
true);
|
||||
}
|
||||
|
||||
@ -1975,6 +1975,49 @@ public class SchemaConfigGeneratorTests
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证同一对象内不同 schema key 若归一化后映射到同一属性名,会在生成前直接给出冲突诊断。
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void Run_Should_Report_Diagnostic_When_Schema_Keys_Collide_After_Identifier_Normalization()
|
||||
{
|
||||
const string source = """
|
||||
namespace TestApp
|
||||
{
|
||||
public sealed class Dummy
|
||||
{
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
const string schema = """
|
||||
{
|
||||
"type": "object",
|
||||
"required": ["id"],
|
||||
"properties": {
|
||||
"id": { "type": "integer" },
|
||||
"foo-bar": { "type": "string" },
|
||||
"foo_bar": { "type": "string" }
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
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_014"));
|
||||
Assert.That(diagnostic.Severity, Is.EqualTo(DiagnosticSeverity.Error));
|
||||
Assert.That(diagnostic.GetMessage(), Does.Contain("foo_bar"));
|
||||
Assert.That(diagnostic.GetMessage(), Does.Contain("FooBar"));
|
||||
Assert.That(diagnostic.GetMessage(), Does.Contain("foo-bar"));
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证 schema 顶层允许通过元数据覆盖默认配置目录,并会统一路径分隔符。
|
||||
/// </summary>
|
||||
|
||||
@ -1463,12 +1463,106 @@ public class CqrsHandlerRegistryGeneratorTests
|
||||
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"));
|
||||
var generatedSource = execution.GeneratedSources[0].content;
|
||||
Assert.That(
|
||||
execution.GeneratedSources[0].content,
|
||||
generatedSource,
|
||||
Does.Contain("registryAssembly.GetType(\"MissingResponse\", throwOnError: false, ignoreCase: false);"));
|
||||
Assert.That(
|
||||
execution.GeneratedSources[0].content,
|
||||
generatedSource,
|
||||
Does.Contain("internal sealed class __GFrameworkGeneratedCqrsHandlerRegistry"));
|
||||
Assert.That(generatedSource, Does.Not.Contain("typeof(MissingResponse)"));
|
||||
Assert.That(generatedSource, Does.Not.Contain("CqrsReflectionFallbackAttribute("));
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证 <see langword="dynamic" /> 响应类型会在生成阶段归一化为 <see cref="System.Object" />,
|
||||
/// 避免注册器发射非法的 <c>typeof(dynamic)</c>。
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void Emits_Object_Type_Reference_When_Handler_Response_Uses_Dynamic()
|
||||
{
|
||||
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) { }
|
||||
}
|
||||
}
|
||||
|
||||
namespace TestApp
|
||||
{
|
||||
using GFramework.Cqrs.Abstractions.Cqrs;
|
||||
|
||||
public sealed record DynamicRequest() : IRequest<dynamic>;
|
||||
|
||||
public sealed class DynamicHandler : IRequestHandler<DynamicRequest, dynamic>
|
||||
{
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
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("CS1966"));
|
||||
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"));
|
||||
var generatedSource = execution.GeneratedSources[0].content;
|
||||
Assert.That(generatedSource, Does.Contain("typeof(global::System.Object)"));
|
||||
Assert.That(generatedSource, Does.Not.Contain("typeof(dynamic)"));
|
||||
Assert.That(generatedSource, Does.Contain("internal sealed class __GFrameworkGeneratedCqrsHandlerRegistry"));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -7,8 +7,8 @@
|
||||
|
||||
## 当前恢复点
|
||||
|
||||
- 恢复点编号:`ANALYZER-WARNING-REDUCTION-RP-024`
|
||||
- 当前阶段:`Phase 24`
|
||||
- 恢复点编号:`ANALYZER-WARNING-REDUCTION-RP-025`
|
||||
- 当前阶段:`Phase 25`
|
||||
- 当前焦点:
|
||||
- 已完成 `GFramework.Core` 当前 `MA0016` / `MA0002` / `MA0015` / `MA0077` 低风险收口批次
|
||||
- 已复核 `net10.0` 下的 `MA0158` 基线:`GFramework.Core` / `GFramework.Cqrs` 当前共有 `16` 个 object lock
|
||||
@ -27,6 +27,11 @@
|
||||
并补齐 `LoggingConfigurationTests`、`CollectionExtensionsTests`、`Cqrs` helper 抽取与 `ai-plan` 命令文本修正
|
||||
- 已完成当前 PR #269 第四轮 follow-up:将 `CqrsHandlerRegistryGenerator` 的 Roslyn error type 直接引用改为
|
||||
运行时精确查找路径,并为 `SchemaConfigGenerator` 补上根 `type` 非字符串时的防御与回归测试
|
||||
- 已完成当前 PR #269 第五轮 follow-up:`SchemaConfigGenerator` 补上归一化后属性名冲突诊断并新增
|
||||
`GF_ConfigSchema_014`,`CqrsHandlerRegistryGenerator` 将 `dynamic` 归一化为 `global::System.Object`,
|
||||
同时收紧相关 generator regression tests
|
||||
- 已更新 `AGENTS.md`:变更模块必须运行对应 `dotnet build -c Release`,并处理或显式报告模块构建 warning,
|
||||
不再默认留给长期 warning 清理分支
|
||||
- `CoroutineScheduler` 的 tag/group 字典已显式使用 `StringComparer.Ordinal`,保持既有区分大小写语义
|
||||
- `EasyEvents.AddEvent<T>()` 的重复注册路径已恢复为 `ArgumentException`,以保持既有异常契约
|
||||
- `Option<T>` 已声明 `IEquatable<Option<T>>`,与已有强类型 `Equals(Option<T>)` 契约对齐
|
||||
@ -63,6 +68,8 @@
|
||||
并恢复 `EasyEvents` / `CollectionExtensions` / logging 配置模型的公共 API 兼容形状
|
||||
- 已完成当前 PR #269 的第四轮 review follow-up:确认 5 个 latest-head 未解决线程中仅剩 2 个本地仍成立,
|
||||
已分别在 `CqrsHandlerRegistryGenerator` 与 `SchemaConfigGenerator` 中收口,并补齐定向 generator regression tests
|
||||
- 已完成当前 PR #269 的第五轮 review follow-up:收口 `SchemaConfigGenerator` 的归一化字段名冲突诊断、
|
||||
`CqrsHandlerRegistryGenerator` 的 `dynamic` 类型引用风险,并同步更新 `AGENTS.md` 的模块 build / warning 治理规范
|
||||
- 已完成 `GFramework.Game.SourceGenerators` 中 `SchemaConfigGenerator` 的第一批 `MA0051` 收口;warnings-only 基线剩余 `9` 条
|
||||
`MA0051`
|
||||
|
||||
@ -117,6 +124,9 @@
|
||||
- `RP-024` 使用 `$gframework-pr-review` 继续复核 PR #269 latest-head unresolved threads,确认 `EasyEvents` 异常契约、
|
||||
`SchemaConfigGenerator` 取消传播与 `ContextAwareGenerator` 快照冲突线程均已在本地收口,仅剩 `Cqrs` error type
|
||||
直接引用与根 schema `type` 非字符串防御仍成立;现已补齐实现与回归测试
|
||||
- `RP-025` 继续复核 PR #269 剩余 outside-diff / nitpick 信号后,确认本地仍成立的是 `SchemaConfigGenerator`
|
||||
的归一化字段名冲突与 `Cqrs` 对 `dynamic` 的直接类型引用;已分别补上诊断、运行时类型归一化与回归测试,
|
||||
并把“变更模块必须运行对应 build 且处理 warning”的治理规则写回 `AGENTS.md`
|
||||
- 当前工作树分支 `fix/analyzer-warning-reduction-batch` 已在 `ai-plan/public/README.md` 建立 topic 映射
|
||||
|
||||
## 当前风险
|
||||
@ -132,6 +142,9 @@
|
||||
- source generator warning 外溢风险:运行 `GFramework.SourceGenerators.Tests` 会构建相邻 generator/test 项目并显示既有
|
||||
`GFramework.Game.SourceGenerators` 与测试项目 warning
|
||||
- 缓解措施:继续以被修改 generator 项目的独立 warnings-only build 作为主验收,并用 focused generator test 验证行为
|
||||
- source generator test warning 治理风险:`GFramework.SourceGenerators.Tests` 当前仍有既有 `MA0051` / `MA0004` / `MA0048`
|
||||
warning,本轮 focused test 已通过,但测试项目整包 warning 尚未进入本轮写集
|
||||
- 缓解措施:后续若继续修改该测试项目,应按新增 `AGENTS.md` 规则先跑其独立 build,并在进入下一轮实现前明确 warning 收口范围
|
||||
- Godot 资产文件环境风险:当前 worktree 的 `GFramework.Godot` restore/build 仍会命中 Windows fallback package folder
|
||||
- 缓解措施:后续若继续触达 Godot 模块,先用 Linux 侧 restore 资产或 Windows-hosted 构建链刷新该项目,再补跑定向 build
|
||||
- 并行实现风险:批量收敛时若 subagent 写入边界不清晰,容易引入命名冲突或重复重构
|
||||
|
||||
@ -1,5 +1,41 @@
|
||||
# Analyzer Warning Reduction 追踪
|
||||
|
||||
## 2026-04-23 — RP-025
|
||||
|
||||
### 阶段:PR #269 第五轮 review follow-up 与模块 build / warning 治理补充(RP-025)
|
||||
|
||||
- 启动复核:
|
||||
- 继续使用 `$gframework-pr-review` 读取 PR #269 当前 latest review、outside-diff comment、nitpick comment 与 open-thread 摘要
|
||||
- 本地核对后确认 `SchemaConfigGenerator` 的取消传播、根 `type` 非字符串防御、`ContextAware` 冲突快照与
|
||||
`Cqrs` error type 线程均已是陈旧信号;仍成立的是归一化字段名冲突与 `dynamic` 运行时类型引用问题
|
||||
- 决策:
|
||||
- `SchemaConfigGenerator` 不复用 `GF_ConfigSchema_006`,改为新增专门的冲突诊断 `GF_ConfigSchema_014`,
|
||||
避免把“标识符非法”和“归一化后重名”混成同一类错误
|
||||
- `CqrsHandlerRegistryGenerator` 对 `dynamic` 采用“生成期归一化为 `global::System.Object`”策略,而不是退回更宽泛的
|
||||
fallback 路径,保持精确注册能力且避免发射 `typeof(dynamic)`
|
||||
- `AGENTS.md` 增加模块级 build / warning 治理规则,要求后续改代码时必须对受影响模块跑 Release build,并处理或显式报告 warning
|
||||
- 实施调整:
|
||||
- 为 `SchemaConfigGenerator` 增加对象级生成属性名登记 helper,在 `ParseObjectSpec(...)` 中拦截 `foo-bar` /
|
||||
`foo_bar` 这类归一化后冲突,并新增 `ConfigSchemaDiagnostics.DuplicateGeneratedIdentifier`
|
||||
- 为 `SchemaConfigGeneratorTests` 补上冲突诊断回归测试;为 `CqrsHandlerRegistryGeneratorTests` 收紧
|
||||
unresolved-type 断言并新增 `dynamic` 类型归一化回归测试
|
||||
- 为 `CqrsHandlerRegistryGenerator.RuntimeTypeReferences` 增加 `TypeKind.Dynamic` 归一化处理,并保持
|
||||
`TypeKind.Error` 的保守回退
|
||||
- 为 `AGENTS.md` 补充“受影响模块必须独立 build 且 warning 不能默认甩给长期分支”的硬性规范
|
||||
- 验证结果:
|
||||
- `dotnet restore GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -p:RestoreFallbackFolders="" -nologo`
|
||||
- 结果:通过;并行 restore 时出现一次共享 `obj` 文件已存在的竞争噪音,串行验证后未再复现
|
||||
- `dotnet build GFramework.Cqrs.SourceGenerators/GFramework.Cqrs.SourceGenerators.csproj -c Release --no-restore -p:RestoreFallbackFolders="" -clp:"Summary;WarningsOnly" -nologo`
|
||||
- 结果:`0 Warning(s)`,`0 Error(s)`
|
||||
- `dotnet build GFramework.Game.SourceGenerators/GFramework.Game.SourceGenerators.csproj -c Release --no-restore -p:RestoreFallbackFolders="" -clp:"Summary;WarningsOnly" -nologo`
|
||||
- 结果:`9 Warning(s)`,`0 Error(s)`;维持既有 `SchemaConfigGenerator.cs` `MA0051` 基线,未新增 warning
|
||||
- `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --no-restore --filter "FullyQualifiedName~Run_Should_Report_Diagnostic_When_Schema_Keys_Collide_After_Identifier_Normalization|FullyQualifiedName~Emits_Object_Type_Reference_When_Handler_Response_Uses_Dynamic|FullyQualifiedName~Emits_Runtime_Type_Lookup_When_Handler_Contract_Contains_Unresolved_Error_Types" -m:1 -p:RestoreFallbackFolders="" -nologo`
|
||||
- 结果:`3 Passed`,`0 Failed`
|
||||
- 说明:测试项目构建仍打印既有 `MA0051` / `MA0004` / `MA0048` warning,不属于本轮 generator 模块写集,但已在 tracking 风险中记录
|
||||
- 下一步建议:
|
||||
- 若继续收口 PR #269,可再次抓取最新 unresolved threads,确认 GitHub 上剩余 open thread 是否全部转为陈旧信号
|
||||
- 若回到 analyzer 主线,继续推进 `GFramework.Game.SourceGenerators/Config/SchemaConfigGenerator.cs` 剩余 `MA0051`
|
||||
|
||||
## 2026-04-22 — RP-024
|
||||
|
||||
### 阶段:PR #269 第四轮 review follow-up 收口(RP-024)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user