fix(sourcegenerators): 收口PR审查遗留问题

- 修复 PR review 指出的 XML 文档位置、快照路径防御与换行归一化细节

- 更新 monster schema snapshot 场景,覆盖 dependentRequired、dependentSchemas、allOf 与 if/then/else 约束文档

- 补充 SchemaConfigGenerator 条件与组合校验 helper 的 XML 文档,并同步 ai-plan 恢复点与验证记录
This commit is contained in:
gewuyou 2026-04-23 17:38:35 +08:00
parent 564b45bde1
commit fdf7382717
7 changed files with 200 additions and 10 deletions

View File

@ -1270,6 +1270,17 @@ public sealed class SchemaConfigGenerator : IIncrementalGenerator
return true;
}
/// <summary>
/// 验证单个 <c>dependentRequired</c> 触发项的声明形状。
/// 该 helper 先锁定 trigger 字段本身是否属于当前对象,再把每个 target 交给更细粒度的 sibling 校验,
/// 让诊断能够明确区分“触发字段不存在”和“依赖目标非法”两类失败语义。
/// </summary>
/// <param name="filePath">Schema 文件路径。</param>
/// <param name="displayPath">父对象逻辑路径。</param>
/// <param name="dependency">当前 dependentRequired 触发项。</param>
/// <param name="declaredProperties">父对象已声明属性集合。</param>
/// <param name="diagnostic">失败时返回的诊断。</param>
/// <returns>当前 dependentRequired 触发项是否有效。</returns>
private static bool TryValidateDependentRequiredEntry(
string filePath,
string displayPath,
@ -1317,6 +1328,16 @@ public sealed class SchemaConfigGenerator : IIncrementalGenerator
return true;
}
/// <summary>
/// 验证单个 <c>dependentRequired</c> target 是否为已声明的 sibling 字段名。
/// </summary>
/// <param name="filePath">Schema 文件路径。</param>
/// <param name="displayPath">父对象逻辑路径。</param>
/// <param name="dependencyName">触发依赖的字段名。</param>
/// <param name="dependencyTarget">当前 target 元素。</param>
/// <param name="declaredProperties">父对象已声明属性集合。</param>
/// <param name="diagnostic">失败时返回的诊断。</param>
/// <returns>当前 dependentRequired target 是否有效。</returns>
private static bool TryValidateDependentRequiredTarget(
string filePath,
string displayPath,
@ -1497,6 +1518,15 @@ public sealed class SchemaConfigGenerator : IIncrementalGenerator
return true;
}
/// <summary>
/// 验证单个 <c>dependentSchemas</c> 触发项是否保持为当前运行时支持的 object 子 schema 形状。
/// </summary>
/// <param name="filePath">Schema 文件路径。</param>
/// <param name="displayPath">父对象逻辑路径。</param>
/// <param name="dependency">当前 dependentSchemas 触发项。</param>
/// <param name="declaredProperties">父对象已声明属性集合。</param>
/// <param name="diagnostic">失败时返回的诊断。</param>
/// <returns>当前 dependentSchemas 触发项是否有效。</returns>
private static bool TryValidateDependentSchemaEntry(
string filePath,
string displayPath,
@ -1690,6 +1720,15 @@ public sealed class SchemaConfigGenerator : IIncrementalGenerator
return true;
}
/// <summary>
/// 验证单个 <c>allOf</c> 条目是否维持 object-valued、object-typed 的 focused constraint 形状。
/// </summary>
/// <param name="filePath">Schema 文件路径。</param>
/// <param name="displayPath">父对象逻辑路径。</param>
/// <param name="allOfSchema">当前 allOf 条目。</param>
/// <param name="allOfIndex">从 0 开始的条目索引。</param>
/// <param name="diagnostic">失败时返回的诊断。</param>
/// <returns>当前 allOf 条目形状是否有效。</returns>
private static bool TryValidateAllOfEntryShape(
string filePath,
string displayPath,
@ -1854,6 +1893,21 @@ public sealed class SchemaConfigGenerator : IIncrementalGenerator
out diagnostic);
}
/// <summary>
/// 验证 object-focused 条件分支集合。
/// <c>if</c> 分支始终必检,<c>then</c> / <c>else</c> 仅在声明时校验,
/// 以保持生成器对分支缺失与分支内容错误的诊断顺序稳定。
/// </summary>
/// <param name="filePath">Schema 文件路径。</param>
/// <param name="displayPath">父对象逻辑路径。</param>
/// <param name="ifElement">if 分支 schema。</param>
/// <param name="hasThen">是否声明 then。</param>
/// <param name="thenElement">then 分支 schema。</param>
/// <param name="hasElse">是否声明 else。</param>
/// <param name="elseElement">else 分支 schema。</param>
/// <param name="declaredProperties">父对象已声明属性集合。</param>
/// <param name="diagnostic">失败时返回的诊断。</param>
/// <returns>当前条件分支集合是否有效。</returns>
private static bool TryValidateConditionalSchemaBranches(
string filePath,
string displayPath,
@ -1898,6 +1952,16 @@ public sealed class SchemaConfigGenerator : IIncrementalGenerator
out diagnostic);
}
/// <summary>
/// 验证 object-focused <c>if</c> / <c>then</c> / <c>else</c> 的存在性组合是否合法。
/// </summary>
/// <param name="filePath">Schema 文件路径。</param>
/// <param name="displayPath">父对象逻辑路径。</param>
/// <param name="hasIf">是否声明 if。</param>
/// <param name="hasThen">是否声明 then。</param>
/// <param name="hasElse">是否声明 else。</param>
/// <param name="diagnostic">失败时返回的诊断。</param>
/// <returns>当前条件关键字组合是否有效。</returns>
private static bool TryValidateConditionalSchemaPresence(
string filePath,
string displayPath,
@ -2060,6 +2124,16 @@ public sealed class SchemaConfigGenerator : IIncrementalGenerator
out diagnostic);
}
/// <summary>
/// 验证 object-focused 条件 schema 的 <c>properties</c> 只引用父对象已声明字段。
/// </summary>
/// <param name="filePath">Schema 文件路径。</param>
/// <param name="displayPath">当前分支逻辑路径。</param>
/// <param name="entryLabel">分支标签。</param>
/// <param name="schemaElement">当前分支 schema。</param>
/// <param name="declaredProperties">父对象已声明属性集合。</param>
/// <param name="diagnostic">失败时返回的诊断。</param>
/// <returns>当前分支 properties 是否有效。</returns>
private static bool TryValidateObjectFocusedSchemaProperties(
string filePath,
string displayPath,
@ -2104,6 +2178,16 @@ public sealed class SchemaConfigGenerator : IIncrementalGenerator
return true;
}
/// <summary>
/// 验证 object-focused 条件 schema 的 <c>required</c> 约束只引用父对象已声明字段。
/// </summary>
/// <param name="filePath">Schema 文件路径。</param>
/// <param name="displayPath">当前分支逻辑路径。</param>
/// <param name="entryLabel">分支标签。</param>
/// <param name="schemaElement">当前分支 schema。</param>
/// <param name="declaredProperties">父对象已声明属性集合。</param>
/// <param name="diagnostic">失败时返回的诊断。</param>
/// <returns>当前分支 required 是否有效。</returns>
private static bool TryValidateObjectFocusedSchemaRequiredProperties(
string filePath,
string displayPath,
@ -2211,6 +2295,16 @@ public sealed class SchemaConfigGenerator : IIncrementalGenerator
out diagnostic);
}
/// <summary>
/// 验证单个 <c>allOf</c> 条目的 <c>properties</c> 映射不会引入父对象未声明字段。
/// </summary>
/// <param name="filePath">Schema 文件路径。</param>
/// <param name="allOfEntryPath">当前 allOf 条目逻辑路径。</param>
/// <param name="allOfSchema">当前 allOf 条目。</param>
/// <param name="allOfIndex">从 0 开始的条目索引。</param>
/// <param name="declaredProperties">父对象已声明属性集合。</param>
/// <param name="diagnostic">失败时返回的诊断。</param>
/// <returns>当前 allOf 条目的 properties 映射是否有效。</returns>
private static bool TryValidateAllOfEntryProperties(
string filePath,
string allOfEntryPath,
@ -2255,6 +2349,16 @@ public sealed class SchemaConfigGenerator : IIncrementalGenerator
return true;
}
/// <summary>
/// 验证单个 <c>allOf</c> 条目的 <c>required</c> 约束不会引用父对象未声明字段。
/// </summary>
/// <param name="filePath">Schema 文件路径。</param>
/// <param name="allOfEntryPath">当前 allOf 条目逻辑路径。</param>
/// <param name="allOfSchema">当前 allOf 条目。</param>
/// <param name="allOfIndex">从 0 开始的条目索引。</param>
/// <param name="declaredProperties">父对象已声明属性集合。</param>
/// <param name="diagnostic">失败时返回的诊断。</param>
/// <returns>当前 allOf 条目的 required 约束是否有效。</returns>
private static bool TryValidateAllOfEntryRequiredProperties(
string filePath,
string allOfEntryPath,

View File

@ -3,10 +3,10 @@ using GFramework.SourceGenerators.Tests.Core;
namespace GFramework.SourceGenerators.Tests.Architectures;
[TestFixture]
/// <summary>
/// 验证 <see cref="AutoRegisterModuleGenerator" /> 在模块自动注册场景下的生成契约与输出顺序。
/// </summary>
[TestFixture]
public class AutoRegisterModuleGeneratorTests
{
private const string AttributeOrderSource = """

View File

@ -129,6 +129,58 @@ public class SchemaConfigGeneratorSnapshotTests
"type": "string",
"enum": ["coin", "gem"]
}
},
"dependentRequired": {
"currency": ["gold"]
},
"dependentSchemas": {
"currency": {
"type": "object",
"required": ["gold"],
"properties": {
"gold": {
"type": "integer"
}
}
}
},
"allOf": [
{
"type": "object",
"required": ["gold"],
"properties": {
"gold": {
"type": "integer"
}
}
}
],
"if": {
"type": "object",
"properties": {
"currency": {
"type": "string",
"const": "gem"
}
}
},
"then": {
"type": "object",
"required": ["gold"],
"properties": {
"gold": {
"type": "integer"
}
}
},
"else": {
"type": "object",
"required": ["currency"],
"properties": {
"currency": {
"type": "string"
}
}
}
},
"phases": {
@ -156,8 +208,8 @@ public class SchemaConfigGeneratorSnapshotTests
""";
/// <summary>
/// 验证一个最小 monster schema 能生成配置类型、表包装和注册辅助。
/// </summary>
/// 验证一个最小 monster schema 能生成配置类型、表包装和注册辅助。
/// </summary>
[Test]
public Task Snapshot_SchemaConfigGenerator()
{

View File

@ -78,7 +78,7 @@ public sealed partial class MonsterConfig
/// Reward payload.
/// </summary>
/// <remarks>
/// Constraints: minProperties = 2, maxProperties = 2.
/// Constraints: minProperties = 2, maxProperties = 2, dependentRequired = { currency =&gt; [gold] }, dependentSchemas = { currency =&gt; object (required = [gold]) }, allOf = [ object (required = [gold]) ], if/then/else = if object; properties = { currency: string (const = "gem") }; then object (required = [gold]); properties = { gold: integer }; else object (required = [currency]); properties = { currency: string }.
/// </remarks>
public sealed partial class RewardConfig
{

View File

@ -44,7 +44,7 @@ public static class GeneratorSnapshotTest<TGenerator>
/// <returns>标准化后的文本</returns>
private static string Normalize(string text)
{
return text.Replace("\r\n", "\n").Trim();
return text.Replace("\r\n", "\n", StringComparison.Ordinal).Trim();
}
/// <summary>
@ -194,7 +194,11 @@ public static class GeneratorSnapshotTest<TGenerator>
/// <param name="generatedContent">要写入的生成输出。</param>
private static async Task WriteMissingSnapshotAndFailAsync(string path, string generatedContent)
{
Directory.CreateDirectory(Path.GetDirectoryName(path)!);
// ResolveSnapshotPath 保证快照不会越界,但根目录路径仍会让 GetDirectoryName 返回 null。
var snapshotDirectory = Path.GetDirectoryName(path)
?? throw new InvalidOperationException(
$"Snapshot path '{path}' must include a parent directory.");
Directory.CreateDirectory(snapshotDirectory);
await File.WriteAllTextAsync(path, generatedContent).ConfigureAwait(false);
Assert.Fail($"未找到快照文件,已在以下路径生成新快照:\n{path}");
}

View File

@ -7,8 +7,8 @@
## 当前恢复点
- 恢复点编号:`ANALYZER-WARNING-REDUCTION-RP-033`
- 当前阶段:`Phase 33`
- 恢复点编号:`ANALYZER-WARNING-REDUCTION-RP-034`
- 当前阶段:`Phase 34`
- 当前焦点:
- 已完成 `GFramework.Core` 当前 `MA0016` / `MA0002` / `MA0015` / `MA0077` 低风险收口批次
- 已复核 `net10.0` 下的 `MA0158` 基线:`GFramework.Core` / `GFramework.Cqrs` 当前共有 `16` 个 object lock
@ -66,10 +66,23 @@
生成文件名、快照目录和断言语义不变
- 当前 `GFramework.SourceGenerators.Tests` Release build 基线已从 `40` 条降到 `39` 条;
`SchemaConfigGeneratorSnapshotTests.cs` 已不再出现在 `MA0051` 列表中
- 已完成当前 PR #273 review follow-up 首轮核对:确认本地仍成立的问题集中在
`SchemaConfigGenerator` helper XML 文档、`GeneratorSnapshotTest``StringComparison.Ordinal` /
snapshot 路径空值防御、`AutoRegisterModuleGeneratorTests` 的 XML 文档位置,以及
`SchemaConfigGeneratorSnapshotTests` 的 monster 快照覆盖缺口
- 已将 monster 快照场景扩展到 `dependentRequired``dependentSchemas``allOf` 与 object-focused
`if/then/else`,以便把新增 schema 约束文档纳入 snapshot 验证
- 已完成本轮定向验证:
`GFramework.Game.SourceGenerators` Release build 通过;
`GFramework.SourceGenerators.Tests``-m:1 --no-restore` 下 Release build 通过;
`SchemaConfigGeneratorSnapshotTests``AutoRegisterModuleGeneratorTests` 定向测试共 `4` 项全部通过
- 当前验证仍受环境/基线约束:
`GFramework.SourceGenerators.Tests` Release build 保留既有 `MA0051` warning 基线;
NuGet vulnerability audit 在离线环境下产生 `NU1900`
- `GFramework.Godot``Timing.cs` 已同步适配新事件签名,但当前 worktree 的 Godot restore 资产仍受 Windows fallback package folder 干扰,独立 build 需在修复资产后补跑
- 后续继续按 warning 类型和数量批处理,而不是回退到按单文件切片推进
- 下一轮默认继续拆分 `GFramework.SourceGenerators.Tests``MA0051` 热点,优先处理
`GeneratorSnapshotTest``ContextRegistrationAnalyzerTests`
- 下一轮默认重新抓取 PR #273 最新 review 线程,并确认本轮 snapshot 更新后是否还存在剩余 open thread 或
`dotnet-format` 细项
- 单次 `boot` 的工作树改动上限控制在约 `100` 个文件以内,避免 recovery context 与 review 面同时失控
- 若任务边界互不冲突,允许使用不同模型的 subagent 并行处理不同 warning 类型或不同目录,但必须遵守显式 ownership

View File

@ -964,3 +964,20 @@
1. 若继续 analyzer warning reduction优先回到 `GFramework.Core` 剩余 `MA0051` 热点,并继续保持“单 warning family、单切入点”的节奏
2. 后续所有 WSL 下的 .NET 定向验证命令继续显式附带 `-p:RestoreFallbackFolders=`,避免把环境问题误判成代码回归
# 2026-04-23
- RP-034 / PR #273 review follow-up
- 使用 `gframework-pr-review` 抓取当前分支 PR #273 的 latest-head review threads、MegaLinter 和测试摘要。
- 本地复核后确认仍成立的项集中在 `SchemaConfigGenerator` helper XML 文档、
`GeneratorSnapshotTest``StringComparison.Ordinal` 与 snapshot 路径空值防御、
`AutoRegisterModuleGeneratorTests` 的 XML 文档位置,以及
`SchemaConfigGeneratorSnapshotTests` 的 monster snapshot 覆盖缺口。
- 已扩展 monster schema 场景以覆盖 `dependentRequired``dependentSchemas``allOf` 与 object-focused
`if/then/else`,并同步更新 `MonsterConfig.g.txt` 的约束快照。
- `DOTNET_CLI_HOME=/tmp/dotnet-home dotnet build GFramework.Game.SourceGenerators/GFramework.Game.SourceGenerators.csproj -c Release -p:RestoreFallbackFolders=`
通过;离线 NuGet vulnerability audit 产生 `NU1900`
- `DOTNET_CLI_HOME=/tmp/dotnet-home dotnet build GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --no-restore -p:RestoreFallbackFolders= -m:1`
通过;测试项目保留既有 `MA0051` warning 基线。
- `DOTNET_CLI_HOME=/tmp/dotnet-home dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --no-build --filter "FullyQualifiedName~SchemaConfigGeneratorSnapshotTests|FullyQualifiedName~AutoRegisterModuleGeneratorTests" -m:1`
通过,`4` 个用例全部通过;需要在沙箱外执行以绕过 `vstest` 本地 socket 权限限制。
- 下一步:提交本轮修复并在需要时重新抓取 PR review确认 open threads 是否随新提交收敛。