refactor(sourcegenerators-tests): 收敛 SchemaConfigGeneratorTests 结构性警告

- 重构 SchemaConfigGeneratorTests 的共享 runtime fixture 与 generated-source helper,清理当前 MA0051 长方法

- 补充 project-level registration catalog 的专用契约断言,保持生成输出验证语义不变

- 更新 analyzer-warning-reduction 的 tracking 与 trace,记录基线降至 15 条并切换下一步恢复点
This commit is contained in:
gewuyou 2026-04-23 19:15:13 +08:00
parent aa879d2c9a
commit 2915624e60
3 changed files with 209 additions and 397 deletions

View File

@ -6,6 +6,67 @@ namespace GFramework.SourceGenerators.Tests.Config;
[TestFixture]
public class SchemaConfigGeneratorTests
{
// Keep shared fixture sources at class scope so MA0051 reduction does not change generator inputs.
private const string DummySource = """
namespace TestApp
{
public sealed class Dummy
{
}
}
""";
// These runtime contracts mirror the minimal consumer surface the generator expects when emitting registration helpers.
private const string ConfigRuntimeSource = """
using System;
using System.Collections.Generic;
namespace GFramework.Game.Abstractions.Config
{
public interface IConfigTable
{
Type KeyType { get; }
Type ValueType { get; }
int Count { get; }
}
public interface IConfigTable<TKey, TValue> : IConfigTable
where TKey : notnull
{
TValue Get(TKey key);
bool TryGet(TKey key, out TValue? value);
bool ContainsKey(TKey key);
IReadOnlyCollection<TValue> All();
}
public interface IConfigRegistry
{
IConfigTable<TKey, TValue> GetTable<TKey, TValue>(string name)
where TKey : notnull;
bool TryGetTable<TKey, TValue>(string name, out IConfigTable<TKey, TValue>? table)
where TKey : notnull;
}
}
namespace GFramework.Game.Config
{
public sealed class YamlConfigLoader
{
public YamlConfigLoader RegisterTable<TKey, TValue>(
string tableName,
string relativePath,
string schemaRelativePath,
Func<TValue, TKey> keySelector,
IEqualityComparer<TKey>? comparer = null)
where TKey : notnull
{
return this;
}
}
}
""";
/// <summary>
/// 验证 AdditionalFiles 读取被取消时会向上传播取消,而不是伪造成 schema 诊断。
/// </summary>
@ -1404,15 +1465,6 @@ public class SchemaConfigGeneratorTests
[Test]
public void Run_Should_Write_IfThenElse_Constraint_Into_Generated_Documentation()
{
const string source = """
namespace TestApp
{
public sealed class Dummy
{
}
}
""";
const string schema = """
{
"type": "object",
@ -1454,19 +1506,9 @@ public class SchemaConfigGeneratorTests
}
""";
var result = SchemaGeneratorTestDriver.Run(
source,
var generatedSources = RunAndCollectGeneratedSources(
DummySource,
("monster.schema.json", schema));
var generatedSources = result.Results
.Single()
.GeneratedSources
.ToDictionary(
static sourceResult => sourceResult.HintName,
static sourceResult => sourceResult.SourceText.ToString(),
StringComparer.Ordinal);
Assert.That(result.Results.Single().Diagnostics, Is.Empty);
Assert.That(
generatedSources["MonsterConfig.g.cs"],
Does.Contain(
@ -2024,56 +2066,6 @@ public class SchemaConfigGeneratorTests
[Test]
public void Run_Should_Use_Custom_Config_Path_Metadata_For_Generated_Registration()
{
const string source = """
using System;
using System.Collections.Generic;
namespace GFramework.Game.Abstractions.Config
{
public interface IConfigTable
{
Type KeyType { get; }
Type ValueType { get; }
int Count { get; }
}
public interface IConfigTable<TKey, TValue> : IConfigTable
where TKey : notnull
{
TValue Get(TKey key);
bool TryGet(TKey key, out TValue? value);
bool ContainsKey(TKey key);
IReadOnlyCollection<TValue> All();
}
public interface IConfigRegistry
{
IConfigTable<TKey, TValue> GetTable<TKey, TValue>(string name)
where TKey : notnull;
bool TryGetTable<TKey, TValue>(string name, out IConfigTable<TKey, TValue>? table)
where TKey : notnull;
}
}
namespace GFramework.Game.Config
{
public sealed class YamlConfigLoader
{
public YamlConfigLoader RegisterTable<TKey, TValue>(
string tableName,
string relativePath,
string schemaRelativePath,
Func<TValue, TKey> keySelector,
IEqualityComparer<TKey>? comparer = null)
where TKey : notnull
{
return this;
}
}
}
""";
const string schema = """
{
"type": "object",
@ -2086,19 +2078,9 @@ public class SchemaConfigGeneratorTests
}
""";
var result = SchemaGeneratorTestDriver.Run(
source,
var generatedSources = RunAndCollectGeneratedSources(
ConfigRuntimeSource,
("monster.schema.json", schema));
var generatedSources = result.Results
.Single()
.GeneratedSources
.ToDictionary(
static sourceResult => sourceResult.HintName,
static sourceResult => sourceResult.SourceText.ToString(),
StringComparer.Ordinal);
Assert.That(result.Results.Single().Diagnostics, Is.Empty);
Assert.That(generatedSources["MonsterConfigBindings.g.cs"],
Does.Contain("public const string ConfigRelativePath = \"config/monster\";"));
Assert.That(generatedSources["MonsterConfigBindings.g.cs"], Does.Contain("Metadata.ConfigRelativePath,"));
@ -2126,56 +2108,6 @@ public class SchemaConfigGeneratorTests
[Test]
public void Run_Should_Skip_Runtime_Null_Keys_When_Generating_Indexed_Lookups()
{
const string source = """
using System;
using System.Collections.Generic;
namespace GFramework.Game.Abstractions.Config
{
public interface IConfigTable
{
Type KeyType { get; }
Type ValueType { get; }
int Count { get; }
}
public interface IConfigTable<TKey, TValue> : IConfigTable
where TKey : notnull
{
TValue Get(TKey key);
bool TryGet(TKey key, out TValue? value);
bool ContainsKey(TKey key);
IReadOnlyCollection<TValue> All();
}
public interface IConfigRegistry
{
IConfigTable<TKey, TValue> GetTable<TKey, TValue>(string name)
where TKey : notnull;
bool TryGetTable<TKey, TValue>(string name, out IConfigTable<TKey, TValue>? table)
where TKey : notnull;
}
}
namespace GFramework.Game.Config
{
public sealed class YamlConfigLoader
{
public YamlConfigLoader RegisterTable<TKey, TValue>(
string tableName,
string relativePath,
string schemaRelativePath,
Func<TValue, TKey> keySelector,
IEqualityComparer<TKey>? comparer = null)
where TKey : notnull
{
return this;
}
}
}
""";
const string schema = """
{
"type": "object",
@ -2190,19 +2122,9 @@ public class SchemaConfigGeneratorTests
}
""";
var result = SchemaGeneratorTestDriver.Run(
source,
var generatedSources = RunAndCollectGeneratedSources(
ConfigRuntimeSource,
("monster.schema.json", schema));
var generatedSources = result.Results
.Single()
.GeneratedSources
.ToDictionary(
static sourceResult => sourceResult.HintName,
static sourceResult => sourceResult.SourceText.ToString(),
StringComparer.Ordinal);
Assert.That(result.Results.Single().Diagnostics, Is.Empty);
Assert.That(generatedSources["MonsterTable.g.cs"], Does.Contain("if (key is null)"));
Assert.That(generatedSources["MonsterTable.g.cs"],
Does.Contain("Throwing here would permanently poison the cached index for this wrapper instance."));
@ -2391,56 +2313,6 @@ public class SchemaConfigGeneratorTests
[Test]
public void Run_Should_Allow_Lookup_Index_For_Direct_Root_Property_With_Dotted_Schema_Key()
{
const string source = """
using System;
using System.Collections.Generic;
namespace GFramework.Game.Abstractions.Config
{
public interface IConfigTable
{
Type KeyType { get; }
Type ValueType { get; }
int Count { get; }
}
public interface IConfigTable<TKey, TValue> : IConfigTable
where TKey : notnull
{
TValue Get(TKey key);
bool TryGet(TKey key, out TValue? value);
bool ContainsKey(TKey key);
IReadOnlyCollection<TValue> All();
}
public interface IConfigRegistry
{
IConfigTable<TKey, TValue> GetTable<TKey, TValue>(string name)
where TKey : notnull;
bool TryGetTable<TKey, TValue>(string name, out IConfigTable<TKey, TValue>? table)
where TKey : notnull;
}
}
namespace GFramework.Game.Config
{
public sealed class YamlConfigLoader
{
public YamlConfigLoader RegisterTable<TKey, TValue>(
string tableName,
string relativePath,
string schemaRelativePath,
Func<TValue, TKey> keySelector,
IEqualityComparer<TKey>? comparer = null)
where TKey : notnull
{
return this;
}
}
}
""";
const string schema = """
{
"type": "object",
@ -2455,19 +2327,9 @@ public class SchemaConfigGeneratorTests
}
""";
var result = SchemaGeneratorTestDriver.Run(
source,
var generatedSources = RunAndCollectGeneratedSources(
ConfigRuntimeSource,
("monster.schema.json", schema));
var generatedSources = result.Results
.Single()
.GeneratedSources
.ToDictionary(
static sourceResult => sourceResult.HintName,
static sourceResult => sourceResult.SourceText.ToString(),
StringComparer.Ordinal);
Assert.That(result.Results.Single().Diagnostics, Is.Empty);
Assert.That(generatedSources["MonsterTable.g.cs"], Does.Contain("FindByDisplayName(string value)"));
Assert.That(generatedSources["MonsterTable.g.cs"], Does.Contain("_displayNameIndex"));
}
@ -2478,56 +2340,6 @@ public class SchemaConfigGeneratorTests
[Test]
public void Run_Should_Assign_Globally_Unique_Reference_Metadata_Member_Names()
{
const string source = """
using System;
using System.Collections.Generic;
namespace GFramework.Game.Abstractions.Config
{
public interface IConfigTable
{
Type KeyType { get; }
Type ValueType { get; }
int Count { get; }
}
public interface IConfigTable<TKey, TValue> : IConfigTable
where TKey : notnull
{
TValue Get(TKey key);
bool TryGet(TKey key, out TValue? value);
bool ContainsKey(TKey key);
IReadOnlyCollection<TValue> All();
}
public interface IConfigRegistry
{
IConfigTable<TKey, TValue> GetTable<TKey, TValue>(string name)
where TKey : notnull;
bool TryGetTable<TKey, TValue>(string name, out IConfigTable<TKey, TValue>? table)
where TKey : notnull;
}
}
namespace GFramework.Game.Config
{
public sealed class YamlConfigLoader
{
public YamlConfigLoader RegisterTable<TKey, TValue>(
string tableName,
string relativePath,
string schemaRelativePath,
Func<TValue, TKey> keySelector,
IEqualityComparer<TKey>? comparer = null)
where TKey : notnull
{
return this;
}
}
}
""";
const string schema = """
{
"type": "object",
@ -2561,19 +2373,9 @@ public class SchemaConfigGeneratorTests
}
""";
var result = SchemaGeneratorTestDriver.Run(
source,
var generatedSources = RunAndCollectGeneratedSources(
ConfigRuntimeSource,
("monster.schema.json", schema));
var generatedSources = result.Results
.Single()
.GeneratedSources
.ToDictionary(
static sourceResult => sourceResult.HintName,
static sourceResult => sourceResult.SourceText.ToString(),
StringComparer.Ordinal);
Assert.That(result.Results.Single().Diagnostics, Is.Empty);
Assert.That(generatedSources.TryGetValue("MonsterConfigBindings.g.cs", out var bindingsSource), Is.True);
Assert.That(bindingsSource, Does.Contain("public static readonly ReferenceMetadata DropItems ="));
Assert.That(bindingsSource, Does.Contain("public static readonly ReferenceMetadata DropItems1 ="));
@ -2588,56 +2390,6 @@ public class SchemaConfigGeneratorTests
[Test]
public void Run_Should_Generate_Query_Helpers_Only_For_Top_Level_Scalar_Properties()
{
const string source = """
using System;
using System.Collections.Generic;
namespace GFramework.Game.Abstractions.Config
{
public interface IConfigTable
{
Type KeyType { get; }
Type ValueType { get; }
int Count { get; }
}
public interface IConfigTable<TKey, TValue> : IConfigTable
where TKey : notnull
{
TValue Get(TKey key);
bool TryGet(TKey key, out TValue? value);
bool ContainsKey(TKey key);
IReadOnlyCollection<TValue> All();
}
public interface IConfigRegistry
{
IConfigTable<TKey, TValue> GetTable<TKey, TValue>(string name)
where TKey : notnull;
bool TryGetTable<TKey, TValue>(string name, out IConfigTable<TKey, TValue>? table)
where TKey : notnull;
}
}
namespace GFramework.Game.Config
{
public sealed class YamlConfigLoader
{
public YamlConfigLoader RegisterTable<TKey, TValue>(
string tableName,
string relativePath,
string schemaRelativePath,
Func<TValue, TKey> keySelector,
IEqualityComparer<TKey>? comparer = null)
where TKey : notnull
{
return this;
}
}
}
""";
const string schema = """
{
"type": "object",
@ -2667,19 +2419,9 @@ public class SchemaConfigGeneratorTests
}
""";
var result = SchemaGeneratorTestDriver.Run(
source,
var generatedSources = RunAndCollectGeneratedSources(
ConfigRuntimeSource,
("monster.schema.json", schema));
var generatedSources = result.Results
.Single()
.GeneratedSources
.ToDictionary(
static sourceResult => sourceResult.HintName,
static sourceResult => sourceResult.SourceText.ToString(),
StringComparer.Ordinal);
Assert.That(result.Results.Single().Diagnostics, Is.Empty);
Assert.That(generatedSources.TryGetValue("MonsterTable.g.cs", out var tableSource), Is.True);
Assert.Multiple(() =>
@ -2713,56 +2455,6 @@ public class SchemaConfigGeneratorTests
[Test]
public void Run_Should_Generate_Project_Level_Registration_Catalog()
{
const string source = """
using System;
using System.Collections.Generic;
namespace GFramework.Game.Abstractions.Config
{
public interface IConfigTable
{
Type KeyType { get; }
Type ValueType { get; }
int Count { get; }
}
public interface IConfigTable<TKey, TValue> : IConfigTable
where TKey : notnull
{
TValue Get(TKey key);
bool TryGet(TKey key, out TValue? value);
bool ContainsKey(TKey key);
IReadOnlyCollection<TValue> All();
}
public interface IConfigRegistry
{
IConfigTable<TKey, TValue> GetTable<TKey, TValue>(string name)
where TKey : notnull;
bool TryGetTable<TKey, TValue>(string name, out IConfigTable<TKey, TValue>? table)
where TKey : notnull;
}
}
namespace GFramework.Game.Config
{
public sealed class YamlConfigLoader
{
public YamlConfigLoader RegisterTable<TKey, TValue>(
string tableName,
string relativePath,
string schemaRelativePath,
Func<TValue, TKey> keySelector,
IEqualityComparer<TKey>? comparer = null)
where TKey : notnull
{
return this;
}
}
}
""";
const string monsterSchema = """
{
"type": "object",
@ -2785,22 +2477,24 @@ public class SchemaConfigGeneratorTests
}
""";
var result = SchemaGeneratorTestDriver.Run(
source,
var generatedSources = RunAndCollectGeneratedSources(
ConfigRuntimeSource,
("monster.schema.json", monsterSchema),
("item.schema.json", itemSchema));
if (!generatedSources.TryGetValue("GeneratedConfigCatalog.g.cs", out var catalogSource))
{
Assert.Fail("Expected GeneratedConfigCatalog.g.cs to be generated.");
}
var generatedSources = result.Results
.Single()
.GeneratedSources
.ToDictionary(
static sourceResult => sourceResult.HintName,
static sourceResult => sourceResult.SourceText.ToString(),
StringComparer.Ordinal);
Assert.That(result.Results.Single().Diagnostics, Is.Empty);
Assert.That(generatedSources.TryGetValue("GeneratedConfigCatalog.g.cs", out var catalogSource), Is.True);
AssertGeneratedRegistrationCatalogContract(catalogSource!);
}
/// <summary>
/// 断言聚合注册目录保留筛选选项、比较器透传和按条件注册的生成契约。
/// </summary>
/// <param name="catalogSource">`GeneratedConfigCatalog.g.cs` 的生成源码。</param>
private static void AssertGeneratedRegistrationCatalogContract(string catalogSource)
{
Assert.Multiple(() =>
{
Assert.That(catalogSource, Does.Contain("public static class GeneratedConfigCatalog"));
@ -2852,4 +2546,24 @@ public class SchemaConfigGeneratorTests
Assert.That(catalogSource, Does.Contain("private static bool MatchesOptionalAllowList("));
});
}
/// <summary>
/// 运行 schema 生成器并收集生成输出,同时断言本次场景不产生诊断。
/// </summary>
/// <param name="source">测试输入源码。</param>
/// <param name="additionalFiles">参与本次生成的 schema 文件集合。</param>
/// <returns>按 HintName 索引的生成源码字典。</returns>
private static global::System.Collections.Generic.IReadOnlyDictionary<string, string> RunAndCollectGeneratedSources(
string source,
params (string path, string content)[] additionalFiles)
{
var result = SchemaGeneratorTestDriver.Run(source, additionalFiles);
var runResult = result.Results.Single();
Assert.That(runResult.Diagnostics, Is.Empty);
return runResult.GeneratedSources.ToDictionary(
static sourceResult => sourceResult.HintName,
static sourceResult => sourceResult.SourceText.ToString(),
StringComparer.Ordinal);
}
}

View File

@ -7,9 +7,27 @@
## 当前恢复点
- 恢复点编号:`ANALYZER-WARNING-REDUCTION-RP-034`
- 当前阶段:`Phase 34`
- 恢复点编号:`ANALYZER-WARNING-REDUCTION-RP-036`
- 当前阶段:`Phase 36`
- 当前焦点:
- 已完成 `GFramework.SourceGenerators.Tests/Config/SchemaConfigGeneratorTests.cs``MA0051` 收口:
将共享 consumer runtime fixture 提取到类级常量,并把生成结果收集与 catalog 契约断言拆成小 helper
保持 schema 文本、断言语义与生成输出契约不变
- 当前 `GFramework.SourceGenerators.Tests` Release warnings-only 基线已从 `22` 条降到 `15` 条;
`SchemaConfigGeneratorTests.cs` 已不再出现在 `MA0051` 列表中,剩余热点全部集中在
`CqrsHandlerRegistryGeneratorTests.cs`
- 已完成 `SchemaConfigGeneratorTests` 定向验证:串行重跑 `50` 个用例全部通过;并确认先前并行 build/test
触发的 `MSB3030` / `CS0006` 属于共享输出竞争噪音,不是代码回归
- 已按 `gframework-boot` 重新恢复当前 worktree确认分支 `fix/analyzer-warning-reduction-batch` 仍映射到
`analyzer-warning-reduction`,且当前不存在 `ai-plan/private/` 私有恢复上下文
- 已重新抓取当前分支关联的 PR #273 review 状态PR 已处于 `CLOSED`latest-head review 仍显示 `2`
CodeRabbit open thread但本地复核后 `GeneratorSnapshotTest` 的 snapshot 路径空值防御已显式改为
`InvalidOperationException` 防御,`SchemaConfigGenerator``dependentSchemas` / `allOf` / conditional helper
也已补齐 XML 文档,当前更像历史线程未随已关闭 PR 一起收敛
- 已重新以 `GFramework.SourceGenerators.Tests` Release warnings-only build 复核当前 `MA0051` 热点:
基线现为 `22` 条,且已不再落在 `GeneratorSnapshotTest``ContextRegistrationAnalyzerTests`
`ContextGetGeneratorTests`,而是集中在 `CqrsHandlerRegistryGeneratorTests.cs``15` 条)与
`SchemaConfigGeneratorTests.cs``7` 条)
- 已完成 `GFramework.Core` 当前 `MA0016` / `MA0002` / `MA0015` / `MA0077` 低风险收口批次
- 已复核 `net10.0` 下的 `MA0158` 基线:`GFramework.Core` / `GFramework.Cqrs` 当前共有 `16` 个 object lock
建议点,属于跨 target 兼容性风险,不在本轮直接批量替换
@ -124,6 +142,11 @@
`40` 条,并通过 focused generator tests 保持输出契约不变
- 已完成 `SchemaConfigGeneratorSnapshotTests` 的单文件 `MA0051` 收口;当前 `GFramework.SourceGenerators.Tests`
Release build 基线已降到 `39` 条,并通过 focused snapshot test 保持生成输出契约不变
- 已完成 `RP-035` 启动复核:确认 PR #273 已关闭、历史 open thread 暂无新的本地修复点,且
`GFramework.SourceGenerators.Tests` 当前剩余 `MA0051` 已重排为 `CqrsHandlerRegistryGeneratorTests` /
`SchemaConfigGeneratorTests` 两个测试写集
- 已完成 `RP-036`:清空 `SchemaConfigGeneratorTests.cs` 当前 `MA0051`,并将
`GFramework.SourceGenerators.Tests` Release warnings-only 基线进一步降到 `15`
## 当前活跃事实
@ -168,6 +191,12 @@
生成文件名、断言路径与源生成输出不变;`GFramework.SourceGenerators.Tests` warnings-only 基线由 `43` 降至 `40`
- `RP-033` 已完成 `SchemaConfigGeneratorSnapshotTests``MA0051` 收口monster schema 运行时契约与 schema 输入已提取为
类级常量,生成结果映射与快照目录解析已拆为小 helper`GFramework.SourceGenerators.Tests` warnings-only 基线由 `40` 降至 `39`
- `RP-035` 已完成启动级恢复核对:当前分支对应的 GitHub PR #273 已关闭,因此 remaining open thread 仅作为历史信号参考;
下一轮应以本地 `warnings-only` build 的实时热点为主,而不是继续按已过时的 `GeneratorSnapshotTest` /
`ContextRegistrationAnalyzerTests` 建议恢复
- `RP-036` 已完成 `SchemaConfigGeneratorTests` 的单文件 `MA0051` 收口:共享 runtime fixture、
generated-source 收集与 catalog 契约断言均已拆出 helper当前测试项目剩余 `MA0051` 已全部收敛到
`CqrsHandlerRegistryGeneratorTests`
- `RP-021` 使用 `$gframework-pr-review` 复核当前分支 PR #269 后,修复仍在本地成立的 4 个项:将
`CqrsHandlerRegistryGenerator` 拆分为职责清晰的 partial 文件、为 `ContextAwareGenerator` 生成字段增加稳定前缀并补上
`SetContextProvider` 的运行时 null 校验、为 `Option<T>` 补齐 `<remarks>`,并新增字段重名场景的生成器快照测试
@ -389,13 +418,27 @@
- 结果:`39 Warning(s)``0 Error(s)``SchemaConfigGeneratorSnapshotTests.cs` 已不再出现在 `MA0051` 列表中
- `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --no-build --disable-build-servers --filter FullyQualifiedName~SchemaConfigGeneratorSnapshotTests -m:1 -p:RestoreFallbackFolders="" -nologo`
- 结果:`1 Passed``0 Failed`
- `RP-035` 的验证结果:
- `python3 .agents/skills/gframework-pr-review/scripts/fetch_current_pr_review.py --json-output /tmp/current-pr-review.json`
- 结果:成功定位当前分支关联的 `PR #273`;状态为 `CLOSED`latest-head review threads 仍显示 `2` 条 open thread
test report 均为通过MegaLinter 仅保留 docstring coverage / `dotnet-format` 历史信号
- `dotnet build GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release -t:Rebuild --no-restore --disable-build-servers -m:1 -p:UseSharedCompilation=false -p:RestoreFallbackFolders="" -nologo -clp:"Summary;WarningsOnly"`
- 结果:`22 Warning(s)``0 Error(s)`;剩余 `MA0051` 全部集中在 `CqrsHandlerRegistryGeneratorTests.cs`
`SchemaConfigGeneratorTests.cs`
- `RP-036` 的验证结果:
- `dotnet restore GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -p:RestoreFallbackFolders="" -nologo`
- 结果:通过;刷新测试依赖输出,规避 `--no-build` 场景下的缺包噪音
- `dotnet build GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release -t:Rebuild --no-restore --disable-build-servers -m:1 -p:UseSharedCompilation=false -p:RestoreFallbackFolders="" -nologo -clp:"Summary;WarningsOnly"`
- 结果:`15 Warning(s)``0 Error(s)``SchemaConfigGeneratorTests.cs` 已不再出现在 `MA0051` 列表中
- `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --no-build --disable-build-servers --filter FullyQualifiedName~SchemaConfigGeneratorTests -m:1 -p:RestoreFallbackFolders="" -nologo`
- 结果:`50 Passed``0 Failed`
- active 跟踪文件只保留当前恢复点、活跃事实、风险与下一步,不再重复保存已完成阶段的长篇历史
## 下一步
1. 若要继续该主题,先读 active tracking再按需展开历史归档中的 warning 热点与验证记录
2. 下一轮优先继续 `GFramework.SourceGenerators.Tests``MA0051` 收口,先在 `GeneratorSnapshotTest`
`ContextRegistrationAnalyzerTests``ContextGetGeneratorTests` 中选择一个单写集推进,不再把已清零的 `MA0004` / `MA0048` 混回写集
2. 下一轮优先继续 `GFramework.SourceGenerators.Tests``MA0051` 收口,并直接进入唯一剩余热点
`CqrsHandlerRegistryGeneratorTests.cs`
3. 若改回推进 `MA0158`,先设计 `net8.0` / `net9.0` / `net10.0` 多 target 条件编译方案,不直接批量替换共享源码中的
`object` lock
4. 若后续继续改动 `GFramework.Godot`,先修复该项目的 Linux 侧 restore 资产,再补跑独立 build

View File

@ -1,5 +1,60 @@
# Analyzer Warning Reduction 追踪
## 2026-04-23 — RP-036
### 阶段:`SchemaConfigGeneratorTests.cs` `MA0051` 收口RP-036
- 启动复核:
- 依据 `RP-035` 的恢复结论,选择低风险单写集 `GFramework.SourceGenerators.Tests/Config/SchemaConfigGeneratorTests.cs`
继续推进,而不直接跳入剩余 warning 数量更多的 `CqrsHandlerRegistryGeneratorTests.cs`
- 先用 `dotnet build ... -clp:"Summary;WarningsOnly"` 复核当前热点,确认该文件仍承担 `7``MA0051`
- 决策:
- 保持全部 schema 文本、断言字符串和生成文件名不变,只收敛测试方法结构
- 将共享 consumer runtime fixture 提到类级常量,并把 generated-source 收集与 registration catalog 契约断言
抽成 helper避免重复内联样板把测试方法重新撑长
- 继续避免并行运行同一测试项目的 build/test该 worktree 下并发验证会触发 `MSB3030` / `CS0006` 级别的输出竞争噪音
- 实施调整:
- 为 `SchemaConfigGeneratorTests` 新增 `DummySource``ConfigRuntimeSource` 类级常量
- 新增 `RunAndCollectGeneratedSources(...)``AssertGeneratedRegistrationCatalogContract(...)` helper
- 将 `if/then/else` 文档、config path、lookup index、reference metadata、query helper 与 project-level catalog
等长测试方法切换为复用共享 fixture / helper
- 验证结果:
- `dotnet restore GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -p:RestoreFallbackFolders="" -nologo`
- 结果:通过
- `dotnet build GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release -t:Rebuild --no-restore --disable-build-servers -m:1 -p:UseSharedCompilation=false -p:RestoreFallbackFolders="" -nologo -clp:"Summary;WarningsOnly"`
- 结果:`15 Warning(s)``0 Error(s)``SchemaConfigGeneratorTests.cs` 已不再出现在 `MA0051` 列表中
- `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --no-build --disable-build-servers --filter FullyQualifiedName~SchemaConfigGeneratorTests -m:1 -p:RestoreFallbackFolders="" -nologo`
- 结果:`50 Passed``0 Failed`
- 下一步建议:
- 继续进入 `GFramework.SourceGenerators.Tests/Cqrs/CqrsHandlerRegistryGeneratorTests.cs`,这是当前项目唯一剩余 `MA0051` 热点
- 若需要再次做定向测试,保持串行执行,避免把共享输出竞争误判成实现回退
## 2026-04-23 — RP-035
### 阶段:`boot` 恢复点重建与实时热点复核RP-035
- 启动复核:
- 按 `gframework-boot` 流程读取 `AGENTS.md``.ai/environment/tools.ai.yaml``ai-plan/public/README.md`
`analyzer-warning-reduction` active tracking / trace确认当前 worktree `GFramework-analyzer`
仍对应分支 `fix/analyzer-warning-reduction-batch`
- 额外检查 `ai-plan/private/`,确认当前 worktree 没有私有恢复上下文需要合并
- 使用 `gframework-pr-review` 脚本抓取当前分支关联 PR确认 `PR #273` 已为 `CLOSED`
- 决策:
- 不再把已关闭 PR 上残留的 open thread 直接当作下一轮主驱动信号;先以当前本地代码和实时 build 结果判断问题是否仍成立
- 将后续恢复入口从“继续看 `GeneratorSnapshotTest` / `ContextRegistrationAnalyzerTests`”切回
“重新跑 `warnings-only` build 后按真实热点推进”
- 现场结论:
- `GeneratorSnapshotTest` 中关于 snapshot 路径的 `Path.GetDirectoryName(...)` 已改为显式空值防御,
相关 CodeRabbit 线程更像历史残留
- `SchemaConfigGenerator``dependentSchemas` / `allOf` / conditional schema 校验 helper 周围已经补齐 XML 文档,
当前也没有新的本地缺口需要仅为关闭旧线程而继续改动
- `dotnet build GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release -t:Rebuild --no-restore --disable-build-servers -m:1 -p:UseSharedCompilation=false -p:RestoreFallbackFolders="" -nologo -clp:"Summary;WarningsOnly"`
结果为 `22 Warning(s)``0 Error(s)`;剩余 `MA0051` 已集中到
`CqrsHandlerRegistryGeneratorTests.cs``15` 条)与 `SchemaConfigGeneratorTests.cs``7` 条)
- 下一步建议:
- 若保持低风险单写集,先进入 `SchemaConfigGeneratorTests.cs`
- 若优先按 warning 数量收敛,则进入 `CqrsHandlerRegistryGeneratorTests.cs`
## 2026-04-23 — RP-033
### 阶段:`SchemaConfigGeneratorSnapshotTests.cs` `MA0051` 收口RP-033