fix(config): 修复条件分支诊断与文档摘要

- 修复 then/else 非 object 分支诊断定位到具体条件路径
- 优化 if/then/else 文档摘要,补充 properties 内约束说明
- 补充生成器回归测试,覆盖分支路径与文档输出
This commit is contained in:
GeWuYou 2026-04-20 14:30:11 +08:00
parent 68d653623a
commit 0da15f6ffd
2 changed files with 117 additions and 5 deletions

View File

@ -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;
}
/// <summary>
/// 汇总条件分支的对象级约束与子属性约束,避免生成文档只保留笼统的 object 描述。
/// </summary>
/// <param name="branchElement">条件分支 schema。</param>
/// <returns>格式化后的条件分支摘要。</returns>
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}";
}
/// <summary>
/// 汇总对象 <c>properties</c> 内每个字段的紧凑约束,补足条件分支文档里的触发条件细节。
/// </summary>
/// <param name="schemaElement">对象 schema 节点。</param>
/// <returns>格式化后的子属性约束摘要。</returns>
private static string? TryBuildInlineObjectPropertiesSummary(JsonElement schemaElement)
{
if (!schemaElement.TryGetProperty("properties", out var propertiesElement) ||
propertiesElement.ValueKind != JsonValueKind.Object)
{
return null;
}
var parts = new List<string>();
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)} }}";
}
/// <summary>
/// 将数组 <c>contains</c> 子 schema 整理成 XML 文档可读字符串。
/// 输出优先保持紧凑,只展示消费者在强类型 API 上最需要看到的匹配摘要。

View File

@ -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 }."));
}
/// <summary>
@ -1394,6 +1397,63 @@ public class SchemaConfigGeneratorTests
});
}
/// <summary>
/// 验证条件分支不是 object schema 时,诊断路径会定位到具体分支而不是父对象。
/// </summary>
[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"));
});
}
/// <summary>
/// 验证条件分支不能引用父对象未声明的字段。
/// </summary>