mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-05-12 22:03:30 +08:00
feat(config): 添加基于YAML的配置加载器实现
- 实现YamlConfigLoader类,支持从文件目录加载YAML配置 - 提供RegisterTable方法支持配置表定义注册 - 实现热重载功能,监听文件变更并自动重新加载 - 支持schema校验,拒绝未知字段和类型错误 - 实现跨表引用校验,确保配置一致性 - 添加YamlConfigTableRegistrationOptions选项类 - 支持防抖机制避免频繁重载 - 提供详细的错误诊断信息
This commit is contained in:
parent
34a333a0c1
commit
a416e093ee
@ -0,0 +1,46 @@
|
|||||||
|
using GFramework.Game.Config;
|
||||||
|
|
||||||
|
namespace GFramework.Game.Tests.Config;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 验证 YAML 配置表注册选项会在构造阶段建立最小不变量,避免非法路径状态继续向后传播。
|
||||||
|
/// </summary>
|
||||||
|
[TestFixture]
|
||||||
|
public class YamlConfigTableRegistrationOptionsTests
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 验证构造函数会拒绝空的或仅空白字符的表名。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="tableName">待验证的表名。</param>
|
||||||
|
[TestCase(null)]
|
||||||
|
[TestCase("")]
|
||||||
|
[TestCase(" ")]
|
||||||
|
public void Constructor_Should_Throw_When_Table_Name_Is_Null_Or_Whitespace(string? tableName)
|
||||||
|
{
|
||||||
|
var exception = Assert.Throws<ArgumentException>(() =>
|
||||||
|
_ = new YamlConfigTableRegistrationOptions<int, string>(
|
||||||
|
tableName!,
|
||||||
|
"monster",
|
||||||
|
static config => config.Length));
|
||||||
|
|
||||||
|
Assert.That(exception!.ParamName, Is.EqualTo("tableName"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 验证构造函数会拒绝空的或仅空白字符的相对目录路径。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="relativePath">待验证的相对目录路径。</param>
|
||||||
|
[TestCase(null)]
|
||||||
|
[TestCase("")]
|
||||||
|
[TestCase(" ")]
|
||||||
|
public void Constructor_Should_Throw_When_Relative_Path_Is_Null_Or_Whitespace(string? relativePath)
|
||||||
|
{
|
||||||
|
var exception = Assert.Throws<ArgumentException>(() =>
|
||||||
|
_ = new YamlConfigTableRegistrationOptions<int, string>(
|
||||||
|
"monster",
|
||||||
|
relativePath!,
|
||||||
|
static config => config.Length));
|
||||||
|
|
||||||
|
Assert.That(exception!.ParamName, Is.EqualTo("relativePath"));
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -161,6 +161,10 @@ public sealed class YamlConfigLoader : IConfigLoader
|
|||||||
/// <param name="keySelector">配置项主键提取器。</param>
|
/// <param name="keySelector">配置项主键提取器。</param>
|
||||||
/// <param name="comparer">可选主键比较器。</param>
|
/// <param name="comparer">可选主键比较器。</param>
|
||||||
/// <returns>当前加载器实例,以便链式注册。</returns>
|
/// <returns>当前加载器实例,以便链式注册。</returns>
|
||||||
|
/// <exception cref="ArgumentException">
|
||||||
|
/// 当 <paramref name="tableName" /> 或 <paramref name="relativePath" /> 为 null、空字符串或空白字符串时抛出。
|
||||||
|
/// </exception>
|
||||||
|
/// <exception cref="ArgumentNullException">当 <paramref name="keySelector" /> 为 null 时抛出。</exception>
|
||||||
public YamlConfigLoader RegisterTable<TKey, TValue>(
|
public YamlConfigLoader RegisterTable<TKey, TValue>(
|
||||||
string tableName,
|
string tableName,
|
||||||
string relativePath,
|
string relativePath,
|
||||||
@ -188,6 +192,11 @@ public sealed class YamlConfigLoader : IConfigLoader
|
|||||||
/// <param name="keySelector">配置项主键提取器。</param>
|
/// <param name="keySelector">配置项主键提取器。</param>
|
||||||
/// <param name="comparer">可选主键比较器。</param>
|
/// <param name="comparer">可选主键比较器。</param>
|
||||||
/// <returns>当前加载器实例,以便链式注册。</returns>
|
/// <returns>当前加载器实例,以便链式注册。</returns>
|
||||||
|
/// <exception cref="ArgumentException">
|
||||||
|
/// 当 <paramref name="tableName" />、<paramref name="relativePath" /> 或 <paramref name="schemaRelativePath" />
|
||||||
|
/// 为 null、空字符串或空白字符串时抛出。
|
||||||
|
/// </exception>
|
||||||
|
/// <exception cref="ArgumentNullException">当 <paramref name="keySelector" /> 为 null 时抛出。</exception>
|
||||||
public YamlConfigLoader RegisterTable<TKey, TValue>(
|
public YamlConfigLoader RegisterTable<TKey, TValue>(
|
||||||
string tableName,
|
string tableName,
|
||||||
string relativePath,
|
string relativePath,
|
||||||
@ -214,6 +223,11 @@ public sealed class YamlConfigLoader : IConfigLoader
|
|||||||
/// <param name="options">配置表注册选项。</param>
|
/// <param name="options">配置表注册选项。</param>
|
||||||
/// <returns>当前加载器实例,以便链式注册。</returns>
|
/// <returns>当前加载器实例,以便链式注册。</returns>
|
||||||
/// <exception cref="ArgumentNullException">当 <paramref name="options" /> 为空时抛出。</exception>
|
/// <exception cref="ArgumentNullException">当 <paramref name="options" /> 为空时抛出。</exception>
|
||||||
|
/// <exception cref="ArgumentException">
|
||||||
|
/// 当 <paramref name="options" /> 内的 <see cref="YamlConfigTableRegistrationOptions{TKey, TValue}.TableName" />、
|
||||||
|
/// <see cref="YamlConfigTableRegistrationOptions{TKey, TValue}.RelativePath" /> 或
|
||||||
|
/// <see cref="YamlConfigTableRegistrationOptions{TKey, TValue}.SchemaRelativePath" /> 为 null、空字符串或空白字符串时抛出。
|
||||||
|
/// </exception>
|
||||||
public YamlConfigLoader RegisterTable<TKey, TValue>(YamlConfigTableRegistrationOptions<TKey, TValue> options)
|
public YamlConfigLoader RegisterTable<TKey, TValue>(YamlConfigTableRegistrationOptions<TKey, TValue> options)
|
||||||
where TKey : notnull
|
where TKey : notnull
|
||||||
{
|
{
|
||||||
|
|||||||
@ -10,18 +10,34 @@ namespace GFramework.Game.Config;
|
|||||||
public sealed class YamlConfigTableRegistrationOptions<TKey, TValue>
|
public sealed class YamlConfigTableRegistrationOptions<TKey, TValue>
|
||||||
where TKey : notnull
|
where TKey : notnull
|
||||||
{
|
{
|
||||||
|
private const string TableNameCannotBeNullOrWhiteSpaceMessage = "Table name cannot be null or whitespace.";
|
||||||
|
private const string RelativePathCannotBeNullOrWhiteSpaceMessage = "Relative path cannot be null or whitespace.";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 使用最小必需参数创建配置表注册选项。
|
/// 使用最小必需参数创建配置表注册选项。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="tableName">运行时配置表名称。</param>
|
/// <param name="tableName">运行时配置表名称。</param>
|
||||||
/// <param name="relativePath">相对配置根目录的子目录。</param>
|
/// <param name="relativePath">相对配置根目录的子目录。</param>
|
||||||
/// <param name="keySelector">配置项主键提取器。</param>
|
/// <param name="keySelector">配置项主键提取器。</param>
|
||||||
|
/// <exception cref="ArgumentException">
|
||||||
|
/// 当 <paramref name="tableName" /> 或 <paramref name="relativePath" /> 为 null、空字符串或空白字符串时抛出。
|
||||||
|
/// </exception>
|
||||||
/// <exception cref="ArgumentNullException">当 <paramref name="keySelector" /> 为 null 时抛出。</exception>
|
/// <exception cref="ArgumentNullException">当 <paramref name="keySelector" /> 为 null 时抛出。</exception>
|
||||||
public YamlConfigTableRegistrationOptions(
|
public YamlConfigTableRegistrationOptions(
|
||||||
string tableName,
|
string tableName,
|
||||||
string relativePath,
|
string relativePath,
|
||||||
Func<TValue, TKey> keySelector)
|
Func<TValue, TKey> keySelector)
|
||||||
{
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(tableName))
|
||||||
|
{
|
||||||
|
throw new ArgumentException(TableNameCannotBeNullOrWhiteSpaceMessage, nameof(tableName));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(relativePath))
|
||||||
|
{
|
||||||
|
throw new ArgumentException(RelativePathCannotBeNullOrWhiteSpaceMessage, nameof(relativePath));
|
||||||
|
}
|
||||||
|
|
||||||
ArgumentNullException.ThrowIfNull(keySelector);
|
ArgumentNullException.ThrowIfNull(keySelector);
|
||||||
|
|
||||||
TableName = tableName;
|
TableName = tableName;
|
||||||
|
|||||||
@ -91,4 +91,144 @@ public class SchemaConfigGeneratorTests
|
|||||||
Assert.That(diagnostic.GetMessage(), Does.Contain("array<array>"));
|
Assert.That(diagnostic.GetMessage(), Does.Contain("array<array>"));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 验证 schema 字段名无法映射为合法 C# 标识符时会直接给出诊断,而不是生成不可编译代码。
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void Run_Should_Report_Diagnostic_When_Schema_Key_Maps_To_Invalid_CSharp_Identifier()
|
||||||
|
{
|
||||||
|
const string source = """
|
||||||
|
namespace TestApp
|
||||||
|
{
|
||||||
|
public sealed class Dummy
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
|
||||||
|
const string schema = """
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"required": ["id", "drop$item"],
|
||||||
|
"properties": {
|
||||||
|
"id": { "type": "integer" },
|
||||||
|
"drop$item": { "type": "string" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
|
||||||
|
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_006"));
|
||||||
|
Assert.That(diagnostic.Severity, Is.EqualTo(DiagnosticSeverity.Error));
|
||||||
|
Assert.That(diagnostic.GetMessage(), Does.Contain("drop$item"));
|
||||||
|
Assert.That(diagnostic.GetMessage(), Does.Contain("Drop$item"));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 验证引用元数据成员名在不同路径规范化后发生碰撞时,生成器仍会分配全局唯一的成员名。
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void Run_Should_Assign_Globally_Unique_Reference_Metadata_Member_Names()
|
||||||
|
{
|
||||||
|
const string source = """
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace GFramework.Game.Abstractions.Config
|
||||||
|
{
|
||||||
|
public interface IConfigTable
|
||||||
|
{
|
||||||
|
Type KeyType { get; }
|
||||||
|
Type ValueType { get; }
|
||||||
|
int Count { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IConfigTable<TKey, TValue> : IConfigTable
|
||||||
|
where TKey : notnull
|
||||||
|
{
|
||||||
|
TValue Get(TKey key);
|
||||||
|
bool TryGet(TKey key, out TValue? value);
|
||||||
|
bool ContainsKey(TKey key);
|
||||||
|
IReadOnlyCollection<TValue> All();
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IConfigRegistry
|
||||||
|
{
|
||||||
|
IConfigTable<TKey, TValue> GetTable<TKey, TValue>(string name)
|
||||||
|
where TKey : notnull;
|
||||||
|
|
||||||
|
bool TryGetTable<TKey, TValue>(string name, out IConfigTable<TKey, TValue>? table)
|
||||||
|
where TKey : notnull;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace GFramework.Game.Config
|
||||||
|
{
|
||||||
|
public sealed class YamlConfigLoader
|
||||||
|
{
|
||||||
|
public YamlConfigLoader RegisterTable<TKey, TValue>(
|
||||||
|
string tableName,
|
||||||
|
string relativePath,
|
||||||
|
string schemaRelativePath,
|
||||||
|
Func<TValue, TKey> keySelector,
|
||||||
|
IEqualityComparer<TKey>? comparer = null)
|
||||||
|
where TKey : notnull
|
||||||
|
{
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
|
||||||
|
const string schema = """
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"required": ["id"],
|
||||||
|
"properties": {
|
||||||
|
"id": { "type": "integer" },
|
||||||
|
"drop-items": {
|
||||||
|
"type": "array",
|
||||||
|
"items": { "type": "string" },
|
||||||
|
"x-gframework-ref-table": "item"
|
||||||
|
},
|
||||||
|
"drop_items": {
|
||||||
|
"type": "array",
|
||||||
|
"items": { "type": "string" },
|
||||||
|
"x-gframework-ref-table": "item"
|
||||||
|
},
|
||||||
|
"dropItems1": {
|
||||||
|
"type": "string",
|
||||||
|
"x-gframework-ref-table": "item"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
|
||||||
|
var result = SchemaGeneratorTestDriver.Run(
|
||||||
|
source,
|
||||||
|
("monster.schema.json", schema));
|
||||||
|
|
||||||
|
var generatedSources = result.Results
|
||||||
|
.Single()
|
||||||
|
.GeneratedSources
|
||||||
|
.ToDictionary(
|
||||||
|
static sourceResult => sourceResult.HintName,
|
||||||
|
static sourceResult => sourceResult.SourceText.ToString(),
|
||||||
|
StringComparer.Ordinal);
|
||||||
|
|
||||||
|
Assert.That(result.Results.Single().Diagnostics, Is.Empty);
|
||||||
|
Assert.That(generatedSources.TryGetValue("MonsterConfigBindings.g.cs", out var bindingsSource), Is.True);
|
||||||
|
Assert.That(bindingsSource, Does.Contain("public static readonly ReferenceMetadata DropItems ="));
|
||||||
|
Assert.That(bindingsSource, Does.Contain("public static readonly ReferenceMetadata DropItems1 ="));
|
||||||
|
Assert.That(bindingsSource, Does.Contain("public static readonly ReferenceMetadata DropItems11 ="));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -20,6 +20,7 @@
|
|||||||
GF_ConfigSchema_003 | GFramework.SourceGenerators.Config | Error | ConfigSchemaDiagnostics
|
GF_ConfigSchema_003 | GFramework.SourceGenerators.Config | Error | ConfigSchemaDiagnostics
|
||||||
GF_ConfigSchema_004 | GFramework.SourceGenerators.Config | Error | ConfigSchemaDiagnostics
|
GF_ConfigSchema_004 | GFramework.SourceGenerators.Config | Error | ConfigSchemaDiagnostics
|
||||||
GF_ConfigSchema_005 | GFramework.SourceGenerators.Config | Error | ConfigSchemaDiagnostics
|
GF_ConfigSchema_005 | GFramework.SourceGenerators.Config | Error | ConfigSchemaDiagnostics
|
||||||
|
GF_ConfigSchema_006 | GFramework.SourceGenerators.Config | Error | ConfigSchemaDiagnostics
|
||||||
GF_Priority_001 | GFramework.Priority | Error | PriorityDiagnostic
|
GF_Priority_001 | GFramework.Priority | Error | PriorityDiagnostic
|
||||||
GF_Priority_002 | GFramework.Priority | Warning | PriorityDiagnostic
|
GF_Priority_002 | GFramework.Priority | Warning | PriorityDiagnostic
|
||||||
GF_Priority_003 | GFramework.Priority | Error | PriorityDiagnostic
|
GF_Priority_003 | GFramework.Priority | Error | PriorityDiagnostic
|
||||||
|
|||||||
@ -253,7 +253,10 @@ public sealed class SchemaConfigGenerator : IIncrementalGenerator
|
|||||||
var title = TryGetMetadataString(property.Value, "title");
|
var title = TryGetMetadataString(property.Value, "title");
|
||||||
var description = TryGetMetadataString(property.Value, "description");
|
var description = TryGetMetadataString(property.Value, "description");
|
||||||
var refTableName = TryGetMetadataString(property.Value, "x-gframework-ref-table");
|
var refTableName = TryGetMetadataString(property.Value, "x-gframework-ref-table");
|
||||||
var propertyName = ToPascalCase(property.Name);
|
if (!TryBuildPropertyIdentifier(filePath, displayPath, property.Name, out var propertyName, out var diagnostic))
|
||||||
|
{
|
||||||
|
return ParsedPropertyResult.FromDiagnostic(diagnostic!);
|
||||||
|
}
|
||||||
|
|
||||||
switch (schemaType)
|
switch (schemaType)
|
||||||
{
|
{
|
||||||
@ -934,26 +937,37 @@ public sealed class SchemaConfigGenerator : IIncrementalGenerator
|
|||||||
/// <returns>生成期引用元数据集合。</returns>
|
/// <returns>生成期引用元数据集合。</returns>
|
||||||
private static IEnumerable<GeneratedReferenceSpec> CollectReferenceSpecs(SchemaObjectSpec rootObject)
|
private static IEnumerable<GeneratedReferenceSpec> CollectReferenceSpecs(SchemaObjectSpec rootObject)
|
||||||
{
|
{
|
||||||
var memberNameCounts = new Dictionary<string, int>(StringComparer.Ordinal);
|
var nextSuffixByBaseMemberName = new Dictionary<string, int>(StringComparer.Ordinal);
|
||||||
|
var allocatedMemberNames = new HashSet<string>(StringComparer.Ordinal);
|
||||||
|
|
||||||
foreach (var referenceSeed in EnumerateReferenceSeeds(rootObject.Properties))
|
foreach (var referenceSeed in EnumerateReferenceSeeds(rootObject.Properties))
|
||||||
{
|
{
|
||||||
var baseMemberName = BuildReferenceMemberName(referenceSeed.DisplayPath);
|
var baseMemberName = BuildReferenceMemberName(referenceSeed.DisplayPath);
|
||||||
if (memberNameCounts.TryGetValue(baseMemberName, out var duplicateCount))
|
var memberName = baseMemberName;
|
||||||
|
if (!allocatedMemberNames.Add(memberName))
|
||||||
{
|
{
|
||||||
// Reuse the tracked duplicate count so repeated reference paths keep their generated member names stable.
|
// Track globally allocated member names because a suffixed duplicate from one path can collide
|
||||||
duplicateCount++;
|
// with the unsuffixed base name produced by a later, different path.
|
||||||
memberNameCounts[baseMemberName] = duplicateCount;
|
var duplicateCount = nextSuffixByBaseMemberName.TryGetValue(baseMemberName, out var nextSuffix)
|
||||||
baseMemberName =
|
? nextSuffix + 1
|
||||||
$"{baseMemberName}{duplicateCount.ToString(CultureInfo.InvariantCulture)}";
|
: 1;
|
||||||
|
|
||||||
|
memberName = $"{baseMemberName}{duplicateCount.ToString(CultureInfo.InvariantCulture)}";
|
||||||
|
while (!allocatedMemberNames.Add(memberName))
|
||||||
|
{
|
||||||
|
duplicateCount++;
|
||||||
|
memberName = $"{baseMemberName}{duplicateCount.ToString(CultureInfo.InvariantCulture)}";
|
||||||
|
}
|
||||||
|
|
||||||
|
nextSuffixByBaseMemberName[baseMemberName] = duplicateCount;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
memberNameCounts[baseMemberName] = 0;
|
nextSuffixByBaseMemberName[baseMemberName] = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
yield return new GeneratedReferenceSpec(
|
yield return new GeneratedReferenceSpec(
|
||||||
baseMemberName,
|
memberName,
|
||||||
referenceSeed.DisplayPath,
|
referenceSeed.DisplayPath,
|
||||||
referenceSeed.ReferencedTableName,
|
referenceSeed.ReferencedTableName,
|
||||||
referenceSeed.ValueSchemaType,
|
referenceSeed.ValueSchemaType,
|
||||||
@ -1165,6 +1179,40 @@ public sealed class SchemaConfigGenerator : IIncrementalGenerator
|
|||||||
builder.AppendLine($"{indent}/// </remarks>");
|
builder.AppendLine($"{indent}/// </remarks>");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 将 schema 字段名转换并验证为生成代码可直接使用的属性标识符。
|
||||||
|
/// 生成器会在这里拒绝无法映射为合法 C# 标识符的外部输入,避免生成源码后才在编译阶段失败。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="filePath">Schema 文件路径。</param>
|
||||||
|
/// <param name="displayPath">逻辑字段路径。</param>
|
||||||
|
/// <param name="schemaName">Schema 原始字段名。</param>
|
||||||
|
/// <param name="propertyName">生成后的属性名。</param>
|
||||||
|
/// <param name="diagnostic">字段名非法时生成的诊断。</param>
|
||||||
|
/// <returns>是否成功生成合法属性标识符。</returns>
|
||||||
|
private static bool TryBuildPropertyIdentifier(
|
||||||
|
string filePath,
|
||||||
|
string displayPath,
|
||||||
|
string schemaName,
|
||||||
|
out string propertyName,
|
||||||
|
out Diagnostic? diagnostic)
|
||||||
|
{
|
||||||
|
propertyName = ToPascalCase(schemaName);
|
||||||
|
if (SyntaxFacts.IsValidIdentifier(propertyName))
|
||||||
|
{
|
||||||
|
diagnostic = null;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
diagnostic = Diagnostic.Create(
|
||||||
|
ConfigSchemaDiagnostics.InvalidGeneratedIdentifier,
|
||||||
|
CreateFileLocation(filePath),
|
||||||
|
Path.GetFileName(filePath),
|
||||||
|
displayPath,
|
||||||
|
schemaName,
|
||||||
|
propertyName);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 从 schema 文件路径提取实体基础名。
|
/// 从 schema 文件路径提取实体基础名。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@ -63,4 +63,15 @@ public static class ConfigSchemaDiagnostics
|
|||||||
SourceGeneratorsConfigCategory,
|
SourceGeneratorsConfigCategory,
|
||||||
DiagnosticSeverity.Error,
|
DiagnosticSeverity.Error,
|
||||||
true);
|
true);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// schema 字段名无法安全映射为 C# 标识符。
|
||||||
|
/// </summary>
|
||||||
|
public static readonly DiagnosticDescriptor InvalidGeneratedIdentifier = new(
|
||||||
|
"GF_ConfigSchema_006",
|
||||||
|
"Config schema property name cannot be converted to a valid C# identifier",
|
||||||
|
"Property '{1}' in schema file '{0}' uses schema key '{2}', which generates invalid C# identifier '{3}'",
|
||||||
|
SourceGeneratorsConfigCategory,
|
||||||
|
DiagnosticSeverity.Error,
|
||||||
|
true);
|
||||||
}
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user