fix(game-source-generators): 清理 SchemaConfigGenerator 字符串比较

- 修复 SchemaConfigGenerator 中 schema 关键字比较缺少 StringComparison 的 analyzer warning

- 新增 schema 类型比较 helper 以统一 ordinal 比较语义

- 更新 analyzer warning reduction 的 RP-019 恢复记录与验证结果
This commit is contained in:
gewuyou 2026-04-22 10:02:55 +08:00
parent de782ae179
commit 78a23bf53a
3 changed files with 103 additions and 32 deletions

View File

@ -203,8 +203,8 @@ public sealed class SchemaConfigGenerator : IIncrementalGenerator
Path.GetFileName(file.Path)));
}
if (idProperty.TypeSpec.SchemaType != "integer" &&
idProperty.TypeSpec.SchemaType != "string")
if (!IsSchemaType(idProperty.TypeSpec.SchemaType, "integer") &&
!IsSchemaType(idProperty.TypeSpec.SchemaType, "string"))
{
return SchemaParseResult.FromDiagnostic(
Diagnostic.Create(
@ -1854,6 +1854,27 @@ public sealed class SchemaConfigGenerator : IIncrementalGenerator
};
}
/// <summary>
/// 使用 schema 关键字的大小写敏感语义比较类型名称。
/// </summary>
/// <param name="schemaType">从 JSON schema 读取或推导出的类型名称。</param>
/// <param name="expectedType">期望匹配的 schema 类型关键字。</param>
/// <returns>当两个类型名称按 schema 关键字语义完全一致时返回 <see langword="true" />。</returns>
private static bool IsSchemaType(string schemaType, string expectedType)
{
return string.Equals(schemaType, expectedType, StringComparison.Ordinal);
}
/// <summary>
/// 判断类型是否支持 JSON schema 数值范围和倍数约束。
/// </summary>
/// <param name="schemaType">从 JSON schema 读取或推导出的类型名称。</param>
/// <returns>当类型为 <c>integer</c> 或 <c>number</c> 时返回 <see langword="true" />。</returns>
private static bool IsNumericSchemaType(string schemaType)
{
return IsSchemaType(schemaType, "integer") || IsSchemaType(schemaType, "number");
}
/// <summary>
/// 解析数组属性,支持标量数组与对象数组。
/// </summary>
@ -3943,57 +3964,57 @@ public sealed class SchemaConfigGenerator : IIncrementalGenerator
parts.Add($"const = {constDocumentation}");
}
if ((schemaType == "integer" || schemaType == "number") &&
if (IsNumericSchemaType(schemaType) &&
TryGetFiniteNumber(element, "minimum", out var minimum))
{
parts.Add($"minimum = {minimum.ToString(CultureInfo.InvariantCulture)}");
}
if ((schemaType == "integer" || schemaType == "number") &&
if (IsNumericSchemaType(schemaType) &&
TryGetFiniteNumber(element, "exclusiveMinimum", out var exclusiveMinimum))
{
parts.Add($"exclusiveMinimum = {exclusiveMinimum.ToString(CultureInfo.InvariantCulture)}");
}
if ((schemaType == "integer" || schemaType == "number") &&
if (IsNumericSchemaType(schemaType) &&
TryGetFiniteNumber(element, "maximum", out var maximum))
{
parts.Add($"maximum = {maximum.ToString(CultureInfo.InvariantCulture)}");
}
if ((schemaType == "integer" || schemaType == "number") &&
if (IsNumericSchemaType(schemaType) &&
TryGetFiniteNumber(element, "exclusiveMaximum", out var exclusiveMaximum))
{
parts.Add($"exclusiveMaximum = {exclusiveMaximum.ToString(CultureInfo.InvariantCulture)}");
}
if ((schemaType == "integer" || schemaType == "number") &&
if (IsNumericSchemaType(schemaType) &&
TryGetFiniteNumber(element, "multipleOf", out var multipleOf) &&
multipleOf > 0d)
{
parts.Add($"multipleOf = {multipleOf.ToString(CultureInfo.InvariantCulture)}");
}
if (schemaType == "string" &&
if (IsSchemaType(schemaType, "string") &&
TryGetNonNegativeInt32(element, "minLength", out var minLength))
{
parts.Add($"minLength = {minLength.ToString(CultureInfo.InvariantCulture)}");
}
if (schemaType == "string" &&
if (IsSchemaType(schemaType, "string") &&
TryGetNonNegativeInt32(element, "maxLength", out var maxLength))
{
parts.Add($"maxLength = {maxLength.ToString(CultureInfo.InvariantCulture)}");
}
if (schemaType == "string" &&
if (IsSchemaType(schemaType, "string") &&
element.TryGetProperty("pattern", out var patternElement) &&
patternElement.ValueKind == JsonValueKind.String)
{
parts.Add($"pattern = '{patternElement.GetString() ?? string.Empty}'");
}
if (schemaType == "string" &&
if (IsSchemaType(schemaType, "string") &&
element.TryGetProperty("format", out var formatElement) &&
formatElement.ValueKind == JsonValueKind.String)
{
@ -4004,26 +4025,26 @@ public sealed class SchemaConfigGenerator : IIncrementalGenerator
}
}
if (schemaType == "array" &&
if (IsSchemaType(schemaType, "array") &&
TryGetNonNegativeInt32(element, "minItems", out var minItems))
{
parts.Add($"minItems = {minItems.ToString(CultureInfo.InvariantCulture)}");
}
if (schemaType == "array" &&
if (IsSchemaType(schemaType, "array") &&
TryGetNonNegativeInt32(element, "maxItems", out var maxItems))
{
parts.Add($"maxItems = {maxItems.ToString(CultureInfo.InvariantCulture)}");
}
if (schemaType == "array" &&
if (IsSchemaType(schemaType, "array") &&
element.TryGetProperty("uniqueItems", out var uniqueItemsElement) &&
uniqueItemsElement.ValueKind == JsonValueKind.True)
{
parts.Add("uniqueItems = true");
}
if (schemaType == "array")
if (IsSchemaType(schemaType, "array"))
{
var containsDocumentation = TryBuildContainsDocumentation(element);
if (containsDocumentation is not null)
@ -4038,31 +4059,31 @@ public sealed class SchemaConfigGenerator : IIncrementalGenerator
parts.Add($"not = {notDocumentation}");
}
if (schemaType == "array" &&
if (IsSchemaType(schemaType, "array") &&
TryGetNonNegativeInt32(element, "minContains", out var minContains))
{
parts.Add($"minContains = {minContains.ToString(CultureInfo.InvariantCulture)}");
}
if (schemaType == "array" &&
if (IsSchemaType(schemaType, "array") &&
TryGetNonNegativeInt32(element, "maxContains", out var maxContains))
{
parts.Add($"maxContains = {maxContains.ToString(CultureInfo.InvariantCulture)}");
}
if (schemaType == "object" &&
if (IsSchemaType(schemaType, "object") &&
TryGetNonNegativeInt32(element, "minProperties", out var minProperties))
{
parts.Add($"minProperties = {minProperties.ToString(CultureInfo.InvariantCulture)}");
}
if (schemaType == "object" &&
if (IsSchemaType(schemaType, "object") &&
TryGetNonNegativeInt32(element, "maxProperties", out var maxProperties))
{
parts.Add($"maxProperties = {maxProperties.ToString(CultureInfo.InvariantCulture)}");
}
if (schemaType == "object")
if (IsSchemaType(schemaType, "object"))
{
var dependentRequiredDocumentation = TryBuildDependentRequiredDocumentation(element);
if (dependentRequiredDocumentation is not null)
@ -4351,14 +4372,14 @@ public sealed class SchemaConfigGenerator : IIncrementalGenerator
}
var schemaType = typeElement.GetString();
if (string.IsNullOrWhiteSpace(schemaType))
if (schemaType is null || string.IsNullOrWhiteSpace(schemaType))
{
return null;
}
var details = new List<string>();
if (includeRequiredProperties &&
schemaType == "object")
IsSchemaType(schemaType, "object"))
{
var requiredDocumentation = TryBuildRequiredPropertiesDocumentation(schemaElement);
if (requiredDocumentation is not null)
@ -4367,13 +4388,13 @@ public sealed class SchemaConfigGenerator : IIncrementalGenerator
}
}
var enumDocumentation = TryBuildEnumDocumentation(schemaElement, schemaType!);
var enumDocumentation = TryBuildEnumDocumentation(schemaElement, schemaType);
if (enumDocumentation is not null)
{
details.Add($"enum = {enumDocumentation}");
}
var constraintDocumentation = TryBuildConstraintDocumentation(schemaElement, schemaType!);
var constraintDocumentation = TryBuildConstraintDocumentation(schemaElement, schemaType);
if (constraintDocumentation is not null)
{
details.Add(constraintDocumentation);
@ -4492,7 +4513,9 @@ public sealed class SchemaConfigGenerator : IIncrementalGenerator
/// <returns>组合后的路径。</returns>
private static string CombinePath(string parentPath, string propertyName)
{
return parentPath == "<root>" ? propertyName : $"{parentPath}.{propertyName}";
return string.Equals(parentPath, "<root>", StringComparison.Ordinal)
? propertyName
: $"{parentPath}.{propertyName}";
}
/// <summary>

View File

@ -7,14 +7,16 @@
## 当前恢复点
- 恢复点编号:`ANALYZER-WARNING-REDUCTION-RP-018`
- 当前阶段:`Phase 18`
- 恢复点编号:`ANALYZER-WARNING-REDUCTION-RP-019`
- 当前阶段:`Phase 19`
- 当前焦点:
- 已完成 `GFramework.Core` 当前 `MA0016` / `MA0002` / `MA0015` / `MA0077` 低风险收口批次
- 已复核 `net10.0` 下的 `MA0158` 基线:`GFramework.Core` / `GFramework.Cqrs` 当前共有 `16` 个 object lock
建议点,属于跨 target 兼容性风险,不在本轮直接批量替换
- 已完成 `GFramework.Core.SourceGenerators/Rule/ContextAwareGenerator.cs` 的剩余 `MA0051` 结构拆分,生成输出保持不变
- 已完成 `GFramework.Cqrs.SourceGenerators/Cqrs/CqrsHandlerRegistryGenerator.cs``MA0051` 结构拆分,生成输出保持不变
- 已完成 `GFramework.Game.SourceGenerators/Config/SchemaConfigGenerator.cs``MA0006` 低风险收口schema 关键字比较显式使用
`StringComparison.Ordinal`
- `LoggingConfiguration``FilterConfiguration``CollectionExtensions` 已改用集合抽象接口,并保留内部具体集合默认值
- `CoroutineScheduler` 的 tag/group 字典已显式使用 `StringComparer.Ordinal`,保持既有区分大小写语义
- `EasyEvents.AddEvent<T>()` 的重复注册路径已改为状态冲突异常,避免把泛型类型参数伪装成方法参数名
@ -22,9 +24,11 @@
- 当前 `GFramework.Core` `net8.0` warnings-only 基线已降到 `0`
- 当前 `GFramework.Core.SourceGenerators` warnings-only 基线已降到 `0`
- 当前 `GFramework.Cqrs.SourceGenerators` warnings-only 基线已降到 `0`
- 当前 `GFramework.Game.SourceGenerators` warnings-only 基线已从 `46` 条降到 `19` 条,剩余均为
`SchemaConfigGenerator.cs``MA0051`
- `GFramework.Godot``Timing.cs` 已同步适配新事件签名,但当前 worktree 的 Godot restore 资产仍受 Windows fallback package folder 干扰,独立 build 需在修复资产后补跑
- 后续继续按 warning 类型和数量批处理,而不是回退到按单文件切片推进
- 下一轮默认转向 `GFramework.Game.SourceGenerators``MA0006` / `MA0051` 热点,或继续评估跨 target 的 `MA0158`
- 下一轮默认继续拆分 `GFramework.Game.SourceGenerators``MA0051` 热点,或评估跨 target 的 `MA0158`
锁替换风险
- 单次 `boot` 的工作树改动上限控制在约 `100` 个文件以内,避免 recovery context 与 review 面同时失控
- 若任务边界互不冲突,允许使用不同模型的 subagent 并行处理不同 warning 类型或不同目录,但必须遵守显式 ownership
@ -45,6 +49,8 @@
- 已完成当前 `GFramework.Core` `net8.0` 剩余低风险 analyzer warning 批次warnings-only 基线已降到 `0`
- 已完成 `GFramework.Core.SourceGenerators``ContextAwareGenerator` 的剩余 `MA0051` 收口warnings-only 基线已降到 `0`
- 已完成 `GFramework.Cqrs.SourceGenerators``CqrsHandlerRegistryGenerator` 的剩余 `MA0051` 收口warnings-only 基线已降到 `0`
- 已完成 `GFramework.Game.SourceGenerators``SchemaConfigGenerator``MA0006` 收口warnings-only 基线剩余 `19`
`MA0051`
## 当前活跃事实
@ -81,6 +87,8 @@
并通过 source generator 项目 build 与 `ContextAwareGeneratorSnapshotTests` 验证生成输出未回归
- `RP-018` 暂缓跨 target `MA0158`,转入 `GFramework.Cqrs.SourceGenerators` 的单文件结构性 warning
通过拆分 handler 分析、运行时类型引用构造、注册器源码发射与精确反射注册输出阶段,清空该项目当前 `MA0051`
- `RP-019` 转入 `GFramework.Game.SourceGenerators/Config/SchemaConfigGenerator.cs`,先完成低风险 `MA0006` 批次;
通过 schema 类型比较 helper 与显式 `StringComparison.Ordinal` 清空当前项目的 `MA0006`
- 当前工作树分支 `fix/analyzer-warning-reduction-batch` 已在 `ai-plan/public/README.md` 建立 topic 映射
## 当前风险
@ -95,8 +103,7 @@
- 缓解措施:下一轮先按 target framework 与 API 可用性评估,不直接批量替换共享源码中的 `object` lock
- source generator warning 外溢风险:运行 `GFramework.SourceGenerators.Tests` 会构建相邻 generator/test 项目并显示既有
`GFramework.Game.SourceGenerators` 与测试项目 warning
- 缓解措施:本轮以 `GFramework.Cqrs.SourceGenerators` 独立 warnings-only build 作为主验收,并用 focused generator test
验证行为;后续若处理相邻 generator warning应另开明确切片
- 缓解措施:继续以被修改 generator 项目的独立 warnings-only build 作为主验收,并用 focused generator test 验证行为
- Godot 资产文件环境风险:当前 worktree 的 `GFramework.Godot` restore/build 仍会命中 Windows fallback package folder
- 缓解措施:后续若继续触达 Godot 模块,先用 Linux 侧 restore 资产或 Windows-hosted 构建链刷新该项目,再补跑定向 build
- 并行实现风险:批量收敛时若 subagent 写入边界不清晰,容易引入命名冲突或重复重构
@ -198,13 +205,24 @@
- 结果:`14 Passed``0 Failed`
- 说明:该 test project 构建仍显示 `GFramework.Game.SourceGenerators` 与测试项目中的既有 analyzer warning本轮关注的
`GFramework.Cqrs.SourceGenerators` 独立 build 已清零
- `RP-019` 的验证结果:
- `dotnet restore GFramework.Game.SourceGenerators/GFramework.Game.SourceGenerators.csproj -p:RestoreFallbackFolders= -nologo`
- 结果:通过;刷新 Linux 侧资产以清除 stale Windows fallback package folder
- `dotnet build GFramework.Game.SourceGenerators/GFramework.Game.SourceGenerators.csproj -c Release -t:Rebuild --no-restore -p:UseSharedCompilation=false -p:RestoreFallbackFolders= -nologo -clp:"Summary;WarningsOnly"`
- 结果:`19 Warning(s)``0 Error(s)`;当前项目输出已不再出现 `MA0006`,剩余均为 `SchemaConfigGenerator.cs`
`MA0051`
- `dotnet restore GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -p:RestoreFallbackFolders= -nologo`
- 结果:通过;刷新 test project 资产以清除 stale Windows fallback package folder
- `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --no-restore --filter FullyQualifiedName~SchemaConfigGenerator -m:1 -p:RestoreFallbackFolders= -nologo`
- 结果:`50 Passed``0 Failed`
- 说明:测试项目构建仍显示既有 source generator test analyzer warning不属于本轮写集
- active 跟踪文件只保留当前恢复点、活跃事实、风险与下一步,不再重复保存已完成阶段的长篇历史
## 下一步
1. 若要继续该主题,先读 active tracking再按需展开历史归档中的 warning 热点与验证记录
2. 下一轮优先转入 `GFramework.Game.SourceGenerators/Config/SchemaConfigGenerator.cs``MA0006` 低风险批次,再评估是否继续拆分
该文件的 `MA0051`
2. 下一轮优先继续拆分 `GFramework.Game.SourceGenerators/Config/SchemaConfigGenerator.cs``MA0051`;建议先从
`TryBuildConstraintDocumentation``GenerateConfigCatalogSource` 这类高收益方法切入
3. 若改回推进 `MA0158`,先设计 `net8.0` / `net9.0` / `net10.0` 多 target 条件编译方案,不直接批量替换共享源码中的
`object` lock
4. 若后续继续改动 `GFramework.Godot`,先修复该项目的 Linux 侧 restore 资产,再补跑独立 build

View File

@ -1,5 +1,35 @@
# Analyzer Warning Reduction 追踪
## 2026-04-22 — RP-019
### 阶段:`SchemaConfigGenerator` 当前 `MA0006` 收口RP-019
- 启动复核:
- 当前 worktree 仍映射到 `analyzer-warning-reduction` active topic
- Windows Git interop 在当前 shell 中返回 WSL socket 错误;本轮使用显式 `--git-dir` / `--work-tree` 读取状态
- `GFramework.Game.SourceGenerators` 首次 build 受 stale Windows fallback package folder 影响,刷新 restore 资产后复现
`46` 条 warning其中 `MA0006=27`,其余为 `SchemaConfigGenerator.cs``MA0051`
- 决策:
- 本轮先收口低风险 `MA0006`,不在同一 slice 中拆分 `SchemaConfigGenerator.cs` 的长方法
- 未使用 subagentcritical path 是本地复现 warning、替换 schema 字符串比较并用 focused schema generator tests 验证输出行为
- 实施调整:
- 为 schema 类型关键字新增 `IsSchemaType` / `IsNumericSchemaType` helper统一使用 `StringComparison.Ordinal`
- 将 id key 类型验证、约束文档生成、required property 文档和路径拼接中的直接字符串比较改为显式 ordinal 比较
- 修正 `JsonElement.GetString()` 后的 nullable flow避免新增 `CS8604`
- 验证结果:
- `dotnet restore GFramework.Game.SourceGenerators/GFramework.Game.SourceGenerators.csproj -p:RestoreFallbackFolders= -nologo`
- 结果:通过
- `dotnet build GFramework.Game.SourceGenerators/GFramework.Game.SourceGenerators.csproj -c Release -t:Rebuild --no-restore -p:UseSharedCompilation=false -p:RestoreFallbackFolders= -nologo -clp:"Summary;WarningsOnly"`
- 结果:`19 Warning(s)``0 Error(s)`;当前项目输出已无 `MA0006`,剩余均为 `MA0051`
- `dotnet restore GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -p:RestoreFallbackFolders= -nologo`
- 结果:通过
- `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --no-restore --filter FullyQualifiedName~SchemaConfigGenerator -m:1 -p:RestoreFallbackFolders= -nologo`
- 结果:`50 Passed``0 Failed`
- 说明:测试项目构建仍显示既有 analyzer warning不属于本轮写集
- 下一步建议:
- 继续该主题时,优先拆分 `GFramework.Game.SourceGenerators/Config/SchemaConfigGenerator.cs``MA0051`
- 若回到 `MA0158`,先设计多 target 条件编译方案,再考虑替换共享源码中的 `object` lock
## 2026-04-22 — RP-018
### 阶段:`CqrsHandlerRegistryGenerator` 剩余 `MA0051` 收口RP-018