mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-05-14 14:58:58 +08:00
Compare commits
No commits in common. "3109beaa9b67f047b1c7f0c59459278a07b41cc2" and "d120236e13a02a35ed8d3975301ac6f45bac5790" have entirely different histories.
3109beaa9b
...
d120236e13
@ -1,21 +1,5 @@
|
|||||||
# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json
|
|
||||||
language: "zh-CN"
|
|
||||||
early_access: false
|
|
||||||
|
|
||||||
reviews:
|
reviews:
|
||||||
profile: "chill"
|
|
||||||
request_changes_workflow: true # 有问题时可以直接 request changes
|
|
||||||
high_level_summary: true # PR 总体总结
|
|
||||||
review_status: true # review 结果状态
|
|
||||||
review_details: true # 展示具体问题
|
|
||||||
poem: false # 关闭诗歌(基本没人用)
|
|
||||||
tools:
|
tools:
|
||||||
github-checks:
|
github-checks:
|
||||||
enabled: true
|
enabled: true
|
||||||
timeout_ms: 90000
|
timeout_ms: 90000
|
||||||
auto_review:
|
|
||||||
enabled: true
|
|
||||||
drafts: false # draft PR 不 review
|
|
||||||
|
|
||||||
chat:
|
|
||||||
auto_reply: true
|
|
||||||
@ -138,135 +138,6 @@ public class SchemaConfigGeneratorTests
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 验证 schema 顶层允许通过元数据覆盖默认配置目录,并会统一路径分隔符。
|
|
||||||
/// </summary>
|
|
||||||
[Test]
|
|
||||||
public void Run_Should_Use_Custom_Config_Path_Metadata_For_Generated_Registration()
|
|
||||||
{
|
|
||||||
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",
|
|
||||||
"x-gframework-config-path": "config\\monster",
|
|
||||||
"required": ["id", "name"],
|
|
||||||
"properties": {
|
|
||||||
"id": { "type": "integer" },
|
|
||||||
"name": { "type": "string" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
""";
|
|
||||||
|
|
||||||
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["MonsterConfigBindings.g.cs"],
|
|
||||||
Does.Contain("public const string ConfigRelativePath = \"config/monster\";"));
|
|
||||||
Assert.That(generatedSources["MonsterConfigBindings.g.cs"], Does.Contain("Metadata.ConfigRelativePath,"));
|
|
||||||
Assert.That(generatedSources["GeneratedConfigCatalog.g.cs"],
|
|
||||||
Does.Contain("MonsterConfigBindings.Metadata.ConfigRelativePath"));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 验证 schema 顶层自定义配置目录元数据不能逃逸配置根目录。
|
|
||||||
/// </summary>
|
|
||||||
[Test]
|
|
||||||
public void Run_Should_Report_Diagnostic_When_Custom_Config_Path_Metadata_Is_Invalid()
|
|
||||||
{
|
|
||||||
const string source = """
|
|
||||||
namespace TestApp
|
|
||||||
{
|
|
||||||
public sealed class Dummy
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
""";
|
|
||||||
|
|
||||||
const string schema = """
|
|
||||||
{
|
|
||||||
"type": "object",
|
|
||||||
"x-gframework-config-path": "../monster",
|
|
||||||
"required": ["id"],
|
|
||||||
"properties": {
|
|
||||||
"id": { "type": "integer" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
""";
|
|
||||||
|
|
||||||
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_007"));
|
|
||||||
Assert.That(diagnostic.Severity, Is.EqualTo(DiagnosticSeverity.Error));
|
|
||||||
Assert.That(diagnostic.GetMessage(), Does.Contain("x-gframework-config-path"));
|
|
||||||
Assert.That(diagnostic.GetMessage(), Does.Contain("relative"));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 验证引用元数据成员名在不同路径规范化后发生碰撞时,生成器仍会分配全局唯一的成员名。
|
/// 验证引用元数据成员名在不同路径规范化后发生碰撞时,生成器仍会分配全局唯一的成员名。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -581,33 +452,20 @@ public class SchemaConfigGeneratorTests
|
|||||||
Assert.That(catalogSource, Does.Contain("public static class GeneratedConfigCatalog"));
|
Assert.That(catalogSource, Does.Contain("public static class GeneratedConfigCatalog"));
|
||||||
Assert.That(catalogSource, Does.Contain("public sealed class GeneratedConfigRegistrationOptions"));
|
Assert.That(catalogSource, Does.Contain("public sealed class GeneratedConfigRegistrationOptions"));
|
||||||
Assert.That(catalogSource, Does.Contain("public static class GeneratedConfigRegistrationExtensions"));
|
Assert.That(catalogSource, Does.Contain("public static class GeneratedConfigRegistrationExtensions"));
|
||||||
Assert.That(catalogSource,
|
Assert.That(catalogSource, Does.Contain("public global::System.Collections.Generic.IReadOnlyCollection<string>? IncludedConfigDomains { get; init; }"));
|
||||||
Does.Contain(
|
Assert.That(catalogSource, Does.Contain("public global::System.Collections.Generic.IReadOnlyCollection<string>? IncludedTableNames { get; init; }"));
|
||||||
"public global::System.Collections.Generic.IReadOnlyCollection<string>? IncludedConfigDomains { get; init; }"));
|
Assert.That(catalogSource, Does.Contain("public global::System.Predicate<GeneratedConfigCatalog.TableMetadata>? TableFilter { get; init; }"));
|
||||||
Assert.That(catalogSource,
|
Assert.That(catalogSource, Does.Contain("public global::System.Collections.Generic.IEqualityComparer<string>? ItemComparer { get; init; }"));
|
||||||
Does.Contain(
|
Assert.That(catalogSource, Does.Contain("public global::System.Collections.Generic.IEqualityComparer<int>? MonsterComparer { get; init; }"));
|
||||||
"public global::System.Collections.Generic.IReadOnlyCollection<string>? IncludedTableNames { get; init; }"));
|
|
||||||
Assert.That(catalogSource,
|
|
||||||
Does.Contain(
|
|
||||||
"public global::System.Predicate<GeneratedConfigCatalog.TableMetadata>? TableFilter { get; init; }"));
|
|
||||||
Assert.That(catalogSource,
|
|
||||||
Does.Contain(
|
|
||||||
"public global::System.Collections.Generic.IEqualityComparer<string>? ItemComparer { get; init; }"));
|
|
||||||
Assert.That(catalogSource,
|
|
||||||
Does.Contain(
|
|
||||||
"public global::System.Collections.Generic.IEqualityComparer<int>? MonsterComparer { get; init; }"));
|
|
||||||
Assert.That(catalogSource, Does.Contain("return RegisterAllGeneratedConfigTables(loader, options: null);"));
|
Assert.That(catalogSource, Does.Contain("return RegisterAllGeneratedConfigTables(loader, options: null);"));
|
||||||
Assert.That(catalogSource, Does.Contain("GeneratedConfigRegistrationOptions? options"));
|
Assert.That(catalogSource, Does.Contain("GeneratedConfigRegistrationOptions? options"));
|
||||||
Assert.That(catalogSource,
|
Assert.That(catalogSource, Does.Contain("if (ShouldRegisterTable(GeneratedConfigCatalog.Tables[0], options))"));
|
||||||
Does.Contain("if (ShouldRegisterTable(GeneratedConfigCatalog.Tables[0], options))"));
|
|
||||||
Assert.That(catalogSource, Does.Contain("loader.RegisterItemTable(options.ItemComparer);"));
|
Assert.That(catalogSource, Does.Contain("loader.RegisterItemTable(options.ItemComparer);"));
|
||||||
Assert.That(catalogSource,
|
Assert.That(catalogSource, Does.Contain("if (ShouldRegisterTable(GeneratedConfigCatalog.Tables[1], options))"));
|
||||||
Does.Contain("if (ShouldRegisterTable(GeneratedConfigCatalog.Tables[1], options))"));
|
|
||||||
Assert.That(catalogSource, Does.Contain("loader.RegisterMonsterTable(options.MonsterComparer);"));
|
Assert.That(catalogSource, Does.Contain("loader.RegisterMonsterTable(options.MonsterComparer);"));
|
||||||
Assert.That(catalogSource, Does.Contain("ItemConfigBindings.Metadata.TableName"));
|
Assert.That(catalogSource, Does.Contain("ItemConfigBindings.Metadata.TableName"));
|
||||||
Assert.That(catalogSource, Does.Contain("MonsterConfigBindings.Metadata.TableName"));
|
Assert.That(catalogSource, Does.Contain("MonsterConfigBindings.Metadata.TableName"));
|
||||||
Assert.That(catalogSource,
|
Assert.That(catalogSource, Does.Contain("public static bool TryGetByTableName(string tableName, out TableMetadata metadata)"));
|
||||||
Does.Contain("public static bool TryGetByTableName(string tableName, out TableMetadata metadata)"));
|
|
||||||
Assert.That(catalogSource, Does.Contain("private static bool ShouldRegisterTable("));
|
Assert.That(catalogSource, Does.Contain("private static bool ShouldRegisterTable("));
|
||||||
Assert.That(catalogSource, Does.Contain("private static bool MatchesOptionalAllowList("));
|
Assert.That(catalogSource, Does.Contain("private static bool MatchesOptionalAllowList("));
|
||||||
});
|
});
|
||||||
|
|||||||
@ -3,31 +3,30 @@
|
|||||||
|
|
||||||
### New Rules
|
### New Rules
|
||||||
|
|
||||||
Rule ID | Category | Severity | Notes
|
Rule ID | Category | Severity | Notes
|
||||||
----------------------------|------------------------------------|----------|--------------------------------
|
-----------------------|------------------------------------|----------|-------------------------
|
||||||
GF_Logging_001 | GFramework.Godot.logging | Warning | LoggerDiagnostics
|
GF_Logging_001 | GFramework.Godot.logging | Warning | LoggerDiagnostics
|
||||||
GF_Rule_001 | GFramework.SourceGenerators.rule | Error | ContextAwareDiagnostic
|
GF_Rule_001 | GFramework.SourceGenerators.rule | Error | ContextAwareDiagnostic
|
||||||
GF_ContextGet_001 | GFramework.SourceGenerators.rule | Error | ContextGetDiagnostics
|
GF_ContextGet_001 | GFramework.SourceGenerators.rule | Error | ContextGetDiagnostics
|
||||||
GF_ContextGet_002 | GFramework.SourceGenerators.rule | Error | ContextGetDiagnostics
|
GF_ContextGet_002 | GFramework.SourceGenerators.rule | Error | ContextGetDiagnostics
|
||||||
GF_ContextGet_003 | GFramework.SourceGenerators.rule | Error | ContextGetDiagnostics
|
GF_ContextGet_003 | GFramework.SourceGenerators.rule | Error | ContextGetDiagnostics
|
||||||
GF_ContextGet_004 | GFramework.SourceGenerators.rule | Error | ContextGetDiagnostics
|
GF_ContextGet_004 | GFramework.SourceGenerators.rule | Error | ContextGetDiagnostics
|
||||||
GF_ContextGet_005 | GFramework.SourceGenerators.rule | Error | ContextGetDiagnostics
|
GF_ContextGet_005 | GFramework.SourceGenerators.rule | Error | ContextGetDiagnostics
|
||||||
GF_ContextGet_006 | GFramework.SourceGenerators.rule | Error | ContextGetDiagnostics
|
GF_ContextGet_006 | GFramework.SourceGenerators.rule | Error | ContextGetDiagnostics
|
||||||
GF_ContextGet_007 | GFramework.SourceGenerators.rule | Warning | ContextGetDiagnostics
|
GF_ContextGet_007 | GFramework.SourceGenerators.rule | Warning | ContextGetDiagnostics
|
||||||
GF_ContextGet_008 | GFramework.SourceGenerators.rule | Warning | ContextGetDiagnostics
|
GF_ContextGet_008 | GFramework.SourceGenerators.rule | Warning | ContextGetDiagnostics
|
||||||
GF_ContextRegistration_001 | GFramework.SourceGenerators.rule | Warning | ContextRegistrationDiagnostics
|
GF_ContextRegistration_001 | GFramework.SourceGenerators.rule | Warning | ContextRegistrationDiagnostics
|
||||||
GF_ContextRegistration_002 | GFramework.SourceGenerators.rule | Warning | ContextRegistrationDiagnostics
|
GF_ContextRegistration_002 | GFramework.SourceGenerators.rule | Warning | ContextRegistrationDiagnostics
|
||||||
GF_ContextRegistration_003 | GFramework.SourceGenerators.rule | Warning | ContextRegistrationDiagnostics
|
GF_ContextRegistration_003 | GFramework.SourceGenerators.rule | Warning | ContextRegistrationDiagnostics
|
||||||
GF_ConfigSchema_001 | GFramework.SourceGenerators.Config | Error | ConfigSchemaDiagnostics
|
GF_ConfigSchema_001 | GFramework.SourceGenerators.Config | Error | ConfigSchemaDiagnostics
|
||||||
GF_ConfigSchema_002 | GFramework.SourceGenerators.Config | Error | ConfigSchemaDiagnostics
|
GF_ConfigSchema_002 | GFramework.SourceGenerators.Config | Error | ConfigSchemaDiagnostics
|
||||||
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_ConfigSchema_006 | GFramework.SourceGenerators.Config | Error | ConfigSchemaDiagnostics
|
||||||
GF_ConfigSchema_007 | 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
|
GF_Priority_004 | GFramework.Priority | Error | PriorityDiagnostic
|
||||||
GF_Priority_004 | GFramework.Priority | Error | PriorityDiagnostic
|
GF_Priority_005 | GFramework.Priority | Error | PriorityDiagnostic
|
||||||
GF_Priority_005 | GFramework.Priority | Error | PriorityDiagnostic
|
GF_Priority_Usage_001 | GFramework.Usage | Info | PriorityUsageAnalyzer
|
||||||
GF_Priority_Usage_001 | GFramework.Usage | Info | PriorityUsageAnalyzer
|
|
||||||
|
|||||||
@ -10,7 +10,6 @@ namespace GFramework.SourceGenerators.Config;
|
|||||||
[Generator]
|
[Generator]
|
||||||
public sealed class SchemaConfigGenerator : IIncrementalGenerator
|
public sealed class SchemaConfigGenerator : IIncrementalGenerator
|
||||||
{
|
{
|
||||||
private const string ConfigPathMetadataKey = "x-gframework-config-path";
|
|
||||||
private const string GeneratedNamespace = "GFramework.Game.Config.Generated";
|
private const string GeneratedNamespace = "GFramework.Game.Config.Generated";
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
@ -148,12 +147,6 @@ public sealed class SchemaConfigGenerator : IIncrementalGenerator
|
|||||||
}
|
}
|
||||||
|
|
||||||
var schemaBaseName = GetSchemaBaseName(file.Path);
|
var schemaBaseName = GetSchemaBaseName(file.Path);
|
||||||
var configRelativePath = ResolveConfigRelativePath(file.Path, root, schemaBaseName);
|
|
||||||
if (configRelativePath.Diagnostic is not null)
|
|
||||||
{
|
|
||||||
return SchemaParseResult.FromDiagnostic(configRelativePath.Diagnostic);
|
|
||||||
}
|
|
||||||
|
|
||||||
var schema = new SchemaFileSpec(
|
var schema = new SchemaFileSpec(
|
||||||
Path.GetFileName(file.Path),
|
Path.GetFileName(file.Path),
|
||||||
entityName,
|
entityName,
|
||||||
@ -163,7 +156,7 @@ public sealed class SchemaConfigGenerator : IIncrementalGenerator
|
|||||||
idProperty.TypeSpec.ClrType.TrimEnd('?'),
|
idProperty.TypeSpec.ClrType.TrimEnd('?'),
|
||||||
idProperty.PropertyName,
|
idProperty.PropertyName,
|
||||||
schemaBaseName,
|
schemaBaseName,
|
||||||
configRelativePath.Path!,
|
schemaBaseName,
|
||||||
GetSchemaRelativePath(file.Path),
|
GetSchemaRelativePath(file.Path),
|
||||||
TryGetMetadataString(root, "title"),
|
TryGetMetadataString(root, "title"),
|
||||||
TryGetMetadataString(root, "description"),
|
TryGetMetadataString(root, "description"),
|
||||||
@ -998,8 +991,7 @@ public sealed class SchemaConfigGenerator : IIncrementalGenerator
|
|||||||
builder.AppendLine(" /// <summary>");
|
builder.AppendLine(" /// <summary>");
|
||||||
builder.AppendLine(" /// Initializes one generated table metadata entry.");
|
builder.AppendLine(" /// Initializes one generated table metadata entry.");
|
||||||
builder.AppendLine(" /// </summary>");
|
builder.AppendLine(" /// </summary>");
|
||||||
builder.AppendLine(
|
builder.AppendLine(" /// <param name=\"configDomain\">Logical config domain derived from the schema base name.</param>");
|
||||||
" /// <param name=\"configDomain\">Logical config domain derived from the schema base name.</param>");
|
|
||||||
builder.AppendLine(" /// <param name=\"tableName\">Runtime registration name.</param>");
|
builder.AppendLine(" /// <param name=\"tableName\">Runtime registration name.</param>");
|
||||||
builder.AppendLine(" /// <param name=\"configRelativePath\">Relative YAML directory path.</param>");
|
builder.AppendLine(" /// <param name=\"configRelativePath\">Relative YAML directory path.</param>");
|
||||||
builder.AppendLine(" /// <param name=\"schemaRelativePath\">Relative schema file path.</param>");
|
builder.AppendLine(" /// <param name=\"schemaRelativePath\">Relative schema file path.</param>");
|
||||||
@ -1025,14 +1017,12 @@ public sealed class SchemaConfigGenerator : IIncrementalGenerator
|
|||||||
builder.AppendLine(" public string ConfigDomain { get; }");
|
builder.AppendLine(" public string ConfigDomain { get; }");
|
||||||
builder.AppendLine();
|
builder.AppendLine();
|
||||||
builder.AppendLine(" /// <summary>");
|
builder.AppendLine(" /// <summary>");
|
||||||
builder.AppendLine(
|
builder.AppendLine(" /// Gets the runtime registration name used by <see cref=\"global::GFramework.Game.Config.YamlConfigLoader\" />.");
|
||||||
" /// Gets the runtime registration name used by <see cref=\"global::GFramework.Game.Config.YamlConfigLoader\" />.");
|
|
||||||
builder.AppendLine(" /// </summary>");
|
builder.AppendLine(" /// </summary>");
|
||||||
builder.AppendLine(" public string TableName { get; }");
|
builder.AppendLine(" public string TableName { get; }");
|
||||||
builder.AppendLine();
|
builder.AppendLine();
|
||||||
builder.AppendLine(" /// <summary>");
|
builder.AppendLine(" /// <summary>");
|
||||||
builder.AppendLine(
|
builder.AppendLine(" /// Gets the relative directory that stores YAML files for the generated config table.");
|
||||||
" /// Gets the relative directory that stores YAML files for the generated config table.");
|
|
||||||
builder.AppendLine(" /// </summary>");
|
builder.AppendLine(" /// </summary>");
|
||||||
builder.AppendLine(" public string ConfigRelativePath { get; }");
|
builder.AppendLine(" public string ConfigRelativePath { get; }");
|
||||||
builder.AppendLine();
|
builder.AppendLine();
|
||||||
@ -1043,8 +1033,7 @@ public sealed class SchemaConfigGenerator : IIncrementalGenerator
|
|||||||
builder.AppendLine(" }");
|
builder.AppendLine(" }");
|
||||||
builder.AppendLine();
|
builder.AppendLine();
|
||||||
builder.AppendLine(" /// <summary>");
|
builder.AppendLine(" /// <summary>");
|
||||||
builder.AppendLine(
|
builder.AppendLine(" /// Gets metadata for every generated config table in the current consumer project.");
|
||||||
" /// Gets metadata for every generated config table in the current consumer project.");
|
|
||||||
builder.AppendLine(" /// </summary>");
|
builder.AppendLine(" /// </summary>");
|
||||||
builder.AppendLine(
|
builder.AppendLine(
|
||||||
" public static global::System.Collections.Generic.IReadOnlyList<TableMetadata> Tables { get; } = global::System.Array.AsReadOnly(new TableMetadata[]");
|
" public static global::System.Collections.Generic.IReadOnlyList<TableMetadata> Tables { get; } = global::System.Array.AsReadOnly(new TableMetadata[]");
|
||||||
@ -1156,8 +1145,7 @@ public sealed class SchemaConfigGenerator : IIncrementalGenerator
|
|||||||
" /// Registers all generated config tables using schema-derived conventions so bootstrap code can stay one-line even as schemas grow.");
|
" /// Registers all generated config tables using schema-derived conventions so bootstrap code can stay one-line even as schemas grow.");
|
||||||
builder.AppendLine(" /// </summary>");
|
builder.AppendLine(" /// </summary>");
|
||||||
builder.AppendLine(" /// <param name=\"loader\">Target YAML config loader.</param>");
|
builder.AppendLine(" /// <param name=\"loader\">Target YAML config loader.</param>");
|
||||||
builder.AppendLine(
|
builder.AppendLine(" /// <returns>The same loader instance after all generated table registrations have been applied.</returns>");
|
||||||
" /// <returns>The same loader instance after all generated table registrations have been applied.</returns>");
|
|
||||||
builder.AppendLine(
|
builder.AppendLine(
|
||||||
" /// <exception cref=\"global::System.ArgumentNullException\">When <paramref name=\"loader\"/> is null.</exception>");
|
" /// <exception cref=\"global::System.ArgumentNullException\">When <paramref name=\"loader\"/> is null.</exception>");
|
||||||
builder.AppendLine(
|
builder.AppendLine(
|
||||||
@ -1179,8 +1167,7 @@ public sealed class SchemaConfigGenerator : IIncrementalGenerator
|
|||||||
builder.AppendLine(" /// <param name=\"loader\">Target YAML config loader.</param>");
|
builder.AppendLine(" /// <param name=\"loader\">Target YAML config loader.</param>");
|
||||||
builder.AppendLine(
|
builder.AppendLine(
|
||||||
" /// <param name=\"options\">Optional per-table overrides for aggregate registration; when null, all tables use their default comparer behavior.</param>");
|
" /// <param name=\"options\">Optional per-table overrides for aggregate registration; when null, all tables use their default comparer behavior.</param>");
|
||||||
builder.AppendLine(
|
builder.AppendLine(" /// <returns>The same loader instance after all generated table registrations have been applied.</returns>");
|
||||||
" /// <returns>The same loader instance after all generated table registrations have been applied.</returns>");
|
|
||||||
builder.AppendLine(
|
builder.AppendLine(
|
||||||
" /// <exception cref=\"global::System.ArgumentNullException\">When <paramref name=\"loader\"/> is null.</exception>");
|
" /// <exception cref=\"global::System.ArgumentNullException\">When <paramref name=\"loader\"/> is null.</exception>");
|
||||||
builder.AppendLine(
|
builder.AppendLine(
|
||||||
@ -1202,8 +1189,7 @@ public sealed class SchemaConfigGenerator : IIncrementalGenerator
|
|||||||
builder.AppendLine(
|
builder.AppendLine(
|
||||||
$" if (ShouldRegisterTable(GeneratedConfigCatalog.Tables[{index.ToString(CultureInfo.InvariantCulture)}], options))");
|
$" if (ShouldRegisterTable(GeneratedConfigCatalog.Tables[{index.ToString(CultureInfo.InvariantCulture)}], options))");
|
||||||
builder.AppendLine(" {");
|
builder.AppendLine(" {");
|
||||||
builder.AppendLine(
|
builder.AppendLine($" loader.Register{schema.EntityName}Table(options.{schema.EntityName}Comparer);");
|
||||||
$" loader.Register{schema.EntityName}Table(options.{schema.EntityName}Comparer);");
|
|
||||||
builder.AppendLine(" }");
|
builder.AppendLine(" }");
|
||||||
builder.AppendLine();
|
builder.AppendLine();
|
||||||
}
|
}
|
||||||
@ -1216,8 +1202,7 @@ public sealed class SchemaConfigGenerator : IIncrementalGenerator
|
|||||||
" /// Applies the generated registration filters in a deterministic order so bootstrap code can narrow aggregate registration without hand-writing per-table calls.");
|
" /// Applies the generated registration filters in a deterministic order so bootstrap code can narrow aggregate registration without hand-writing per-table calls.");
|
||||||
builder.AppendLine(" /// </summary>");
|
builder.AppendLine(" /// </summary>");
|
||||||
builder.AppendLine(" /// <param name=\"metadata\">Generated table metadata under consideration.</param>");
|
builder.AppendLine(" /// <param name=\"metadata\">Generated table metadata under consideration.</param>");
|
||||||
builder.AppendLine(
|
builder.AppendLine(" /// <param name=\"options\">Aggregate registration options supplied by the caller.</param>");
|
||||||
" /// <param name=\"options\">Aggregate registration options supplied by the caller.</param>");
|
|
||||||
builder.AppendLine(
|
builder.AppendLine(
|
||||||
" /// <returns><see langword=\"true\" /> when the generated table should be registered; otherwise <see langword=\"false\" />.</returns>");
|
" /// <returns><see langword=\"true\" /> when the generated table should be registered; otherwise <see langword=\"false\" />.</returns>");
|
||||||
builder.AppendLine(
|
builder.AppendLine(
|
||||||
@ -1227,8 +1212,7 @@ public sealed class SchemaConfigGenerator : IIncrementalGenerator
|
|||||||
builder.AppendLine(" {");
|
builder.AppendLine(" {");
|
||||||
builder.AppendLine(
|
builder.AppendLine(
|
||||||
" // Apply cheap generated allow-lists before invoking the optional caller predicate so startup filtering stays predictable.");
|
" // Apply cheap generated allow-lists before invoking the optional caller predicate so startup filtering stays predictable.");
|
||||||
builder.AppendLine(
|
builder.AppendLine(" if (!MatchesOptionalAllowList(options.IncludedConfigDomains, metadata.ConfigDomain))");
|
||||||
" if (!MatchesOptionalAllowList(options.IncludedConfigDomains, metadata.ConfigDomain))");
|
|
||||||
builder.AppendLine(" {");
|
builder.AppendLine(" {");
|
||||||
builder.AppendLine(" return false;");
|
builder.AppendLine(" return false;");
|
||||||
builder.AppendLine(" }");
|
builder.AppendLine(" }");
|
||||||
@ -1409,8 +1393,7 @@ public sealed class SchemaConfigGenerator : IIncrementalGenerator
|
|||||||
builder.AppendLine(" /// <param name=\"value\">The property value to match.</param>");
|
builder.AppendLine(" /// <param name=\"value\">The property value to match.</param>");
|
||||||
builder.AppendLine(
|
builder.AppendLine(
|
||||||
" /// <param name=\"result\">The first matching config entry when lookup succeeds; otherwise <see langword=\"null\" />.</param>");
|
" /// <param name=\"result\">The first matching config entry when lookup succeeds; otherwise <see langword=\"null\" />.</param>");
|
||||||
builder.AppendLine(
|
builder.AppendLine(" /// <returns><see langword=\"true\" /> when a matching config entry is found; otherwise <see langword=\"false\" />.</returns>");
|
||||||
" /// <returns><see langword=\"true\" /> when a matching config entry is found; otherwise <see langword=\"false\" />.</returns>");
|
|
||||||
builder.AppendLine(" /// <remarks>");
|
builder.AppendLine(" /// <remarks>");
|
||||||
builder.AppendLine(
|
builder.AppendLine(
|
||||||
" /// The generated helper walks the same snapshot exposed by <see cref=\"All\"/> and returns the first match in iteration order.");
|
" /// The generated helper walks the same snapshot exposed by <see cref=\"All\"/> and returns the first match in iteration order.");
|
||||||
@ -1784,98 +1767,6 @@ public sealed class SchemaConfigGenerator : IIncrementalGenerator
|
|||||||
return string.IsNullOrWhiteSpace(value) ? null : value;
|
return string.IsNullOrWhiteSpace(value) ? null : value;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 解析 schema 顶层配置目录元数据。
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="filePath">Schema 文件路径。</param>
|
|
||||||
/// <param name="element">Schema 顶层节点。</param>
|
|
||||||
/// <param name="defaultRelativePath">默认配置目录。</param>
|
|
||||||
/// <returns>最终使用的配置目录或诊断。</returns>
|
|
||||||
private static (string? Path, Diagnostic? Diagnostic) ResolveConfigRelativePath(
|
|
||||||
string filePath,
|
|
||||||
JsonElement element,
|
|
||||||
string defaultRelativePath)
|
|
||||||
{
|
|
||||||
if (!element.TryGetProperty(ConfigPathMetadataKey, out var configPathElement))
|
|
||||||
{
|
|
||||||
return (defaultRelativePath, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (configPathElement.ValueKind != JsonValueKind.String)
|
|
||||||
{
|
|
||||||
return (
|
|
||||||
null,
|
|
||||||
Diagnostic.Create(
|
|
||||||
ConfigSchemaDiagnostics.InvalidConfigRelativePathMetadata,
|
|
||||||
CreateFileLocation(filePath),
|
|
||||||
Path.GetFileName(filePath),
|
|
||||||
ConfigPathMetadataKey,
|
|
||||||
$"Expected a JSON string but found '{configPathElement.ValueKind}'."));
|
|
||||||
}
|
|
||||||
|
|
||||||
var configuredPath = configPathElement.GetString();
|
|
||||||
if (string.IsNullOrWhiteSpace(configuredPath))
|
|
||||||
{
|
|
||||||
return (
|
|
||||||
null,
|
|
||||||
Diagnostic.Create(
|
|
||||||
ConfigSchemaDiagnostics.InvalidConfigRelativePathMetadata,
|
|
||||||
CreateFileLocation(filePath),
|
|
||||||
Path.GetFileName(filePath),
|
|
||||||
ConfigPathMetadataKey,
|
|
||||||
"Path cannot be null, empty, or whitespace."));
|
|
||||||
}
|
|
||||||
|
|
||||||
var normalizedPath = NormalizeConfigRelativePath(configuredPath!);
|
|
||||||
if (normalizedPath is null)
|
|
||||||
{
|
|
||||||
return (
|
|
||||||
null,
|
|
||||||
Diagnostic.Create(
|
|
||||||
ConfigSchemaDiagnostics.InvalidConfigRelativePathMetadata,
|
|
||||||
CreateFileLocation(filePath),
|
|
||||||
Path.GetFileName(filePath),
|
|
||||||
ConfigPathMetadataKey,
|
|
||||||
"Path must be relative and cannot contain '..' segments."));
|
|
||||||
}
|
|
||||||
|
|
||||||
return (normalizedPath, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 标准化配置目录元数据,统一斜杠并拒绝逃逸配置根目录的写法。
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="configuredPath">Schema 中声明的相对目录。</param>
|
|
||||||
/// <returns>标准化后的相对目录;无效时返回空。</returns>
|
|
||||||
private static string? NormalizeConfigRelativePath(string configuredPath)
|
|
||||||
{
|
|
||||||
var normalizedPath = configuredPath.Replace('\\', '/').Trim();
|
|
||||||
if (string.IsNullOrWhiteSpace(normalizedPath) ||
|
|
||||||
normalizedPath.StartsWith("/", StringComparison.Ordinal) ||
|
|
||||||
Path.IsPathRooted(normalizedPath))
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
var normalizedSegments = new List<string>();
|
|
||||||
foreach (var segment in normalizedPath.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries))
|
|
||||||
{
|
|
||||||
if (string.Equals(segment, ".", StringComparison.Ordinal))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (string.Equals(segment, "..", StringComparison.Ordinal))
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
normalizedSegments.Add(segment);
|
|
||||||
}
|
|
||||||
|
|
||||||
return normalizedSegments.Count == 0 ? null : string.Join("/", normalizedSegments);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 为标量字段构建可直接生成到属性上的默认值初始化器。
|
/// 为标量字段构建可直接生成到属性上的默认值初始化器。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@ -74,15 +74,4 @@ public static class ConfigSchemaDiagnostics
|
|||||||
SourceGeneratorsConfigCategory,
|
SourceGeneratorsConfigCategory,
|
||||||
DiagnosticSeverity.Error,
|
DiagnosticSeverity.Error,
|
||||||
true);
|
true);
|
||||||
|
}
|
||||||
/// <summary>
|
|
||||||
/// schema 顶层自定义配置目录元数据无效。
|
|
||||||
/// </summary>
|
|
||||||
public static readonly DiagnosticDescriptor InvalidConfigRelativePathMetadata = new(
|
|
||||||
"GF_ConfigSchema_007",
|
|
||||||
"Config schema uses invalid custom config path metadata",
|
|
||||||
"Schema file '{0}' uses invalid '{1}' metadata: {2}",
|
|
||||||
SourceGeneratorsConfigCategory,
|
|
||||||
DiagnosticSeverity.Error,
|
|
||||||
true);
|
|
||||||
}
|
|
||||||
@ -1,26 +1,5 @@
|
|||||||
import { defineConfig } from 'vitepress'
|
import { defineConfig } from 'vitepress'
|
||||||
|
|
||||||
const localSearch = {
|
|
||||||
provider: 'local' as const,
|
|
||||||
options: {
|
|
||||||
translations: {
|
|
||||||
button: {
|
|
||||||
buttonText: '搜索',
|
|
||||||
buttonAriaLabel: '搜索文档'
|
|
||||||
},
|
|
||||||
modal: {
|
|
||||||
noResultsText: '无法找到相关结果',
|
|
||||||
resetButtonTitle: '清除查询条件',
|
|
||||||
footer: {
|
|
||||||
selectText: '选择',
|
|
||||||
navigateText: '切换',
|
|
||||||
closeText: '关闭'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function safeGenericEscapePlugin() {
|
function safeGenericEscapePlugin() {
|
||||||
return {
|
return {
|
||||||
name: 'safe-generic-escape',
|
name: 'safe-generic-escape',
|
||||||
@ -83,10 +62,6 @@ export default defineConfig({
|
|||||||
chunkSizeWarningLimit: 1000
|
chunkSizeWarningLimit: 1000
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
themeConfig: {
|
|
||||||
// 在顶层保留搜索配置,避免构建期只读取站点级配置时把搜索入口裁掉。
|
|
||||||
search: localSearch
|
|
||||||
},
|
|
||||||
/** 多语言 */
|
/** 多语言 */
|
||||||
locales: {
|
locales: {
|
||||||
root: {
|
root: {
|
||||||
@ -96,21 +71,41 @@ export default defineConfig({
|
|||||||
|
|
||||||
themeConfig: {
|
themeConfig: {
|
||||||
logo: '/logo-icon.png',
|
logo: '/logo-icon.png',
|
||||||
search: localSearch,
|
|
||||||
|
search: {
|
||||||
|
provider: 'local',
|
||||||
|
options: {
|
||||||
|
translations: {
|
||||||
|
button: {
|
||||||
|
buttonText: '搜索文档',
|
||||||
|
buttonAriaLabel: '搜索文档'
|
||||||
|
},
|
||||||
|
modal: {
|
||||||
|
noResultsText: '无法找到相关结果',
|
||||||
|
resetButtonTitle: '清除查询条件',
|
||||||
|
footer: {
|
||||||
|
selectText: '选择',
|
||||||
|
navigateText: '切换',
|
||||||
|
closeText: '关闭'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
nav: [
|
nav: [
|
||||||
{ text: '首页', link: '/zh-CN/' },
|
{ text: '首页', link: '/zh-CN/' },
|
||||||
{ text: '入门指南', link: '/zh-CN/getting-started' },
|
{ text: '入门指南', link: '/zh-CN/getting-started' },
|
||||||
{ text: 'Core', link: '/zh-CN/core/' },
|
{ text: 'Core', link: '/zh-CN/core/' },
|
||||||
|
{ text: 'ECS', link: '/zh-CN/ecs/' },
|
||||||
{ text: 'Game', link: '/zh-CN/game/' },
|
{ text: 'Game', link: '/zh-CN/game/' },
|
||||||
{ text: 'Godot', link: '/zh-CN/godot/' },
|
{ text: 'Godot', link: '/zh-CN/godot/' },
|
||||||
|
{ text: '源码生成器', link: '/zh-CN/source-generators' },
|
||||||
{ text: '教程', link: '/zh-CN/tutorials/' },
|
{ text: '教程', link: '/zh-CN/tutorials/' },
|
||||||
|
{ text: '最佳实践', link: '/zh-CN/best-practices/' },
|
||||||
{
|
{
|
||||||
text: '更多',
|
text: '更多',
|
||||||
items: [
|
items: [
|
||||||
{ text: 'ECS', link: '/zh-CN/ecs/' },
|
|
||||||
{ text: '源码生成器', link: '/zh-CN/source-generators' },
|
|
||||||
{ text: '最佳实践', link: '/zh-CN/best-practices/' },
|
|
||||||
{ text: 'API 参考', link: '/zh-CN/api-reference' },
|
{ text: 'API 参考', link: '/zh-CN/api-reference' },
|
||||||
{ text: '常见问题', link: '/zh-CN/faq' },
|
{ text: '常见问题', link: '/zh-CN/faq' },
|
||||||
{ text: '故障排查', link: '/zh-CN/troubleshooting' },
|
{ text: '故障排查', link: '/zh-CN/troubleshooting' },
|
||||||
|
|||||||
@ -132,46 +132,3 @@
|
|||||||
--vp-custom-block-tip-code-bg: var(--vp-c-brand-soft);
|
--vp-custom-block-tip-code-bg: var(--vp-c-brand-soft);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Component: Navigation
|
|
||||||
* -------------------------------------------------------------------------- */
|
|
||||||
|
|
||||||
@media (min-width: 768px) and (max-width: 1279px) {
|
|
||||||
/* Keep the search entry visible on medium desktop widths where nav links are the tightest. */
|
|
||||||
.VPNavBarSearch {
|
|
||||||
flex: 0 0 auto;
|
|
||||||
padding-left: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.VPNavBarSearch .VPNavBarSearchButton {
|
|
||||||
min-width: 0;
|
|
||||||
padding: 8px 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.VPNavBarSearch .keys {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.VPNavBarMenu {
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.VPNavBarMenu .VPNavBarMenuLink,
|
|
||||||
.VPNavBarMenu .VPFlyout .button {
|
|
||||||
padding-left: 10px;
|
|
||||||
padding-right: 10px;
|
|
||||||
font-size: 13px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 1280px) {
|
|
||||||
/* Reserve stable room for the search button before appearance and social actions appear. */
|
|
||||||
.VPNavBarSearch {
|
|
||||||
min-width: 176px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.VPNavBarSearch .VPNavBarSearchButton {
|
|
||||||
justify-content: space-between;
|
|
||||||
min-width: 176px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@ -37,7 +37,6 @@ GameProject/
|
|||||||
{
|
{
|
||||||
"title": "Monster Config",
|
"title": "Monster Config",
|
||||||
"description": "定义怪物静态配置。",
|
"description": "定义怪物静态配置。",
|
||||||
"x-gframework-config-path": "config/monster",
|
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": ["id", "name"],
|
"required": ["id", "name"],
|
||||||
"properties": {
|
"properties": {
|
||||||
@ -72,31 +71,6 @@ GameProject/
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
顶层可选元数据:
|
|
||||||
|
|
||||||
- `x-gframework-config-path`:覆盖生成器默认的配置目录。未声明时,默认使用 schema 基名,例如
|
|
||||||
`monster.schema.json -> monster`
|
|
||||||
|
|
||||||
例如项目希望继续把 YAML 放在 `config/monster/*.yaml` 下,而不是根目录 `monster/*.yaml`,可以这样声明:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"type": "object",
|
|
||||||
"x-gframework-config-path": "config/monster",
|
|
||||||
"required": ["id"],
|
|
||||||
"properties": {
|
|
||||||
"id": { "type": "integer" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
约束如下:
|
|
||||||
|
|
||||||
- 必须是 JSON 字符串
|
|
||||||
- 必须是相对路径
|
|
||||||
- 不允许包含 `..` 段
|
|
||||||
- 生成器会把反斜杠标准化为 `/`
|
|
||||||
|
|
||||||
## YAML 示例
|
## YAML 示例
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user