diff --git a/GFramework.Game.SourceGenerators/Config/SchemaConfigGenerator.cs b/GFramework.Game.SourceGenerators/Config/SchemaConfigGenerator.cs index 92be27f5..0ca5ea00 100644 --- a/GFramework.Game.SourceGenerators/Config/SchemaConfigGenerator.cs +++ b/GFramework.Game.SourceGenerators/Config/SchemaConfigGenerator.cs @@ -1590,7 +1590,7 @@ public sealed class SchemaConfigGenerator : IIncrementalGenerator ConfigSchemaDiagnostics.InvalidConditionalSchemaMetadata, CreateFileLocation(filePath), Path.GetFileName(filePath), - displayPath, + branchPath, $"The '{keywordName}' value must be an object-valued schema."); return false; } @@ -4214,7 +4214,7 @@ public sealed class SchemaConfigGenerator : IIncrementalGenerator return null; } - var ifSummary = TryBuildInlineSchemaSummary(ifElement, includeRequiredProperties: true); + var ifSummary = TryBuildConditionalBranchSummary(ifElement); if (ifSummary is null) { return null; @@ -4224,7 +4224,7 @@ public sealed class SchemaConfigGenerator : IIncrementalGenerator if (element.TryGetProperty("then", out var thenElement) && thenElement.ValueKind == JsonValueKind.Object) { - var thenSummary = TryBuildInlineSchemaSummary(thenElement, includeRequiredProperties: true); + var thenSummary = TryBuildConditionalBranchSummary(thenElement); if (thenSummary is not null) { parts.Add($"then {thenSummary}"); @@ -4234,7 +4234,7 @@ public sealed class SchemaConfigGenerator : IIncrementalGenerator if (element.TryGetProperty("else", out var elseElement) && elseElement.ValueKind == JsonValueKind.Object) { - var elseSummary = TryBuildInlineSchemaSummary(elseElement, includeRequiredProperties: true); + var elseSummary = TryBuildConditionalBranchSummary(elseElement); if (elseSummary is not null) { parts.Add($"else {elseSummary}"); @@ -4246,6 +4246,58 @@ public sealed class SchemaConfigGenerator : IIncrementalGenerator : null; } + /// + /// 汇总条件分支的对象级约束与子属性约束,避免生成文档只保留笼统的 object 描述。 + /// + /// 条件分支 schema。 + /// 格式化后的条件分支摘要。 + private static string? TryBuildConditionalBranchSummary(JsonElement branchElement) + { + var branchSummary = TryBuildInlineSchemaSummary(branchElement, includeRequiredProperties: true); + if (branchSummary is null) + { + return null; + } + + var propertiesSummary = TryBuildInlineObjectPropertiesSummary(branchElement); + return propertiesSummary is null + ? branchSummary + : $"{branchSummary}; properties = {propertiesSummary}"; + } + + /// + /// 汇总对象 properties 内每个字段的紧凑约束,补足条件分支文档里的触发条件细节。 + /// + /// 对象 schema 节点。 + /// 格式化后的子属性约束摘要。 + private static string? TryBuildInlineObjectPropertiesSummary(JsonElement schemaElement) + { + if (!schemaElement.TryGetProperty("properties", out var propertiesElement) || + propertiesElement.ValueKind != JsonValueKind.Object) + { + return null; + } + + var parts = new List(); + foreach (var property in propertiesElement.EnumerateObject()) + { + if (property.Value.ValueKind != JsonValueKind.Object) + { + continue; + } + + var propertySummary = TryBuildInlineSchemaSummary(property.Value); + if (propertySummary is not null) + { + parts.Add($"{property.Name}: {propertySummary}"); + } + } + + return parts.Count == 0 + ? null + : $"{{ {string.Join("; ", parts)} }}"; + } + /// /// 将数组 contains 子 schema 整理成 XML 文档可读字符串。 /// 输出优先保持紧凑,只展示消费者在强类型 API 上最需要看到的匹配摘要。 diff --git a/GFramework.SourceGenerators.Tests/Config/SchemaConfigGeneratorTests.cs b/GFramework.SourceGenerators.Tests/Config/SchemaConfigGeneratorTests.cs index 77b12495..dae574a3 100644 --- a/GFramework.SourceGenerators.Tests/Config/SchemaConfigGeneratorTests.cs +++ b/GFramework.SourceGenerators.Tests/Config/SchemaConfigGeneratorTests.cs @@ -1338,7 +1338,10 @@ public class SchemaConfigGeneratorTests Assert.That(result.Results.Single().Diagnostics, Is.Empty); Assert.That( generatedSources["MonsterConfig.g.cs"], - Does.Contain("Constraints: if/then/else = if object; then object (required = [itemCount]); else object (required = [bonus]).")); + Does.Contain( + "Constraints: if/then/else = if object; properties = { itemId: string (const = \"potion\") }; " + + "then object (required = [itemCount]); properties = { itemCount: integer }; " + + "else object (required = [bonus]); properties = { bonus: integer }.")); } /// @@ -1394,6 +1397,63 @@ public class SchemaConfigGeneratorTests }); } + /// + /// 验证条件分支不是 object schema 时,诊断路径会定位到具体分支而不是父对象。 + /// + [Test] + public void Run_Should_Report_Diagnostic_With_Branch_Path_When_Then_Schema_Is_Not_Object() + { + const string source = """ + namespace TestApp + { + public sealed class Dummy + { + } + } + """; + + const string schema = """ + { + "type": "object", + "required": ["id", "reward"], + "properties": { + "id": { "type": "integer" }, + "reward": { + "type": "object", + "properties": { + "itemId": { "type": "string" }, + "itemCount": { "type": "integer" } + }, + "if": { + "type": "object", + "properties": { + "itemId": { + "type": "string", + "const": "potion" + } + } + }, + "then": [] + } + } + } + """; + + 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_013")); + Assert.That(diagnostic.Severity, Is.EqualTo(DiagnosticSeverity.Error)); + Assert.That(diagnostic.GetMessage(), Does.Contain("reward[then]")); + Assert.That(diagnostic.GetMessage(), Does.Contain("must be an object-valued schema")); + }); + } + /// /// 验证条件分支不能引用父对象未声明的字段。 ///