diff --git a/GFramework.Game.SourceGenerators/Config/SchemaConfigGenerator.cs b/GFramework.Game.SourceGenerators/Config/SchemaConfigGenerator.cs index 0ca5ea00..ece23f07 100644 --- a/GFramework.Game.SourceGenerators/Config/SchemaConfigGenerator.cs +++ b/GFramework.Game.SourceGenerators/Config/SchemaConfigGenerator.cs @@ -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 }; } + /// + /// 使用 schema 关键字的大小写敏感语义比较类型名称。 + /// + /// 从 JSON schema 读取或推导出的类型名称。 + /// 期望匹配的 schema 类型关键字。 + /// 当两个类型名称按 schema 关键字语义完全一致时返回 + private static bool IsSchemaType(string schemaType, string expectedType) + { + return string.Equals(schemaType, expectedType, StringComparison.Ordinal); + } + + /// + /// 判断类型是否支持 JSON schema 数值范围和倍数约束。 + /// + /// 从 JSON schema 读取或推导出的类型名称。 + /// 当类型为 integernumber 时返回 + private static bool IsNumericSchemaType(string schemaType) + { + return IsSchemaType(schemaType, "integer") || IsSchemaType(schemaType, "number"); + } + /// /// 解析数组属性,支持标量数组与对象数组。 /// @@ -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(); 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 /// 组合后的路径。 private static string CombinePath(string parentPath, string propertyName) { - return parentPath == "" ? propertyName : $"{parentPath}.{propertyName}"; + return string.Equals(parentPath, "", StringComparison.Ordinal) + ? propertyName + : $"{parentPath}.{propertyName}"; } /// diff --git a/ai-plan/public/analyzer-warning-reduction/todos/analyzer-warning-reduction-tracking.md b/ai-plan/public/analyzer-warning-reduction/todos/analyzer-warning-reduction-tracking.md index b13e0e74..d27049dc 100644 --- a/ai-plan/public/analyzer-warning-reduction/todos/analyzer-warning-reduction-tracking.md +++ b/ai-plan/public/analyzer-warning-reduction/todos/analyzer-warning-reduction-tracking.md @@ -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()` 的重复注册路径已改为状态冲突异常,避免把泛型类型参数伪装成方法参数名 @@ -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 diff --git a/ai-plan/public/analyzer-warning-reduction/traces/analyzer-warning-reduction-trace.md b/ai-plan/public/analyzer-warning-reduction/traces/analyzer-warning-reduction-trace.md index b567a7e5..58b721a6 100644 --- a/ai-plan/public/analyzer-warning-reduction/traces/analyzer-warning-reduction-trace.md +++ b/ai-plan/public/analyzer-warning-reduction/traces/analyzer-warning-reduction-trace.md @@ -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` 的长方法 + - 未使用 subagent;critical 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)