mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-05-07 00:39:00 +08:00
fix(game): 清理剩余配置 schema warning
- 重构 YamlConfigSchemaValidator 的长方法为语义化 helper,清理剩余 MA0051 warning - 修复 条件分支 helper 的字符串比较方式,避免新增 MA0006 warning - 更新 analyzer warning reduction 跟踪与 trace,记录仓库根 clean build 已归零
This commit is contained in:
parent
104ac25dc3
commit
7da985947c
@ -320,6 +320,30 @@ internal static partial class YamlConfigSchemaValidator
|
||||
string propertyPath,
|
||||
JsonElement element,
|
||||
bool isRoot = false)
|
||||
{
|
||||
var typeName = ResolveNodeTypeName(tableName, schemaPath, propertyPath, element);
|
||||
var referenceTableName = TryGetReferenceTableName(tableName, schemaPath, propertyPath, element);
|
||||
ValidateObjectOnlyKeywords(tableName, schemaPath, propertyPath, element, typeName);
|
||||
|
||||
var parsedNode = CreateParsedNodeForType(
|
||||
tableName,
|
||||
schemaPath,
|
||||
propertyPath,
|
||||
element,
|
||||
typeName,
|
||||
referenceTableName,
|
||||
isRoot);
|
||||
return parsedNode.WithNegatedSchemaNode(ParseNegatedSchemaNode(tableName, schemaPath, propertyPath, element));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 解析 schema 节点声明的类型名称,并在缺失或类型错误时立刻给出定位清晰的诊断。
|
||||
/// </summary>
|
||||
private static string ResolveNodeTypeName(
|
||||
string tableName,
|
||||
string schemaPath,
|
||||
string propertyPath,
|
||||
JsonElement element)
|
||||
{
|
||||
if (!element.TryGetProperty("type", out var typeElement) ||
|
||||
typeElement.ValueKind != JsonValueKind.String)
|
||||
@ -332,20 +356,46 @@ internal static partial class YamlConfigSchemaValidator
|
||||
displayPath: GetDiagnosticPath(propertyPath));
|
||||
}
|
||||
|
||||
var typeName = typeElement.GetString() ?? string.Empty;
|
||||
var referenceTableName = TryGetReferenceTableName(tableName, schemaPath, propertyPath, element);
|
||||
if (!string.Equals(typeName, "object", StringComparison.Ordinal) &&
|
||||
TryGetObjectOnlyKeywordName(element) is { } objectOnlyKeywordName)
|
||||
return typeElement.GetString() ?? string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 限制只允许对象 schema 使用对象专属关键字,避免后续分支在运行时才发现语义不兼容。
|
||||
/// </summary>
|
||||
private static void ValidateObjectOnlyKeywords(
|
||||
string tableName,
|
||||
string schemaPath,
|
||||
string propertyPath,
|
||||
JsonElement element,
|
||||
string typeName)
|
||||
{
|
||||
if (string.Equals(typeName, "object", StringComparison.Ordinal) ||
|
||||
TryGetObjectOnlyKeywordName(element) is not { } objectOnlyKeywordName)
|
||||
{
|
||||
throw ConfigLoadExceptionFactory.Create(
|
||||
ConfigLoadFailureKind.SchemaUnsupported,
|
||||
tableName,
|
||||
$"Property '{propertyPath}' in schema file '{schemaPath}' can only declare '{objectOnlyKeywordName}' on object schemas.",
|
||||
schemaPath: schemaPath,
|
||||
displayPath: GetDiagnosticPath(propertyPath));
|
||||
return;
|
||||
}
|
||||
|
||||
var parsedNode = typeName switch
|
||||
throw ConfigLoadExceptionFactory.Create(
|
||||
ConfigLoadFailureKind.SchemaUnsupported,
|
||||
tableName,
|
||||
$"Property '{propertyPath}' in schema file '{schemaPath}' can only declare '{objectOnlyKeywordName}' on object schemas.",
|
||||
schemaPath: schemaPath,
|
||||
displayPath: GetDiagnosticPath(propertyPath));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据声明的 schema 类型分派到对应的节点解析器。
|
||||
/// </summary>
|
||||
private static YamlConfigSchemaNode CreateParsedNodeForType(
|
||||
string tableName,
|
||||
string schemaPath,
|
||||
string propertyPath,
|
||||
JsonElement element,
|
||||
string typeName,
|
||||
string? referenceTableName,
|
||||
bool isRoot)
|
||||
{
|
||||
return typeName switch
|
||||
{
|
||||
"object" => ParseObjectSchemaNode(
|
||||
tableName,
|
||||
@ -391,7 +441,6 @@ internal static partial class YamlConfigSchemaValidator
|
||||
displayPath: GetDiagnosticPath(propertyPath),
|
||||
rawValue: typeName)
|
||||
};
|
||||
return parsedNode.WithNegatedSchemaNode(ParseNegatedSchemaNode(tableName, schemaPath, propertyPath, element));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -727,18 +776,68 @@ internal static partial class YamlConfigSchemaValidator
|
||||
ICollection<YamlConfigReferenceUsage>? references,
|
||||
bool allowUnknownObjectProperties)
|
||||
{
|
||||
if (node is not YamlMappingNode mappingNode)
|
||||
var mappingNode = GetObjectMappingNode(tableName, yamlPath, displayPath, node, schemaNode);
|
||||
var seenProperties = ValidateObjectPropertyEntries(
|
||||
tableName,
|
||||
yamlPath,
|
||||
displayPath,
|
||||
mappingNode,
|
||||
schemaNode,
|
||||
references,
|
||||
allowUnknownObjectProperties);
|
||||
ValidateRequiredObjectProperties(tableName, yamlPath, displayPath, schemaNode, seenProperties);
|
||||
|
||||
ValidateObjectConstraints(
|
||||
tableName,
|
||||
yamlPath,
|
||||
displayPath,
|
||||
mappingNode,
|
||||
seenProperties,
|
||||
schemaNode,
|
||||
references);
|
||||
|
||||
ValidateAllowedValues(tableName, yamlPath, displayPath, mappingNode, schemaNode);
|
||||
ValidateConstantValue(tableName, yamlPath, displayPath, mappingNode, schemaNode);
|
||||
ValidateNegatedSchemaConstraint(tableName, yamlPath, displayPath, mappingNode, schemaNode);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 确认当前 YAML 节点确实是对象节点,避免后续属性枚举阶段再做重复判空与类型判断。
|
||||
/// </summary>
|
||||
private static YamlMappingNode GetObjectMappingNode(
|
||||
string tableName,
|
||||
string yamlPath,
|
||||
string displayPath,
|
||||
YamlNode node,
|
||||
YamlConfigSchemaNode schemaNode)
|
||||
{
|
||||
if (node is YamlMappingNode mappingNode)
|
||||
{
|
||||
var subject = displayPath.Length == 0 ? "Root object" : $"Property '{displayPath}'";
|
||||
throw ConfigLoadExceptionFactory.Create(
|
||||
ConfigLoadFailureKind.PropertyTypeMismatch,
|
||||
tableName,
|
||||
$"{subject} in config file '{yamlPath}' must be an object.",
|
||||
yamlPath: yamlPath,
|
||||
schemaPath: schemaNode.SchemaPathHint,
|
||||
displayPath: GetDiagnosticPath(displayPath));
|
||||
return mappingNode;
|
||||
}
|
||||
|
||||
var subject = displayPath.Length == 0 ? "Root object" : $"Property '{displayPath}'";
|
||||
throw ConfigLoadExceptionFactory.Create(
|
||||
ConfigLoadFailureKind.PropertyTypeMismatch,
|
||||
tableName,
|
||||
$"{subject} in config file '{yamlPath}' must be an object.",
|
||||
yamlPath: yamlPath,
|
||||
schemaPath: schemaNode.SchemaPathHint,
|
||||
displayPath: GetDiagnosticPath(displayPath));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 遍历对象属性并递归校验每个已声明字段,同时记录用于后续 required 与 dependency 判断的 sibling 集合。
|
||||
/// </summary>
|
||||
private static HashSet<string> ValidateObjectPropertyEntries(
|
||||
string tableName,
|
||||
string yamlPath,
|
||||
string displayPath,
|
||||
YamlMappingNode mappingNode,
|
||||
YamlConfigSchemaNode schemaNode,
|
||||
ICollection<YamlConfigReferenceUsage>? references,
|
||||
bool allowUnknownObjectProperties)
|
||||
{
|
||||
var seenProperties = new HashSet<string>(StringComparer.Ordinal);
|
||||
foreach (var entry in mappingNode.Children)
|
||||
{
|
||||
@ -795,6 +894,19 @@ internal static partial class YamlConfigSchemaValidator
|
||||
allowUnknownObjectProperties);
|
||||
}
|
||||
|
||||
return seenProperties;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 在对象主体字段遍历结束后统一检查缺失的 required 字段,保证错误消息使用稳定的完整路径。
|
||||
/// </summary>
|
||||
private static void ValidateRequiredObjectProperties(
|
||||
string tableName,
|
||||
string yamlPath,
|
||||
string displayPath,
|
||||
YamlConfigSchemaNode schemaNode,
|
||||
HashSet<string> seenProperties)
|
||||
{
|
||||
if (schemaNode.RequiredProperties is null)
|
||||
{
|
||||
return;
|
||||
@ -816,19 +928,6 @@ internal static partial class YamlConfigSchemaValidator
|
||||
schemaPath: schemaNode.SchemaPathHint,
|
||||
displayPath: requiredPath);
|
||||
}
|
||||
|
||||
ValidateObjectConstraints(
|
||||
tableName,
|
||||
yamlPath,
|
||||
displayPath,
|
||||
mappingNode,
|
||||
seenProperties,
|
||||
schemaNode,
|
||||
references);
|
||||
|
||||
ValidateAllowedValues(tableName, yamlPath, displayPath, mappingNode, schemaNode);
|
||||
ValidateConstantValue(tableName, yamlPath, displayPath, mappingNode, schemaNode);
|
||||
ValidateNegatedSchemaConstraint(tableName, yamlPath, displayPath, mappingNode, schemaNode);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -864,11 +963,76 @@ internal static partial class YamlConfigSchemaValidator
|
||||
}
|
||||
|
||||
var propertyCount = seenProperties.Count;
|
||||
var subject = string.IsNullOrWhiteSpace(displayPath)
|
||||
var subject = GetObjectConstraintSubject(displayPath);
|
||||
|
||||
ValidateObjectPropertyCountConstraints(
|
||||
tableName,
|
||||
yamlPath,
|
||||
displayPath,
|
||||
schemaNode,
|
||||
constraints,
|
||||
subject,
|
||||
propertyCount);
|
||||
ValidateDependentRequiredConstraints(
|
||||
tableName,
|
||||
yamlPath,
|
||||
displayPath,
|
||||
schemaNode,
|
||||
constraints,
|
||||
seenProperties);
|
||||
ValidateDependentSchemas(
|
||||
tableName,
|
||||
yamlPath,
|
||||
displayPath,
|
||||
mappingNode,
|
||||
schemaNode,
|
||||
constraints,
|
||||
references,
|
||||
seenProperties,
|
||||
subject);
|
||||
ValidateAllOfSchemas(
|
||||
tableName,
|
||||
yamlPath,
|
||||
displayPath,
|
||||
mappingNode,
|
||||
schemaNode,
|
||||
constraints,
|
||||
references,
|
||||
subject);
|
||||
ValidateConditionalObjectSchemas(
|
||||
tableName,
|
||||
yamlPath,
|
||||
displayPath,
|
||||
mappingNode,
|
||||
schemaNode,
|
||||
constraints,
|
||||
references,
|
||||
subject);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 为对象约束构造统一的诊断主语,保证根对象与嵌套对象的错误消息格式一致。
|
||||
/// </summary>
|
||||
private static string GetObjectConstraintSubject(string displayPath)
|
||||
{
|
||||
return string.IsNullOrWhiteSpace(displayPath)
|
||||
? "Root object"
|
||||
: $"Property '{displayPath}'";
|
||||
var rawValue = propertyCount.ToString(CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 校验对象属性数量上下限。
|
||||
/// </summary>
|
||||
private static void ValidateObjectPropertyCountConstraints(
|
||||
string tableName,
|
||||
string yamlPath,
|
||||
string displayPath,
|
||||
YamlConfigSchemaNode schemaNode,
|
||||
YamlConfigObjectConstraints constraints,
|
||||
string subject,
|
||||
int propertyCount)
|
||||
{
|
||||
var rawValue = propertyCount.ToString(CultureInfo.InvariantCulture);
|
||||
if (constraints.MinProperties.HasValue &&
|
||||
propertyCount < constraints.MinProperties.Value)
|
||||
{
|
||||
@ -898,116 +1062,177 @@ internal static partial class YamlConfigSchemaValidator
|
||||
detail:
|
||||
$"Maximum property count: {constraints.MaxProperties.Value.ToString(CultureInfo.InvariantCulture)}.");
|
||||
}
|
||||
}
|
||||
|
||||
if (constraints.DependentRequired is not null &&
|
||||
constraints.DependentRequired.Count > 0)
|
||||
/// <summary>
|
||||
/// 使用已见 sibling 集合校验 dependentRequired,确保对象主路径与试匹配路径共用同一判定语义。
|
||||
/// </summary>
|
||||
private static void ValidateDependentRequiredConstraints(
|
||||
string tableName,
|
||||
string yamlPath,
|
||||
string displayPath,
|
||||
YamlConfigSchemaNode schemaNode,
|
||||
YamlConfigObjectConstraints constraints,
|
||||
HashSet<string> seenProperties)
|
||||
{
|
||||
if (constraints.DependentRequired is null ||
|
||||
constraints.DependentRequired.Count == 0)
|
||||
{
|
||||
// Reuse the collected sibling-name set so the main validation path and
|
||||
// the contains/not matcher both interpret object dependencies identically.
|
||||
foreach (var dependency in constraints.DependentRequired)
|
||||
{
|
||||
if (!seenProperties.Contains(dependency.Key))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var triggerPath = CombineDisplayPath(displayPath, dependency.Key);
|
||||
foreach (var dependentProperty in dependency.Value)
|
||||
{
|
||||
if (seenProperties.Contains(dependentProperty))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var requiredPath = CombineDisplayPath(displayPath, dependentProperty);
|
||||
throw ConfigLoadExceptionFactory.Create(
|
||||
ConfigLoadFailureKind.MissingRequiredProperty,
|
||||
tableName,
|
||||
$"Property '{requiredPath}' in config file '{yamlPath}' is required when sibling property '{triggerPath}' is present.",
|
||||
yamlPath: yamlPath,
|
||||
schemaPath: schemaNode.SchemaPathHint,
|
||||
displayPath: requiredPath,
|
||||
detail:
|
||||
$"Dependent requirement: when '{triggerPath}' exists, '{requiredPath}' must also be declared.");
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (constraints.DependentSchemas is not null &&
|
||||
constraints.DependentSchemas.Count > 0)
|
||||
// Reuse the collected sibling-name set so the main validation path and
|
||||
// the contains/not matcher both interpret object dependencies identically.
|
||||
foreach (var dependency in constraints.DependentRequired)
|
||||
{
|
||||
foreach (var dependency in constraints.DependentSchemas)
|
||||
if (!seenProperties.Contains(dependency.Key))
|
||||
{
|
||||
if (!seenProperties.Contains(dependency.Key))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var triggerPath = CombineDisplayPath(displayPath, dependency.Key);
|
||||
// dependentSchemas acts as an additional conditional constraint block on the
|
||||
// current object. Keep undeclared sibling fields outside the dependent sub-schema
|
||||
// from blocking the match so schema authors can express focused follow-up rules.
|
||||
// The trial matcher merges only new reference usages back into the outer collector,
|
||||
// so re-checking the same scalar via a conditional sub-schema does not duplicate
|
||||
// cross-table validation work later in the loader pipeline.
|
||||
if (TryMatchSchemaNode(
|
||||
tableName,
|
||||
yamlPath,
|
||||
displayPath,
|
||||
mappingNode,
|
||||
dependency.Value,
|
||||
references,
|
||||
allowUnknownObjectProperties: true))
|
||||
continue;
|
||||
}
|
||||
|
||||
var triggerPath = CombineDisplayPath(displayPath, dependency.Key);
|
||||
foreach (var dependentProperty in dependency.Value)
|
||||
{
|
||||
if (seenProperties.Contains(dependentProperty))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var requiredPath = CombineDisplayPath(displayPath, dependentProperty);
|
||||
throw ConfigLoadExceptionFactory.Create(
|
||||
ConfigLoadFailureKind.ConstraintViolation,
|
||||
ConfigLoadFailureKind.MissingRequiredProperty,
|
||||
tableName,
|
||||
$"{subject} in config file '{yamlPath}' must satisfy the 'dependentSchemas' schema triggered by sibling property '{triggerPath}'.",
|
||||
$"Property '{requiredPath}' in config file '{yamlPath}' is required when sibling property '{triggerPath}' is present.",
|
||||
yamlPath: yamlPath,
|
||||
schemaPath: schemaNode.SchemaPathHint,
|
||||
displayPath: GetDiagnosticPath(displayPath),
|
||||
displayPath: requiredPath,
|
||||
detail:
|
||||
$"Dependent schema: when '{triggerPath}' exists, the current object must satisfy the corresponding inline schema.");
|
||||
$"Dependent requirement: when '{triggerPath}' exists, '{requiredPath}' must also be declared.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (constraints.AllOfSchemas is not null &&
|
||||
constraints.AllOfSchemas.Count > 0)
|
||||
/// <summary>
|
||||
/// 在触发字段出现时,以 focused matcher 语义试跑 dependentSchemas。
|
||||
/// </summary>
|
||||
private static void ValidateDependentSchemas(
|
||||
string tableName,
|
||||
string yamlPath,
|
||||
string displayPath,
|
||||
YamlMappingNode mappingNode,
|
||||
YamlConfigSchemaNode schemaNode,
|
||||
YamlConfigObjectConstraints constraints,
|
||||
ICollection<YamlConfigReferenceUsage>? references,
|
||||
HashSet<string> seenProperties,
|
||||
string subject)
|
||||
{
|
||||
if (constraints.DependentSchemas is null ||
|
||||
constraints.DependentSchemas.Count == 0)
|
||||
{
|
||||
for (var index = 0; index < constraints.AllOfSchemas.Count; index++)
|
||||
{
|
||||
var allOfSchema = constraints.AllOfSchemas[index];
|
||||
// allOf follows the same focused constraint block semantics as dependentSchemas:
|
||||
// the inline schema may validate a subset of the current object without forcing
|
||||
// unrelated sibling fields to be restated.
|
||||
if (TryMatchSchemaNode(
|
||||
tableName,
|
||||
yamlPath,
|
||||
displayPath,
|
||||
mappingNode,
|
||||
allOfSchema,
|
||||
references,
|
||||
allowUnknownObjectProperties: true))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var allOfEntryNumber = index + 1;
|
||||
throw ConfigLoadExceptionFactory.Create(
|
||||
ConfigLoadFailureKind.ConstraintViolation,
|
||||
tableName,
|
||||
$"{subject} in config file '{yamlPath}' must satisfy all 'allOf' schemas, but entry #{allOfEntryNumber.ToString(CultureInfo.InvariantCulture)} did not match.",
|
||||
yamlPath: yamlPath,
|
||||
schemaPath: schemaNode.SchemaPathHint,
|
||||
displayPath: GetDiagnosticPath(displayPath),
|
||||
detail:
|
||||
$"allOf entry #{allOfEntryNumber.ToString(CultureInfo.InvariantCulture)} must match the current object.");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var dependency in constraints.DependentSchemas)
|
||||
{
|
||||
if (!seenProperties.Contains(dependency.Key))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var triggerPath = CombineDisplayPath(displayPath, dependency.Key);
|
||||
// dependentSchemas acts as an additional conditional constraint block on the
|
||||
// current object. Keep undeclared sibling fields outside the dependent sub-schema
|
||||
// from blocking the match so schema authors can express focused follow-up rules.
|
||||
// The trial matcher merges only new reference usages back into the outer collector,
|
||||
// so re-checking the same scalar via a conditional sub-schema does not duplicate
|
||||
// cross-table validation work later in the loader pipeline.
|
||||
if (TryMatchSchemaNode(
|
||||
tableName,
|
||||
yamlPath,
|
||||
displayPath,
|
||||
mappingNode,
|
||||
dependency.Value,
|
||||
references,
|
||||
allowUnknownObjectProperties: true))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
throw ConfigLoadExceptionFactory.Create(
|
||||
ConfigLoadFailureKind.ConstraintViolation,
|
||||
tableName,
|
||||
$"{subject} in config file '{yamlPath}' must satisfy the 'dependentSchemas' schema triggered by sibling property '{triggerPath}'.",
|
||||
yamlPath: yamlPath,
|
||||
schemaPath: schemaNode.SchemaPathHint,
|
||||
displayPath: GetDiagnosticPath(displayPath),
|
||||
detail:
|
||||
$"Dependent schema: when '{triggerPath}' exists, the current object must satisfy the corresponding inline schema.");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 逐条校验 allOf 约束,保持与 dependentSchemas 相同的 focused object 匹配语义。
|
||||
/// </summary>
|
||||
private static void ValidateAllOfSchemas(
|
||||
string tableName,
|
||||
string yamlPath,
|
||||
string displayPath,
|
||||
YamlMappingNode mappingNode,
|
||||
YamlConfigSchemaNode schemaNode,
|
||||
YamlConfigObjectConstraints constraints,
|
||||
ICollection<YamlConfigReferenceUsage>? references,
|
||||
string subject)
|
||||
{
|
||||
if (constraints.AllOfSchemas is null ||
|
||||
constraints.AllOfSchemas.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (var index = 0; index < constraints.AllOfSchemas.Count; index++)
|
||||
{
|
||||
var allOfSchema = constraints.AllOfSchemas[index];
|
||||
// allOf follows the same focused constraint block semantics as dependentSchemas:
|
||||
// the inline schema may validate a subset of the current object without forcing
|
||||
// unrelated sibling fields to be restated.
|
||||
if (TryMatchSchemaNode(
|
||||
tableName,
|
||||
yamlPath,
|
||||
displayPath,
|
||||
mappingNode,
|
||||
allOfSchema,
|
||||
references,
|
||||
allowUnknownObjectProperties: true))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var allOfEntryNumber = index + 1;
|
||||
throw ConfigLoadExceptionFactory.Create(
|
||||
ConfigLoadFailureKind.ConstraintViolation,
|
||||
tableName,
|
||||
$"{subject} in config file '{yamlPath}' must satisfy all 'allOf' schemas, but entry #{allOfEntryNumber.ToString(CultureInfo.InvariantCulture)} did not match.",
|
||||
yamlPath: yamlPath,
|
||||
schemaPath: schemaNode.SchemaPathHint,
|
||||
displayPath: GetDiagnosticPath(displayPath),
|
||||
detail:
|
||||
$"allOf entry #{allOfEntryNumber.ToString(CultureInfo.InvariantCulture)} must match the current object.");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 执行对象级 if/then/else 约束,并沿用 focused matcher 允许条件 schema 只声明关注字段。
|
||||
/// </summary>
|
||||
private static void ValidateConditionalObjectSchemas(
|
||||
string tableName,
|
||||
string yamlPath,
|
||||
string displayPath,
|
||||
YamlMappingNode mappingNode,
|
||||
YamlConfigSchemaNode schemaNode,
|
||||
YamlConfigObjectConstraints constraints,
|
||||
ICollection<YamlConfigReferenceUsage>? references,
|
||||
string subject)
|
||||
{
|
||||
var conditionalSchemas = constraints.ConditionalSchemas;
|
||||
if (conditionalSchemas is null)
|
||||
{
|
||||
@ -1025,49 +1250,72 @@ internal static partial class YamlConfigSchemaValidator
|
||||
conditionalSchemas.IfSchema,
|
||||
references,
|
||||
allowUnknownObjectProperties: true);
|
||||
if (ifMatched &&
|
||||
conditionalSchemas.ThenSchema is not null &&
|
||||
!TryMatchSchemaNode(
|
||||
ValidateConditionalSchemaBranch(
|
||||
tableName,
|
||||
yamlPath,
|
||||
displayPath,
|
||||
mappingNode,
|
||||
schemaNode,
|
||||
references,
|
||||
subject,
|
||||
ifMatched,
|
||||
conditionalSchemas.ThenSchema,
|
||||
branchName: "then",
|
||||
failureDetail:
|
||||
"Conditional schema: the current object matched the inline 'if' schema, so it must also satisfy the corresponding 'then' schema.");
|
||||
ValidateConditionalSchemaBranch(
|
||||
tableName,
|
||||
yamlPath,
|
||||
displayPath,
|
||||
mappingNode,
|
||||
schemaNode,
|
||||
references,
|
||||
subject,
|
||||
!ifMatched,
|
||||
conditionalSchemas.ElseSchema,
|
||||
branchName: "else",
|
||||
failureDetail:
|
||||
"Conditional schema: the current object did not match the inline 'if' schema, so it must satisfy the corresponding 'else' schema.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 校验 if/then/else 的单个分支,并在条件命中但分支未通过时提供统一诊断。
|
||||
/// </summary>
|
||||
private static void ValidateConditionalSchemaBranch(
|
||||
string tableName,
|
||||
string yamlPath,
|
||||
string displayPath,
|
||||
YamlMappingNode mappingNode,
|
||||
YamlConfigSchemaNode schemaNode,
|
||||
ICollection<YamlConfigReferenceUsage>? references,
|
||||
string subject,
|
||||
bool shouldValidate,
|
||||
YamlConfigSchemaNode? branchSchema,
|
||||
string branchName,
|
||||
string failureDetail)
|
||||
{
|
||||
if (!shouldValidate ||
|
||||
branchSchema is null ||
|
||||
TryMatchSchemaNode(
|
||||
tableName,
|
||||
yamlPath,
|
||||
displayPath,
|
||||
mappingNode,
|
||||
conditionalSchemas.ThenSchema,
|
||||
branchSchema,
|
||||
references,
|
||||
allowUnknownObjectProperties: true))
|
||||
{
|
||||
throw ConfigLoadExceptionFactory.Create(
|
||||
ConfigLoadFailureKind.ConstraintViolation,
|
||||
tableName,
|
||||
$"{subject} in config file '{yamlPath}' must satisfy the 'then' schema because the inline 'if' condition matched.",
|
||||
yamlPath: yamlPath,
|
||||
schemaPath: schemaNode.SchemaPathHint,
|
||||
displayPath: GetDiagnosticPath(displayPath),
|
||||
detail:
|
||||
"Conditional schema: the current object matched the inline 'if' schema, so it must also satisfy the corresponding 'then' schema.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ifMatched &&
|
||||
conditionalSchemas.ElseSchema is not null &&
|
||||
!TryMatchSchemaNode(
|
||||
tableName,
|
||||
yamlPath,
|
||||
displayPath,
|
||||
mappingNode,
|
||||
conditionalSchemas.ElseSchema,
|
||||
references,
|
||||
allowUnknownObjectProperties: true))
|
||||
{
|
||||
throw ConfigLoadExceptionFactory.Create(
|
||||
ConfigLoadFailureKind.ConstraintViolation,
|
||||
tableName,
|
||||
$"{subject} in config file '{yamlPath}' must satisfy the 'else' schema because the inline 'if' condition did not match.",
|
||||
yamlPath: yamlPath,
|
||||
schemaPath: schemaNode.SchemaPathHint,
|
||||
displayPath: GetDiagnosticPath(displayPath),
|
||||
detail:
|
||||
"Conditional schema: the current object did not match the inline 'if' schema, so it must satisfy the corresponding 'else' schema.");
|
||||
}
|
||||
throw ConfigLoadExceptionFactory.Create(
|
||||
ConfigLoadFailureKind.ConstraintViolation,
|
||||
tableName,
|
||||
$"{subject} in config file '{yamlPath}' must satisfy the '{branchName}' schema because the inline 'if' condition {(string.Equals(branchName, "then", StringComparison.Ordinal) ? "matched" : "did not match")}.",
|
||||
yamlPath: yamlPath,
|
||||
schemaPath: schemaNode.SchemaPathHint,
|
||||
displayPath: GetDiagnosticPath(displayPath),
|
||||
detail: failureDetail);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -1154,29 +1402,81 @@ internal static partial class YamlConfigSchemaValidator
|
||||
YamlConfigSchemaNode schemaNode,
|
||||
ICollection<YamlConfigReferenceUsage>? references)
|
||||
{
|
||||
if (node is not YamlScalarNode scalarNode)
|
||||
var scalarNode = GetScalarNodeOrThrow(tableName, yamlPath, displayPath, node, schemaNode);
|
||||
var value = GetScalarValueOrThrow(tableName, yamlPath, displayPath, scalarNode, schemaNode);
|
||||
ValidateScalarTypeOrThrow(tableName, yamlPath, displayPath, scalarNode, schemaNode, value);
|
||||
var normalizedValue = NormalizeScalarValue(schemaNode.NodeType, value);
|
||||
ValidateAllowedValues(tableName, yamlPath, displayPath, scalarNode, schemaNode);
|
||||
|
||||
if (schemaNode.Constraints is not null)
|
||||
{
|
||||
throw ConfigLoadExceptionFactory.Create(
|
||||
ConfigLoadFailureKind.PropertyTypeMismatch,
|
||||
tableName,
|
||||
$"Property '{displayPath}' in config file '{yamlPath}' must be a scalar value of type '{GetTypeName(schemaNode.NodeType)}'.",
|
||||
yamlPath: yamlPath,
|
||||
schemaPath: schemaNode.SchemaPathHint,
|
||||
displayPath: GetDiagnosticPath(displayPath));
|
||||
ValidateScalarConstraints(tableName, yamlPath, displayPath, value, normalizedValue, schemaNode);
|
||||
}
|
||||
|
||||
var value = scalarNode.Value;
|
||||
if (value is null)
|
||||
ValidateConstantValue(tableName, yamlPath, displayPath, scalarNode, schemaNode);
|
||||
ValidateNegatedSchemaConstraint(tableName, yamlPath, displayPath, scalarNode, schemaNode);
|
||||
CollectScalarReferenceUsage(yamlPath, displayPath, schemaNode, references, normalizedValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 确认 schema 期望的节点在 YAML 中仍然表现为标量。
|
||||
/// </summary>
|
||||
private static YamlScalarNode GetScalarNodeOrThrow(
|
||||
string tableName,
|
||||
string yamlPath,
|
||||
string displayPath,
|
||||
YamlNode node,
|
||||
YamlConfigSchemaNode schemaNode)
|
||||
{
|
||||
if (node is YamlScalarNode scalarNode)
|
||||
{
|
||||
throw ConfigLoadExceptionFactory.Create(
|
||||
ConfigLoadFailureKind.NullScalarValue,
|
||||
tableName,
|
||||
$"Property '{displayPath}' in config file '{yamlPath}' cannot be null when schema type is '{GetTypeName(schemaNode.NodeType)}'.",
|
||||
yamlPath: yamlPath,
|
||||
schemaPath: schemaNode.SchemaPathHint,
|
||||
displayPath: GetDiagnosticPath(displayPath));
|
||||
return scalarNode;
|
||||
}
|
||||
|
||||
throw ConfigLoadExceptionFactory.Create(
|
||||
ConfigLoadFailureKind.PropertyTypeMismatch,
|
||||
tableName,
|
||||
$"Property '{displayPath}' in config file '{yamlPath}' must be a scalar value of type '{GetTypeName(schemaNode.NodeType)}'.",
|
||||
yamlPath: yamlPath,
|
||||
schemaPath: schemaNode.SchemaPathHint,
|
||||
displayPath: GetDiagnosticPath(displayPath));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 读取标量文本值,并把 YAML 显式 null 与普通空字符串区分开。
|
||||
/// </summary>
|
||||
private static string GetScalarValueOrThrow(
|
||||
string tableName,
|
||||
string yamlPath,
|
||||
string displayPath,
|
||||
YamlScalarNode scalarNode,
|
||||
YamlConfigSchemaNode schemaNode)
|
||||
{
|
||||
if (scalarNode.Value is { } value)
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
throw ConfigLoadExceptionFactory.Create(
|
||||
ConfigLoadFailureKind.NullScalarValue,
|
||||
tableName,
|
||||
$"Property '{displayPath}' in config file '{yamlPath}' cannot be null when schema type is '{GetTypeName(schemaNode.NodeType)}'.",
|
||||
yamlPath: yamlPath,
|
||||
schemaPath: schemaNode.SchemaPathHint,
|
||||
displayPath: GetDiagnosticPath(displayPath));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 按 schema 声明的标量类型验证 YAML 文本值。
|
||||
/// </summary>
|
||||
private static void ValidateScalarTypeOrThrow(
|
||||
string tableName,
|
||||
string yamlPath,
|
||||
string displayPath,
|
||||
YamlScalarNode scalarNode,
|
||||
YamlConfigSchemaNode schemaNode,
|
||||
string value)
|
||||
{
|
||||
var tag = scalarNode.Tag.ToString();
|
||||
var isValid = schemaNode.NodeType switch
|
||||
{
|
||||
@ -1194,42 +1494,45 @@ internal static partial class YamlConfigSchemaValidator
|
||||
YamlConfigSchemaPropertyType.Boolean => bool.TryParse(value, out _),
|
||||
_ => false
|
||||
};
|
||||
|
||||
if (!isValid)
|
||||
if (isValid)
|
||||
{
|
||||
throw ConfigLoadExceptionFactory.Create(
|
||||
ConfigLoadFailureKind.PropertyTypeMismatch,
|
||||
tableName,
|
||||
$"Property '{displayPath}' in config file '{yamlPath}' must be of type '{GetTypeName(schemaNode.NodeType)}', but the current YAML scalar value is '{value}'.",
|
||||
yamlPath: yamlPath,
|
||||
schemaPath: schemaNode.SchemaPathHint,
|
||||
displayPath: GetDiagnosticPath(displayPath),
|
||||
rawValue: value);
|
||||
return;
|
||||
}
|
||||
|
||||
var normalizedValue = NormalizeScalarValue(schemaNode.NodeType, value);
|
||||
ValidateAllowedValues(tableName, yamlPath, displayPath, scalarNode, schemaNode);
|
||||
throw ConfigLoadExceptionFactory.Create(
|
||||
ConfigLoadFailureKind.PropertyTypeMismatch,
|
||||
tableName,
|
||||
$"Property '{displayPath}' in config file '{yamlPath}' must be of type '{GetTypeName(schemaNode.NodeType)}', but the current YAML scalar value is '{value}'.",
|
||||
yamlPath: yamlPath,
|
||||
schemaPath: schemaNode.SchemaPathHint,
|
||||
displayPath: GetDiagnosticPath(displayPath),
|
||||
rawValue: value);
|
||||
}
|
||||
|
||||
if (schemaNode.Constraints is not null)
|
||||
/// <summary>
|
||||
/// 在标量值成功通过本地校验后,再把引用信息回写给外层收集器。
|
||||
/// </summary>
|
||||
private static void CollectScalarReferenceUsage(
|
||||
string yamlPath,
|
||||
string displayPath,
|
||||
YamlConfigSchemaNode schemaNode,
|
||||
ICollection<YamlConfigReferenceUsage>? references,
|
||||
string normalizedValue)
|
||||
{
|
||||
if (schemaNode.ReferenceTableName is null ||
|
||||
references is null)
|
||||
{
|
||||
ValidateScalarConstraints(tableName, yamlPath, displayPath, value, normalizedValue, schemaNode);
|
||||
return;
|
||||
}
|
||||
|
||||
ValidateConstantValue(tableName, yamlPath, displayPath, scalarNode, schemaNode);
|
||||
ValidateNegatedSchemaConstraint(tableName, yamlPath, displayPath, scalarNode, schemaNode);
|
||||
|
||||
if (schemaNode.ReferenceTableName != null &&
|
||||
references is not null)
|
||||
{
|
||||
references.Add(
|
||||
new YamlConfigReferenceUsage(
|
||||
yamlPath,
|
||||
schemaNode.SchemaPathHint,
|
||||
displayPath,
|
||||
normalizedValue,
|
||||
schemaNode.ReferenceTableName,
|
||||
schemaNode.NodeType));
|
||||
}
|
||||
references.Add(
|
||||
new YamlConfigReferenceUsage(
|
||||
yamlPath,
|
||||
schemaNode.SchemaPathHint,
|
||||
displayPath,
|
||||
normalizedValue,
|
||||
schemaNode.ReferenceTableName,
|
||||
schemaNode.NodeType));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -2392,6 +2695,45 @@ internal static partial class YamlConfigSchemaValidator
|
||||
rawValue,
|
||||
normalizedValue,
|
||||
schemaNode);
|
||||
ValidateNumericLowerBounds(
|
||||
tableName,
|
||||
yamlPath,
|
||||
displayPath,
|
||||
rawValue,
|
||||
schemaNode,
|
||||
constraints,
|
||||
numericValue);
|
||||
ValidateNumericUpperBounds(
|
||||
tableName,
|
||||
yamlPath,
|
||||
displayPath,
|
||||
rawValue,
|
||||
schemaNode,
|
||||
constraints,
|
||||
numericValue);
|
||||
ValidateNumericMultipleOfConstraint(
|
||||
tableName,
|
||||
yamlPath,
|
||||
displayPath,
|
||||
rawValue,
|
||||
normalizedValue,
|
||||
schemaNode,
|
||||
constraints,
|
||||
numericValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 校验数值的最小值与开区间下界。
|
||||
/// </summary>
|
||||
private static void ValidateNumericLowerBounds(
|
||||
string tableName,
|
||||
string yamlPath,
|
||||
string displayPath,
|
||||
string rawValue,
|
||||
YamlConfigSchemaNode schemaNode,
|
||||
YamlConfigNumericConstraints constraints,
|
||||
double numericValue)
|
||||
{
|
||||
if (constraints.Minimum.HasValue && numericValue < constraints.Minimum.Value)
|
||||
{
|
||||
throw ConfigLoadExceptionFactory.Create(
|
||||
@ -2418,7 +2760,20 @@ internal static partial class YamlConfigSchemaValidator
|
||||
detail:
|
||||
$"Exclusive minimum allowed value: {constraints.ExclusiveMinimum.Value.ToString(CultureInfo.InvariantCulture)}.");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 校验数值的最大值与开区间上界。
|
||||
/// </summary>
|
||||
private static void ValidateNumericUpperBounds(
|
||||
string tableName,
|
||||
string yamlPath,
|
||||
string displayPath,
|
||||
string rawValue,
|
||||
YamlConfigSchemaNode schemaNode,
|
||||
YamlConfigNumericConstraints constraints,
|
||||
double numericValue)
|
||||
{
|
||||
if (constraints.Maximum.HasValue && numericValue > constraints.Maximum.Value)
|
||||
{
|
||||
throw ConfigLoadExceptionFactory.Create(
|
||||
@ -2445,21 +2800,37 @@ internal static partial class YamlConfigSchemaValidator
|
||||
detail:
|
||||
$"Exclusive maximum allowed value: {constraints.ExclusiveMaximum.Value.ToString(CultureInfo.InvariantCulture)}.");
|
||||
}
|
||||
}
|
||||
|
||||
if (constraints.MultipleOf.HasValue &&
|
||||
!IsMultipleOf(normalizedValue, numericValue, constraints.MultipleOf.Value))
|
||||
/// <summary>
|
||||
/// 校验数值是否满足 multipleOf 步进约束。
|
||||
/// </summary>
|
||||
private static void ValidateNumericMultipleOfConstraint(
|
||||
string tableName,
|
||||
string yamlPath,
|
||||
string displayPath,
|
||||
string rawValue,
|
||||
string normalizedValue,
|
||||
YamlConfigSchemaNode schemaNode,
|
||||
YamlConfigNumericConstraints constraints,
|
||||
double numericValue)
|
||||
{
|
||||
if (!constraints.MultipleOf.HasValue ||
|
||||
IsMultipleOf(normalizedValue, numericValue, constraints.MultipleOf.Value))
|
||||
{
|
||||
throw ConfigLoadExceptionFactory.Create(
|
||||
ConfigLoadFailureKind.ConstraintViolation,
|
||||
tableName,
|
||||
$"Property '{displayPath}' in config file '{yamlPath}' must be a multiple of {constraints.MultipleOf.Value.ToString(CultureInfo.InvariantCulture)}, but the current YAML scalar value is '{rawValue}'.",
|
||||
yamlPath: yamlPath,
|
||||
schemaPath: schemaNode.SchemaPathHint,
|
||||
displayPath: GetDiagnosticPath(displayPath),
|
||||
rawValue: rawValue,
|
||||
detail:
|
||||
$"Required numeric step: {constraints.MultipleOf.Value.ToString(CultureInfo.InvariantCulture)}.");
|
||||
return;
|
||||
}
|
||||
|
||||
throw ConfigLoadExceptionFactory.Create(
|
||||
ConfigLoadFailureKind.ConstraintViolation,
|
||||
tableName,
|
||||
$"Property '{displayPath}' in config file '{yamlPath}' must be a multiple of {constraints.MultipleOf.Value.ToString(CultureInfo.InvariantCulture)}, but the current YAML scalar value is '{rawValue}'.",
|
||||
yamlPath: yamlPath,
|
||||
schemaPath: schemaNode.SchemaPathHint,
|
||||
displayPath: GetDiagnosticPath(displayPath),
|
||||
rawValue: rawValue,
|
||||
detail:
|
||||
$"Required numeric step: {constraints.MultipleOf.Value.ToString(CultureInfo.InvariantCulture)}.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@ -6,48 +6,43 @@
|
||||
|
||||
## 当前恢复点
|
||||
|
||||
- 恢复点编号:`ANALYZER-WARNING-REDUCTION-RP-093`
|
||||
- 当前阶段:`Phase 93`
|
||||
- 恢复点编号:`ANALYZER-WARNING-REDUCTION-RP-094`
|
||||
- 当前阶段:`Phase 94`
|
||||
- 当前焦点:
|
||||
- `2026-04-29` 使用 `$gframework-batch-boot 50` 从 clean build warning 基线继续分批清理 analyzer warnings
|
||||
- 已接受三个 worker 的 `GFramework.Cqrs.Tests/Mediator/*` 独立切片,三个 Mediator 测试文件的 warning 已清零
|
||||
- 主线程补齐 `YamlConfigSchemaValidator` 运行时正则 timeout 与 ordinal 字符串比较,先收掉低风险 `MA0009` / `MA0006`
|
||||
- 已收口两个 Game 追加切片:`YamlConfigSchemaValidator.ObjectKeywords.cs` 方法拆分与 schema model 类型拆文件
|
||||
- 当前停止条件为相对 `origin/main` 接近 `50` 个变更文件;本轮按用户要求到此结束,不再继续开新切片
|
||||
- `2026-04-29` 继续按 `$gframework-batch-boot 50` 从仓库根 `dotnet clean` + `dotnet build` 的权威 warning 基线收尾 `YamlConfigSchemaValidator`
|
||||
- 本轮 clean build 只剩 `15` 条 warning,但实际只对应 `YamlConfigSchemaValidator.cs` 同一文件中的 `5` 个独立 `MA0051` 热点,因此不再并发派发 worker,避免同文件冲突
|
||||
- 已将 `ParseNode`、`ValidateObjectNode`、`ValidateObjectConstraints`、`ValidateScalarNode`、`ValidateNumericScalarConstraints` 按语义拆成 helper,并补齐对象条件分支 helper
|
||||
- 当前仓库根 clean build 已收敛到 `0` warnings、`0` errors;本轮停止原因从“接近文件阈值”切换为“当前 warning hotspot 已耗尽”
|
||||
|
||||
## 当前活跃事实
|
||||
|
||||
- 当前 `origin/main` 基线提交为 `0e32dab`(`2026-04-28T17:15:47+08:00`)。
|
||||
- 当前直接验证结果:
|
||||
- `dotnet clean -p:RestoreFallbackFolders= -v:quiet`
|
||||
- 最新结果:成功;标准 `dotnet clean` 仍会先命中当前 WSL 环境的 Windows NuGet fallback 目录,已按既有环境口径先执行 `dotnet restore GFramework.sln -p:RestoreFallbackFolders= --disable-parallel` 后清理
|
||||
- `dotnet build -p:RestoreFallbackFolders= -clp:WarningsOnly -v:minimal -m:1 -nodeReuse:false`
|
||||
- 最新结果:成功;`15` warnings、`0` errors;warning 从本轮基线 `236` 降到 `15`
|
||||
- `dotnet build GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj -c Release -p:RestoreFallbackFolders= -m:1 -nodeReuse:false -clp:Summary`
|
||||
- `dotnet clean`
|
||||
- 最新结果:成功;标准仓库根 clean 本轮可直接运行,未再命中需要额外绕开的环境噪音
|
||||
- `dotnet build`
|
||||
- 最新结果:成功;`0 Warning(s)`、`0 Error(s)`;本轮开始时同一口径 clean build 的 `15` 条 warning 已全部清零
|
||||
- `dotnet build GFramework.Game/GFramework.Game.csproj -c Release -clp:Summary`
|
||||
- 最新结果:成功;`0 Warning(s)`、`0 Error(s)`
|
||||
- `dotnet test GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj -c Release --no-build -p:RestoreFallbackFolders= -m:1 -nodeReuse:false --filter "FullyQualifiedName~Mediator"`
|
||||
- 最新结果:成功;`45` 通过、`0` 失败
|
||||
- `dotnet build GFramework.Game/GFramework.Game.csproj -c Release -p:RestoreFallbackFolders= -m:1 -nodeReuse:false -clp:Summary`
|
||||
- 最新结果:成功;`0 Warning(s)`、`0 Error(s)`
|
||||
- `dotnet test GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release -p:RestoreFallbackFolders= -m:1 -nodeReuse:false --filter "FullyQualifiedName~YamlConfigLoaderTests|FullyQualifiedName~YamlConfigSchemaValidatorTests"`
|
||||
- `dotnet test GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release --filter "FullyQualifiedName~YamlConfigLoaderTests|FullyQualifiedName~YamlConfigSchemaValidatorTests"`
|
||||
- 最新结果:成功;`80` 通过、`0` 失败
|
||||
- `git diff --check`
|
||||
- 最新结果:成功;无新增 whitespace / conflict-marker 问题
|
||||
- 当前批次摘要:
|
||||
- 当前分支提交后预计相对 `origin/main...HEAD` 包含 `22` 个变更文件,低于 `50` 个文件阈值
|
||||
- 已完成 worker 切片:
|
||||
- `ed269d4`:`MediatorArchitectureIntegrationTests.cs`,清理 `MA0048` / `MA0004` / `MA0016`
|
||||
- `121df44`:`MediatorAdvancedFeaturesTests.cs`,清理 `MA0048` / `MA0004` / `MA0015`
|
||||
- `9109eec`:`MediatorComprehensiveTests.cs`,清理 `MA0048` / `MA0004` / `MA0016` / `MA0002` / `MA0015`
|
||||
- 主线程切片:`YamlConfigSchemaValidator.cs` 正则 timeout 与 ordinal equality,清理 `MA0009` / `MA0006`
|
||||
- 主线程切片:`YamlConfigSchemaValidator.cs` 方法拆分,清理剩余 `MA0051`,并修正新增 helper 里的 `MA0006`
|
||||
- Game 追加切片:
|
||||
- `1395b84`:`YamlConfigSchemaValidator.ObjectKeywords.cs`,清理该文件 `MA0051`
|
||||
- 待提交:将 `YamlConfigSchemaValidator.cs` 末尾 schema model 类型拆到独立同名文件,清理 `MA0048`
|
||||
- 已完成:将 `YamlConfigSchemaValidator.cs` 末尾 schema model 类型拆到独立同名文件,清理 `MA0048`
|
||||
|
||||
## 当前风险
|
||||
|
||||
- `GFramework.Game/Config/YamlConfigSchemaValidator.cs` 仍有 `5` 个 `MA0051` 方法长度 warning,跨 `net8.0` / `net9.0` / `net10.0` 重复为 `15` 条。
|
||||
- 缓解措施:下一轮只做主 validator 方法拆分,不再混入拆文件或正则安全修复。
|
||||
- 标准 `dotnet clean` 在当前 WSL 环境仍会读取失效的 Windows fallback package folder。
|
||||
- 缓解措施:本主题验证继续沿用 `-p:RestoreFallbackFolders=`,必要时先执行 solution restore 刷新 Linux 侧资产。
|
||||
- 当前仓库根 clean build warning 已清零,本主题暂时没有剩余源码 warning 风险。
|
||||
- 缓解措施:若后续继续 batch warning 清理,先重新执行同轮 `dotnet clean` + `dotnet build` 采样,再决定是否需要分派 subagent。
|
||||
|
||||
## 活跃文档
|
||||
|
||||
@ -68,13 +63,13 @@
|
||||
## 验证说明
|
||||
|
||||
- 权威验证结果统一维护在“当前活跃事实”。
|
||||
- `GFramework.Cqrs.Tests` 的当前受影响项目 Release 构建已清零,并通过 Mediator 定向测试回归。
|
||||
- `GFramework.Game` 当前 Release 构建已清零,并通过 config 定向测试;仓库 Debug 构建剩余 warning 属于主 validator 方法复杂度拆分。
|
||||
- `GFramework.Game` 当前 Release 构建已清零,并通过 config 定向测试;本轮标准仓库根 Debug clean build 也已清零。
|
||||
- 本轮标准仓库根 `dotnet clean` + `dotnet build` 已直接回到 `0 Warning(s)`、`0 Error(s)`,因此 warning reduction 真值已从模块级验证收口到仓库级 clean build。
|
||||
- `git diff --check` 结果为空,说明本轮新增改动没有引入新的尾随空格或冲突标记。
|
||||
- warning reduction 的仓库级真值以同轮 `dotnet build`、定向 `dotnet test` 与 `git diff --check` 为准,并与 trace 中的验证里程碑保持一致。
|
||||
|
||||
## 下一步建议
|
||||
|
||||
1. 提交 schema model 拆文件与本轮 `ai-plan` 收口。
|
||||
2. 下一轮只处理 `GFramework.Game/Config/YamlConfigSchemaValidator.cs` 剩余 `MA0051` 方法拆分。
|
||||
3. 保持 `RestoreFallbackFolders=` 验证口径,避免当前 WSL fallback package folder 干扰。
|
||||
1. 提交 `YamlConfigSchemaValidator` 收尾重构与本轮 `ai-plan` 同步。
|
||||
2. 如需继续 warning reduction,先从新的仓库根 clean build 重新采样是否还有新增 warning hotspot。
|
||||
3. 若未来 warning 再次分散到多个文件,再按 `$gframework-batch-boot 50` 规则切换回多 worker 并行模式。
|
||||
|
||||
@ -1,5 +1,40 @@
|
||||
# Analyzer Warning Reduction 追踪
|
||||
|
||||
## 2026-04-29 — RP-094
|
||||
|
||||
### 阶段:收尾 `YamlConfigSchemaValidator` 剩余 `MA0051` 并将仓库根 clean build 归零
|
||||
|
||||
- 触发背景:
|
||||
- 用户要求先拿构建 warning,再在 warning 很多时分批指派 subagent;本轮按 `$gframework-batch-boot 50` 继续执行
|
||||
- 基线与停机判断:
|
||||
- 当前 `origin/main` 仍为 `0e32dab`(`2026-04-28T17:15:47+08:00`)
|
||||
- 本轮标准仓库根 `dotnet clean` + `dotnet build` 直接成功;warning 总数为 `15`,但全部集中在 `GFramework.Game/Config/YamlConfigSchemaValidator.cs`
|
||||
- 由于 `15` 条 warning 实际只对应同一文件内 `5` 个独立 `MA0051` 方法,不满足“warning 非常多且可安全分派多个独立写边界”的条件,因此不再新增 worker
|
||||
- 主线程实施:
|
||||
- 将 `ParseNode` 拆成 `ResolveNodeTypeName`、`ValidateObjectOnlyKeywords`、`CreateParsedNodeForType`
|
||||
- 将 `ValidateObjectNode` 拆成对象类型确认、属性遍历与 required 校验 helper
|
||||
- 将 `ValidateObjectConstraints` 拆成 property count、`dependentRequired`、`dependentSchemas`、`allOf`、条件分支五个 helper
|
||||
- 将 `ValidateScalarNode` 与 `ValidateNumericScalarConstraints` 分别拆成标量类型确认、引用回写、数值上下界和 `multipleOf` helper
|
||||
- 追加 `ValidateConditionalSchemaBranch` 收口 if/then/else 分支;随后修正该 helper 引入的 `MA0006`
|
||||
- 验证里程碑:
|
||||
- `dotnet build GFramework.Game/GFramework.Game.csproj -c Release -clp:Summary`
|
||||
- 第一次结果:成功;`3` warnings、`0` errors(均为新 helper 中 `branchName == "then"` 引入的 `MA0006`)
|
||||
- 第二次结果:成功;`0 Warning(s)`、`0 Error(s)`
|
||||
- `dotnet test GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release --filter "FullyQualifiedName~YamlConfigLoaderTests|FullyQualifiedName~YamlConfigSchemaValidatorTests"`
|
||||
- 结果:成功;`80` 通过、`0` 失败
|
||||
- `dotnet clean`
|
||||
- 结果:成功
|
||||
- `dotnet build`
|
||||
- 结果:成功;`0 Warning(s)`、`0 Error(s)`
|
||||
- `git diff --check`
|
||||
- 结果:成功;无新增 whitespace / conflict-marker 问题
|
||||
- 当前指标:
|
||||
- 仓库根 clean build warning:`15` -> `0`
|
||||
- 当前分支相对 `origin/main...HEAD` 仍为 `22` 个变更文件,低于 `$gframework-batch-boot 50` 的文件阈值
|
||||
- 当前停止原因:warning hotspot 已耗尽,不再有可重复切片
|
||||
- 下一步:
|
||||
- 提交 `YamlConfigSchemaValidator` 收尾重构与本轮 `ai-plan` 真值更新
|
||||
|
||||
## 2026-04-29 — RP-093
|
||||
|
||||
### 阶段:按 `$gframework-batch-boot 50` 从 clean build warning 基线分批清理
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user