Merge pull request #235 from GeWuYou/refactor/cqrs-and-config-system

refactor: 移除 Mediator 兼容性 API 并重组源生成器项目结构
This commit is contained in:
gewuyou 2026-04-16 22:50:39 +08:00 committed by GitHub
commit 1d6ff223d5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
93 changed files with 1457 additions and 1302 deletions

View File

@ -73,9 +73,8 @@ Architecture 负责统一生命周期编排,核心阶段包括:
### CQRS ### CQRS
命令与查询分离,支持同步与异步执行。当前版本内建自有 CQRS runtime、行为管道和 handler 自动注册;公开 API 里仍保留少量历史 命令与查询分离,支持同步与异步执行。当前版本内建自有 CQRS runtime、行为管道和 handler 自动注册;历史 `Mediator`
`Mediator` 命名以兼容旧调用点,但这些别名已进入正式弃用周期:新代码应使用 `Cqrs` 命名入口,旧别名会继续兼容一段时间并计划在未来 兼容别名已从公开 API 移除,统一使用 `Cqrs` 命名入口。
major 版本中移除。
### EventBus ### EventBus

View File

@ -1,4 +1,3 @@
using System.ComponentModel;
using System.Reflection; using System.Reflection;
using GFramework.Core.Abstractions.Lifecycle; using GFramework.Core.Abstractions.Lifecycle;
using GFramework.Core.Abstractions.Model; using GFramework.Core.Abstractions.Model;
@ -82,20 +81,6 @@ public interface IArchitecture : IAsyncInitializable, IAsyncDestroyable, IInitia
void RegisterCqrsPipelineBehavior<TBehavior>() void RegisterCqrsPipelineBehavior<TBehavior>()
where TBehavior : class; where TBehavior : class;
/// <summary>
/// 注册 CQRS 请求管道行为。
/// 该成员保留旧名称以兼容历史调用点,内部行为与 <see cref="RegisterCqrsPipelineBehavior{TBehavior}" /> 一致。
/// 新代码不应继续依赖该别名;兼容层计划在未来的 major 版本中移除。
/// 既支持实现 <c>IPipelineBehavior&lt;,&gt;</c> 的开放泛型行为类型,
/// 也支持绑定到单一请求/响应对的封闭行为类型。
/// </summary>
/// <typeparam name="TBehavior">行为类型,必须是引用类型</typeparam>
[EditorBrowsable(EditorBrowsableState.Never)]
[Obsolete(
"Use RegisterCqrsPipelineBehavior<TBehavior>() instead. This compatibility alias will be removed in a future major version.")]
void RegisterMediatorBehavior<TBehavior>()
where TBehavior : class;
/// <summary> /// <summary>
/// 从指定程序集显式注册 CQRS 处理器。 /// 从指定程序集显式注册 CQRS 处理器。
/// 当处理器位于默认架构程序集之外的模块或扩展程序集中时,可在初始化阶段调用该入口接入对应程序集。 /// 当处理器位于默认架构程序集之外的模块或扩展程序集中时,可在初始化阶段调用该入口接入对应程序集。

View File

@ -1,5 +1,4 @@
using System.ComponentModel; using System.Reflection;
using System.Reflection;
using GFramework.Core.Abstractions.Rule; using GFramework.Core.Abstractions.Rule;
using GFramework.Core.Abstractions.Systems; using GFramework.Core.Abstractions.Systems;
@ -97,18 +96,6 @@ public interface IIocContainer : IContextAware
void RegisterCqrsPipelineBehavior<TBehavior>() void RegisterCqrsPipelineBehavior<TBehavior>()
where TBehavior : class; where TBehavior : class;
/// <summary>
/// 注册 CQRS 请求管道行为。
/// 该成员保留旧名称以兼容历史调用点,内部行为与 <see cref="RegisterCqrsPipelineBehavior{TBehavior}" /> 一致。
/// 新代码不应继续依赖该别名;兼容层计划在未来的 major 版本中移除。
/// </summary>
/// <typeparam name="TBehavior">行为类型,必须是引用类型</typeparam>
[EditorBrowsable(EditorBrowsableState.Never)]
[Obsolete(
"Use RegisterCqrsPipelineBehavior<TBehavior>() instead. This compatibility alias will be removed in a future major version.")]
void RegisterMediatorBehavior<TBehavior>()
where TBehavior : class;
/// <summary> /// <summary>
/// 从指定程序集显式注册 CQRS 处理器。 /// 从指定程序集显式注册 CQRS 处理器。
/// 该入口适用于处理器不位于默认架构程序集中的场景,例如扩展包、模块程序集或拆分后的业务程序集。 /// 该入口适用于处理器不位于默认架构程序集中的场景,例如扩展包、模块程序集或拆分后的业务程序集。

View File

@ -1,4 +1,4 @@
namespace GFramework.SourceGenerators.Abstractions.Architectures; namespace GFramework.Core.SourceGenerators.Abstractions.Architectures;
/// <summary> /// <summary>
/// 标记架构模块类型Source Generator 会根据注册特性生成 <c>Install</c> 方法。 /// 标记架构模块类型Source Generator 会根据注册特性生成 <c>Install</c> 方法。

View File

@ -1,4 +1,4 @@
namespace GFramework.SourceGenerators.Abstractions.Architectures; namespace GFramework.Core.SourceGenerators.Abstractions.Architectures;
/// <summary> /// <summary>
/// 声明架构模块需要自动注册的模型类型。 /// 声明架构模块需要自动注册的模型类型。

View File

@ -1,4 +1,4 @@
namespace GFramework.SourceGenerators.Abstractions.Architectures; namespace GFramework.Core.SourceGenerators.Abstractions.Architectures;
/// <summary> /// <summary>
/// 声明架构模块需要自动注册的系统类型。 /// 声明架构模块需要自动注册的系统类型。

View File

@ -1,4 +1,4 @@
namespace GFramework.SourceGenerators.Abstractions.Architectures; namespace GFramework.Core.SourceGenerators.Abstractions.Architectures;
/// <summary> /// <summary>
/// 声明架构模块需要自动注册的工具类型。 /// 声明架构模块需要自动注册的工具类型。

View File

@ -1,4 +1,4 @@
namespace GFramework.SourceGenerators.Abstractions.Bases; namespace GFramework.Core.SourceGenerators.Abstractions.Bases;
/// <summary> /// <summary>
/// 标记类的优先级,自动生成 <c>GFramework.Core.Abstractions.Bases.IPrioritized</c> 接口实现。 /// 标记类的优先级,自动生成 <c>GFramework.Core.Abstractions.Bases.IPrioritized</c> 接口实现。

View File

@ -1,4 +1,4 @@
namespace GFramework.SourceGenerators.Abstractions.Enums; namespace GFramework.Core.SourceGenerators.Abstractions.Enums;
/// <summary> /// <summary>
/// 标注在 enum 上Source Generator 会为该 enum 生成扩展方法。 /// 标注在 enum 上Source Generator 会为该 enum 生成扩展方法。

View File

@ -12,7 +12,7 @@
<!-- 引入必要的命名空间 --> <!-- 引入必要的命名空间 -->
<ItemGroup> <ItemGroup>
<Using Include="GFramework.SourceGenerators.Abstractions"/> <Using Include="GFramework.Core.SourceGenerators.Abstractions"/>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Update="Meziantou.Analyzer" Version="3.0.46"> <PackageReference Update="Meziantou.Analyzer" Version="3.0.46">

View File

@ -1,5 +1,5 @@
#nullable enable #nullable enable
namespace GFramework.SourceGenerators.Abstractions.Logging; namespace GFramework.Core.SourceGenerators.Abstractions.Logging;
/// <summary> /// <summary>
/// 标注在类上Source Generator 会为该类自动生成一个日志记录器字段。 /// 标注在类上Source Generator 会为该类自动生成一个日志记录器字段。

View File

@ -1,4 +1,4 @@
namespace GFramework.SourceGenerators.Abstractions.Rule; namespace GFramework.Core.SourceGenerators.Abstractions.Rule;
/// <summary> /// <summary>
/// 标记该类需要自动实现 IContextAware /// 标记该类需要自动实现 IContextAware

View File

@ -1,4 +1,4 @@
namespace GFramework.SourceGenerators.Abstractions.Rule; namespace GFramework.Core.SourceGenerators.Abstractions.Rule;
/// <summary> /// <summary>
/// 标记类需要自动推断并注入上下文相关字段。 /// 标记类需要自动推断并注入上下文相关字段。

View File

@ -1,4 +1,4 @@
namespace GFramework.SourceGenerators.Abstractions.Rule; namespace GFramework.Core.SourceGenerators.Abstractions.Rule;
/// <summary> /// <summary>
/// 标记字段需要自动注入单个模型实例。 /// 标记字段需要自动注入单个模型实例。

View File

@ -1,4 +1,4 @@
namespace GFramework.SourceGenerators.Abstractions.Rule; namespace GFramework.Core.SourceGenerators.Abstractions.Rule;
/// <summary> /// <summary>
/// 标记字段需要自动注入模型集合。 /// 标记字段需要自动注入模型集合。

View File

@ -1,4 +1,4 @@
namespace GFramework.SourceGenerators.Abstractions.Rule; namespace GFramework.Core.SourceGenerators.Abstractions.Rule;
/// <summary> /// <summary>
/// 标记字段需要自动注入单个服务实例。 /// 标记字段需要自动注入单个服务实例。

View File

@ -1,4 +1,4 @@
namespace GFramework.SourceGenerators.Abstractions.Rule; namespace GFramework.Core.SourceGenerators.Abstractions.Rule;
/// <summary> /// <summary>
/// 标记字段需要自动注入服务集合。 /// 标记字段需要自动注入服务集合。

View File

@ -1,4 +1,4 @@
namespace GFramework.SourceGenerators.Abstractions.Rule; namespace GFramework.Core.SourceGenerators.Abstractions.Rule;
/// <summary> /// <summary>
/// 标记字段需要自动注入单个系统实例。 /// 标记字段需要自动注入单个系统实例。

View File

@ -0,0 +1,23 @@
namespace GFramework.Core.SourceGenerators.Abstractions.Rule;
/// <summary>
/// 标记字段需要自动注入系统集合。
/// </summary>
/// <remarks>
/// Source Generator 会为标记字段生成从当前架构上下文收集系统实例的注入代码,用于避免在组件内部重复书写
/// <c>GetSystems()</c> 一类的样板访问逻辑。
/// 被标记字段应声明为可承载多个系统实例的类型,例如 <c>IEnumerable&lt;ISystem&gt;</c> 或兼容集合接口。
/// </remarks>
/// <example>
/// <code>
/// public partial class CombatPanel : IContextAware
/// {
/// [GetSystems]
/// private IEnumerable&lt;ISystem&gt; _systems = Array.Empty&lt;ISystem&gt;();
/// }
/// </code>
/// </example>
[AttributeUsage(AttributeTargets.Field, Inherited = false, AllowMultiple = false)]
public sealed class GetSystemsAttribute : Attribute
{
}

View File

@ -1,4 +1,4 @@
namespace GFramework.SourceGenerators.Abstractions.Rule; namespace GFramework.Core.SourceGenerators.Abstractions.Rule;
/// <summary> /// <summary>
/// 标记字段需要自动注入工具集合。 /// 标记字段需要自动注入工具集合。

View File

@ -1,4 +1,4 @@
namespace GFramework.SourceGenerators.Abstractions.Rule; namespace GFramework.Core.SourceGenerators.Abstractions.Rule;
/// <summary> /// <summary>
/// 标记字段需要自动注入单个工具实例。 /// 标记字段需要自动注入单个工具实例。

View File

@ -18,16 +18,6 @@
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_002 | GFramework.SourceGenerators.Config | Error | ConfigSchemaDiagnostics
GF_ConfigSchema_003 | GFramework.SourceGenerators.Config | Error | ConfigSchemaDiagnostics
GF_ConfigSchema_004 | GFramework.SourceGenerators.Config | Error | ConfigSchemaDiagnostics
GF_ConfigSchema_005 | GFramework.SourceGenerators.Config | Error | ConfigSchemaDiagnostics
GF_ConfigSchema_006 | GFramework.SourceGenerators.Config | Error | ConfigSchemaDiagnostics
GF_ConfigSchema_007 | GFramework.SourceGenerators.Config | Error | ConfigSchemaDiagnostics
GF_ConfigSchema_008 | GFramework.SourceGenerators.Config | Error | ConfigSchemaDiagnostics
GF_ConfigSchema_009 | GFramework.SourceGenerators.Config | Error | ConfigSchemaDiagnostics
GF_ConfigSchema_010 | GFramework.SourceGenerators.Config | Error | ConfigSchemaDiagnostics
GF_AutoModule_001 | GFramework.SourceGenerators.Architecture | Error | AutoRegisterModuleDiagnostics GF_AutoModule_001 | GFramework.SourceGenerators.Architecture | Error | AutoRegisterModuleDiagnostics
GF_AutoModule_002 | GFramework.SourceGenerators.Architecture | Error | AutoRegisterModuleDiagnostics GF_AutoModule_002 | GFramework.SourceGenerators.Architecture | Error | AutoRegisterModuleDiagnostics
GF_AutoModule_003 | GFramework.SourceGenerators.Architecture | Error | AutoRegisterModuleDiagnostics GF_AutoModule_003 | GFramework.SourceGenerators.Architecture | Error | AutoRegisterModuleDiagnostics

View File

@ -1,10 +1,9 @@
using System.Collections.Immutable; using GFramework.Core.SourceGenerators.Diagnostics;
using GFramework.SourceGenerators.Common.Constants; using GFramework.SourceGenerators.Common.Constants;
using GFramework.SourceGenerators.Diagnostics;
using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Operations; using Microsoft.CodeAnalysis.Operations;
namespace GFramework.SourceGenerators.Analyzers; namespace GFramework.Core.SourceGenerators.Analyzers;
/// <summary> /// <summary>
/// 分析 Context Get 使用点是否能在所属架构中找到静态可见的 Model、System、Utility 注册。 /// 分析 Context Get 使用点是否能在所属架构中找到静态可见的 Model、System、Utility 注册。
@ -12,6 +11,17 @@ namespace GFramework.SourceGenerators.Analyzers;
[DiagnosticAnalyzer(LanguageNames.CSharp)] [DiagnosticAnalyzer(LanguageNames.CSharp)]
public sealed class ContextRegistrationAnalyzer : DiagnosticAnalyzer public sealed class ContextRegistrationAnalyzer : DiagnosticAnalyzer
{ {
private static readonly IReadOnlyDictionary<string, ComponentKind> _contextAwareBindingNames =
new Dictionary<string, ComponentKind>(StringComparer.Ordinal)
{
["GetModel"] = ComponentKind.Model,
["GetModels"] = ComponentKind.Model,
["GetSystem"] = ComponentKind.System,
["GetSystems"] = ComponentKind.System,
["GetUtility"] = ComponentKind.Utility,
["GetUtilities"] = ComponentKind.Utility
};
/// <summary> /// <summary>
/// 当前分析器支持的诊断规则。 /// 当前分析器支持的诊断规则。
/// </summary> /// </summary>
@ -58,10 +68,12 @@ public sealed class ContextRegistrationAnalyzer : DiagnosticAnalyzer
if (context.Node is not VariableDeclaratorSyntax variableDeclarator) if (context.Node is not VariableDeclaratorSyntax variableDeclarator)
return; return;
if (context.SemanticModel.GetDeclaredSymbol(variableDeclarator, context.CancellationToken) is not IFieldSymbol fieldSymbol) if (context.SemanticModel.GetDeclaredSymbol(variableDeclarator, context.CancellationToken) is not IFieldSymbol
fieldSymbol)
return; return;
if (!TryCreateBindingRequest(fieldSymbol, variableDeclarator.Identifier.GetLocation(), symbols, out var request)) if (!TryCreateBindingRequest(fieldSymbol, variableDeclarator.Identifier.GetLocation(), symbols,
out var request))
return; return;
ReportMissingRegistration(context, registrationIndex, request); ReportMissingRegistration(context, registrationIndex, request);
@ -272,17 +284,6 @@ public sealed class ContextRegistrationAnalyzer : DiagnosticAnalyzer
return namedType.TypeArguments[0] as INamedTypeSymbol; return namedType.TypeArguments[0] as INamedTypeSymbol;
} }
private static readonly IReadOnlyDictionary<string, ComponentKind> _contextAwareBindingNames =
new Dictionary<string, ComponentKind>(StringComparer.Ordinal)
{
["GetModel"] = ComponentKind.Model,
["GetModels"] = ComponentKind.Model,
["GetSystem"] = ComponentKind.System,
["GetSystems"] = ComponentKind.System,
["GetUtility"] = ComponentKind.Utility,
["GetUtilities"] = ComponentKind.Utility
};
private enum ComponentKind private enum ComponentKind
{ {
Model, Model,
@ -368,22 +369,35 @@ public sealed class ContextRegistrationAnalyzer : DiagnosticAnalyzer
{ {
return new SymbolCache( return new SymbolCache(
compilation.GetTypeByMetadataName($"{PathContests.CoreNamespace}.Architectures.Architecture"), compilation.GetTypeByMetadataName($"{PathContests.CoreNamespace}.Architectures.Architecture"),
compilation.GetTypeByMetadataName($"{PathContests.CoreAbstractionsNamespace}.Architectures.IArchitecture"), compilation.GetTypeByMetadataName(
compilation.GetTypeByMetadataName($"{PathContests.CoreAbstractionsNamespace}.Architectures.IArchitectureModule"), $"{PathContests.CoreAbstractionsNamespace}.Architectures.IArchitecture"),
compilation.GetTypeByMetadataName($"{PathContests.CoreAbstractionsNamespace}.Architectures.IArchitectureContext"), compilation.GetTypeByMetadataName(
$"{PathContests.CoreAbstractionsNamespace}.Architectures.IArchitectureModule"),
compilation.GetTypeByMetadataName(
$"{PathContests.CoreAbstractionsNamespace}.Architectures.IArchitectureContext"),
compilation.GetTypeByMetadataName("System.Collections.Generic.IReadOnlyList`1"), compilation.GetTypeByMetadataName("System.Collections.Generic.IReadOnlyList`1"),
compilation.GetTypeByMetadataName($"{PathContests.CoreNamespace}.Extensions.ContextAwareServiceExtensions"), compilation.GetTypeByMetadataName(
compilation.GetTypeByMetadataName($"{PathContests.SourceGeneratorsAbstractionsPath}.Rule.GetModelAttribute"), $"{PathContests.CoreNamespace}.Extensions.ContextAwareServiceExtensions"),
compilation.GetTypeByMetadataName($"{PathContests.SourceGeneratorsAbstractionsPath}.Rule.GetModelsAttribute"), compilation.GetTypeByMetadataName(
compilation.GetTypeByMetadataName($"{PathContests.SourceGeneratorsAbstractionsPath}.Rule.GetSystemAttribute"), $"{PathContests.SourceGeneratorsAbstractionsPath}.Rule.GetModelAttribute"),
compilation.GetTypeByMetadataName($"{PathContests.SourceGeneratorsAbstractionsPath}.Rule.GetSystemsAttribute"), compilation.GetTypeByMetadataName(
compilation.GetTypeByMetadataName($"{PathContests.SourceGeneratorsAbstractionsPath}.Rule.GetUtilityAttribute"), $"{PathContests.SourceGeneratorsAbstractionsPath}.Rule.GetModelsAttribute"),
compilation.GetTypeByMetadataName($"{PathContests.SourceGeneratorsAbstractionsPath}.Rule.GetUtilitiesAttribute")); compilation.GetTypeByMetadataName(
$"{PathContests.SourceGeneratorsAbstractionsPath}.Rule.GetSystemAttribute"),
compilation.GetTypeByMetadataName(
$"{PathContests.SourceGeneratorsAbstractionsPath}.Rule.GetSystemsAttribute"),
compilation.GetTypeByMetadataName(
$"{PathContests.SourceGeneratorsAbstractionsPath}.Rule.GetUtilityAttribute"),
compilation.GetTypeByMetadataName(
$"{PathContests.SourceGeneratorsAbstractionsPath}.Rule.GetUtilitiesAttribute"));
} }
} }
private sealed class RegistrationIndex private sealed class RegistrationIndex
{ {
private readonly Compilation _compilation;
private readonly IReadOnlyDictionary<INamedTypeSymbol, ArchitectureRegistrationData> _registrations;
private RegistrationIndex( private RegistrationIndex(
Compilation compilation, Compilation compilation,
IReadOnlyDictionary<INamedTypeSymbol, ArchitectureRegistrationData> registrations) IReadOnlyDictionary<INamedTypeSymbol, ArchitectureRegistrationData> registrations)
@ -392,14 +406,12 @@ public sealed class ContextRegistrationAnalyzer : DiagnosticAnalyzer
_registrations = registrations; _registrations = registrations;
} }
private readonly Compilation _compilation;
private readonly IReadOnlyDictionary<INamedTypeSymbol, ArchitectureRegistrationData> _registrations;
public static RegistrationIndex Build( public static RegistrationIndex Build(
Compilation compilation, Compilation compilation,
SymbolCache symbols) SymbolCache symbols)
{ {
var registrations = new Dictionary<INamedTypeSymbol, ArchitectureRegistrationData>(SymbolEqualityComparer.Default); var registrations =
new Dictionary<INamedTypeSymbol, ArchitectureRegistrationData>(SymbolEqualityComparer.Default);
foreach (var type in SymbolHelpers.EnumerateNamedTypes(compilation.Assembly.GlobalNamespace)) foreach (var type in SymbolHelpers.EnumerateNamedTypes(compilation.Assembly.GlobalNamespace))
{ {
@ -690,7 +702,8 @@ public sealed class ContextRegistrationAnalyzer : DiagnosticAnalyzer
return helperMethod.DeclaringSyntaxReferences.Length > 0; return helperMethod.DeclaringSyntaxReferences.Length > 0;
} }
helperMethod = SymbolHelpers.ResolveHierarchyMethodImplementation(targetMethod, concreteType) ?? targetMethod; helperMethod = SymbolHelpers.ResolveHierarchyMethodImplementation(targetMethod, concreteType) ??
targetMethod;
return helperMethod.DeclaringSyntaxReferences.Length > 0; return helperMethod.DeclaringSyntaxReferences.Length > 0;
} }
} }
@ -813,9 +826,12 @@ public sealed class ContextRegistrationAnalyzer : DiagnosticAnalyzer
if (SymbolEqualityComparer.Default.Equals(candidate.OriginalDefinition, method.OriginalDefinition)) if (SymbolEqualityComparer.Default.Equals(candidate.OriginalDefinition, method.OriginalDefinition))
return candidate; return candidate;
for (var overridden = candidate.OverriddenMethod; overridden != null; overridden = overridden.OverriddenMethod) for (var overridden = candidate.OverriddenMethod;
overridden != null;
overridden = overridden.OverriddenMethod)
{ {
if (SymbolEqualityComparer.Default.Equals(overridden.OriginalDefinition, method.OriginalDefinition)) if (SymbolEqualityComparer.Default.Equals(overridden.OriginalDefinition,
method.OriginalDefinition))
return candidate; return candidate;
} }
} }

View File

@ -1,10 +1,8 @@
using System.Collections.Immutable; using GFramework.Core.SourceGenerators.Diagnostics;
using GFramework.SourceGenerators.Diagnostics;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Operations; using Microsoft.CodeAnalysis.Operations;
namespace GFramework.SourceGenerators.Analyzers; namespace GFramework.Core.SourceGenerators.Analyzers;
/// <summary> /// <summary>
/// 优先级使用分析器,检测应该使用 GetAllByPriority 而非 GetAll 的场景 /// 优先级使用分析器,检测应该使用 GetAllByPriority 而非 GetAll 的场景

View File

@ -1,10 +1,10 @@
using GFramework.SourceGenerators.Abstractions.Architectures; using GFramework.Core.SourceGenerators.Abstractions.Architectures;
using GFramework.Core.SourceGenerators.Diagnostics;
using GFramework.SourceGenerators.Common.Constants; using GFramework.SourceGenerators.Common.Constants;
using GFramework.SourceGenerators.Common.Diagnostics; using GFramework.SourceGenerators.Common.Diagnostics;
using GFramework.SourceGenerators.Common.Extensions; using GFramework.SourceGenerators.Common.Extensions;
using GFramework.SourceGenerators.Diagnostics;
namespace GFramework.SourceGenerators.Architectures; namespace GFramework.Core.SourceGenerators.Architectures;
/// <summary> /// <summary>
/// 为标记了 <see cref="AutoRegisterModuleAttribute" /> 的模块生成固定顺序的组件注册代码。 /// 为标记了 <see cref="AutoRegisterModuleAttribute" /> 的模块生成固定顺序的组件注册代码。

View File

@ -1,12 +1,8 @@
using System.Text; using GFramework.Core.SourceGenerators.Diagnostics;
using GFramework.SourceGenerators.Common.Constants; using GFramework.SourceGenerators.Common.Constants;
using GFramework.SourceGenerators.Common.Generator; using GFramework.SourceGenerators.Common.Generator;
using GFramework.SourceGenerators.Diagnostics;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
namespace GFramework.SourceGenerators.Bases; namespace GFramework.Core.SourceGenerators.Bases;
/// <summary> /// <summary>
/// Priority 特性生成器,为标记了 [Priority] 的类自动生成 IPrioritized 接口实现 /// Priority 特性生成器,为标记了 [Priority] 的类自动生成 IPrioritized 接口实现

View File

@ -1,6 +1,6 @@
using GFramework.SourceGenerators.Common.Constants; using GFramework.SourceGenerators.Common.Constants;
namespace GFramework.SourceGenerators.Diagnostics; namespace GFramework.Core.SourceGenerators.Diagnostics;
internal static class AutoRegisterModuleDiagnostics internal static class AutoRegisterModuleDiagnostics
{ {

View File

@ -1,6 +1,4 @@
using Microsoft.CodeAnalysis; namespace GFramework.Core.SourceGenerators.Diagnostics;
namespace GFramework.SourceGenerators.Diagnostics;
/// <summary> /// <summary>
/// 提供与上下文感知相关的诊断规则定义 /// 提供与上下文感知相关的诊断规则定义

View File

@ -1,6 +1,6 @@
using GFramework.SourceGenerators.Common.Constants; using GFramework.SourceGenerators.Common.Constants;
namespace GFramework.SourceGenerators.Diagnostics; namespace GFramework.Core.SourceGenerators.Diagnostics;
/// <summary> /// <summary>
/// 提供 Context Get 注入生成器相关诊断。 /// 提供 Context Get 注入生成器相关诊断。

View File

@ -1,6 +1,6 @@
using GFramework.SourceGenerators.Common.Constants; using GFramework.SourceGenerators.Common.Constants;
namespace GFramework.SourceGenerators.Diagnostics; namespace GFramework.Core.SourceGenerators.Diagnostics;
/// <summary> /// <summary>
/// 提供 Context Get 注册可见性分析相关诊断。 /// 提供 Context Get 注册可见性分析相关诊断。

View File

@ -1,6 +1,4 @@
using Microsoft.CodeAnalysis; namespace GFramework.Core.SourceGenerators.Diagnostics;
namespace GFramework.SourceGenerators.Diagnostics;
/// <summary> /// <summary>
/// 提供诊断描述符的静态类用于GFramework日志生成器的编译时检查 /// 提供诊断描述符的静态类用于GFramework日志生成器的编译时检查

View File

@ -1,6 +1,4 @@
using Microsoft.CodeAnalysis; namespace GFramework.Core.SourceGenerators.Diagnostics;
namespace GFramework.SourceGenerators.Diagnostics;
/// <summary> /// <summary>
/// Priority 特性相关的诊断信息 /// Priority 特性相关的诊断信息

View File

@ -0,0 +1,204 @@
using GFramework.Core.SourceGenerators.Abstractions.Enums;
using GFramework.SourceGenerators.Common.Constants;
using GFramework.SourceGenerators.Common.Diagnostics;
using GFramework.SourceGenerators.Common.Generator;
namespace GFramework.Core.SourceGenerators.Enums;
/// <summary>
/// 枚举扩展方法生成器,用于自动生成枚举相关的扩展方法
/// </summary>
[Generator]
public sealed class EnumExtensionsGenerator : AttributeEnumGeneratorBase
{
private static string AttributeMetadataName =>
$"{PathContests.SourceGeneratorsAbstractionsPath}.Enums.GenerateEnumExtensionsAttribute";
/// <summary>
/// 仅用于 Syntax 粗筛选
/// </summary>
protected override string AttributeShortNameWithoutSuffix => "GenerateEnumExtensions";
/// <summary>
/// 按元数据名称解析枚举上的 <c>GenerateEnumExtensionsAttribute</c>。
/// </summary>
/// <param name="compilation">当前编译上下文。</param>
/// <param name="symbol">待检查的枚举符号。</param>
/// <returns>匹配到的属性数据;若未标注目标属性则返回 <see langword="null" />。</returns>
protected override AttributeData? ResolveAttribute(Compilation compilation, INamedTypeSymbol symbol)
{
var attrSymbol = compilation.GetTypeByMetadataName(AttributeMetadataName);
if (attrSymbol is null)
return null;
return symbol.GetAttributes()
.FirstOrDefault(a =>
SymbolEqualityComparer.Default.Equals(a.AttributeClass, attrSymbol));
}
/// <summary>
/// 验证候选符号仍然是可生成扩展方法的枚举类型。
/// </summary>
/// <param name="context">源生成诊断上下文。</param>
/// <param name="compilation">当前编译上下文。</param>
/// <param name="syntax">候选枚举声明语法。</param>
/// <param name="symbol">候选命名类型符号。</param>
/// <param name="attr">已解析出的生成器属性。</param>
/// <returns>当符号满足生成前置条件时返回 <see langword="true" />。</returns>
protected override bool ValidateSymbol(SourceProductionContext context, Compilation compilation,
EnumDeclarationSyntax syntax,
INamedTypeSymbol symbol, AttributeData attr)
{
if (symbol.TypeKind == TypeKind.Enum) return true;
var loc = syntax.Identifier.GetLocation();
context.ReportDiagnostic(Diagnostic.Create(
CommonDiagnostics.ClassMustBePartial,
loc,
symbol.Name
));
return false;
}
/// <summary>
/// 生成枚举扩展方法的源代码
/// </summary>
/// <param name="symbol">枚举类型符号</param>
/// <param name="attr">属性数据</param>
/// <returns>生成的源代码字符串</returns>
protected override string Generate(INamedTypeSymbol symbol, AttributeData attr)
{
var ns = symbol.ContainingNamespace.IsGlobalNamespace
? null
: symbol.ContainingNamespace.ToDisplayString();
var generateIsMethods = GetNamedBooleanArgument(
attr,
nameof(GenerateEnumExtensionsAttribute.GenerateIsMethods),
true);
var generateIsInMethod = GetNamedBooleanArgument(
attr,
nameof(GenerateEnumExtensionsAttribute.GenerateIsInMethod),
true);
var enumName = symbol.Name;
var fullEnumName = symbol.ToDisplayString();
var members = symbol.GetMembers()
.OfType<IFieldSymbol>()
.Where(f => f.ConstantValue != null)
.ToArray();
var sb = new StringBuilder();
sb.AppendLine("// <auto-generated />");
sb.AppendLine("using System;");
sb.AppendLine(!string.IsNullOrEmpty(ns) ? $"namespace {ns}" : "namespace EnumExtensionsGenerated");
sb.AppendLine("{");
sb.AppendLine($" public static partial class {enumName}Extensions");
sb.AppendLine(" {");
// 两个生成开关是彼此独立的契约,需要分别控制输出,并保持空行布局稳定,便于快照精确回归。
var hasGeneratedMembers = false;
if (generateIsMethods)
{
hasGeneratedMembers = AppendIsMethods(
sb,
members,
fullEnumName);
}
if (generateIsInMethod)
{
if (hasGeneratedMembers)
{
sb.AppendLine();
}
AppendIsInMethod(
sb,
fullEnumName);
}
sb.AppendLine(" }");
sb.AppendLine("}"); // namespace
return sb.ToString();
}
/// <summary>
/// 获取生成文件的提示名称
/// </summary>
/// <param name="symbol">命名类型符号</param>
/// <returns>生成文件的提示名称</returns>
protected override string GetHintName(INamedTypeSymbol symbol)
{
return $"{symbol.Name}.EnumExtensions.g.cs";
}
/// <summary>
/// 读取属性上的命名布尔参数,并在参数未显式提供时回退到属性契约默认值。
/// </summary>
/// <param name="attribute">待读取的属性数据。</param>
/// <param name="argumentName">命名参数名称。</param>
/// <param name="defaultValue">属性未提供该参数时使用的默认值。</param>
/// <returns>解析得到的布尔值;若参数缺失或类型不匹配则返回 <paramref name="defaultValue" />。</returns>
private static bool GetNamedBooleanArgument(AttributeData attribute, string argumentName, bool defaultValue)
{
foreach (var namedArgument in attribute.NamedArguments)
{
if (namedArgument.Key == argumentName &&
namedArgument.Value.Value is bool value)
{
return value;
}
}
return defaultValue;
}
/// <summary>
/// 为每个枚举成员追加单值判断扩展方法。
/// </summary>
/// <param name="builder">目标源码构建器。</param>
/// <param name="members">需要生成扩展方法的枚举成员。</param>
/// <param name="fullEnumName">枚举的完整类型名。</param>
/// <returns>当至少生成了一个方法时返回 <see langword="true" />。</returns>
private static bool AppendIsMethods(StringBuilder builder, IEnumerable<IFieldSymbol> members, string fullEnumName)
{
var hasGeneratedMembers = false;
foreach (var memberName in members.Select(m => m.Name))
{
if (hasGeneratedMembers)
{
builder.AppendLine();
}
builder.AppendLine($" /// <summary>是否为 {memberName}</summary>");
builder.AppendLine(
$" public static bool Is{memberName}(this {fullEnumName} value) => value == {fullEnumName}.{memberName};");
hasGeneratedMembers = true;
}
return hasGeneratedMembers;
}
/// <summary>
/// 追加用于多值匹配的 <c>IsIn</c> 扩展方法。
/// </summary>
/// <param name="builder">目标源码构建器。</param>
/// <param name="fullEnumName">枚举的完整类型名。</param>
private static void AppendIsInMethod(StringBuilder builder, string fullEnumName)
{
builder.AppendLine(" /// <summary>判断是否属于指定集合</summary>");
builder.AppendLine(
$" public static bool IsIn(this {fullEnumName} value, params {fullEnumName}[] values)");
builder.AppendLine(" {");
builder.AppendLine(" if (values == null) return false;");
builder.AppendLine(" foreach (var v in values) if (value == v) return true;");
builder.AppendLine(" return false;");
builder.AppendLine(" }");
}
}

View File

@ -30,7 +30,7 @@
<!-- Generator 编译期引用 SourceGenerators.Abstractions / Common / Core.Abstractions但不打包 --> <!-- Generator 编译期引用 SourceGenerators.Abstractions / Common / Core.Abstractions但不打包 -->
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\$(AssemblyName).Abstractions\$(AssemblyName).Abstractions.csproj" PrivateAssets="all"/> <ProjectReference Include="..\$(AssemblyName).Abstractions\$(AssemblyName).Abstractions.csproj" PrivateAssets="all"/>
<ProjectReference Include="..\$(AssemblyName).Common\$(AssemblyName).Common.csproj" PrivateAssets="all"/> <ProjectReference Include="..\GFramework.SourceGenerators.Common\GFramework.SourceGenerators.Common.csproj" PrivateAssets="all"/>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@ -63,6 +63,6 @@
<None Include="$(OutputPath)\$(AssemblyName).Abstractions.xml" Pack="true" PackagePath="lib\netstandard2.0" Visible="true"/> <None Include="$(OutputPath)\$(AssemblyName).Abstractions.xml" Pack="true" PackagePath="lib\netstandard2.0" Visible="true"/>
<None Include="$(OutputPath)\$(AssemblyName).Common.dll" Pack="true" PackagePath="lib\netstandard2.0" Visible="true"/> <None Include="$(OutputPath)\$(AssemblyName).Common.dll" Pack="true" PackagePath="lib\netstandard2.0" Visible="true"/>
<None Include="$(OutputPath)\$(AssemblyName).Common.xml" Pack="true" PackagePath="lib\netstandard2.0" Visible="true"/> <None Include="$(OutputPath)\$(AssemblyName).Common.xml" Pack="true" PackagePath="lib\netstandard2.0" Visible="true"/>
<None Include="GeWuYou.$(AssemblyName).targets" Pack="true" PackagePath="build" Visible="false"/> <None Include="GeWuYou.GFramework.Core.SourceGenerators.targets" Pack="true" PackagePath="build" Visible="false"/>
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Analyzer Include="$(MSBuildThisFileDirectory)../analyzers/dotnet/cs/GFramework.Core.SourceGenerators.dll"
Condition="Exists('$(MSBuildThisFileDirectory)../analyzers/dotnet/cs/GFramework.Core.SourceGenerators.dll')"/>
<Analyzer Include="$(MSBuildThisFileDirectory)../analyzers/dotnet/cs/GFramework.Core.SourceGenerators.Abstractions.dll"
Condition="Exists('$(MSBuildThisFileDirectory)../analyzers/dotnet/cs/GFramework.Core.SourceGenerators.Abstractions.dll')"/>
<Analyzer Include="$(MSBuildThisFileDirectory)../analyzers/dotnet/cs/GFramework.SourceGenerators.Common.dll"
Condition="Exists('$(MSBuildThisFileDirectory)../analyzers/dotnet/cs/GFramework.SourceGenerators.Common.dll')"/>
</ItemGroup>
<Target Name="EnsureGFrameworkCoreAnalyzers" BeforeTargets="CoreCompile">
<Message Text="Loading GFramework.Core source generators" Importance="high"/>
</Target>
</Project>

View File

@ -1,12 +1,9 @@
using System.Text; using GFramework.Core.SourceGenerators.Abstractions.Logging;
using GFramework.SourceGenerators.Abstractions.Logging;
using GFramework.SourceGenerators.Common.Constants; using GFramework.SourceGenerators.Common.Constants;
using GFramework.SourceGenerators.Common.Extensions; using GFramework.SourceGenerators.Common.Extensions;
using GFramework.SourceGenerators.Common.Generator; using GFramework.SourceGenerators.Common.Generator;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
namespace GFramework.SourceGenerators.Logging; namespace GFramework.Core.SourceGenerators.Logging;
/// <summary> /// <summary>
/// 日志生成器用于为标记了LogAttribute的类自动生成日志字段 /// 日志生成器用于为标记了LogAttribute的类自动生成日志字段

View File

@ -1,13 +1,9 @@
using System.Text; using GFramework.Core.SourceGenerators.Diagnostics;
using GFramework.SourceGenerators.Common.Constants; using GFramework.SourceGenerators.Common.Constants;
using GFramework.SourceGenerators.Common.Diagnostics; using GFramework.SourceGenerators.Common.Diagnostics;
using GFramework.SourceGenerators.Common.Generator; using GFramework.SourceGenerators.Common.Generator;
using GFramework.SourceGenerators.Diagnostics;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
namespace GFramework.SourceGenerators.Rule; namespace GFramework.Core.SourceGenerators.Rule;
/// <summary> /// <summary>
/// 上下文感知生成器用于为标记了ContextAware特性的类自动生成IContextAware接口实现 /// 上下文感知生成器用于为标记了ContextAware特性的类自动生成IContextAware接口实现

View File

@ -1,11 +1,10 @@
using System.Text; using GFramework.Core.SourceGenerators.Diagnostics;
using GFramework.SourceGenerators.Common.Constants; using GFramework.SourceGenerators.Common.Constants;
using GFramework.SourceGenerators.Common.Diagnostics; using GFramework.SourceGenerators.Common.Diagnostics;
using GFramework.SourceGenerators.Common.Extensions; using GFramework.SourceGenerators.Common.Extensions;
using GFramework.SourceGenerators.Common.Info; using GFramework.SourceGenerators.Common.Info;
using GFramework.SourceGenerators.Diagnostics;
namespace GFramework.SourceGenerators.Rule; namespace GFramework.Core.SourceGenerators.Rule;
/// <summary> /// <summary>
/// 为上下文感知类生成 Core 上下文 Get 注入方法。 /// 为上下文感知类生成 Core 上下文 Get 注入方法。

View File

@ -78,28 +78,6 @@ public class ArchitectureModulesBehaviorTests
await architecture.DestroyAsync(); await architecture.DestroyAsync();
} }
/// <summary>
/// 验证兼容别名 <c>RegisterMediatorBehavior</c> 仍会把 CQRS 行为接入请求管道。
/// </summary>
[Test]
public async Task RegisterMediatorBehavior_Should_Apply_Pipeline_Behavior_To_Request()
{
var architecture = new ModuleTestArchitecture(target =>
target.RegisterMediatorBehavior<TrackingPipelineBehavior<ModuleBehaviorRequest, string>>());
await architecture.InitializeAsync();
var response = await architecture.Context.SendRequestAsync(new ModuleBehaviorRequest());
Assert.Multiple(() =>
{
Assert.That(response, Is.EqualTo("handled"));
Assert.That(TrackingPipelineBehavior<ModuleBehaviorRequest, string>.InvocationCount, Is.EqualTo(1));
});
await architecture.DestroyAsync();
}
/// <summary> /// <summary>
/// 用于测试模块行为的最小架构实现。 /// 用于测试模块行为的最小架构实现。
/// </summary> /// </summary>

View File

@ -206,12 +206,6 @@ public class TestArchitectureWithRegistry : IArchitecture
throw new NotImplementedException(); throw new NotImplementedException();
} }
[Obsolete("Use RegisterCqrsPipelineBehavior<TBehavior>() instead.")]
public void RegisterMediatorBehavior<TBehavior>() where TBehavior : class
{
RegisterCqrsPipelineBehavior<TBehavior>();
}
public IArchitectureModule InstallModule(IArchitectureModule module) public IArchitectureModule InstallModule(IArchitectureModule module)
{ {
throw new NotImplementedException(); throw new NotImplementedException();
@ -357,12 +351,6 @@ public class TestArchitectureWithoutRegistry : IArchitecture
throw new NotImplementedException(); throw new NotImplementedException();
} }
[Obsolete("Use RegisterCqrsPipelineBehavior<TBehavior>() instead.")]
public void RegisterMediatorBehavior<TBehavior>() where TBehavior : class
{
RegisterCqrsPipelineBehavior<TBehavior>();
}
public IArchitectureModule InstallModule(IArchitectureModule module) public IArchitectureModule InstallModule(IArchitectureModule module)
{ {
throw new NotImplementedException(); throw new NotImplementedException();

View File

@ -1,99 +0,0 @@
using System.ComponentModel;
using System.Reflection;
using GFramework.Core.Abstractions.Architectures;
using GFramework.Core.Abstractions.Ioc;
using GFramework.Core.Architectures;
using GFramework.Core.Coroutine.Extensions;
using GFramework.Core.Ioc;
namespace GFramework.Core.Tests.Cqrs;
/// <summary>
/// 锁定历史 Mediator 兼容入口的正式弃用策略。
/// 这些测试确保旧 API 不仅保留行为兼容,还会通过编译期提示和 IntelliSense 隐藏引导调用方迁移到新的 CQRS 命名。
/// </summary>
[TestFixture]
public class MediatorCompatibilityDeprecationTests
{
/// <summary>
/// 验证公开兼容方法仍可用,但已被显式标记为未来移除的旧别名。
/// </summary>
[Test]
public void Legacy_Public_Methods_Should_Be_Obsolete_And_Hidden_From_Editor_Browsing()
{
AssertLegacyMethod(typeof(IArchitecture), nameof(IArchitecture.RegisterMediatorBehavior));
AssertLegacyMethod(typeof(IIocContainer), nameof(IIocContainer.RegisterMediatorBehavior));
AssertLegacyMethod(typeof(Architecture), nameof(Architecture.RegisterMediatorBehavior));
AssertLegacyMethod(typeof(MicrosoftDiContainer), nameof(MicrosoftDiContainer.RegisterMediatorBehavior));
}
/// <summary>
/// 验证历史扩展类型会把迁移目标写入弃用说明,并从 IntelliSense 主路径隐藏。
/// </summary>
[Test]
public void Legacy_Extension_Types_Should_Be_Obsolete_And_Hidden_From_Editor_Browsing()
{
AssertLegacyType(
typeof(ContextAwareMediatorExtensions),
"Use GFramework.Core.Extensions.ContextAwareCqrsExtensions instead.");
AssertLegacyType(
typeof(ContextAwareMediatorCommandExtensions),
"Use GFramework.Cqrs.Extensions.ContextAwareCqrsCommandExtensions instead.");
AssertLegacyType(
typeof(ContextAwareMediatorQueryExtensions),
"Use GFramework.Cqrs.Extensions.ContextAwareCqrsQueryExtensions instead.");
AssertLegacyType(
typeof(MediatorCoroutineExtensions),
"Use GFramework.Core.Coroutine.Extensions.CqrsCoroutineExtensions instead.");
}
/// <summary>
/// 断言方法级兼容 API 具备统一的弃用元数据。
/// </summary>
/// <param name="declaringType">声明该方法的类型。</param>
/// <param name="methodName">方法名称。</param>
private static void AssertLegacyMethod(Type declaringType, string methodName)
{
var method = declaringType
.GetMethods(BindingFlags.Public | BindingFlags.Instance)
.Single(candidate => candidate.Name == methodName);
var obsoleteAttribute = method.GetCustomAttribute<ObsoleteAttribute>();
var editorBrowsableAttribute = method.GetCustomAttribute<EditorBrowsableAttribute>();
Assert.Multiple(() =>
{
Assert.That(obsoleteAttribute, Is.Not.Null);
Assert.That(
obsoleteAttribute!.Message,
Does.Contain("Use RegisterCqrsPipelineBehavior<TBehavior>() instead."));
Assert.That(
obsoleteAttribute.Message,
Does.Contain("removed in a future major version"));
Assert.That(editorBrowsableAttribute, Is.Not.Null);
Assert.That(editorBrowsableAttribute!.State, Is.EqualTo(EditorBrowsableState.Never));
});
}
/// <summary>
/// 断言类型级兼容扩展具备统一的弃用元数据。
/// </summary>
/// <param name="type">兼容扩展类型。</param>
/// <param name="expectedReplacementHint">期望的迁移提示。</param>
private static void AssertLegacyType(Type type, string expectedReplacementHint)
{
var obsoleteAttribute = type.GetCustomAttribute<ObsoleteAttribute>();
var editorBrowsableAttribute = type.GetCustomAttribute<EditorBrowsableAttribute>();
Assert.Multiple(() =>
{
Assert.That(obsoleteAttribute, Is.Not.Null);
Assert.That(obsoleteAttribute!.Message, Does.Contain(expectedReplacementHint));
Assert.That(
obsoleteAttribute.Message,
Does.Contain("removed in a future major version"));
Assert.That(editorBrowsableAttribute, Is.Not.Null);
Assert.That(editorBrowsableAttribute!.State, Is.EqualTo(EditorBrowsableState.Never));
});
}
}

View File

@ -21,9 +21,11 @@
<ProjectReference Include="..\GFramework.Tests.Common\GFramework.Tests.Common.csproj"/> <ProjectReference Include="..\GFramework.Tests.Common\GFramework.Tests.Common.csproj"/>
<ProjectReference Include="..\GFramework.Core.Abstractions\GFramework.Core.Abstractions.csproj"/> <ProjectReference Include="..\GFramework.Core.Abstractions\GFramework.Core.Abstractions.csproj"/>
<ProjectReference Include="..\GFramework.Core\GFramework.Core.csproj"/> <ProjectReference Include="..\GFramework.Core\GFramework.Core.csproj"/>
<ProjectReference Include="..\GFramework.SourceGenerators.Abstractions\GFramework.SourceGenerators.Abstractions.csproj"/> <ProjectReference Include="..\GFramework.Core.SourceGenerators.Abstractions\GFramework.Core.SourceGenerators.Abstractions.csproj"/>
<ProjectReference Include="..\GFramework.SourceGenerators.Common\GFramework.SourceGenerators.Common.csproj"/> <ProjectReference Include="..\GFramework.SourceGenerators.Common\GFramework.SourceGenerators.Common.csproj"
<ProjectReference Include="..\GFramework.SourceGenerators\GFramework.SourceGenerators.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false"/> OutputItemType="Analyzer"
ReferenceOutputAssembly="false"/>
<ProjectReference Include="..\GFramework.Core.SourceGenerators\GFramework.Core.SourceGenerators.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false"/>
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -1,4 +1,3 @@
using System.ComponentModel;
using System.Reflection; using System.Reflection;
using GFramework.Core.Abstractions.Architectures; using GFramework.Core.Abstractions.Architectures;
using GFramework.Core.Abstractions.Enums; using GFramework.Core.Abstractions.Enums;
@ -8,7 +7,6 @@ using GFramework.Core.Abstractions.Model;
using GFramework.Core.Abstractions.Systems; using GFramework.Core.Abstractions.Systems;
using GFramework.Core.Abstractions.Utility; using GFramework.Core.Abstractions.Utility;
using GFramework.Core.Environment; using GFramework.Core.Environment;
using GFramework.Core.Logging;
namespace GFramework.Core.Architectures; namespace GFramework.Core.Architectures;
@ -156,20 +154,6 @@ public abstract class Architecture : IArchitecture
_modules.RegisterCqrsPipelineBehavior<TBehavior>(); _modules.RegisterCqrsPipelineBehavior<TBehavior>();
} }
/// <summary>
/// 注册 CQRS 请求管道行为。
/// 该成员保留旧名称以兼容历史调用点,内部行为与 <see cref="RegisterCqrsPipelineBehavior{TBehavior}" /> 一致。
/// 新代码不应继续依赖该别名;兼容层计划在未来的 major 版本中移除。
/// </summary>
/// <typeparam name="TBehavior">行为类型,必须是引用类型</typeparam>
[EditorBrowsable(EditorBrowsableState.Never)]
[Obsolete(
"Use RegisterCqrsPipelineBehavior<TBehavior>() instead. This compatibility alias will be removed in a future major version.")]
public void RegisterMediatorBehavior<TBehavior>() where TBehavior : class
{
RegisterCqrsPipelineBehavior<TBehavior>();
}
/// <summary> /// <summary>
/// 从指定程序集显式注册 CQRS 处理器。 /// 从指定程序集显式注册 CQRS 处理器。
/// 该入口适用于把拆分到其他模块或扩展包程序集中的 handlers 接入当前架构。 /// 该入口适用于把拆分到其他模块或扩展包程序集中的 handlers 接入当前架构。

View File

@ -1,4 +1,3 @@
using System.ComponentModel;
using System.Reflection; using System.Reflection;
using GFramework.Core.Abstractions.Architectures; using GFramework.Core.Abstractions.Architectures;
using GFramework.Core.Abstractions.Logging; using GFramework.Core.Abstractions.Logging;
@ -25,20 +24,6 @@ internal sealed class ArchitectureModules(
services.Container.RegisterCqrsPipelineBehavior<TBehavior>(); services.Container.RegisterCqrsPipelineBehavior<TBehavior>();
} }
/// <summary>
/// 注册 CQRS 请求管道行为。
/// 该成员保留旧名称以兼容历史调用点,内部行为与 <see cref="RegisterCqrsPipelineBehavior{TBehavior}" /> 一致。
/// 新代码不应继续依赖该别名;兼容层计划在未来的 major 版本中移除。
/// </summary>
/// <typeparam name="TBehavior">行为类型,必须是引用类型</typeparam>
[EditorBrowsable(EditorBrowsableState.Never)]
[Obsolete(
"Use RegisterCqrsPipelineBehavior<TBehavior>() instead. This compatibility alias will be removed in a future major version.")]
public void RegisterMediatorBehavior<TBehavior>() where TBehavior : class
{
RegisterCqrsPipelineBehavior<TBehavior>();
}
/// <summary> /// <summary>
/// 从指定程序集显式注册 CQRS 处理器。 /// 从指定程序集显式注册 CQRS 处理器。
/// 该入口用于把默认架构程序集之外的扩展处理器接入当前架构容器。 /// 该入口用于把默认架构程序集之外的扩展处理器接入当前架构容器。

View File

@ -1,47 +0,0 @@
// Copyright (c) 2026 GeWuYou
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
using System.ComponentModel;
using GFramework.Core.Abstractions.Coroutine;
using GFramework.Core.Abstractions.Rule;
using GFramework.Cqrs.Abstractions.Cqrs;
namespace GFramework.Core.Coroutine.Extensions;
/// <summary>
/// 提供 CQRS 命令与协程集成的扩展方法。
/// 该类型保留旧名称以兼容历史调用点;新代码应改用 <see cref="CqrsCoroutineExtensions" />。
/// 兼容层计划在未来的 major 版本中移除,因此不会继续承载新能力。
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
[Obsolete(
"Use GFramework.Core.Coroutine.Extensions.CqrsCoroutineExtensions instead. This compatibility alias will be removed in a future major version.")]
public static class MediatorCoroutineExtensions
{
/// <summary>
/// 以协程方式发送无返回值 CQRS 命令并处理可能的异常。
/// </summary>
/// <typeparam name="TCommand">命令的类型。</typeparam>
/// <param name="contextAware">上下文感知对象,用于获取服务</param>
/// <param name="command">要发送的命令对象</param>
/// <param name="onError">发生异常时的回调处理函数</param>
/// <returns>协程枚举器,用于协程执行</returns>
public static IEnumerator<IYieldInstruction> SendCommandCoroutine<TCommand>(
this IContextAware contextAware,
TCommand command,
Action<Exception>? onError = null)
where TCommand : IRequest<Unit>
{
return CqrsCoroutineExtensions.SendCommandCoroutine(contextAware, command, onError);
}
}

View File

@ -1,49 +0,0 @@
using System.ComponentModel;
using GFramework.Core.Abstractions.Rule;
using GFramework.Cqrs.Abstractions.Cqrs.Command;
using GFramework.Cqrs.Extensions;
namespace GFramework.Core.Extensions;
/// <summary>
/// 提供对 <see cref="IContextAware" /> 接口的 CQRS 命令扩展方法。
/// 该类型保留旧名称以兼容历史调用点;新代码应改用 <see cref="ContextAwareCqrsCommandExtensions" />。
/// 兼容层计划在未来的 major 版本中移除,因此不会继续承载新能力。
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
[Obsolete(
"Use GFramework.Cqrs.Extensions.ContextAwareCqrsCommandExtensions instead. This compatibility alias will be removed in a future major version.")]
public static class ContextAwareMediatorCommandExtensions
{
/// <summary>
/// 发送命令的同步版本(不推荐,仅用于兼容性)
/// </summary>
/// <typeparam name="TResponse">命令响应类型</typeparam>
/// <param name="contextAware">实现 IContextAware 接口的对象</param>
/// <param name="command">要发送的命令对象</param>
/// <returns>命令执行结果</returns>
/// <exception cref="ArgumentNullException">当 contextAware 或 command 为 null 时抛出</exception>
public static TResponse SendCommand<TResponse>(this IContextAware contextAware,
ICommand<TResponse> command)
{
return ContextAwareCqrsCommandExtensions.SendCommand(contextAware, command);
}
/// <summary>
/// 异步发送命令并返回结果
/// </summary>
/// <typeparam name="TResponse">命令响应类型</typeparam>
/// <param name="contextAware">实现 IContextAware 接口的对象</param>
/// <param name="command">要发送的命令对象</param>
/// <param name="cancellationToken">取消令牌,用于取消操作</param>
/// <returns>包含命令执行结果的ValueTask</returns>
/// <exception cref="ArgumentNullException">当 contextAware 或 command 为 null 时抛出</exception>
public static ValueTask<TResponse> SendCommandAsync<TResponse>(this IContextAware contextAware,
ICommand<TResponse> command, CancellationToken cancellationToken = default)
{
return ContextAwareCqrsCommandExtensions.SendCommandAsync(
contextAware,
command,
cancellationToken);
}
}

View File

@ -1,123 +0,0 @@
using System.ComponentModel;
using GFramework.Core.Abstractions.Rule;
using GFramework.Cqrs.Abstractions.Cqrs;
using GFramework.Cqrs.Extensions;
namespace GFramework.Core.Extensions;
/// <summary>
/// 提供对 <see cref="IContextAware" /> 接口的 CQRS 统一接口扩展方法。
/// 该类型保留旧名称以兼容历史调用点;新代码应改用 <see cref="ContextAwareCqrsExtensions" />。
/// 兼容层计划在未来的 major 版本中移除,因此不会继续承载新能力。
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
[Obsolete(
"Use GFramework.Core.Extensions.ContextAwareCqrsExtensions instead. This compatibility alias will be removed in a future major version.")]
public static class ContextAwareMediatorExtensions
{
/// <summary>
/// 发送请求(统一处理 Command/Query
/// </summary>
/// <typeparam name="TResponse">响应类型</typeparam>
/// <param name="contextAware">实现 IContextAware 接口的对象</param>
/// <param name="request">要发送的请求</param>
/// <param name="cancellationToken">取消令牌</param>
/// <returns>请求结果</returns>
/// <exception cref="ArgumentNullException">当 contextAware 或 request 为 null 时抛出</exception>
public static ValueTask<TResponse> SendRequestAsync<TResponse>(this IContextAware contextAware,
IRequest<TResponse> request, CancellationToken cancellationToken = default)
{
return ContextAwareCqrsExtensions.SendRequestAsync(
contextAware,
request,
cancellationToken);
}
/// <summary>
/// 发送请求(同步版本,不推荐)
/// </summary>
/// <typeparam name="TResponse">响应类型</typeparam>
/// <param name="contextAware">实现 IContextAware 接口的对象</param>
/// <param name="request">要发送的请求</param>
/// <returns>请求结果</returns>
/// <exception cref="ArgumentNullException">当 contextAware 或 request 为 null 时抛出</exception>
public static TResponse SendRequest<TResponse>(this IContextAware contextAware,
IRequest<TResponse> request)
{
return ContextAwareCqrsExtensions.SendRequest(contextAware, request);
}
/// <summary>
/// 发布通知(一对多事件)
/// </summary>
/// <typeparam name="TNotification">通知类型</typeparam>
/// <param name="contextAware">实现 IContextAware 接口的对象</param>
/// <param name="notification">要发布的通知</param>
/// <param name="cancellationToken">取消令牌</param>
/// <returns>异步任务</returns>
/// <exception cref="ArgumentNullException">当 contextAware 或 notification 为 null 时抛出</exception>
public static ValueTask PublishAsync<TNotification>(this IContextAware contextAware,
TNotification notification, CancellationToken cancellationToken = default)
where TNotification : INotification
{
return ContextAwareCqrsExtensions.PublishAsync(
contextAware,
notification,
cancellationToken);
}
/// <summary>
/// 创建流式请求(用于大数据集)
/// </summary>
/// <typeparam name="TResponse">响应类型</typeparam>
/// <param name="contextAware">实现 IContextAware 接口的对象</param>
/// <param name="request">流式请求</param>
/// <param name="cancellationToken">取消令牌</param>
/// <returns>异步响应流</returns>
/// <exception cref="ArgumentNullException">当 contextAware 或 request 为 null 时抛出</exception>
public static IAsyncEnumerable<TResponse> CreateStream<TResponse>(this IContextAware contextAware,
IStreamRequest<TResponse> request, CancellationToken cancellationToken = default)
{
return ContextAwareCqrsExtensions.CreateStream(
contextAware,
request,
cancellationToken);
}
/// <summary>
/// 发送命令(无返回值)
/// </summary>
/// <typeparam name="TCommand">命令类型</typeparam>
/// <param name="contextAware">实现 IContextAware 接口的对象</param>
/// <param name="command">要发送的命令</param>
/// <param name="cancellationToken">取消令牌</param>
/// <returns>异步任务</returns>
/// <exception cref="ArgumentNullException">当 contextAware 或 command 为 null 时抛出</exception>
public static ValueTask SendAsync<TCommand>(this IContextAware contextAware, TCommand command,
CancellationToken cancellationToken = default)
where TCommand : IRequest<Unit>
{
return ContextAwareCqrsExtensions.SendAsync(
contextAware,
command,
cancellationToken);
}
/// <summary>
/// 发送命令(有返回值)
/// </summary>
/// <typeparam name="TResponse">响应类型</typeparam>
/// <param name="contextAware">实现 IContextAware 接口的对象</param>
/// <param name="command">要发送的命令</param>
/// <param name="cancellationToken">取消令牌</param>
/// <returns>命令执行结果</returns>
/// <exception cref="ArgumentNullException">当 contextAware 或 command 为 null 时抛出</exception>
public static ValueTask<TResponse> SendAsync<TResponse>(this IContextAware contextAware,
IRequest<TResponse> command, CancellationToken cancellationToken = default)
{
return ContextAwareCqrsExtensions.SendAsync(
contextAware,
command,
cancellationToken);
}
}

View File

@ -1,48 +0,0 @@
using System.ComponentModel;
using GFramework.Core.Abstractions.Rule;
using GFramework.Cqrs.Abstractions.Cqrs.Query;
using GFramework.Cqrs.Extensions;
namespace GFramework.Core.Extensions;
/// <summary>
/// 提供对 <see cref="IContextAware" /> 接口的 CQRS 查询扩展方法。
/// 该类型保留旧名称以兼容历史调用点;新代码应改用 <see cref="ContextAwareCqrsQueryExtensions" />。
/// 兼容层计划在未来的 major 版本中移除,因此不会继续承载新能力。
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
[Obsolete(
"Use GFramework.Cqrs.Extensions.ContextAwareCqrsQueryExtensions instead. This compatibility alias will be removed in a future major version.")]
public static class ContextAwareMediatorQueryExtensions
{
/// <summary>
/// 发送查询的同步版本(不推荐,仅用于兼容性)
/// </summary>
/// <typeparam name="TResponse">查询响应类型</typeparam>
/// <param name="contextAware">实现 IContextAware 接口的对象</param>
/// <param name="query">要发送的查询对象</param>
/// <returns>查询结果</returns>
/// <exception cref="ArgumentNullException">当 contextAware 或 query 为 null 时抛出</exception>
public static TResponse SendQuery<TResponse>(this IContextAware contextAware, IQuery<TResponse> query)
{
return ContextAwareCqrsQueryExtensions.SendQuery(contextAware, query);
}
/// <summary>
/// 异步发送查询并返回结果
/// </summary>
/// <typeparam name="TResponse">查询响应类型</typeparam>
/// <param name="contextAware">实现 IContextAware 接口的对象</param>
/// <param name="query">要发送的查询对象</param>
/// <param name="cancellationToken">取消令牌,用于取消操作</param>
/// <returns>包含查询结果的ValueTask</returns>
/// <exception cref="ArgumentNullException">当 contextAware 或 query 为 null 时抛出</exception>
public static ValueTask<TResponse> SendQueryAsync<TResponse>(this IContextAware contextAware,
IQuery<TResponse> query, CancellationToken cancellationToken = default)
{
return ContextAwareCqrsQueryExtensions.SendQueryAsync(
contextAware,
query,
cancellationToken);
}
}

View File

@ -1,4 +1,3 @@
using System.ComponentModel;
using System.Reflection; using System.Reflection;
using GFramework.Core.Abstractions.Bases; using GFramework.Core.Abstractions.Bases;
using GFramework.Core.Abstractions.Ioc; using GFramework.Core.Abstractions.Ioc;
@ -367,20 +366,6 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null)
} }
} }
/// <summary>
/// 注册 CQRS 请求管道行为。
/// 该成员保留旧名称以兼容历史调用点,内部行为与 <see cref="RegisterCqrsPipelineBehavior{TBehavior}" /> 一致。
/// 新代码不应继续依赖该别名;兼容层计划在未来的 major 版本中移除。
/// </summary>
/// <typeparam name="TBehavior">行为类型,必须是引用类型</typeparam>
[EditorBrowsable(EditorBrowsableState.Never)]
[Obsolete(
"Use RegisterCqrsPipelineBehavior<TBehavior>() instead. This compatibility alias will be removed in a future major version.")]
public void RegisterMediatorBehavior<TBehavior>() where TBehavior : class
{
RegisterCqrsPipelineBehavior<TBehavior>();
}
/// <summary> /// <summary>
/// 从指定程序集显式注册 CQRS 处理器。 /// 从指定程序集显式注册 CQRS 处理器。
/// </summary> /// </summary>

View File

@ -1,6 +1,6 @@
using GFramework.SourceGenerators.Common.Constants; using GFramework.SourceGenerators.Common.Constants;
namespace GFramework.SourceGenerators.Cqrs; namespace GFramework.Cqrs.SourceGenerators.Cqrs;
/// <summary> /// <summary>
/// 为当前编译程序集生成 CQRS 处理器注册器,以减少运行时的程序集反射扫描成本。 /// 为当前编译程序集生成 CQRS 处理器注册器,以减少运行时的程序集反射扫描成本。
@ -337,10 +337,17 @@ public sealed class CqrsHandlerRegistryGenerator : IIncrementalGenerator
return true; return true;
} }
if (type is INamedTypeSymbol namedType && if (type is INamedTypeSymbol namedType)
SymbolEqualityComparer.Default.Equals(namedType.ContainingAssembly, compilation.Assembly))
{ {
runtimeTypeReference = RuntimeTypeReferenceSpec.FromReflectionLookup( if (SymbolEqualityComparer.Default.Equals(namedType.ContainingAssembly, compilation.Assembly))
{
runtimeTypeReference = RuntimeTypeReferenceSpec.FromReflectionLookup(
GetReflectionTypeMetadataName(namedType));
return true;
}
runtimeTypeReference = RuntimeTypeReferenceSpec.FromExternalReflectionLookup(
namedType.ContainingAssembly.Identity.ToString(),
GetReflectionTypeMetadataName(namedType)); GetReflectionTypeMetadataName(namedType));
return true; return true;
} }
@ -388,8 +395,10 @@ public sealed class CqrsHandlerRegistryGenerator : IIncrementalGenerator
return true; return true;
} }
genericTypeDefinitionReference = null; genericTypeDefinitionReference = RuntimeTypeReferenceSpec.FromExternalReflectionLookup(
return false; genericTypeDefinition.ContainingAssembly.Identity.ToString(),
GetReflectionTypeMetadataName(genericTypeDefinition));
return true;
} }
private static bool CanReferenceFromGeneratedRegistry(Compilation compilation, ITypeSymbol type) private static bool CanReferenceFromGeneratedRegistry(Compilation compilation, ITypeSymbol type)
@ -492,6 +501,9 @@ public sealed class CqrsHandlerRegistryGenerator : IIncrementalGenerator
!registration.PreciseReflectedRegistrations.IsDefaultOrEmpty); !registration.PreciseReflectedRegistrations.IsDefaultOrEmpty);
var hasRuntimeInterfaceDiscovery = registrations.Any(static registration => var hasRuntimeInterfaceDiscovery = registrations.Any(static registration =>
registration.RequiresRuntimeInterfaceDiscovery); registration.RequiresRuntimeInterfaceDiscovery);
var hasExternalAssemblyTypeLookups = registrations.Any(static registration =>
registration.PreciseReflectedRegistrations.Any(static preciseRegistration =>
preciseRegistration.ServiceTypeArguments.Any(ContainsExternalAssemblyTypeLookup)));
var builder = new StringBuilder(); var builder = new StringBuilder();
builder.AppendLine("// <auto-generated />"); builder.AppendLine("// <auto-generated />");
builder.AppendLine("#nullable enable"); builder.AppendLine("#nullable enable");
@ -556,10 +568,13 @@ public sealed class CqrsHandlerRegistryGenerator : IIncrementalGenerator
builder.AppendLine(" }"); builder.AppendLine(" }");
if (hasRuntimeInterfaceDiscovery) if (hasRuntimeInterfaceDiscovery || hasExternalAssemblyTypeLookups)
{ {
builder.AppendLine(); builder.AppendLine();
AppendReflectionHelpers(builder); AppendReflectionHelpers(
builder,
hasRuntimeInterfaceDiscovery,
hasExternalAssemblyTypeLookups);
} }
builder.AppendLine("}"); builder.AppendLine("}");
@ -910,14 +925,79 @@ public sealed class CqrsHandlerRegistryGenerator : IIncrementalGenerator
builder.Append(indent); builder.Append(indent);
builder.Append("var "); builder.Append("var ");
builder.Append(variableBaseName); builder.Append(variableBaseName);
builder.Append(" = registryAssembly.GetType(\""); if (string.IsNullOrWhiteSpace(runtimeTypeReference.ReflectionAssemblyName))
builder.Append(EscapeStringLiteral(reflectionTypeMetadataName)); {
builder.AppendLine("\", throwOnError: false, ignoreCase: false);"); builder.Append(" = registryAssembly.GetType(\"");
builder.Append(EscapeStringLiteral(reflectionTypeMetadataName));
builder.AppendLine("\", throwOnError: false, ignoreCase: false);");
}
else
{
builder.Append(" = ResolveReferencedAssemblyType(\"");
builder.Append(EscapeStringLiteral(runtimeTypeReference.ReflectionAssemblyName!));
builder.Append("\", \"");
builder.Append(EscapeStringLiteral(reflectionTypeMetadataName));
builder.AppendLine("\");");
}
return variableBaseName; return variableBaseName;
} }
private static void AppendReflectionHelpers(StringBuilder builder) private static void AppendReflectionHelpers(
StringBuilder builder,
bool includeRuntimeInterfaceDiscoveryHelpers,
bool includeExternalAssemblyTypeLookupHelpers)
{ {
if (includeExternalAssemblyTypeLookupHelpers)
{
builder.AppendLine(
" private static global::System.Type? ResolveReferencedAssemblyType(string assemblyIdentity, string typeMetadataName)");
builder.AppendLine(" {");
builder.AppendLine(" var assembly = ResolveReferencedAssembly(assemblyIdentity);");
builder.AppendLine(
" return assembly?.GetType(typeMetadataName, throwOnError: false, ignoreCase: false);");
builder.AppendLine(" }");
builder.AppendLine();
builder.AppendLine(
" private static global::System.Reflection.Assembly? ResolveReferencedAssembly(string assemblyIdentity)");
builder.AppendLine(" {");
builder.AppendLine(" global::System.Reflection.AssemblyName targetAssemblyName;");
builder.AppendLine(" try");
builder.AppendLine(" {");
builder.AppendLine(
" targetAssemblyName = new global::System.Reflection.AssemblyName(assemblyIdentity);");
builder.AppendLine(" }");
builder.AppendLine(" catch");
builder.AppendLine(" {");
builder.AppendLine(" return null;");
builder.AppendLine(" }");
builder.AppendLine();
builder.AppendLine(
" foreach (var assembly in global::System.AppDomain.CurrentDomain.GetAssemblies())");
builder.AppendLine(" {");
builder.AppendLine(
" if (global::System.Reflection.AssemblyName.ReferenceMatchesDefinition(targetAssemblyName, assembly.GetName()))");
builder.AppendLine(" return assembly;");
builder.AppendLine(" }");
builder.AppendLine();
builder.AppendLine(" try");
builder.AppendLine(" {");
builder.AppendLine(
" return global::System.Reflection.Assembly.Load(targetAssemblyName);");
builder.AppendLine(" }");
builder.AppendLine(" catch");
builder.AppendLine(" {");
builder.AppendLine(" return null;");
builder.AppendLine(" }");
builder.AppendLine(" }");
}
if (!includeRuntimeInterfaceDiscoveryHelpers)
return;
if (includeExternalAssemblyTypeLookupHelpers)
builder.AppendLine();
// Emit the runtime helper methods only when at least one handler still needs implementation-scoped // Emit the runtime helper methods only when at least one handler still needs implementation-scoped
// interface discovery after all direct / precise registrations have been emitted. // interface discovery after all direct / precise registrations have been emitted.
builder.AppendLine( builder.AppendLine(
@ -1038,6 +1118,32 @@ public sealed class CqrsHandlerRegistryGenerator : IIncrementalGenerator
.Replace("\r", "\\r"); .Replace("\r", "\\r");
} }
private static bool ContainsExternalAssemblyTypeLookup(RuntimeTypeReferenceSpec runtimeTypeReference)
{
if (!string.IsNullOrWhiteSpace(runtimeTypeReference.ReflectionAssemblyName))
return true;
if (runtimeTypeReference.ArrayElementTypeReference is not null &&
ContainsExternalAssemblyTypeLookup(runtimeTypeReference.ArrayElementTypeReference))
{
return true;
}
if (runtimeTypeReference.GenericTypeDefinitionReference is not null &&
ContainsExternalAssemblyTypeLookup(runtimeTypeReference.GenericTypeDefinitionReference))
{
return true;
}
foreach (var genericTypeArgument in runtimeTypeReference.GenericTypeArguments)
{
if (ContainsExternalAssemblyTypeLookup(genericTypeArgument))
return true;
}
return false;
}
private readonly record struct HandlerRegistrationSpec( private readonly record struct HandlerRegistrationSpec(
string HandlerInterfaceDisplayName, string HandlerInterfaceDisplayName,
string ImplementationTypeDisplayName, string ImplementationTypeDisplayName,
@ -1058,6 +1164,7 @@ public sealed class CqrsHandlerRegistryGenerator : IIncrementalGenerator
private sealed record RuntimeTypeReferenceSpec( private sealed record RuntimeTypeReferenceSpec(
string? TypeDisplayName, string? TypeDisplayName,
string? ReflectionTypeMetadataName, string? ReflectionTypeMetadataName,
string? ReflectionAssemblyName,
RuntimeTypeReferenceSpec? ArrayElementTypeReference, RuntimeTypeReferenceSpec? ArrayElementTypeReference,
int ArrayRank, int ArrayRank,
RuntimeTypeReferenceSpec? GenericTypeDefinitionReference, RuntimeTypeReferenceSpec? GenericTypeDefinitionReference,
@ -1065,19 +1172,28 @@ public sealed class CqrsHandlerRegistryGenerator : IIncrementalGenerator
{ {
public static RuntimeTypeReferenceSpec FromDirectReference(string typeDisplayName) public static RuntimeTypeReferenceSpec FromDirectReference(string typeDisplayName)
{ {
return new RuntimeTypeReferenceSpec(typeDisplayName, null, null, 0, null, return new RuntimeTypeReferenceSpec(typeDisplayName, null, null, null, 0, null,
ImmutableArray<RuntimeTypeReferenceSpec>.Empty); ImmutableArray<RuntimeTypeReferenceSpec>.Empty);
} }
public static RuntimeTypeReferenceSpec FromReflectionLookup(string reflectionTypeMetadataName) public static RuntimeTypeReferenceSpec FromReflectionLookup(string reflectionTypeMetadataName)
{ {
return new RuntimeTypeReferenceSpec(null, reflectionTypeMetadataName, null, 0, null, return new RuntimeTypeReferenceSpec(null, reflectionTypeMetadataName, null, null, 0, null,
ImmutableArray<RuntimeTypeReferenceSpec>.Empty);
}
public static RuntimeTypeReferenceSpec FromExternalReflectionLookup(
string reflectionAssemblyName,
string reflectionTypeMetadataName)
{
return new RuntimeTypeReferenceSpec(null, reflectionTypeMetadataName, reflectionAssemblyName, null, 0,
null,
ImmutableArray<RuntimeTypeReferenceSpec>.Empty); ImmutableArray<RuntimeTypeReferenceSpec>.Empty);
} }
public static RuntimeTypeReferenceSpec FromArray(RuntimeTypeReferenceSpec elementTypeReference, int arrayRank) public static RuntimeTypeReferenceSpec FromArray(RuntimeTypeReferenceSpec elementTypeReference, int arrayRank)
{ {
return new RuntimeTypeReferenceSpec(null, null, elementTypeReference, arrayRank, null, return new RuntimeTypeReferenceSpec(null, null, null, elementTypeReference, arrayRank, null,
ImmutableArray<RuntimeTypeReferenceSpec>.Empty); ImmutableArray<RuntimeTypeReferenceSpec>.Empty);
} }
@ -1085,7 +1201,7 @@ public sealed class CqrsHandlerRegistryGenerator : IIncrementalGenerator
RuntimeTypeReferenceSpec genericTypeDefinitionReference, RuntimeTypeReferenceSpec genericTypeDefinitionReference,
ImmutableArray<RuntimeTypeReferenceSpec> genericTypeArguments) ImmutableArray<RuntimeTypeReferenceSpec> genericTypeArguments)
{ {
return new RuntimeTypeReferenceSpec(null, null, null, 0, genericTypeDefinitionReference, return new RuntimeTypeReferenceSpec(null, null, null, null, 0, genericTypeDefinitionReference,
genericTypeArguments); genericTypeArguments);
} }
} }

View File

@ -0,0 +1,49 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<PackageId>GeWuYou.$(AssemblyName)</PackageId>
<TargetFramework>netstandard2.0</TargetFramework>
<IsRoslynAnalyzer>true</IsRoslynAnalyzer>
<IncludeBuildOutput>false</IncludeBuildOutput>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
<CompilerGeneratedFilesOutputPath>Generated</CompilerGeneratedFilesOutputPath>
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.14.0" PrivateAssets="all"/>
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="4.14.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
<PackageReference Include="System.Text.Json" Version="8.0.6" PrivateAssets="all"/>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\GFramework.SourceGenerators.Common\GFramework.SourceGenerators.Common.csproj" PrivateAssets="all"/>
</ItemGroup>
<ItemGroup>
<Compile Include="..\GFramework.SourceGenerators.Common\Internals\IsExternalInit.cs"
Link="Internals\IsExternalInit.cs"/>
</ItemGroup>
<ItemGroup>
<None Include="$(OutputPath)\$(AssemblyName).dll"
Pack="true"
PackagePath="analyzers/dotnet/cs"
Visible="false"/>
<None Include="$(OutputPath)\GFramework.SourceGenerators.Common.dll"
Pack="true"
PackagePath="analyzers/dotnet/cs"
Visible="false"/>
</ItemGroup>
<ItemGroup>
<None Include="$(OutputPath)\GFramework.SourceGenerators.Common.dll" Pack="true" PackagePath="lib\netstandard2.0" Visible="true"/>
<None Include="$(OutputPath)\GFramework.SourceGenerators.Common.xml" Pack="true" PackagePath="lib\netstandard2.0" Visible="true"/>
</ItemGroup>
</Project>

View File

@ -0,0 +1,13 @@
global using System;
global using System.Collections.Generic;
global using System.Collections.Immutable;
global using System.IO;
global using System.Linq;
global using System.Text;
global using System.Text.Json;
global using System.Threading;
global using System.Threading.Tasks;
global using Microsoft.CodeAnalysis;
global using Microsoft.CodeAnalysis.CSharp;
global using Microsoft.CodeAnalysis.CSharp.Syntax;
global using Microsoft.CodeAnalysis.Text;

View File

@ -0,0 +1,2 @@
; Shipped analyzer release
; Intentionally empty until the split game source-generator package ships a stable analyzer release.

View File

@ -0,0 +1,17 @@
; Unshipped analyzer release
; https://github.com/dotnet/roslyn/blob/main/src/RoslynAnalyzers/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md
### New Rules
Rule ID | Category | Severity | Notes
---------------------|------------------------------------|----------|-------------------------
GF_ConfigSchema_001 | GFramework.SourceGenerators.Config | Error | ConfigSchemaDiagnostics
GF_ConfigSchema_002 | GFramework.SourceGenerators.Config | Error | ConfigSchemaDiagnostics
GF_ConfigSchema_003 | GFramework.SourceGenerators.Config | Error | ConfigSchemaDiagnostics
GF_ConfigSchema_004 | GFramework.SourceGenerators.Config | Error | ConfigSchemaDiagnostics
GF_ConfigSchema_005 | GFramework.SourceGenerators.Config | Error | ConfigSchemaDiagnostics
GF_ConfigSchema_006 | GFramework.SourceGenerators.Config | Error | ConfigSchemaDiagnostics
GF_ConfigSchema_007 | GFramework.SourceGenerators.Config | Error | ConfigSchemaDiagnostics
GF_ConfigSchema_008 | GFramework.SourceGenerators.Config | Error | ConfigSchemaDiagnostics
GF_ConfigSchema_009 | GFramework.SourceGenerators.Config | Error | ConfigSchemaDiagnostics
GF_ConfigSchema_010 | GFramework.SourceGenerators.Config | Error | ConfigSchemaDiagnostics

View File

@ -1,6 +1,6 @@
using GFramework.SourceGenerators.Diagnostics; using GFramework.Game.SourceGenerators.Diagnostics;
namespace GFramework.SourceGenerators.Config; namespace GFramework.Game.SourceGenerators.Config;
/// <summary> /// <summary>
/// 根据 AdditionalFiles 中的 JSON schema 生成配置类型和配置表包装。 /// 根据 AdditionalFiles 中的 JSON schema 生成配置类型和配置表包装。
@ -31,7 +31,8 @@ public sealed class SchemaConfigGenerator : IIncrementalGenerator
private const string LookupIndexReferencePropertyMessage = private const string LookupIndexReferencePropertyMessage =
"Reference properties are excluded from generated lookup indexes because they already carry cross-table semantics."; "Reference properties are excluded from generated lookup indexes because they already carry cross-table semantics.";
private const string SupportedStringFormatNames = "'date', 'date-time', 'duration', 'email', 'time', 'uri', and 'uuid'"; private const string SupportedStringFormatNames =
"'date', 'date-time', 'duration', 'email', 'time', 'uri', and 'uuid'";
/// <inheritdoc /> /// <inheritdoc />
public void Initialize(IncrementalGeneratorInitializationContext context) public void Initialize(IncrementalGeneratorInitializationContext context)
@ -296,6 +297,7 @@ public sealed class SchemaConfigGenerator : IIncrementalGenerator
/// <param name="property">属性 JSON 节点。</param> /// <param name="property">属性 JSON 节点。</param>
/// <param name="isRequired">属性是否必填。</param> /// <param name="isRequired">属性是否必填。</param>
/// <param name="displayPath">逻辑字段路径。</param> /// <param name="displayPath">逻辑字段路径。</param>
/// <param name="isDirectChildOfRoot">属性是否为根对象下的直接子属性。</param>
/// <returns>解析后的属性信息或诊断。</returns> /// <returns>解析后的属性信息或诊断。</returns>
private static ParsedPropertyResult ParseProperty( private static ParsedPropertyResult ParseProperty(
string filePath, string filePath,
@ -953,6 +955,7 @@ public sealed class SchemaConfigGenerator : IIncrementalGenerator
/// <param name="title">标题元数据。</param> /// <param name="title">标题元数据。</param>
/// <param name="description">说明元数据。</param> /// <param name="description">说明元数据。</param>
/// <param name="refTableName">目标引用表名称。</param> /// <param name="refTableName">目标引用表名称。</param>
/// <param name="isIndexedLookup">是否为索引查找。</param>
/// <returns>解析后的属性信息或诊断。</returns> /// <returns>解析后的属性信息或诊断。</returns>
private static ParsedPropertyResult ParseArrayProperty( private static ParsedPropertyResult ParseArrayProperty(
string filePath, string filePath,
@ -3183,7 +3186,8 @@ public sealed class SchemaConfigGenerator : IIncrementalGenerator
var targets = dependency.Value var targets = dependency.Value
.EnumerateArray() .EnumerateArray()
.Where(static item => item.ValueKind == JsonValueKind.String && !string.IsNullOrWhiteSpace(item.GetString())) .Where(static item =>
item.ValueKind == JsonValueKind.String && !string.IsNullOrWhiteSpace(item.GetString()))
.Select(static item => item.GetString()!) .Select(static item => item.GetString()!)
.Distinct(StringComparer.Ordinal) .Distinct(StringComparer.Ordinal)
.ToArray(); .ToArray();

View File

@ -1,6 +1,6 @@
using GFramework.SourceGenerators.Common.Constants; using GFramework.SourceGenerators.Common.Constants;
namespace GFramework.SourceGenerators.Diagnostics; namespace GFramework.Game.SourceGenerators.Diagnostics;
/// <summary> /// <summary>
/// 提供配置 schema 代码生成相关诊断。 /// 提供配置 schema 代码生成相关诊断。

View File

@ -0,0 +1,56 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<PackageId>GeWuYou.$(AssemblyName)</PackageId>
<TargetFramework>netstandard2.0</TargetFramework>
<IsRoslynAnalyzer>true</IsRoslynAnalyzer>
<IncludeBuildOutput>false</IncludeBuildOutput>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
<CompilerGeneratedFilesOutputPath>Generated</CompilerGeneratedFilesOutputPath>
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.14.0" PrivateAssets="all"/>
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="4.14.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
<PackageReference Include="System.Text.Json" Version="8.0.6" PrivateAssets="all"/>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\GFramework.SourceGenerators.Common\GFramework.SourceGenerators.Common.csproj" PrivateAssets="all"/>
</ItemGroup>
<ItemGroup>
<Compile Include="..\GFramework.SourceGenerators.Common\Internals\IsExternalInit.cs"
Link="Internals\IsExternalInit.cs"/>
</ItemGroup>
<ItemGroup>
<AdditionalFiles Include="AnalyzerReleases.Shipped.md"/>
<AdditionalFiles Include="AnalyzerReleases.Unshipped.md"/>
</ItemGroup>
<ItemGroup>
<None Include="$(OutputPath)\$(AssemblyName).dll"
Pack="true"
PackagePath="analyzers/dotnet/cs"
Visible="false"/>
<None Include="$(OutputPath)\GFramework.SourceGenerators.Common.dll"
Pack="true"
PackagePath="analyzers/dotnet/cs"
Visible="false"/>
</ItemGroup>
<ItemGroup>
<None Include="$(OutputPath)\GFramework.SourceGenerators.Common.dll" Pack="true" PackagePath="lib\netstandard2.0" Visible="true"/>
<None Include="$(OutputPath)\GFramework.SourceGenerators.Common.xml" Pack="true" PackagePath="lib\netstandard2.0" Visible="true"/>
<None Include="GeWuYou.GFramework.Game.SourceGenerators.targets" Pack="true" PackagePath="build" Visible="false"/>
</ItemGroup>
</Project>

View File

@ -1,8 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<!-- This file is automatically generated by the NuGet package -->
<!-- It ensures that the source generators are properly registered during build -->
<PropertyGroup> <PropertyGroup>
<!-- <!--
消费项目默认从 schemas/ 目录收集配置 schema。 消费项目默认从 schemas/ 目录收集配置 schema。
@ -12,15 +9,8 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<!-- <Analyzer Include="$(MSBuildThisFileDirectory)../analyzers/dotnet/cs/GFramework.Game.SourceGenerators.dll"
仅在 NuGet 打包布局存在时自动注入 analyzer。 Condition="Exists('$(MSBuildThisFileDirectory)../analyzers/dotnet/cs/GFramework.Game.SourceGenerators.dll')"/>
仓库内项目引用场景会通过 ProjectReference(OutputItemType=Analyzer) 提供生成器,
因此这里需要避免对不存在的打包路径做无效引用。
-->
<Analyzer Include="$(MSBuildThisFileDirectory)../analyzers/dotnet/cs/GFramework.SourceGenerators.dll"
Condition="Exists('$(MSBuildThisFileDirectory)../analyzers/dotnet/cs/GFramework.SourceGenerators.dll')"/>
<Analyzer Include="$(MSBuildThisFileDirectory)../analyzers/dotnet/cs/GFramework.SourceGenerators.Abstractions.dll"
Condition="Exists('$(MSBuildThisFileDirectory)../analyzers/dotnet/cs/GFramework.SourceGenerators.Abstractions.dll')"/>
<Analyzer Include="$(MSBuildThisFileDirectory)../analyzers/dotnet/cs/GFramework.SourceGenerators.Common.dll" <Analyzer Include="$(MSBuildThisFileDirectory)../analyzers/dotnet/cs/GFramework.SourceGenerators.Common.dll"
Condition="Exists('$(MSBuildThisFileDirectory)../analyzers/dotnet/cs/GFramework.SourceGenerators.Common.dll')"/> Condition="Exists('$(MSBuildThisFileDirectory)../analyzers/dotnet/cs/GFramework.SourceGenerators.Common.dll')"/>
</ItemGroup> </ItemGroup>
@ -33,8 +23,7 @@
<AdditionalFiles Include="$(MSBuildProjectDirectory)/$(GFrameworkConfigSchemaDirectory)/**/*.schema.json"/> <AdditionalFiles Include="$(MSBuildProjectDirectory)/$(GFrameworkConfigSchemaDirectory)/**/*.schema.json"/>
</ItemGroup> </ItemGroup>
<!-- Ensure the analyzers are loaded --> <Target Name="EnsureGFrameworkGameAnalyzers" BeforeTargets="CoreCompile">
<Target Name="EnsureGFrameworkAnalyzers" BeforeTargets="CoreCompile"> <Message Text="Loading GFramework.Game source generators" Importance="high"/>
<Message Text="Loading GFramework source generators" Importance="high"/>
</Target> </Target>
</Project> </Project>

View File

@ -0,0 +1,14 @@
global using System;
global using System.Collections.Generic;
global using System.Collections.Immutable;
global using System.Globalization;
global using System.IO;
global using System.Linq;
global using System.Text;
global using System.Text.Json;
global using System.Threading;
global using System.Threading.Tasks;
global using Microsoft.CodeAnalysis;
global using Microsoft.CodeAnalysis.CSharp;
global using Microsoft.CodeAnalysis.CSharp.Syntax;
global using Microsoft.CodeAnalysis.Text;

View File

@ -20,13 +20,14 @@
<ProjectReference Include="..\GFramework.Game\GFramework.Game.csproj"/> <ProjectReference Include="..\GFramework.Game\GFramework.Game.csproj"/>
<ProjectReference Include="..\GFramework.Core\GFramework.Core.csproj"/> <ProjectReference Include="..\GFramework.Core\GFramework.Core.csproj"/>
<ProjectReference Include="..\GFramework.Godot\GFramework.Godot.csproj"/> <ProjectReference Include="..\GFramework.Godot\GFramework.Godot.csproj"/>
<ProjectReference Include="..\GFramework.SourceGenerators.Abstractions\GFramework.SourceGenerators.Abstractions.csproj" <ProjectReference Include="..\GFramework.Core.SourceGenerators.Abstractions\GFramework.Core.SourceGenerators.Abstractions.csproj"/>
OutputItemType="Analyzer"
ReferenceOutputAssembly="false"/>
<ProjectReference Include="..\GFramework.SourceGenerators.Common\GFramework.SourceGenerators.Common.csproj" <ProjectReference Include="..\GFramework.SourceGenerators.Common\GFramework.SourceGenerators.Common.csproj"
OutputItemType="Analyzer" OutputItemType="Analyzer"
ReferenceOutputAssembly="false"/> ReferenceOutputAssembly="false"/>
<ProjectReference Include="..\GFramework.SourceGenerators\GFramework.SourceGenerators.csproj" <ProjectReference Include="..\GFramework.Core.SourceGenerators\GFramework.Core.SourceGenerators.csproj"
OutputItemType="Analyzer"
ReferenceOutputAssembly="false"/>
<ProjectReference Include="..\GFramework.Game.SourceGenerators\GFramework.Game.SourceGenerators.csproj"
OutputItemType="Analyzer" OutputItemType="Analyzer"
ReferenceOutputAssembly="false"/> ReferenceOutputAssembly="false"/>
</ItemGroup> </ItemGroup>
@ -35,6 +36,6 @@
通过仓库内的 targets 复用消费者默认约定,确保测试项目与真实消费项目一样 通过仓库内的 targets 复用消费者默认约定,确保测试项目与真实消费项目一样
自动拾取 schemas/**/*.schema.json 作为 Source Generator 的 AdditionalFiles。 自动拾取 schemas/**/*.schema.json 作为 Source Generator 的 AdditionalFiles。
--> -->
<Import Project="..\GFramework.SourceGenerators\GeWuYou.GFramework.SourceGenerators.targets"/> <Import Project="..\GFramework.Game.SourceGenerators\GeWuYou.GFramework.Game.SourceGenerators.targets"/>
</Project> </Project>

View File

@ -1,9 +0,0 @@
namespace GFramework.SourceGenerators.Abstractions.Rule;
/// <summary>
/// 标记字段需要自动注入系统集合。
/// </summary>
[AttributeUsage(AttributeTargets.Field, Inherited = false, AllowMultiple = false)]
public sealed class GetSystemsAttribute : Attribute
{
}

View File

@ -39,7 +39,7 @@ public static class PathContests
/// <summary> /// <summary>
/// GFramework源代码生成器抽象层命名空间 /// GFramework源代码生成器抽象层命名空间
/// </summary> /// </summary>
public const string SourceGeneratorsAbstractionsPath = $"{BaseNamespace}.SourceGenerators.Abstractions"; public const string SourceGeneratorsAbstractionsPath = $"{CoreNamespace}.SourceGenerators.Abstractions";
/// <summary> /// <summary>
/// GFramework Godot源代码生成器抽象层命名空间 /// GFramework Godot源代码生成器抽象层命名空间

View File

@ -1,4 +1,4 @@
using GFramework.SourceGenerators.Analyzers; using GFramework.Core.SourceGenerators.Analyzers;
using GFramework.SourceGenerators.Tests.Core; using GFramework.SourceGenerators.Tests.Core;
namespace GFramework.SourceGenerators.Tests.Analyzers; namespace GFramework.SourceGenerators.Tests.Analyzers;
@ -9,128 +9,128 @@ namespace GFramework.SourceGenerators.Tests.Analyzers;
public sealed class ContextRegistrationAnalyzerTests public sealed class ContextRegistrationAnalyzerTests
{ {
private const string TestPreamble = """ private const string TestPreamble = """
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
namespace GFramework.Core.Abstractions.Rule namespace GFramework.Core.Abstractions.Rule
{ {
public interface IContextAware { } public interface IContextAware { }
} }
namespace GFramework.Core.Abstractions.Model namespace GFramework.Core.Abstractions.Model
{ {
public interface IModel : GFramework.Core.Abstractions.Rule.IContextAware { } public interface IModel : GFramework.Core.Abstractions.Rule.IContextAware { }
} }
namespace GFramework.Core.Abstractions.Systems namespace GFramework.Core.Abstractions.Systems
{ {
public interface ISystem : GFramework.Core.Abstractions.Rule.IContextAware { } public interface ISystem : GFramework.Core.Abstractions.Rule.IContextAware { }
} }
namespace GFramework.Core.Abstractions.Utility namespace GFramework.Core.Abstractions.Utility
{ {
public interface IUtility : GFramework.Core.Abstractions.Rule.IContextAware { } public interface IUtility : GFramework.Core.Abstractions.Rule.IContextAware { }
} }
namespace GFramework.Core.Abstractions.Architectures namespace GFramework.Core.Abstractions.Architectures
{ {
public interface IArchitecture public interface IArchitecture
{ {
T RegisterModel<T>(T model) where T : GFramework.Core.Abstractions.Model.IModel; T RegisterModel<T>(T model) where T : GFramework.Core.Abstractions.Model.IModel;
void RegisterModel<T>(Action<T> onCreated = null) where T : class, GFramework.Core.Abstractions.Model.IModel; void RegisterModel<T>(Action<T> onCreated = null) where T : class, GFramework.Core.Abstractions.Model.IModel;
T RegisterSystem<T>(T system) where T : GFramework.Core.Abstractions.Systems.ISystem; T RegisterSystem<T>(T system) where T : GFramework.Core.Abstractions.Systems.ISystem;
void RegisterSystem<T>(Action<T> onCreated = null) where T : class, GFramework.Core.Abstractions.Systems.ISystem; void RegisterSystem<T>(Action<T> onCreated = null) where T : class, GFramework.Core.Abstractions.Systems.ISystem;
T RegisterUtility<T>(T utility) where T : GFramework.Core.Abstractions.Utility.IUtility; T RegisterUtility<T>(T utility) where T : GFramework.Core.Abstractions.Utility.IUtility;
void RegisterUtility<T>(Action<T> onCreated = null) where T : class, GFramework.Core.Abstractions.Utility.IUtility; void RegisterUtility<T>(Action<T> onCreated = null) where T : class, GFramework.Core.Abstractions.Utility.IUtility;
IArchitectureModule InstallModule(IArchitectureModule module); IArchitectureModule InstallModule(IArchitectureModule module);
} }
public interface IArchitectureModule public interface IArchitectureModule
{ {
void Install(IArchitecture architecture); void Install(IArchitecture architecture);
} }
public interface IArchitectureContext public interface IArchitectureContext
{ {
TModel GetModel<TModel>() where TModel : class, GFramework.Core.Abstractions.Model.IModel; TModel GetModel<TModel>() where TModel : class, GFramework.Core.Abstractions.Model.IModel;
IReadOnlyList<TModel> GetModels<TModel>() where TModel : class, GFramework.Core.Abstractions.Model.IModel; IReadOnlyList<TModel> GetModels<TModel>() where TModel : class, GFramework.Core.Abstractions.Model.IModel;
TSystem GetSystem<TSystem>() where TSystem : class, GFramework.Core.Abstractions.Systems.ISystem; TSystem GetSystem<TSystem>() where TSystem : class, GFramework.Core.Abstractions.Systems.ISystem;
IReadOnlyList<TSystem> GetSystems<TSystem>() where TSystem : class, GFramework.Core.Abstractions.Systems.ISystem; IReadOnlyList<TSystem> GetSystems<TSystem>() where TSystem : class, GFramework.Core.Abstractions.Systems.ISystem;
TUtility GetUtility<TUtility>() where TUtility : class, GFramework.Core.Abstractions.Utility.IUtility; TUtility GetUtility<TUtility>() where TUtility : class, GFramework.Core.Abstractions.Utility.IUtility;
IReadOnlyList<TUtility> GetUtilities<TUtility>() where TUtility : class, GFramework.Core.Abstractions.Utility.IUtility; IReadOnlyList<TUtility> GetUtilities<TUtility>() where TUtility : class, GFramework.Core.Abstractions.Utility.IUtility;
} }
} }
namespace GFramework.Core.Architectures namespace GFramework.Core.Architectures
{ {
public abstract class Architecture : GFramework.Core.Abstractions.Architectures.IArchitecture public abstract class Architecture : GFramework.Core.Abstractions.Architectures.IArchitecture
{ {
protected abstract void OnInitialize(); protected abstract void OnInitialize();
public virtual T RegisterModel<T>(T model) where T : GFramework.Core.Abstractions.Model.IModel => model; public virtual T RegisterModel<T>(T model) where T : GFramework.Core.Abstractions.Model.IModel => model;
public virtual void RegisterModel<T>(Action<T> onCreated = null) public virtual void RegisterModel<T>(Action<T> onCreated = null)
where T : class, GFramework.Core.Abstractions.Model.IModel where T : class, GFramework.Core.Abstractions.Model.IModel
{ {
} }
public virtual T RegisterSystem<T>(T system) where T : GFramework.Core.Abstractions.Systems.ISystem => system; public virtual T RegisterSystem<T>(T system) where T : GFramework.Core.Abstractions.Systems.ISystem => system;
public virtual void RegisterSystem<T>(Action<T> onCreated = null) public virtual void RegisterSystem<T>(Action<T> onCreated = null)
where T : class, GFramework.Core.Abstractions.Systems.ISystem where T : class, GFramework.Core.Abstractions.Systems.ISystem
{ {
} }
public virtual T RegisterUtility<T>(T utility) where T : GFramework.Core.Abstractions.Utility.IUtility => utility; public virtual T RegisterUtility<T>(T utility) where T : GFramework.Core.Abstractions.Utility.IUtility => utility;
public virtual void RegisterUtility<T>(Action<T> onCreated = null) public virtual void RegisterUtility<T>(Action<T> onCreated = null)
where T : class, GFramework.Core.Abstractions.Utility.IUtility where T : class, GFramework.Core.Abstractions.Utility.IUtility
{ {
} }
public virtual GFramework.Core.Abstractions.Architectures.IArchitectureModule InstallModule( public virtual GFramework.Core.Abstractions.Architectures.IArchitectureModule InstallModule(
GFramework.Core.Abstractions.Architectures.IArchitectureModule module) GFramework.Core.Abstractions.Architectures.IArchitectureModule module)
{ {
module.Install(this); module.Install(this);
return module; return module;
} }
} }
} }
namespace GFramework.Core.Extensions namespace GFramework.Core.Extensions
{ {
public static class ContextAwareServiceExtensions public static class ContextAwareServiceExtensions
{ {
public static TModel GetModel<TModel>(this GFramework.Core.Abstractions.Rule.IContextAware contextAware) public static TModel GetModel<TModel>(this GFramework.Core.Abstractions.Rule.IContextAware contextAware)
where TModel : class, GFramework.Core.Abstractions.Model.IModel => throw new NotImplementedException(); where TModel : class, GFramework.Core.Abstractions.Model.IModel => throw new NotImplementedException();
public static IReadOnlyList<TModel> GetModels<TModel>(this GFramework.Core.Abstractions.Rule.IContextAware contextAware) public static IReadOnlyList<TModel> GetModels<TModel>(this GFramework.Core.Abstractions.Rule.IContextAware contextAware)
where TModel : class, GFramework.Core.Abstractions.Model.IModel => throw new NotImplementedException(); where TModel : class, GFramework.Core.Abstractions.Model.IModel => throw new NotImplementedException();
public static TSystem GetSystem<TSystem>(this GFramework.Core.Abstractions.Rule.IContextAware contextAware) public static TSystem GetSystem<TSystem>(this GFramework.Core.Abstractions.Rule.IContextAware contextAware)
where TSystem : class, GFramework.Core.Abstractions.Systems.ISystem => throw new NotImplementedException(); where TSystem : class, GFramework.Core.Abstractions.Systems.ISystem => throw new NotImplementedException();
public static IReadOnlyList<TSystem> GetSystems<TSystem>(this GFramework.Core.Abstractions.Rule.IContextAware contextAware) public static IReadOnlyList<TSystem> GetSystems<TSystem>(this GFramework.Core.Abstractions.Rule.IContextAware contextAware)
where TSystem : class, GFramework.Core.Abstractions.Systems.ISystem => throw new NotImplementedException(); where TSystem : class, GFramework.Core.Abstractions.Systems.ISystem => throw new NotImplementedException();
public static TUtility GetUtility<TUtility>(this GFramework.Core.Abstractions.Rule.IContextAware contextAware) public static TUtility GetUtility<TUtility>(this GFramework.Core.Abstractions.Rule.IContextAware contextAware)
where TUtility : class, GFramework.Core.Abstractions.Utility.IUtility => throw new NotImplementedException(); where TUtility : class, GFramework.Core.Abstractions.Utility.IUtility => throw new NotImplementedException();
public static IReadOnlyList<TUtility> GetUtilities<TUtility>(this GFramework.Core.Abstractions.Rule.IContextAware contextAware) public static IReadOnlyList<TUtility> GetUtilities<TUtility>(this GFramework.Core.Abstractions.Rule.IContextAware contextAware)
where TUtility : class, GFramework.Core.Abstractions.Utility.IUtility => throw new NotImplementedException(); where TUtility : class, GFramework.Core.Abstractions.Utility.IUtility => throw new NotImplementedException();
} }
} }
namespace GFramework.SourceGenerators.Abstractions.Rule namespace GFramework.Core.SourceGenerators.Abstractions.Rule
{ {
public sealed class GetModelAttribute : Attribute { } public sealed class GetModelAttribute : Attribute { }
public sealed class GetModelsAttribute : Attribute { } public sealed class GetModelsAttribute : Attribute { }
public sealed class GetSystemAttribute : Attribute { } public sealed class GetSystemAttribute : Attribute { }
public sealed class GetSystemsAttribute : Attribute { } public sealed class GetSystemsAttribute : Attribute { }
public sealed class GetUtilityAttribute : Attribute { } public sealed class GetUtilityAttribute : Attribute { }
public sealed class GetUtilitiesAttribute : Attribute { } public sealed class GetUtilitiesAttribute : Attribute { }
} }
"""; """;
[Test] [Test]
public async Task Reports_Warning_When_FieldInjectedModel_Is_Not_Registered() public async Task Reports_Warning_When_FieldInjectedModel_Is_Not_Registered()
@ -142,7 +142,7 @@ public sealed class ContextRegistrationAnalyzerTests
using GFramework.Core.Abstractions.Model; using GFramework.Core.Abstractions.Model;
using GFramework.Core.Abstractions.Systems; using GFramework.Core.Abstractions.Systems;
using GFramework.Core.Architectures; using GFramework.Core.Architectures;
using GFramework.SourceGenerators.Abstractions.Rule; using GFramework.Core.SourceGenerators.Abstractions.Rule;
public interface IInventoryModel : IModel { } public interface IInventoryModel : IModel { }
@ -180,7 +180,7 @@ public sealed class ContextRegistrationAnalyzerTests
using GFramework.Core.Abstractions.Model; using GFramework.Core.Abstractions.Model;
using GFramework.Core.Abstractions.Systems; using GFramework.Core.Abstractions.Systems;
using GFramework.Core.Architectures; using GFramework.Core.Architectures;
using GFramework.SourceGenerators.Abstractions.Rule; using GFramework.Core.SourceGenerators.Abstractions.Rule;
public interface IInventoryModel : IModel { } public interface IInventoryModel : IModel { }
@ -255,7 +255,7 @@ public sealed class ContextRegistrationAnalyzerTests
using GFramework.Core.Abstractions.Model; using GFramework.Core.Abstractions.Model;
using GFramework.Core.Abstractions.Systems; using GFramework.Core.Abstractions.Systems;
using GFramework.Core.Architectures; using GFramework.Core.Architectures;
using GFramework.SourceGenerators.Abstractions.Rule; using GFramework.Core.SourceGenerators.Abstractions.Rule;
public interface IInventoryModel : IModel { } public interface IInventoryModel : IModel { }
@ -297,7 +297,7 @@ public sealed class ContextRegistrationAnalyzerTests
using GFramework.Core.Abstractions.Model; using GFramework.Core.Abstractions.Model;
using GFramework.Core.Abstractions.Systems; using GFramework.Core.Abstractions.Systems;
using GFramework.Core.Architectures; using GFramework.Core.Architectures;
using GFramework.SourceGenerators.Abstractions.Rule; using GFramework.Core.SourceGenerators.Abstractions.Rule;
public interface IInventoryModel : IModel { } public interface IInventoryModel : IModel { }
@ -337,7 +337,7 @@ public sealed class ContextRegistrationAnalyzerTests
using GFramework.Core.Abstractions.Systems; using GFramework.Core.Abstractions.Systems;
using GFramework.Core.Abstractions.Utility; using GFramework.Core.Abstractions.Utility;
using GFramework.Core.Architectures; using GFramework.Core.Architectures;
using GFramework.SourceGenerators.Abstractions.Rule; using GFramework.Core.SourceGenerators.Abstractions.Rule;
public interface IInventoryUtility : IUtility { } public interface IInventoryUtility : IUtility { }
@ -366,7 +366,8 @@ public sealed class ContextRegistrationAnalyzerTests
} }
[Test] [Test]
public async Task Does_Not_Report_When_Inherited_OnInitialize_Calls_Virtual_Helper_Overridden_In_Derived_Architecture() public async Task
Does_Not_Report_When_Inherited_OnInitialize_Calls_Virtual_Helper_Overridden_In_Derived_Architecture()
{ {
await AnalyzerTestDriver<ContextRegistrationAnalyzer>.RunAsync( await AnalyzerTestDriver<ContextRegistrationAnalyzer>.RunAsync(
Wrap(""" Wrap("""
@ -375,7 +376,7 @@ public sealed class ContextRegistrationAnalyzerTests
using GFramework.Core.Abstractions.Model; using GFramework.Core.Abstractions.Model;
using GFramework.Core.Abstractions.Systems; using GFramework.Core.Abstractions.Systems;
using GFramework.Core.Architectures; using GFramework.Core.Architectures;
using GFramework.SourceGenerators.Abstractions.Rule; using GFramework.Core.SourceGenerators.Abstractions.Rule;
public interface IInventoryModel : IModel { } public interface IInventoryModel : IModel { }
@ -422,7 +423,7 @@ public sealed class ContextRegistrationAnalyzerTests
using GFramework.Core.Abstractions.Model; using GFramework.Core.Abstractions.Model;
using GFramework.Core.Abstractions.Systems; using GFramework.Core.Abstractions.Systems;
using GFramework.Core.Architectures; using GFramework.Core.Architectures;
using GFramework.SourceGenerators.Abstractions.Rule; using GFramework.Core.SourceGenerators.Abstractions.Rule;
public interface IInventoryModel : IModel { } public interface IInventoryModel : IModel { }
@ -476,7 +477,7 @@ public sealed class ContextRegistrationAnalyzerTests
using GFramework.Core.Abstractions.Model; using GFramework.Core.Abstractions.Model;
using GFramework.Core.Abstractions.Systems; using GFramework.Core.Abstractions.Systems;
using GFramework.Core.Architectures; using GFramework.Core.Architectures;
using GFramework.SourceGenerators.Abstractions.Rule; using GFramework.Core.SourceGenerators.Abstractions.Rule;
public interface IInventoryModel : IModel { } public interface IInventoryModel : IModel { }
@ -531,7 +532,7 @@ public sealed class ContextRegistrationAnalyzerTests
using GFramework.Core.Abstractions.Model; using GFramework.Core.Abstractions.Model;
using GFramework.Core.Abstractions.Systems; using GFramework.Core.Abstractions.Systems;
using GFramework.Core.Architectures; using GFramework.Core.Architectures;
using GFramework.SourceGenerators.Abstractions.Rule; using GFramework.Core.SourceGenerators.Abstractions.Rule;
public interface IInventoryModel : IModel { } public interface IInventoryModel : IModel { }

View File

@ -1,4 +1,4 @@
using GFramework.SourceGenerators.Architectures; using GFramework.Core.SourceGenerators.Architectures;
using GFramework.SourceGenerators.Tests.Core; using GFramework.SourceGenerators.Tests.Core;
namespace GFramework.SourceGenerators.Tests.Architectures; namespace GFramework.SourceGenerators.Tests.Architectures;
@ -14,9 +14,9 @@ public class AutoRegisterModuleGeneratorTests
{ {
const string source = """ const string source = """
using System; using System;
using GFramework.SourceGenerators.Abstractions.Architectures; using GFramework.Core.SourceGenerators.Abstractions.Architectures;
namespace GFramework.SourceGenerators.Abstractions.Architectures namespace GFramework.Core.SourceGenerators.Abstractions.Architectures
{ {
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)] [AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
public sealed class AutoRegisterModuleAttribute : Attribute { } public sealed class AutoRegisterModuleAttribute : Attribute { }
@ -70,7 +70,7 @@ public class AutoRegisterModuleGeneratorTests
using GFramework.Core.Abstractions.Model; using GFramework.Core.Abstractions.Model;
using GFramework.Core.Abstractions.Systems; using GFramework.Core.Abstractions.Systems;
using GFramework.Core.Abstractions.Utility; using GFramework.Core.Abstractions.Utility;
using GFramework.SourceGenerators.Abstractions.Architectures; using GFramework.Core.SourceGenerators.Abstractions.Architectures;
public sealed class PlayerModel : IModel { } public sealed class PlayerModel : IModel { }
public sealed class CombatSystem : ISystem { } public sealed class CombatSystem : ISystem { }
@ -118,7 +118,7 @@ public class AutoRegisterModuleGeneratorTests
const string commonSource = """ const string commonSource = """
using System; using System;
namespace GFramework.SourceGenerators.Abstractions.Architectures namespace GFramework.Core.SourceGenerators.Abstractions.Architectures
{ {
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)] [AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
public sealed class AutoRegisterModuleAttribute : Attribute { } public sealed class AutoRegisterModuleAttribute : Attribute { }
@ -180,42 +180,42 @@ public class AutoRegisterModuleGeneratorTests
"""; """;
const string partASource = """ const string partASource = """
namespace TestApp namespace TestApp
{ {
using GFramework.SourceGenerators.Abstractions.Architectures; using GFramework.Core.SourceGenerators.Abstractions.Architectures;
// Padding ensures this attribute lives later in the file than the attributes in PartB. // Padding ensures this attribute lives later in the file than the attributes in PartB.
// The generator should still place it first because PartA sorts before PartB. // The generator should still place it first because PartA sorts before PartB.
// padding 01 // padding 01
// padding 02 // padding 02
// padding 03 // padding 03
// padding 04 // padding 04
// padding 05 // padding 05
// padding 06 // padding 06
// padding 07 // padding 07
// padding 08 // padding 08
// padding 09 // padding 09
// padding 10 // padding 10
[AutoRegisterModule] [AutoRegisterModule]
[RegisterUtility(typeof(AudioUtility))] [RegisterUtility(typeof(AudioUtility))]
public partial class GameplayModule public partial class GameplayModule
{ {
} }
} }
"""; """;
const string partBSource = """ const string partBSource = """
namespace TestApp namespace TestApp
{ {
using GFramework.SourceGenerators.Abstractions.Architectures; using GFramework.Core.SourceGenerators.Abstractions.Architectures;
[RegisterSystem(typeof(CombatSystem))] [RegisterSystem(typeof(CombatSystem))]
[RegisterModel(typeof(PlayerModel))] [RegisterModel(typeof(PlayerModel))]
public partial class GameplayModule public partial class GameplayModule
{ {
} }
} }
"""; """;
const string expected = """ const string expected = """
// <auto-generated /> // <auto-generated />
@ -247,7 +247,8 @@ public class AutoRegisterModuleGeneratorTests
}, },
GeneratedSources = GeneratedSources =
{ {
(typeof(AutoRegisterModuleGenerator), "TestApp_GameplayModule.AutoRegisterModule.g.cs", NormalizeLineEndings(expected)) (typeof(AutoRegisterModuleGenerator), "TestApp_GameplayModule.AutoRegisterModule.g.cs",
NormalizeLineEndings(expected))
} }
}, },
DisabledDiagnostics = { "GF_Common_Trace_001" } DisabledDiagnostics = { "GF_Common_Trace_001" }
@ -265,9 +266,9 @@ public class AutoRegisterModuleGeneratorTests
const string source = """ const string source = """
#nullable enable #nullable enable
using System; using System;
using GFramework.SourceGenerators.Abstractions.Architectures; using GFramework.Core.SourceGenerators.Abstractions.Architectures;
namespace GFramework.SourceGenerators.Abstractions.Architectures namespace GFramework.Core.SourceGenerators.Abstractions.Architectures
{ {
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)] [AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
public sealed class AutoRegisterModuleAttribute : Attribute { } public sealed class AutoRegisterModuleAttribute : Attribute { }
@ -319,7 +320,7 @@ public class AutoRegisterModuleGeneratorTests
namespace TestApp namespace TestApp
{ {
using GFramework.Core.Abstractions.Model; using GFramework.Core.Abstractions.Model;
using GFramework.SourceGenerators.Abstractions.Architectures; using GFramework.Core.SourceGenerators.Abstractions.Architectures;
public sealed class PlayerModel : IModel { } public sealed class PlayerModel : IModel { }

View File

@ -1,7 +1,6 @@
using System.IO; using System.IO;
using GFramework.SourceGenerators.Bases; using GFramework.Core.SourceGenerators.Bases;
using GFramework.SourceGenerators.Tests.Core; using GFramework.SourceGenerators.Tests.Core;
using NUnit.Framework;
namespace GFramework.SourceGenerators.Tests.Bases; namespace GFramework.SourceGenerators.Tests.Bases;
@ -20,7 +19,7 @@ public class PriorityGeneratorSnapshotTests
const string source = """ const string source = """
using System; using System;
namespace GFramework.SourceGenerators.Abstractions.Bases namespace GFramework.Core.SourceGenerators.Abstractions.Bases
{ {
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)] [AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
public sealed class PriorityAttribute : Attribute public sealed class PriorityAttribute : Attribute
@ -40,7 +39,7 @@ public class PriorityGeneratorSnapshotTests
namespace TestApp namespace TestApp
{ {
using GFramework.SourceGenerators.Abstractions.Bases; using GFramework.Core.SourceGenerators.Abstractions.Bases;
[Priority(10)] [Priority(10)]
public partial class MySystem public partial class MySystem
@ -68,7 +67,7 @@ public class PriorityGeneratorSnapshotTests
const string source = """ const string source = """
using System; using System;
namespace GFramework.SourceGenerators.Abstractions.Bases namespace GFramework.Core.SourceGenerators.Abstractions.Bases
{ {
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)] [AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
public sealed class PriorityAttribute : Attribute public sealed class PriorityAttribute : Attribute
@ -88,7 +87,7 @@ public class PriorityGeneratorSnapshotTests
namespace TestApp namespace TestApp
{ {
using GFramework.SourceGenerators.Abstractions.Bases; using GFramework.Core.SourceGenerators.Abstractions.Bases;
[Priority(-100)] [Priority(-100)]
public partial class CriticalSystem public partial class CriticalSystem
@ -116,7 +115,7 @@ public class PriorityGeneratorSnapshotTests
const string source = """ const string source = """
using System; using System;
namespace GFramework.SourceGenerators.Abstractions.Bases namespace GFramework.Core.SourceGenerators.Abstractions.Bases
{ {
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)] [AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
public sealed class PriorityAttribute : Attribute public sealed class PriorityAttribute : Attribute
@ -145,7 +144,7 @@ public class PriorityGeneratorSnapshotTests
namespace TestApp namespace TestApp
{ {
using GFramework.SourceGenerators.Abstractions.Bases; using GFramework.Core.SourceGenerators.Abstractions.Bases;
using GFramework.Core.Abstractions.Bases; using GFramework.Core.Abstractions.Bases;
[Priority(PriorityGroup.High)] [Priority(PriorityGroup.High)]
@ -174,7 +173,7 @@ public class PriorityGeneratorSnapshotTests
const string source = """ const string source = """
using System; using System;
namespace GFramework.SourceGenerators.Abstractions.Bases namespace GFramework.Core.SourceGenerators.Abstractions.Bases
{ {
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)] [AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
public sealed class PriorityAttribute : Attribute public sealed class PriorityAttribute : Attribute
@ -194,7 +193,7 @@ public class PriorityGeneratorSnapshotTests
namespace TestApp namespace TestApp
{ {
using GFramework.SourceGenerators.Abstractions.Bases; using GFramework.Core.SourceGenerators.Abstractions.Bases;
[Priority(20)] [Priority(20)]
public partial class GenericSystem<T> public partial class GenericSystem<T>

View File

@ -1,7 +1,6 @@
using System.Collections.Immutable; using System.Collections.Immutable;
using System.IO; using System.IO;
using GFramework.SourceGenerators.Config; using GFramework.Game.SourceGenerators.Config;
using Microsoft.CodeAnalysis.CSharp;
namespace GFramework.SourceGenerators.Tests.Config; namespace GFramework.SourceGenerators.Tests.Config;

View File

@ -1,7 +1,4 @@
using System.IO; using System.IO;
using Microsoft.CodeAnalysis.CSharp.Testing;
using Microsoft.CodeAnalysis.Testing;
using NUnit.Framework;
namespace GFramework.SourceGenerators.Tests.Core; namespace GFramework.SourceGenerators.Tests.Core;
@ -17,10 +14,12 @@ public static class GeneratorSnapshotTest<TGenerator>
/// </summary> /// </summary>
/// <param name="source">输入的源代码字符串</param> /// <param name="source">输入的源代码字符串</param>
/// <param name="snapshotFolder">快照文件存储的文件夹路径</param> /// <param name="snapshotFolder">快照文件存储的文件夹路径</param>
/// <param name="snapshotFileNameSelector">将生成文件名映射为快照文件名的规则;为空时使用原始生成文件名。</param>
/// <returns>异步任务</returns> /// <returns>异步任务</returns>
public static async Task RunAsync( public static async Task RunAsync(
string source, string source,
string snapshotFolder) string snapshotFolder,
Func<string, string>? snapshotFileNameSelector = null)
{ {
var test = new CSharpSourceGeneratorTest<TGenerator, DefaultVerifier> var test = new CSharpSourceGeneratorTest<TGenerator, DefaultVerifier>
{ {
@ -38,9 +37,11 @@ public static class GeneratorSnapshotTest<TGenerator>
foreach (var (filename, content) in generated) foreach (var (filename, content) in generated)
{ {
// 不同测试套件可能需要将生成文件映射到非 .cs 快照,以避免测试资产被当作可编译源码参与构建。
var snapshotFileName = snapshotFileNameSelector?.Invoke(filename) ?? filename;
var path = Path.Combine( var path = Path.Combine(
snapshotFolder, snapshotFolder,
filename); snapshotFileName);
if (!File.Exists(path)) if (!File.Exists(path))
{ {
@ -57,7 +58,7 @@ public static class GeneratorSnapshotTest<TGenerator>
Assert.That( Assert.That(
Normalize(expected), Normalize(expected),
Is.EqualTo(Normalize(content.ToString())), Is.EqualTo(Normalize(content.ToString())),
$"Snapshot mismatch: {filename}"); $"Snapshot mismatch: {snapshotFileName}");
} }
} }

View File

@ -1,5 +1,5 @@
using System.Reflection; using System.Reflection;
using GFramework.SourceGenerators.Cqrs; using GFramework.Cqrs.SourceGenerators.Cqrs;
using GFramework.SourceGenerators.Tests.Core; using GFramework.SourceGenerators.Tests.Core;
namespace GFramework.SourceGenerators.Tests.Cqrs; namespace GFramework.SourceGenerators.Tests.Cqrs;
@ -252,6 +252,84 @@ public class CqrsHandlerRegistryGeneratorTests
"""; """;
private const string ExternalAssemblyPreciseLookupExpected = """
// <auto-generated />
#nullable enable
[assembly: global::GFramework.Cqrs.CqrsHandlerRegistryAttribute(typeof(global::GFramework.Generated.Cqrs.__GFrameworkGeneratedCqrsHandlerRegistry))]
namespace GFramework.Generated.Cqrs;
internal sealed class __GFrameworkGeneratedCqrsHandlerRegistry : global::GFramework.Cqrs.ICqrsHandlerRegistry
{
public void Register(global::Microsoft.Extensions.DependencyInjection.IServiceCollection services, global::GFramework.Core.Abstractions.Logging.ILogger logger)
{
if (services is null)
throw new global::System.ArgumentNullException(nameof(services));
if (logger is null)
throw new global::System.ArgumentNullException(nameof(logger));
var registryAssembly = typeof(global::GFramework.Generated.Cqrs.__GFrameworkGeneratedCqrsHandlerRegistry).Assembly;
var implementationType0 = typeof(global::TestApp.DerivedHandler);
if (implementationType0 is not null)
{
var serviceType0_0Argument0 = ResolveReferencedAssemblyType("Dependency, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null", "Dep.VisibilityScope+ProtectedRequest");
var serviceType0_0Argument1Element = ResolveReferencedAssemblyType("Dependency, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null", "Dep.VisibilityScope+ProtectedResponse");
if (serviceType0_0Argument0 is not null && serviceType0_0Argument1Element is not null)
{
var serviceType0_0 = typeof(global::GFramework.Cqrs.Abstractions.Cqrs.IRequestHandler<,>).MakeGenericType(serviceType0_0Argument0, serviceType0_0Argument1Element.MakeArrayType());
global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddTransient(
services,
serviceType0_0,
implementationType0);
logger.Debug("Registered CQRS handler TestApp.DerivedHandler as GFramework.Cqrs.Abstractions.Cqrs.IRequestHandler<Dep.VisibilityScope.ProtectedRequest, Dep.VisibilityScope.ProtectedResponse[]>.");
}
global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddTransient(
services,
typeof(global::GFramework.Cqrs.Abstractions.Cqrs.IRequestHandler<global::Dep.VisibleRequest, string>),
implementationType0);
logger.Debug("Registered CQRS handler TestApp.DerivedHandler as GFramework.Cqrs.Abstractions.Cqrs.IRequestHandler<Dep.VisibleRequest, string>.");
}
}
private static global::System.Type? ResolveReferencedAssemblyType(string assemblyIdentity, string typeMetadataName)
{
var assembly = ResolveReferencedAssembly(assemblyIdentity);
return assembly?.GetType(typeMetadataName, throwOnError: false, ignoreCase: false);
}
private static global::System.Reflection.Assembly? ResolveReferencedAssembly(string assemblyIdentity)
{
global::System.Reflection.AssemblyName targetAssemblyName;
try
{
targetAssemblyName = new global::System.Reflection.AssemblyName(assemblyIdentity);
}
catch
{
return null;
}
foreach (var assembly in global::System.AppDomain.CurrentDomain.GetAssemblies())
{
if (global::System.Reflection.AssemblyName.ReferenceMatchesDefinition(targetAssemblyName, assembly.GetName()))
return assembly;
}
try
{
return global::System.Reflection.Assembly.Load(targetAssemblyName);
}
catch
{
return null;
}
}
}
""";
/// <summary> /// <summary>
/// 验证生成器会为当前程序集中的 request、notification 和 stream 处理器生成稳定顺序的注册器。 /// 验证生成器会为当前程序集中的 request、notification 和 stream 处理器生成稳定顺序的注册器。
/// </summary> /// </summary>
@ -827,10 +905,10 @@ public class CqrsHandlerRegistryGeneratorTests
/// <summary> /// <summary>
/// 验证当外部基类暴露的 handler interface 含有生成注册器顶层上下文不可直接引用的 protected 类型时, /// 验证当外部基类暴露的 handler interface 含有生成注册器顶层上下文不可直接引用的 protected 类型时,
/// 生成器会保留已知直注册,并只对剩余未知接口做本地 interface discovery /// 生成器会输出定向程序集查找,而不是继续退回 implementation 级接口发现
/// </summary> /// </summary>
[Test] [Test]
public void Generates_Partial_Runtime_Interface_Discovery_For_Inaccessible_External_Protected_Types() public void Generates_Precise_Assembly_Type_Lookups_For_Inaccessible_External_Protected_Types()
{ {
const string contractsSource = """ const string contractsSource = """
namespace GFramework.Cqrs.Abstractions.Cqrs namespace GFramework.Cqrs.Abstractions.Cqrs
@ -923,39 +1001,9 @@ public class CqrsHandlerRegistryGeneratorTests
contractsReference, contractsReference,
dependencyReference); dependencyReference);
Assert.Multiple(() => Assert.That(
{ generatedSource,
Assert.That( Is.EqualTo(ExternalAssemblyPreciseLookupExpected));
generatedSource,
Does.Contain("var implementationType0 = typeof(global::TestApp.DerivedHandler);"));
Assert.That(
generatedSource,
Does.Contain(
"var knownServiceTypes0 = new global::System.Collections.Generic.HashSet<global::System.Type>();"));
Assert.That(
generatedSource,
Does.Contain(
"// Remaining runtime interface discovery target: GFramework.Cqrs.Abstractions.Cqrs.IRequestHandler<Dep.VisibilityScope.ProtectedRequest, Dep.VisibilityScope.ProtectedResponse[]>"));
Assert.That(
generatedSource,
Does.Contain(
"knownServiceTypes0.Add(typeof(global::GFramework.Cqrs.Abstractions.Cqrs.IRequestHandler<global::Dep.VisibleRequest, string>));"));
Assert.That(
generatedSource,
Does.Contain(
"RegisterRemainingReflectedHandlerInterfaces(services, logger, implementationType0, knownServiceTypes0);"));
Assert.That(
generatedSource,
Does.Contain("if (knownServiceTypes.Contains(handlerInterface))"));
Assert.That(
generatedSource,
Does.Contain(
"Registered CQRS handler TestApp.DerivedHandler as GFramework.Cqrs.Abstractions.Cqrs.IRequestHandler<Dep.VisibleRequest, string>."));
Assert.That(
generatedSource,
Does.Not.Contain(
"typeof(global::GFramework.Cqrs.Abstractions.Cqrs.IRequestHandler<global::Dep.VisibilityScope.ProtectedRequest"));
});
} }
/// <summary> /// <summary>

View File

@ -1,209 +1,213 @@
using System.IO; using System.IO;
using GFramework.SourceGenerators.Enums; using GFramework.Core.SourceGenerators.Enums;
using GFramework.SourceGenerators.Tests.Core; using GFramework.SourceGenerators.Tests.Core;
using NUnit.Framework;
namespace GFramework.SourceGenerators.Tests.Enums; namespace GFramework.SourceGenerators.Tests.Enums;
/// <summary>
/// 验证枚举扩展生成器在不同属性开关组合下的快照输出。
/// </summary>
[TestFixture] [TestFixture]
public class EnumExtensionsGeneratorSnapshotTests public class EnumExtensionsGeneratorSnapshotTests
{ {
private const string EnumAttributeNamespace = "GFramework.Core.SourceGenerators.Abstractions.Enums";
/// <summary>
/// 验证默认配置会为普通枚举生成逐项判断方法与集合判断方法。
/// </summary>
[Test] [Test]
public async Task Snapshot_BasicEnum_IsMethods() public async Task Snapshot_BasicEnum_IsMethods()
{ {
const string source = """ var source = BuildSource(
using System; """
public enum Status
namespace GFramework.SourceGenerators.Abstractions.Enums {
{ Active,
[AttributeUsage(AttributeTargets.Enum)] Inactive,
public sealed class GenerateEnumExtensionsAttribute : Attribute Pending
{ }
public bool GenerateIsMethods { get; set; } = true; """);
public bool GenerateIsInMethod { get; set; } = true;
}
}
namespace TestApp
{
using GFramework.SourceGenerators.Abstractions.Enums;
[GenerateEnumExtensions]
public enum Status
{
Active,
Inactive,
Pending
}
}
""";
await GeneratorSnapshotTest<EnumExtensionsGenerator>.RunAsync( await GeneratorSnapshotTest<EnumExtensionsGenerator>.RunAsync(
source, source,
Path.Combine( GetSnapshotFolder("BasicEnum_IsMethods"),
TestContext.CurrentContext.TestDirectory, GetSnapshotFileName);
"enums",
"snapshots",
"EnumExtensionsGenerator",
"BasicEnum_IsMethods"));
} }
/// <summary>
/// 验证默认配置在较小枚举上仍会生成集合判断方法。
/// </summary>
[Test] [Test]
public async Task Snapshot_BasicEnum_IsInMethod() public async Task Snapshot_BasicEnum_IsInMethod()
{ {
const string source = """ var source = BuildSource(
using System; """
public enum Status
namespace GFramework.SourceGenerators.Abstractions.Enums {
{ Active,
[AttributeUsage(AttributeTargets.Enum)] Inactive
public sealed class GenerateEnumExtensionsAttribute : Attribute }
{ """);
public bool GenerateIsMethods { get; set; } = true;
public bool GenerateIsInMethod { get; set; } = true;
}
}
namespace TestApp
{
using GFramework.SourceGenerators.Abstractions.Enums;
[GenerateEnumExtensions]
public enum Status
{
Active,
Inactive
}
}
""";
await GeneratorSnapshotTest<EnumExtensionsGenerator>.RunAsync( await GeneratorSnapshotTest<EnumExtensionsGenerator>.RunAsync(
source, source,
Path.Combine( GetSnapshotFolder("BasicEnum_IsInMethod"),
TestContext.CurrentContext.TestDirectory, GetSnapshotFileName);
"enums",
"snapshots",
"EnumExtensionsGenerator",
"BasicEnum_IsInMethod"));
} }
/// <summary>
/// 验证带显式位标志值的枚举也会生成对应扩展方法。
/// </summary>
[Test] [Test]
public async Task Snapshot_EnumWithFlagValues() public async Task Snapshot_EnumWithFlagValues()
{ {
const string source = """ var source = BuildSource(
using System; """
[Flags]
namespace GFramework.SourceGenerators.Abstractions.Enums public enum Permissions
{ {
[AttributeUsage(AttributeTargets.Enum)] None = 0,
public sealed class GenerateEnumExtensionsAttribute : Attribute Read = 1,
{ Write = 2,
public bool GenerateIsMethods { get; set; } = true; Execute = 4
public bool GenerateIsInMethod { get; set; } = true; }
} """);
}
namespace TestApp
{
using GFramework.SourceGenerators.Abstractions.Enums;
[GenerateEnumExtensions]
[Flags]
public enum Permissions
{
None = 0,
Read = 1,
Write = 2,
Execute = 4
}
}
""";
await GeneratorSnapshotTest<EnumExtensionsGenerator>.RunAsync( await GeneratorSnapshotTest<EnumExtensionsGenerator>.RunAsync(
source, source,
Path.Combine( GetSnapshotFolder("EnumWithFlagValues"),
TestContext.CurrentContext.TestDirectory, GetSnapshotFileName);
"enums",
"snapshots",
"EnumExtensionsGenerator",
"EnumWithFlagValues"));
} }
/// <summary>
/// 验证关闭逐项判断开关后仅保留集合判断方法。
/// </summary>
[Test] [Test]
public async Task Snapshot_DisableIsMethods() public async Task Snapshot_DisableIsMethods()
{ {
const string source = """ var source = BuildSource(
using System; """
public enum Status
namespace GFramework.SourceGenerators.Abstractions.Enums {
{ Active,
[AttributeUsage(AttributeTargets.Enum)] Inactive
public sealed class GenerateEnumExtensionsAttribute : Attribute }
{ """,
public bool GenerateIsMethods { get; set; } = true; "[GenerateEnumExtensions(GenerateIsMethods = false)]");
public bool GenerateIsInMethod { get; set; } = true;
}
}
namespace TestApp
{
using GFramework.SourceGenerators.Abstractions.Enums;
[GenerateEnumExtensions(GenerateIsMethods = false)]
public enum Status
{
Active,
Inactive
}
}
""";
await GeneratorSnapshotTest<EnumExtensionsGenerator>.RunAsync( await GeneratorSnapshotTest<EnumExtensionsGenerator>.RunAsync(
source, source,
Path.Combine( GetSnapshotFolder("DisableIsMethods"),
TestContext.CurrentContext.TestDirectory, GetSnapshotFileName);
"enums",
"snapshots",
"EnumExtensionsGenerator",
"DisableIsMethods"));
} }
/// <summary>
/// 验证关闭集合判断开关后仅保留逐项判断方法。
/// </summary>
[Test] [Test]
public async Task Snapshot_DisableIsInMethod() public async Task Snapshot_DisableIsInMethod()
{ {
const string source = """ var source = BuildSource(
using System; """
public enum Status
namespace GFramework.SourceGenerators.Abstractions.Enums {
{ Active,
[AttributeUsage(AttributeTargets.Enum)] Inactive
public sealed class GenerateEnumExtensionsAttribute : Attribute }
{ """,
public bool GenerateIsMethods { get; set; } = true; "[GenerateEnumExtensions(GenerateIsInMethod = false)]");
public bool GenerateIsInMethod { get; set; } = true;
}
}
namespace TestApp
{
using GFramework.SourceGenerators.Abstractions.Enums;
[GenerateEnumExtensions(GenerateIsInMethod = false)]
public enum Status
{
Active,
Inactive
}
}
""";
await GeneratorSnapshotTest<EnumExtensionsGenerator>.RunAsync( await GeneratorSnapshotTest<EnumExtensionsGenerator>.RunAsync(
source, source,
GetSnapshotFolder("DisableIsInMethod"),
GetSnapshotFileName);
}
/// <summary>
/// 验证同时关闭两个生成开关时不会输出任何扩展方法。
/// </summary>
[Test]
public async Task Snapshot_DisableAllGeneratedMethods()
{
var source = BuildSource(
"""
public enum Status
{
Active,
Inactive
}
""",
"[GenerateEnumExtensions(GenerateIsMethods = false, GenerateIsInMethod = false)]");
await GeneratorSnapshotTest<EnumExtensionsGenerator>.RunAsync(
source,
GetSnapshotFolder("DisableAllGeneratedMethods"),
GetSnapshotFileName);
}
/// <summary>
/// 将运行时测试目录映射回仓库内已提交的枚举快照目录。
/// </summary>
/// <param name="scenarioName">快照场景名称。</param>
/// <returns>场景对应的绝对快照目录。</returns>
private static string GetSnapshotFolder(string scenarioName)
{
return Path.GetFullPath(
Path.Combine( Path.Combine(
TestContext.CurrentContext.TestDirectory, TestContext.CurrentContext.TestDirectory,
"enums", "..",
"..",
"..",
"Enums",
"snapshots", "snapshots",
"EnumExtensionsGenerator", "EnumExtensionsGenerator",
"DisableIsInMethod")); scenarioName));
}
/// <summary>
/// 将生成器输出文件名映射为非 C# 快照文件名,避免快照资产被命名校验和项目编译误判为源码。
/// </summary>
/// <param name="generatedFileName">生成器输出的提示文件名。</param>
/// <returns>对应的快照文件名。</returns>
private static string GetSnapshotFileName(string generatedFileName)
{
return Path.ChangeExtension(generatedFileName, ".txt");
}
/// <summary>
/// 构造最小自洽的测试输入源码,以稳定驱动枚举扩展生成器的快照测试。
/// </summary>
/// <param name="enumBody">要注入到测试命名空间中的枚举声明文本。</param>
/// <param name="attributeUsage">枚举上的属性使用方式,默认启用所有生成选项。</param>
/// <returns>包含内联测试属性与目标枚举声明的完整源码。</returns>
/// <remarks>
/// 这里内联声明 <c>GenerateEnumExtensionsAttribute</c>,以便每个快照输入保持最小自洽。
/// 属性命名空间必须与生成器按 metadata name 查找的契约保持一致;如果命名空间、属性名或参数发生变更,
/// 需要同步更新该模板与相关快照,否则测试可能出现静默漂移。
/// </remarks>
private static string BuildSource(string enumBody, string attributeUsage = "[GenerateEnumExtensions]")
{
// 保持属性声明与测试输入同处一个模板中,能够明确锁定生成器对元数据名称和可选参数的语义假设。
return $$"""
using System;
namespace {{EnumAttributeNamespace}}
{
[AttributeUsage(AttributeTargets.Enum)]
public sealed class GenerateEnumExtensionsAttribute : Attribute
{
public bool GenerateIsMethods { get; set; } = true;
public bool GenerateIsInMethod { get; set; } = true;
}
}
namespace TestApp
{
using {{EnumAttributeNamespace}};
{{attributeUsage}}
{{enumBody}}
}
""";
} }
} }

View File

@ -0,0 +1,21 @@
// <auto-generated />
using System;
namespace TestApp
{
public static partial class StatusExtensions
{
/// <summary>是否为 Active</summary>
public static bool IsActive(this TestApp.Status value) => value == TestApp.Status.Active;
/// <summary>是否为 Inactive</summary>
public static bool IsInactive(this TestApp.Status value) => value == TestApp.Status.Inactive;
/// <summary>判断是否属于指定集合</summary>
public static bool IsIn(this TestApp.Status value, params TestApp.Status[] values)
{
if (values == null) return false;
foreach (var v in values) if (value == v) return true;
return false;
}
}
}

View File

@ -0,0 +1,24 @@
// <auto-generated />
using System;
namespace TestApp
{
public static partial class StatusExtensions
{
/// <summary>是否为 Active</summary>
public static bool IsActive(this TestApp.Status value) => value == TestApp.Status.Active;
/// <summary>是否为 Inactive</summary>
public static bool IsInactive(this TestApp.Status value) => value == TestApp.Status.Inactive;
/// <summary>是否为 Pending</summary>
public static bool IsPending(this TestApp.Status value) => value == TestApp.Status.Pending;
/// <summary>判断是否属于指定集合</summary>
public static bool IsIn(this TestApp.Status value, params TestApp.Status[] values)
{
if (values == null) return false;
foreach (var v in values) if (value == v) return true;
return false;
}
}
}

View File

@ -0,0 +1,8 @@
// <auto-generated />
using System;
namespace TestApp
{
public static partial class StatusExtensions
{
}
}

View File

@ -0,0 +1,13 @@
// <auto-generated />
using System;
namespace TestApp
{
public static partial class StatusExtensions
{
/// <summary>是否为 Active</summary>
public static bool IsActive(this TestApp.Status value) => value == TestApp.Status.Active;
/// <summary>是否为 Inactive</summary>
public static bool IsInactive(this TestApp.Status value) => value == TestApp.Status.Inactive;
}
}

View File

@ -0,0 +1,15 @@
// <auto-generated />
using System;
namespace TestApp
{
public static partial class StatusExtensions
{
/// <summary>判断是否属于指定集合</summary>
public static bool IsIn(this TestApp.Status value, params TestApp.Status[] values)
{
if (values == null) return false;
foreach (var v in values) if (value == v) return true;
return false;
}
}
}

View File

@ -0,0 +1,27 @@
// <auto-generated />
using System;
namespace TestApp
{
public static partial class PermissionsExtensions
{
/// <summary>是否为 None</summary>
public static bool IsNone(this TestApp.Permissions value) => value == TestApp.Permissions.None;
/// <summary>是否为 Read</summary>
public static bool IsRead(this TestApp.Permissions value) => value == TestApp.Permissions.Read;
/// <summary>是否为 Write</summary>
public static bool IsWrite(this TestApp.Permissions value) => value == TestApp.Permissions.Write;
/// <summary>是否为 Execute</summary>
public static bool IsExecute(this TestApp.Permissions value) => value == TestApp.Permissions.Execute;
/// <summary>判断是否属于指定集合</summary>
public static bool IsIn(this TestApp.Permissions value, params TestApp.Permissions[] values)
{
if (values == null) return false;
foreach (var v in values) if (value == v) return true;
return false;
}
}
}

View File

@ -23,10 +23,16 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\GFramework.SourceGenerators\GFramework.SourceGenerators.csproj"/> <ProjectReference Include="..\GFramework.Core.SourceGenerators\GFramework.Core.SourceGenerators.csproj"/>
<ProjectReference Include="..\GFramework.Cqrs.SourceGenerators\GFramework.Cqrs.SourceGenerators.csproj"/>
<ProjectReference Include="..\GFramework.Game.SourceGenerators\GFramework.Game.SourceGenerators.csproj"/>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Remove="**\snapshots\**\*.cs"/>
<Compile Remove="**\Snapshots\**\*.cs"/>
<None Include="**\snapshots\**\*.cs"/>
<None Include="**\Snapshots\**\*.cs"/>
<Folder Include="rule\snapshots\ContextAwareGenerator\"/> <Folder Include="rule\snapshots\ContextAwareGenerator\"/>
</ItemGroup> </ItemGroup>

View File

@ -1,7 +1,6 @@
using System.IO; using System.IO;
using GFramework.SourceGenerators.Logging; using GFramework.Core.SourceGenerators.Logging;
using GFramework.SourceGenerators.Tests.Core; using GFramework.SourceGenerators.Tests.Core;
using NUnit.Framework;
namespace GFramework.SourceGenerators.Tests.Logging; namespace GFramework.SourceGenerators.Tests.Logging;
@ -14,7 +13,7 @@ public class LoggerGeneratorSnapshotTests
const string source = """ const string source = """
using System; using System;
namespace GFramework.SourceGenerators.Abstractions.Logging namespace GFramework.Core.SourceGenerators.Abstractions.Logging
{ {
[AttributeUsage(AttributeTargets.Class)] [AttributeUsage(AttributeTargets.Class)]
public sealed class LogAttribute : Attribute public sealed class LogAttribute : Attribute
@ -86,7 +85,7 @@ public class LoggerGeneratorSnapshotTests
namespace TestApp namespace TestApp
{ {
using GFramework.SourceGenerators.Abstractions.Logging; using GFramework.Core.SourceGenerators.Abstractions.Logging;
[Log] [Log]
public partial class MyService public partial class MyService
@ -111,7 +110,7 @@ public class LoggerGeneratorSnapshotTests
const string source = """ const string source = """
using System; using System;
namespace GFramework.SourceGenerators.Abstractions.Logging namespace GFramework.Core.SourceGenerators.Abstractions.Logging
{ {
[AttributeUsage(AttributeTargets.Class)] [AttributeUsage(AttributeTargets.Class)]
public sealed class LogAttribute : Attribute public sealed class LogAttribute : Attribute
@ -183,7 +182,7 @@ public class LoggerGeneratorSnapshotTests
namespace TestApp namespace TestApp
{ {
using GFramework.SourceGenerators.Abstractions.Logging; using GFramework.Core.SourceGenerators.Abstractions.Logging;
[Log(Name = "CustomLogger")] [Log(Name = "CustomLogger")]
public partial class MyService public partial class MyService
@ -208,7 +207,7 @@ public class LoggerGeneratorSnapshotTests
const string source = """ const string source = """
using System; using System;
namespace GFramework.SourceGenerators.Abstractions.Logging namespace GFramework.Core.SourceGenerators.Abstractions.Logging
{ {
[AttributeUsage(AttributeTargets.Class)] [AttributeUsage(AttributeTargets.Class)]
public sealed class LogAttribute : Attribute public sealed class LogAttribute : Attribute
@ -280,7 +279,7 @@ public class LoggerGeneratorSnapshotTests
namespace TestApp namespace TestApp
{ {
using GFramework.SourceGenerators.Abstractions.Logging; using GFramework.Core.SourceGenerators.Abstractions.Logging;
[Log(FieldName = "MyLogger")] [Log(FieldName = "MyLogger")]
public partial class MyService public partial class MyService
@ -305,7 +304,7 @@ public class LoggerGeneratorSnapshotTests
const string source = """ const string source = """
using System; using System;
namespace GFramework.SourceGenerators.Abstractions.Logging namespace GFramework.Core.SourceGenerators.Abstractions.Logging
{ {
[AttributeUsage(AttributeTargets.Class)] [AttributeUsage(AttributeTargets.Class)]
public sealed class LogAttribute : Attribute public sealed class LogAttribute : Attribute
@ -377,7 +376,7 @@ public class LoggerGeneratorSnapshotTests
namespace TestApp namespace TestApp
{ {
using GFramework.SourceGenerators.Abstractions.Logging; using GFramework.Core.SourceGenerators.Abstractions.Logging;
[Log(IsStatic = false)] [Log(IsStatic = false)]
public partial class MyService public partial class MyService
@ -402,7 +401,7 @@ public class LoggerGeneratorSnapshotTests
const string source = """ const string source = """
using System; using System;
namespace GFramework.SourceGenerators.Abstractions.Logging namespace GFramework.Core.SourceGenerators.Abstractions.Logging
{ {
[AttributeUsage(AttributeTargets.Class)] [AttributeUsage(AttributeTargets.Class)]
public sealed class LogAttribute : Attribute public sealed class LogAttribute : Attribute
@ -474,7 +473,7 @@ public class LoggerGeneratorSnapshotTests
namespace TestApp namespace TestApp
{ {
using GFramework.SourceGenerators.Abstractions.Logging; using GFramework.Core.SourceGenerators.Abstractions.Logging;
[Log(AccessModifier = "public")] [Log(AccessModifier = "public")]
public partial class MyService public partial class MyService
@ -499,7 +498,7 @@ public class LoggerGeneratorSnapshotTests
const string source = """ const string source = """
using System; using System;
namespace GFramework.SourceGenerators.Abstractions.Logging namespace GFramework.Core.SourceGenerators.Abstractions.Logging
{ {
[AttributeUsage(AttributeTargets.Class)] [AttributeUsage(AttributeTargets.Class)]
public sealed class LogAttribute : Attribute public sealed class LogAttribute : Attribute
@ -571,7 +570,7 @@ public class LoggerGeneratorSnapshotTests
namespace TestApp namespace TestApp
{ {
using GFramework.SourceGenerators.Abstractions.Logging; using GFramework.Core.SourceGenerators.Abstractions.Logging;
[Log] [Log]
public partial class MyService<T> public partial class MyService<T>

View File

@ -1,7 +1,6 @@
using System.IO; using System.IO;
using GFramework.SourceGenerators.Rule; using GFramework.Core.SourceGenerators.Rule;
using GFramework.SourceGenerators.Tests.Core; using GFramework.SourceGenerators.Tests.Core;
using NUnit.Framework;
namespace GFramework.SourceGenerators.Tests.Rule; namespace GFramework.SourceGenerators.Tests.Rule;
@ -24,7 +23,7 @@ public class ContextAwareGeneratorSnapshotTests
const string source = """ const string source = """
using System; using System;
namespace GFramework.SourceGenerators.Abstractions.Rule namespace GFramework.Core.SourceGenerators.Abstractions.Rule
{ {
[AttributeUsage(AttributeTargets.Class)] [AttributeUsage(AttributeTargets.Class)]
public sealed class ContextAwareAttribute : Attribute { } public sealed class ContextAwareAttribute : Attribute { }
@ -74,7 +73,7 @@ public class ContextAwareGeneratorSnapshotTests
namespace TestApp namespace TestApp
{ {
using GFramework.SourceGenerators.Abstractions.Rule; using GFramework.Core.SourceGenerators.Abstractions.Rule;
using GFramework.Core.Abstractions.Rule; using GFramework.Core.Abstractions.Rule;
[ContextAware] [ContextAware]

View File

@ -1,4 +1,4 @@
using GFramework.SourceGenerators.Rule; using GFramework.Core.SourceGenerators.Rule;
using GFramework.SourceGenerators.Tests.Core; using GFramework.SourceGenerators.Tests.Core;
namespace GFramework.SourceGenerators.Tests.Rule; namespace GFramework.SourceGenerators.Tests.Rule;
@ -12,9 +12,9 @@ public class ContextGetGeneratorTests
var source = """ var source = """
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using GFramework.SourceGenerators.Abstractions.Rule; using GFramework.Core.SourceGenerators.Abstractions.Rule;
namespace GFramework.SourceGenerators.Abstractions.Rule namespace GFramework.Core.SourceGenerators.Abstractions.Rule
{ {
[AttributeUsage(AttributeTargets.Class, Inherited = false)] [AttributeUsage(AttributeTargets.Class, Inherited = false)]
public sealed class ContextAwareAttribute : Attribute { } public sealed class ContextAwareAttribute : Attribute { }
@ -102,9 +102,9 @@ public class ContextGetGeneratorTests
{ {
var source = """ var source = """
using System; using System;
using GFramework.SourceGenerators.Abstractions.Rule; using GFramework.Core.SourceGenerators.Abstractions.Rule;
namespace GFramework.SourceGenerators.Abstractions.Rule namespace GFramework.Core.SourceGenerators.Abstractions.Rule
{ {
[AttributeUsage(AttributeTargets.Class, Inherited = false)] [AttributeUsage(AttributeTargets.Class, Inherited = false)]
public sealed class ContextAwareAttribute : Attribute { } public sealed class ContextAwareAttribute : Attribute { }
@ -148,7 +148,7 @@ public class ContextGetGeneratorTests
[ContextAware] [ContextAware]
public partial class InventoryPanel public partial class InventoryPanel
{ {
[global::GFramework.SourceGenerators.Abstractions.Rule.GetModel] [global::GFramework.Core.SourceGenerators.Abstractions.Rule.GetModel]
private IInventoryModel _model = null!; private IInventoryModel _model = null!;
} }
} }
@ -184,9 +184,9 @@ public class ContextGetGeneratorTests
var source = """ var source = """
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using GFramework.SourceGenerators.Abstractions.Rule; using GFramework.Core.SourceGenerators.Abstractions.Rule;
namespace GFramework.SourceGenerators.Abstractions.Rule namespace GFramework.Core.SourceGenerators.Abstractions.Rule
{ {
[AttributeUsage(AttributeTargets.Class, Inherited = false)] [AttributeUsage(AttributeTargets.Class, Inherited = false)]
public sealed class GetAllAttribute : Attribute { } public sealed class GetAllAttribute : Attribute { }
@ -291,9 +291,9 @@ public class ContextGetGeneratorTests
{ {
var source = """ var source = """
using System; using System;
using GFramework.SourceGenerators.Abstractions.Rule; using GFramework.Core.SourceGenerators.Abstractions.Rule;
namespace GFramework.SourceGenerators.Abstractions.Rule namespace GFramework.Core.SourceGenerators.Abstractions.Rule
{ {
[AttributeUsage(AttributeTargets.Class, Inherited = false)] [AttributeUsage(AttributeTargets.Class, Inherited = false)]
public sealed class GetAllAttribute : Attribute { } public sealed class GetAllAttribute : Attribute { }
@ -382,9 +382,9 @@ public class ContextGetGeneratorTests
{ {
var source = """ var source = """
using System; using System;
using GFramework.SourceGenerators.Abstractions.Rule; using GFramework.Core.SourceGenerators.Abstractions.Rule;
namespace GFramework.SourceGenerators.Abstractions.Rule namespace GFramework.Core.SourceGenerators.Abstractions.Rule
{ {
[AttributeUsage(AttributeTargets.Class, Inherited = false)] [AttributeUsage(AttributeTargets.Class, Inherited = false)]
public sealed class ContextAwareAttribute : Attribute { } public sealed class ContextAwareAttribute : Attribute { }
@ -460,9 +460,9 @@ public class ContextGetGeneratorTests
{ {
var source = """ var source = """
using System; using System;
using GFramework.SourceGenerators.Abstractions.Rule; using GFramework.Core.SourceGenerators.Abstractions.Rule;
namespace GFramework.SourceGenerators.Abstractions.Rule namespace GFramework.Core.SourceGenerators.Abstractions.Rule
{ {
[AttributeUsage(AttributeTargets.Class, Inherited = false)] [AttributeUsage(AttributeTargets.Class, Inherited = false)]
public sealed class GetAllAttribute : Attribute { } public sealed class GetAllAttribute : Attribute { }
@ -543,9 +543,9 @@ public class ContextGetGeneratorTests
{ {
var source = MarkupTestSource.Parse(""" var source = MarkupTestSource.Parse("""
using System; using System;
using GFramework.SourceGenerators.Abstractions.Rule; using GFramework.Core.SourceGenerators.Abstractions.Rule;
namespace GFramework.SourceGenerators.Abstractions.Rule namespace GFramework.Core.SourceGenerators.Abstractions.Rule
{ {
[AttributeUsage(AttributeTargets.Class, Inherited = false)] [AttributeUsage(AttributeTargets.Class, Inherited = false)]
public sealed class GetAllAttribute : Attribute { } public sealed class GetAllAttribute : Attribute { }
@ -644,9 +644,9 @@ public class ContextGetGeneratorTests
{ {
var source = MarkupTestSource.Parse(""" var source = MarkupTestSource.Parse("""
using System; using System;
using GFramework.SourceGenerators.Abstractions.Rule; using GFramework.Core.SourceGenerators.Abstractions.Rule;
namespace GFramework.SourceGenerators.Abstractions.Rule namespace GFramework.Core.SourceGenerators.Abstractions.Rule
{ {
[AttributeUsage(AttributeTargets.Class, Inherited = false)] [AttributeUsage(AttributeTargets.Class, Inherited = false)]
public sealed class GetAllAttribute : Attribute { } public sealed class GetAllAttribute : Attribute { }
@ -753,9 +753,9 @@ public class ContextGetGeneratorTests
{ {
var source = """ var source = """
using System; using System;
using GFramework.SourceGenerators.Abstractions.Rule; using GFramework.Core.SourceGenerators.Abstractions.Rule;
namespace GFramework.SourceGenerators.Abstractions.Rule namespace GFramework.Core.SourceGenerators.Abstractions.Rule
{ {
[AttributeUsage(AttributeTargets.Class, Inherited = false)] [AttributeUsage(AttributeTargets.Class, Inherited = false)]
public sealed class ContextAwareAttribute : Attribute { } public sealed class ContextAwareAttribute : Attribute { }
@ -845,9 +845,9 @@ public class ContextGetGeneratorTests
{ {
var source = """ var source = """
using System; using System;
using GFramework.SourceGenerators.Abstractions.Rule; using GFramework.Core.SourceGenerators.Abstractions.Rule;
namespace GFramework.SourceGenerators.Abstractions.Rule namespace GFramework.Core.SourceGenerators.Abstractions.Rule
{ {
[AttributeUsage(AttributeTargets.Field, Inherited = false)] [AttributeUsage(AttributeTargets.Field, Inherited = false)]
public sealed class GetServiceAttribute : Attribute { } public sealed class GetServiceAttribute : Attribute { }
@ -922,9 +922,9 @@ public class ContextGetGeneratorTests
{ {
var source = MarkupTestSource.Parse(""" var source = MarkupTestSource.Parse("""
using System; using System;
using GFramework.SourceGenerators.Abstractions.Rule; using GFramework.Core.SourceGenerators.Abstractions.Rule;
namespace GFramework.SourceGenerators.Abstractions.Rule namespace GFramework.Core.SourceGenerators.Abstractions.Rule
{ {
[AttributeUsage(AttributeTargets.Field, Inherited = false)] [AttributeUsage(AttributeTargets.Field, Inherited = false)]
public sealed class GetModelAttribute : Attribute { } public sealed class GetModelAttribute : Attribute { }
@ -989,9 +989,9 @@ public class ContextGetGeneratorTests
var source = MarkupTestSource.Parse(""" var source = MarkupTestSource.Parse("""
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using GFramework.SourceGenerators.Abstractions.Rule; using GFramework.Core.SourceGenerators.Abstractions.Rule;
namespace GFramework.SourceGenerators.Abstractions.Rule namespace GFramework.Core.SourceGenerators.Abstractions.Rule
{ {
[AttributeUsage(AttributeTargets.Field, Inherited = false)] [AttributeUsage(AttributeTargets.Field, Inherited = false)]
public sealed class GetModelsAttribute : Attribute { } public sealed class GetModelsAttribute : Attribute { }
@ -1060,9 +1060,9 @@ public class ContextGetGeneratorTests
{ {
var source = MarkupTestSource.Parse(""" var source = MarkupTestSource.Parse("""
using System; using System;
using GFramework.SourceGenerators.Abstractions.Rule; using GFramework.Core.SourceGenerators.Abstractions.Rule;
namespace GFramework.SourceGenerators.Abstractions.Rule namespace GFramework.Core.SourceGenerators.Abstractions.Rule
{ {
[AttributeUsage(AttributeTargets.Field, Inherited = false)] [AttributeUsage(AttributeTargets.Field, Inherited = false)]
public sealed class GetModelAttribute : Attribute { } public sealed class GetModelAttribute : Attribute { }
@ -1131,9 +1131,9 @@ public class ContextGetGeneratorTests
{ {
var source = MarkupTestSource.Parse(""" var source = MarkupTestSource.Parse("""
using System; using System;
using GFramework.SourceGenerators.Abstractions.Rule; using GFramework.Core.SourceGenerators.Abstractions.Rule;
namespace GFramework.SourceGenerators.Abstractions.Rule namespace GFramework.Core.SourceGenerators.Abstractions.Rule
{ {
[AttributeUsage(AttributeTargets.Field, Inherited = false)] [AttributeUsage(AttributeTargets.Field, Inherited = false)]
public sealed class GetModelAttribute : Attribute { } public sealed class GetModelAttribute : Attribute { }
@ -1203,9 +1203,9 @@ public class ContextGetGeneratorTests
var source = """ var source = """
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using GFramework.SourceGenerators.Abstractions.Rule; using GFramework.Core.SourceGenerators.Abstractions.Rule;
namespace GFramework.SourceGenerators.Abstractions.Rule namespace GFramework.Core.SourceGenerators.Abstractions.Rule
{ {
[AttributeUsage(AttributeTargets.Field, Inherited = false)] [AttributeUsage(AttributeTargets.Field, Inherited = false)]
public sealed class GetModelsAttribute : Attribute { } public sealed class GetModelsAttribute : Attribute { }

View File

@ -1,113 +0,0 @@
using System.Text;
using GFramework.SourceGenerators.Common.Constants;
using GFramework.SourceGenerators.Common.Diagnostics;
using GFramework.SourceGenerators.Common.Generator;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
namespace GFramework.SourceGenerators.Enums;
/// <summary>
/// 枚举扩展方法生成器,用于自动生成枚举相关的扩展方法
/// </summary>
[Generator]
public sealed class EnumExtensionsGenerator : AttributeEnumGeneratorBase
{
private static string AttributeMetadataName =>
$"{PathContests.SourceGeneratorsAbstractionsPath}.Enums.GenerateEnumExtensionsAttribute";
/// <summary>
/// 仅用于 Syntax 粗筛选
/// </summary>
protected override string AttributeShortNameWithoutSuffix => "GenerateEnumExtensions";
protected override AttributeData? ResolveAttribute(Compilation compilation, INamedTypeSymbol symbol)
{
var attrSymbol = compilation.GetTypeByMetadataName(AttributeMetadataName);
if (attrSymbol is null)
return null;
return symbol.GetAttributes()
.FirstOrDefault(a =>
SymbolEqualityComparer.Default.Equals(a.AttributeClass, attrSymbol));
}
protected override bool ValidateSymbol(SourceProductionContext context, Compilation compilation,
EnumDeclarationSyntax syntax,
INamedTypeSymbol symbol, AttributeData attr)
{
if (symbol.TypeKind == TypeKind.Enum) return true;
var loc = syntax.Identifier.GetLocation();
context.ReportDiagnostic(Diagnostic.Create(
CommonDiagnostics.ClassMustBePartial,
loc,
symbol.Name
));
return false;
}
/// <summary>
/// 生成枚举扩展方法的源代码
/// </summary>
/// <param name="symbol">枚举类型符号</param>
/// <param name="attr">属性数据</param>
/// <returns>生成的源代码字符串</returns>
protected override string Generate(INamedTypeSymbol symbol, AttributeData attr)
{
var ns = symbol.ContainingNamespace.IsGlobalNamespace
? null
: symbol.ContainingNamespace.ToDisplayString();
var enumName = symbol.Name;
var fullEnumName = symbol.ToDisplayString();
var members = symbol.GetMembers()
.OfType<IFieldSymbol>()
.Where(f => f.ConstantValue != null)
.ToArray();
var sb = new StringBuilder();
sb.AppendLine("// <auto-generated />");
sb.AppendLine("using System;");
sb.AppendLine(!string.IsNullOrEmpty(ns) ? $"namespace {ns}" : "namespace EnumExtensionsGenerated");
sb.AppendLine("{");
sb.AppendLine($" public static partial class {enumName}Extensions");
sb.AppendLine(" {");
// 生成 IsX 方法
foreach (var memberName in members.Select(m => m.Name))
{
sb.AppendLine($" /// <summary>是否为 {memberName}</summary>");
sb.AppendLine(
$" public static bool Is{memberName}(this {fullEnumName} value) => value == {fullEnumName}.{memberName};");
sb.AppendLine();
}
// 生成 IsIn 方法
sb.AppendLine(" /// <summary>判断是否属于指定集合</summary>");
sb.AppendLine($" public static bool IsIn(this {fullEnumName} value, params {fullEnumName}[] values)");
sb.AppendLine(" {");
sb.AppendLine(" if (values == null) return false;");
sb.AppendLine(" foreach (var v in values) if (value == v) return true;");
sb.AppendLine(" return false;");
sb.AppendLine(" }");
sb.AppendLine(" }");
sb.AppendLine("}"); // namespace
return sb.ToString();
}
/// <summary>
/// 获取生成文件的提示名称
/// </summary>
/// <param name="symbol">命名类型符号</param>
/// <returns>生成文件的提示名称</returns>
protected override string GetHintName(INamedTypeSymbol symbol)
{
return $"{symbol.Name}.EnumExtensions.g.cs";
}
}

View File

@ -29,21 +29,24 @@
<None Remove="GFramework.Core\**"/> <None Remove="GFramework.Core\**"/>
<None Remove="GFramework.Game\**"/> <None Remove="GFramework.Game\**"/>
<None Remove="GFramework.Godot\**"/> <None Remove="GFramework.Godot\**"/>
<None Update="GFramework.SourceGenerators\logging\README.md"> <None Update="GFramework.Core.SourceGenerators\logging\README.md">
<Link>GFramework.SorceGenerators\logging\README.md</Link> <Link>GFramework.Core.SourceGenerators\logging\README.md</Link>
</None> </None>
<None Update="GFramework.SourceGenerators\README.md"> <None Update="GFramework.Core.SourceGenerators\README.md">
<Link>GFramework.SorceGenerators\README.md</Link> <Link>GFramework.Core.SourceGenerators\README.md</Link>
</None> </None>
<None Update="GFramework.SourceGenerators\AnalyzerReleases.Shipped.md"> <None Update="GFramework.Core.SourceGenerators\AnalyzerReleases.Shipped.md">
<Link>GFramework.SorceGenerators\AnalyzerReleases.Shipped.md</Link> <Link>GFramework.Core.SourceGenerators\AnalyzerReleases.Shipped.md</Link>
</None> </None>
<None Update="GFramework.SourceGenerators\AnalyzerReleases.Unshipped.md"> <None Update="GFramework.Core.SourceGenerators\AnalyzerReleases.Unshipped.md">
<Link>GFramework.SorceGenerators\AnalyzerReleases.Unshipped.md</Link> <Link>GFramework.Core.SourceGenerators\AnalyzerReleases.Unshipped.md</Link>
</None> </None>
<None Remove="GFramework.Godot.SourceGenerators\**"/> <None Remove="GFramework.Godot.SourceGenerators\**"/>
<None Remove="GFramework.SorceGenerators\**"/>
<None Remove="GFramework.SourceGenerators\**"/> <None Remove="GFramework.SourceGenerators\**"/>
<None Remove="GFramework.Core.SourceGenerators\**"/>
<None Remove="GFramework.Core.SourceGenerators.Abstractions\**"/>
<None Remove="GFramework.Cqrs.SourceGenerators\**"/>
<None Remove="GFramework.Game.SourceGenerators\**"/>
<None Remove="GFramework.SourceGenerators.Common\**"/> <None Remove="GFramework.SourceGenerators.Common\**"/>
<None Remove="GFramework.SourceGenerators.Tests\**"/> <None Remove="GFramework.SourceGenerators.Tests\**"/>
<None Remove="GFramework.Godot.SourceGenerators.Tests\**"/> <None Remove="GFramework.Godot.SourceGenerators.Tests\**"/>
@ -77,18 +80,21 @@
<Compile Remove="GFramework.Core\**"/> <Compile Remove="GFramework.Core\**"/>
<Compile Remove="GFramework.Game\**"/> <Compile Remove="GFramework.Game\**"/>
<Compile Remove="GFramework.Godot\**"/> <Compile Remove="GFramework.Godot\**"/>
<Compile Update="GFramework.SourceGenerators\enums\EnumExtensionsGenerator.cs"> <Compile Update="GFramework.Core.SourceGenerators\enums\EnumExtensionsGenerator.cs">
<Link>GFramework.SorceGenerators\enums\EnumExtensionsGenerator.cs</Link> <Link>GFramework.Core.SourceGenerators\enums\EnumExtensionsGenerator.cs</Link>
</Compile> </Compile>
<Compile Update="GFramework.SourceGenerators\logging\Diagnostic.cs"> <Compile Update="GFramework.Core.SourceGenerators\logging\Diagnostic.cs">
<Link>GFramework.SorceGenerators\logging\Diagnostic.cs</Link> <Link>GFramework.Core.SourceGenerators\logging\Diagnostic.cs</Link>
</Compile> </Compile>
<Compile Update="GFramework.SourceGenerators\logging\LoggerGenerator.cs"> <Compile Update="GFramework.Core.SourceGenerators\logging\LoggerGenerator.cs">
<Link>GFramework.SorceGenerators\logging\LoggerGenerator.cs</Link> <Link>GFramework.Core.SourceGenerators\logging\LoggerGenerator.cs</Link>
</Compile> </Compile>
<Compile Remove="GFramework.Godot.SourceGenerators\**"/> <Compile Remove="GFramework.Godot.SourceGenerators\**"/>
<Compile Remove="GFramework.SorceGenerators\**"/>
<Compile Remove="GFramework.SourceGenerators\**"/> <Compile Remove="GFramework.SourceGenerators\**"/>
<Compile Remove="GFramework.Core.SourceGenerators\**"/>
<Compile Remove="GFramework.Core.SourceGenerators.Abstractions\**"/>
<Compile Remove="GFramework.Cqrs.SourceGenerators\**"/>
<Compile Remove="GFramework.Game.SourceGenerators\**"/>
<Compile Remove="GFramework.SourceGenerators.Common\**"/> <Compile Remove="GFramework.SourceGenerators.Common\**"/>
<Compile Remove="GFramework.SourceGenerators.Tests\**"/> <Compile Remove="GFramework.SourceGenerators.Tests\**"/>
<Compile Remove="GFramework.Godot.SourceGenerators.Tests\**"/> <Compile Remove="GFramework.Godot.SourceGenerators.Tests\**"/>
@ -118,8 +124,11 @@
<EmbeddedResource Remove="GFramework.Game\**"/> <EmbeddedResource Remove="GFramework.Game\**"/>
<EmbeddedResource Remove="GFramework.Godot\**"/> <EmbeddedResource Remove="GFramework.Godot\**"/>
<EmbeddedResource Remove="GFramework.Godot.SourceGenerators\**"/> <EmbeddedResource Remove="GFramework.Godot.SourceGenerators\**"/>
<EmbeddedResource Remove="GFramework.SorceGenerators\**"/>
<EmbeddedResource Remove="GFramework.SourceGenerators\**"/> <EmbeddedResource Remove="GFramework.SourceGenerators\**"/>
<EmbeddedResource Remove="GFramework.Core.SourceGenerators\**"/>
<EmbeddedResource Remove="GFramework.Core.SourceGenerators.Abstractions\**"/>
<EmbeddedResource Remove="GFramework.Cqrs.SourceGenerators\**"/>
<EmbeddedResource Remove="GFramework.Game.SourceGenerators\**"/>
<EmbeddedResource Remove="GFramework.SourceGenerators.Common\**"/> <EmbeddedResource Remove="GFramework.SourceGenerators.Common\**"/>
<EmbeddedResource Remove="GFramework.SourceGenerators.Tests\**"/> <EmbeddedResource Remove="GFramework.SourceGenerators.Tests\**"/>
<EmbeddedResource Remove="GFramework.Godot.SourceGenerators.Tests\**"/> <EmbeddedResource Remove="GFramework.Godot.SourceGenerators.Tests\**"/>

View File

@ -2,10 +2,6 @@
Microsoft Visual Studio Solution File, Format Version 12.00 Microsoft Visual Studio Solution File, Format Version 12.00
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GFramework", "GFramework.csproj", "{9BEDDD6C-DF8B-4E71-9C75-F44EC669ABBD}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GFramework", "GFramework.csproj", "{9BEDDD6C-DF8B-4E71-9C75-F44EC669ABBD}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GFramework.SourceGenerators", "GFramework.SourceGenerators\GFramework.SourceGenerators.csproj", "{E9D51809-0351-4B83-B85B-B5F469AAB3B8}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GFramework.SourceGenerators.Abstractions", "GFramework.SourceGenerators.Abstractions\GFramework.SourceGenerators.Abstractions.csproj", "{84C5C3C9-5620-4924-BA04-92F813F2B70F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GFramework.Core", "GFramework.Core\GFramework.Core.csproj", "{A6D5854D-79EA-487A-9ED9-396E6A1F8031}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GFramework.Core", "GFramework.Core\GFramework.Core.csproj", "{A6D5854D-79EA-487A-9ED9-396E6A1F8031}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GFramework.Godot", "GFramework.Godot\GFramework.Godot.csproj", "{FC56D81A-3A3B-4B49-B318-363DFA0D8206}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GFramework.Godot", "GFramework.Godot\GFramework.Godot.csproj", "{FC56D81A-3A3B-4B49-B318-363DFA0D8206}"
@ -46,6 +42,14 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GFramework.Cqrs.Tests", "GF
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GFramework.Tests.Common", "GFramework.Tests.Common\GFramework.Tests.Common.csproj", "{1100EE3E-A12D-4DE5-ABA8-591D3126570B}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GFramework.Tests.Common", "GFramework.Tests.Common\GFramework.Tests.Common.csproj", "{1100EE3E-A12D-4DE5-ABA8-591D3126570B}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GFramework.Core.SourceGenerators", "GFramework.Core.SourceGenerators\GFramework.Core.SourceGenerators.csproj", "{2E8A4BB6-DA58-484F-ACC5-A8F2FA885B36}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GFramework.Core.SourceGenerators.Abstractions", "GFramework.Core.SourceGenerators.Abstractions\GFramework.Core.SourceGenerators.Abstractions.csproj", "{8858F489-4EDD-41F1-9A74-1CA1CB287EB4}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GFramework.Cqrs.SourceGenerators", "GFramework.Cqrs.SourceGenerators\GFramework.Cqrs.SourceGenerators.csproj", "{3FDCD803-604F-48D9-B2A8-2EC621E8D598}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GFramework.Game.SourceGenerators", "GFramework.Game.SourceGenerators\GFramework.Game.SourceGenerators.csproj", "{9D3AADF0-55E6-4F80-B9C5-875F63E170D8}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@ -68,30 +72,6 @@ Global
{9BEDDD6C-DF8B-4E71-9C75-F44EC669ABBD}.Release|x64.Build.0 = Release|Any CPU {9BEDDD6C-DF8B-4E71-9C75-F44EC669ABBD}.Release|x64.Build.0 = Release|Any CPU
{9BEDDD6C-DF8B-4E71-9C75-F44EC669ABBD}.Release|x86.ActiveCfg = Release|Any CPU {9BEDDD6C-DF8B-4E71-9C75-F44EC669ABBD}.Release|x86.ActiveCfg = Release|Any CPU
{9BEDDD6C-DF8B-4E71-9C75-F44EC669ABBD}.Release|x86.Build.0 = Release|Any CPU {9BEDDD6C-DF8B-4E71-9C75-F44EC669ABBD}.Release|x86.Build.0 = Release|Any CPU
{E9D51809-0351-4B83-B85B-B5F469AAB3B8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E9D51809-0351-4B83-B85B-B5F469AAB3B8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E9D51809-0351-4B83-B85B-B5F469AAB3B8}.Debug|x64.ActiveCfg = Debug|Any CPU
{E9D51809-0351-4B83-B85B-B5F469AAB3B8}.Debug|x64.Build.0 = Debug|Any CPU
{E9D51809-0351-4B83-B85B-B5F469AAB3B8}.Debug|x86.ActiveCfg = Debug|Any CPU
{E9D51809-0351-4B83-B85B-B5F469AAB3B8}.Debug|x86.Build.0 = Debug|Any CPU
{E9D51809-0351-4B83-B85B-B5F469AAB3B8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E9D51809-0351-4B83-B85B-B5F469AAB3B8}.Release|Any CPU.Build.0 = Release|Any CPU
{E9D51809-0351-4B83-B85B-B5F469AAB3B8}.Release|x64.ActiveCfg = Release|Any CPU
{E9D51809-0351-4B83-B85B-B5F469AAB3B8}.Release|x64.Build.0 = Release|Any CPU
{E9D51809-0351-4B83-B85B-B5F469AAB3B8}.Release|x86.ActiveCfg = Release|Any CPU
{E9D51809-0351-4B83-B85B-B5F469AAB3B8}.Release|x86.Build.0 = Release|Any CPU
{84C5C3C9-5620-4924-BA04-92F813F2B70F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{84C5C3C9-5620-4924-BA04-92F813F2B70F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{84C5C3C9-5620-4924-BA04-92F813F2B70F}.Debug|x64.ActiveCfg = Debug|Any CPU
{84C5C3C9-5620-4924-BA04-92F813F2B70F}.Debug|x64.Build.0 = Debug|Any CPU
{84C5C3C9-5620-4924-BA04-92F813F2B70F}.Debug|x86.ActiveCfg = Debug|Any CPU
{84C5C3C9-5620-4924-BA04-92F813F2B70F}.Debug|x86.Build.0 = Debug|Any CPU
{84C5C3C9-5620-4924-BA04-92F813F2B70F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{84C5C3C9-5620-4924-BA04-92F813F2B70F}.Release|Any CPU.Build.0 = Release|Any CPU
{84C5C3C9-5620-4924-BA04-92F813F2B70F}.Release|x64.ActiveCfg = Release|Any CPU
{84C5C3C9-5620-4924-BA04-92F813F2B70F}.Release|x64.Build.0 = Release|Any CPU
{84C5C3C9-5620-4924-BA04-92F813F2B70F}.Release|x86.ActiveCfg = Release|Any CPU
{84C5C3C9-5620-4924-BA04-92F813F2B70F}.Release|x86.Build.0 = Release|Any CPU
{A6D5854D-79EA-487A-9ED9-396E6A1F8031}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {A6D5854D-79EA-487A-9ED9-396E6A1F8031}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A6D5854D-79EA-487A-9ED9-396E6A1F8031}.Debug|Any CPU.Build.0 = Debug|Any CPU {A6D5854D-79EA-487A-9ED9-396E6A1F8031}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A6D5854D-79EA-487A-9ED9-396E6A1F8031}.Debug|x64.ActiveCfg = Debug|Any CPU {A6D5854D-79EA-487A-9ED9-396E6A1F8031}.Debug|x64.ActiveCfg = Debug|Any CPU
@ -332,6 +312,54 @@ Global
{1100EE3E-A12D-4DE5-ABA8-591D3126570B}.Release|x64.Build.0 = Release|Any CPU {1100EE3E-A12D-4DE5-ABA8-591D3126570B}.Release|x64.Build.0 = Release|Any CPU
{1100EE3E-A12D-4DE5-ABA8-591D3126570B}.Release|x86.ActiveCfg = Release|Any CPU {1100EE3E-A12D-4DE5-ABA8-591D3126570B}.Release|x86.ActiveCfg = Release|Any CPU
{1100EE3E-A12D-4DE5-ABA8-591D3126570B}.Release|x86.Build.0 = Release|Any CPU {1100EE3E-A12D-4DE5-ABA8-591D3126570B}.Release|x86.Build.0 = Release|Any CPU
{2E8A4BB6-DA58-484F-ACC5-A8F2FA885B36}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2E8A4BB6-DA58-484F-ACC5-A8F2FA885B36}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2E8A4BB6-DA58-484F-ACC5-A8F2FA885B36}.Debug|x64.ActiveCfg = Debug|Any CPU
{2E8A4BB6-DA58-484F-ACC5-A8F2FA885B36}.Debug|x64.Build.0 = Debug|Any CPU
{2E8A4BB6-DA58-484F-ACC5-A8F2FA885B36}.Debug|x86.ActiveCfg = Debug|Any CPU
{2E8A4BB6-DA58-484F-ACC5-A8F2FA885B36}.Debug|x86.Build.0 = Debug|Any CPU
{2E8A4BB6-DA58-484F-ACC5-A8F2FA885B36}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2E8A4BB6-DA58-484F-ACC5-A8F2FA885B36}.Release|Any CPU.Build.0 = Release|Any CPU
{2E8A4BB6-DA58-484F-ACC5-A8F2FA885B36}.Release|x64.ActiveCfg = Release|Any CPU
{2E8A4BB6-DA58-484F-ACC5-A8F2FA885B36}.Release|x64.Build.0 = Release|Any CPU
{2E8A4BB6-DA58-484F-ACC5-A8F2FA885B36}.Release|x86.ActiveCfg = Release|Any CPU
{2E8A4BB6-DA58-484F-ACC5-A8F2FA885B36}.Release|x86.Build.0 = Release|Any CPU
{8858F489-4EDD-41F1-9A74-1CA1CB287EB4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8858F489-4EDD-41F1-9A74-1CA1CB287EB4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8858F489-4EDD-41F1-9A74-1CA1CB287EB4}.Debug|x64.ActiveCfg = Debug|Any CPU
{8858F489-4EDD-41F1-9A74-1CA1CB287EB4}.Debug|x64.Build.0 = Debug|Any CPU
{8858F489-4EDD-41F1-9A74-1CA1CB287EB4}.Debug|x86.ActiveCfg = Debug|Any CPU
{8858F489-4EDD-41F1-9A74-1CA1CB287EB4}.Debug|x86.Build.0 = Debug|Any CPU
{8858F489-4EDD-41F1-9A74-1CA1CB287EB4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8858F489-4EDD-41F1-9A74-1CA1CB287EB4}.Release|Any CPU.Build.0 = Release|Any CPU
{8858F489-4EDD-41F1-9A74-1CA1CB287EB4}.Release|x64.ActiveCfg = Release|Any CPU
{8858F489-4EDD-41F1-9A74-1CA1CB287EB4}.Release|x64.Build.0 = Release|Any CPU
{8858F489-4EDD-41F1-9A74-1CA1CB287EB4}.Release|x86.ActiveCfg = Release|Any CPU
{8858F489-4EDD-41F1-9A74-1CA1CB287EB4}.Release|x86.Build.0 = Release|Any CPU
{3FDCD803-604F-48D9-B2A8-2EC621E8D598}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3FDCD803-604F-48D9-B2A8-2EC621E8D598}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3FDCD803-604F-48D9-B2A8-2EC621E8D598}.Debug|x64.ActiveCfg = Debug|Any CPU
{3FDCD803-604F-48D9-B2A8-2EC621E8D598}.Debug|x64.Build.0 = Debug|Any CPU
{3FDCD803-604F-48D9-B2A8-2EC621E8D598}.Debug|x86.ActiveCfg = Debug|Any CPU
{3FDCD803-604F-48D9-B2A8-2EC621E8D598}.Debug|x86.Build.0 = Debug|Any CPU
{3FDCD803-604F-48D9-B2A8-2EC621E8D598}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3FDCD803-604F-48D9-B2A8-2EC621E8D598}.Release|Any CPU.Build.0 = Release|Any CPU
{3FDCD803-604F-48D9-B2A8-2EC621E8D598}.Release|x64.ActiveCfg = Release|Any CPU
{3FDCD803-604F-48D9-B2A8-2EC621E8D598}.Release|x64.Build.0 = Release|Any CPU
{3FDCD803-604F-48D9-B2A8-2EC621E8D598}.Release|x86.ActiveCfg = Release|Any CPU
{3FDCD803-604F-48D9-B2A8-2EC621E8D598}.Release|x86.Build.0 = Release|Any CPU
{9D3AADF0-55E6-4F80-B9C5-875F63E170D8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9D3AADF0-55E6-4F80-B9C5-875F63E170D8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9D3AADF0-55E6-4F80-B9C5-875F63E170D8}.Debug|x64.ActiveCfg = Debug|Any CPU
{9D3AADF0-55E6-4F80-B9C5-875F63E170D8}.Debug|x64.Build.0 = Debug|Any CPU
{9D3AADF0-55E6-4F80-B9C5-875F63E170D8}.Debug|x86.ActiveCfg = Debug|Any CPU
{9D3AADF0-55E6-4F80-B9C5-875F63E170D8}.Debug|x86.Build.0 = Debug|Any CPU
{9D3AADF0-55E6-4F80-B9C5-875F63E170D8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9D3AADF0-55E6-4F80-B9C5-875F63E170D8}.Release|Any CPU.Build.0 = Release|Any CPU
{9D3AADF0-55E6-4F80-B9C5-875F63E170D8}.Release|x64.ActiveCfg = Release|Any CPU
{9D3AADF0-55E6-4F80-B9C5-875F63E170D8}.Release|x64.Build.0 = Release|Any CPU
{9D3AADF0-55E6-4F80-B9C5-875F63E170D8}.Release|x86.ActiveCfg = Release|Any CPU
{9D3AADF0-55E6-4F80-B9C5-875F63E170D8}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE

View File

@ -68,8 +68,11 @@ dotnet add package GeWuYou.GFramework.Game.Abstractions
# Godot 集成(仅 Godot 项目需要) # Godot 集成(仅 Godot 项目需要)
dotnet add package GeWuYou.GFramework.Godot dotnet add package GeWuYou.GFramework.Godot
# 源码生成器(可选,但推荐) # 按场景选择源码生成器(可选,但推荐)
dotnet add package GeWuYou.GFramework.SourceGenerators dotnet add package GeWuYou.GFramework.Core.SourceGenerators
dotnet add package GeWuYou.GFramework.Game.SourceGenerators
dotnet add package GeWuYou.GFramework.Godot.SourceGenerators
dotnet add package GeWuYou.GFramework.Cqrs.SourceGenerators
``` ```
## 可选模块导入 ## 可选模块导入

View File

@ -246,9 +246,10 @@ public class GameArchitecture : Architecture
优先使用程序集级生成注册器,失败时自动回退到反射扫描;如果同一程序集已经由默认路径或其他模块接入,框架会自动去重,避免重复注册 优先使用程序集级生成注册器,失败时自动回退到反射扫描;如果同一程序集已经由默认路径或其他模块接入,框架会自动去重,避免重复注册
handler。 handler。
`RegisterCqrsPipelineBehavior<TBehavior>()` 是推荐入口;旧的 `RegisterMediatorBehavior<TBehavior>()` `RegisterCqrsPipelineBehavior<TBehavior>()` 是唯一保留的公开入口;旧的 `Mediator` 兼容别名与扩展已移除,不再继续维护。
仅作为兼容名称保留,当前已标记为 `Obsolete` 并从 IntelliSense 主路径隐藏,计划在未来 major 版本中移除。 如果你正在从旧版本迁移,显式替换关系就是
`ContextAwareMediator*Extensions``MediatorCoroutineExtensions` 也遵循同样的弃用节奏。当前接口支持两种形式: `RegisterMediatorBehavior<TBehavior>() -> RegisterCqrsPipelineBehavior<TBehavior>()`
当前接口支持两种形式:
- 开放泛型行为,例如 `LoggingBehavior<,>`,用于匹配所有请求 - 开放泛型行为,例如 `LoggingBehavior<,>`,用于匹配所有请求
- 封闭行为类型,例如某个只服务于单一请求的 `SpecialBehavior` - 封闭行为类型,例如某个只服务于单一请求的 `SpecialBehavior`

View File

@ -153,31 +153,36 @@ GameProject/
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\GFramework.Game\GFramework.Game.csproj" /> <ProjectReference Include="..\GFramework.Game\GFramework.Game.csproj" />
<ProjectReference Include="..\GFramework.SourceGenerators.Abstractions\GFramework.SourceGenerators.Abstractions.csproj" <ProjectReference Include="..\GFramework.Core.SourceGenerators.Abstractions\GFramework.Core.SourceGenerators.Abstractions.csproj" />
OutputItemType="Analyzer"
ReferenceOutputAssembly="false" />
<ProjectReference Include="..\GFramework.SourceGenerators.Common\GFramework.SourceGenerators.Common.csproj" <ProjectReference Include="..\GFramework.SourceGenerators.Common\GFramework.SourceGenerators.Common.csproj"
OutputItemType="Analyzer" OutputItemType="Analyzer"
ReferenceOutputAssembly="false" /> ReferenceOutputAssembly="false" />
<ProjectReference Include="..\GFramework.SourceGenerators\GFramework.SourceGenerators.csproj" <ProjectReference Include="..\GFramework.Core.SourceGenerators\GFramework.Core.SourceGenerators.csproj"
OutputItemType="Analyzer"
ReferenceOutputAssembly="false" />
<ProjectReference Include="..\GFramework.Game.SourceGenerators\GFramework.Game.SourceGenerators.csproj"
OutputItemType="Analyzer" OutputItemType="Analyzer"
ReferenceOutputAssembly="false" /> ReferenceOutputAssembly="false" />
</ItemGroup> </ItemGroup>
<Import Project="..\GFramework.SourceGenerators\GeWuYou.GFramework.SourceGenerators.targets" /> <Import Project="..\GFramework.Game.SourceGenerators\GeWuYou.GFramework.Game.SourceGenerators.targets" />
</Project> </Project>
``` ```
这段配置的作用: 这段配置的作用:
- `GFramework.Game` 提供运行时 `YamlConfigLoader``ConfigRegistry``GameConfigBootstrap``GameConfigModule` 和只读表实现 - `GFramework.Game` 提供运行时 `YamlConfigLoader``ConfigRegistry``GameConfigBootstrap``GameConfigModule` 和只读表实现
- 三个 `ProjectReference(... OutputItemType="Analyzer")` 把生成器接进当前消费者项目 - `GFramework.Core.SourceGenerators.Abstractions` 提供 `Core` 侧 source-generator attributes
- `GeWuYou.GFramework.SourceGenerators.targets` 自动把 `schemas/**/*.schema.json` 加入 `AdditionalFiles` - `GFramework.SourceGenerators.Common``GFramework.Core.SourceGenerators``GFramework.Game.SourceGenerators`
共同把生成器接进当前消费者项目
- `GeWuYou.GFramework.Game.SourceGenerators.targets` 自动把 `schemas/**/*.schema.json` 加入 `AdditionalFiles`
如果你使用打包后的 NuGet而不是仓库内项目引用原则保持不变 如果你使用打包后的 NuGet而不是仓库内项目引用原则保持不变
- 运行时项目需要引用 `GeWuYou.GFramework.Game` - 运行时项目需要引用 `GeWuYou.GFramework.Game`
- 生成器项目需要引用 `GeWuYou.GFramework.SourceGenerators` - 配置 schema 生成器需要引用 `GeWuYou.GFramework.Game.SourceGenerators`
- 如果同一项目还会使用 `[Log]``[ContextAware]``[GetSystem]` 等 Core 侧生成器特性,再额外引用
`GeWuYou.GFramework.Core.SourceGenerators`
- schema 目录默认仍然是 `schemas/` - schema 目录默认仍然是 `schemas/`
如果你的 schema 不放在默认目录,可以在项目文件里覆盖: 如果你的 schema 不放在默认目录,可以在项目文件里覆盖:

View File

@ -6,13 +6,18 @@ GFramework 提供多种安装方式,您可以根据项目需求选择合适的
GFramework 采用模块化设计,不同包提供不同的功能: GFramework 采用模块化设计,不同包提供不同的功能:
| 包名 | 说明 | 适用场景 | | 包名 | 说明 | 适用场景 |
|---------------------------------------|---------|-----------| |---------------------------------------------|-------------|--------------------------------|
| `GeWuYou.GFramework` | 聚合元包 | 快速试用、原型开发 | | `GeWuYou.GFramework` | 聚合元包 | 快速试用、原型开发 |
| `GeWuYou.GFramework.Core` | 核心框架 | 生产项目推荐 | | `GeWuYou.GFramework.Core` | 核心框架 | 生产项目推荐 |
| `GeWuYou.GFramework.Game` | 游戏模块 | 需要游戏特定功能 | | `GeWuYou.GFramework.Game` | 游戏模块 | 需要游戏特定功能 |
| `GeWuYou.GFramework.Godot` | Godot集成 | Godot项目必需 | | `GeWuYou.GFramework.Godot` | Godot集成 | Godot项目必需 |
| `GeWuYou.GFramework.SourceGenerators` | 源码生成器 | 推荐安装 | | `GeWuYou.GFramework.Core.SourceGenerators` | Core 源码生成器 | `[Log]``[ContextAware]`、架构注入等 |
| `GeWuYou.GFramework.Game.SourceGenerators` | Game 源码生成器 | 配置 schema / 配表生成 |
| `GeWuYou.GFramework.Godot.SourceGenerators` | Godot 源码生成器 | Godot 节点、UI、项目元数据生成 |
| `GeWuYou.GFramework.Cqrs.SourceGenerators` | CQRS 源码生成器 | 处理器注册表生成 |
当前 NuGet 发布按模块拆分 source generator 包,不存在 `GeWuYou.GFramework.SourceGenerators` 聚合包。
## 安装方式 ## 安装方式
@ -30,8 +35,17 @@ dotnet add package GeWuYou.GFramework.Game.Abstractions
# Godot 集成(仅 Godot 项目需要) # Godot 集成(仅 Godot 项目需要)
dotnet add package GeWuYou.GFramework.Godot dotnet add package GeWuYou.GFramework.Godot
# 源码生成器(可选,但推荐) # Core 侧源码生成器([Log] / [ContextAware] / [GetSystem] 等)
dotnet add package GeWuYou.GFramework.SourceGenerators dotnet add package GeWuYou.GFramework.Core.SourceGenerators
# Game 配置 schema 生成器
dotnet add package GeWuYou.GFramework.Game.SourceGenerators
# Godot 生成器(仅 Godot 项目需要)
dotnet add package GeWuYou.GFramework.Godot.SourceGenerators
# CQRS 处理器注册生成器(仅使用 CQRS source generator 时需要)
dotnet add package GeWuYou.GFramework.Cqrs.SourceGenerators
``` ```
### 2. 使用 PackageReference ### 2. 使用 PackageReference
@ -56,8 +70,14 @@ dotnet add package GeWuYou.GFramework.SourceGenerators
<!-- Godot 集成 --> <!-- Godot 集成 -->
<PackageReference Include="GeWuYou.GFramework.Godot" Version="1.0.0" /> <PackageReference Include="GeWuYou.GFramework.Godot" Version="1.0.0" />
<!-- 源码生成器 --> <!-- 按场景选择的源码生成器 -->
<PackageReference Include="GeWuYou.GFramework.SourceGenerators" Version="1.0.0" <PackageReference Include="GeWuYou.GFramework.Core.SourceGenerators" Version="1.0.0"
PrivateAssets="all" ExcludeAssets="runtime" />
<PackageReference Include="GeWuYou.GFramework.Game.SourceGenerators" Version="1.0.0"
PrivateAssets="all" ExcludeAssets="runtime" />
<PackageReference Include="GeWuYou.GFramework.Godot.SourceGenerators" Version="1.0.0"
PrivateAssets="all" ExcludeAssets="runtime" />
<PackageReference Include="GeWuYou.GFramework.Cqrs.SourceGenerators" Version="1.0.0"
PrivateAssets="all" ExcludeAssets="runtime" /> PrivateAssets="all" ExcludeAssets="runtime" />
</ItemGroup> </ItemGroup>
</Project> </Project>
@ -183,6 +203,8 @@ dotnet build
检查: 检查:
- 确保安装了 `GeWuYou.GFramework.SourceGenerators` - 确保安装了与你正在使用的特性对应的拆分生成器包,例如:
`GeWuYou.GFramework.Core.SourceGenerators``GeWuYou.GFramework.Game.SourceGenerators`
`GeWuYou.GFramework.Godot.SourceGenerators``GeWuYou.GFramework.Cqrs.SourceGenerators`
- 重启 IDE - 重启 IDE
- 清理并重新构建项目 - 清理并重新构建项目

View File

@ -30,7 +30,16 @@ GFramework.SourceGenerators 是 GFramework 框架的源代码生成器包,通
## 概述 ## 概述
GFramework.SourceGenerators 利用 Roslyn 源代码生成器技术,在编译时分析你的代码并自动生成常用的样板代码,让开发者专注于业务逻辑而不是重复的模板代码。 GFramework 的 source generators 利用 Roslyn 源代码生成器技术,在编译时分析你的代码并自动生成常用的样板代码,让开发者专注于业务逻辑而不是重复的模板代码。
当前 NuGet 发布按模块拆分为:
- `GeWuYou.GFramework.Core.SourceGenerators`
- `GeWuYou.GFramework.Game.SourceGenerators`
- `GeWuYou.GFramework.Godot.SourceGenerators`
- `GeWuYou.GFramework.Cqrs.SourceGenerators`
不存在 `GeWuYou.GFramework.SourceGenerators``GeWuYou.GFramework.SourceGenerators.Attributes` 这类聚合包。
### 核心设计理念 ### 核心设计理念
@ -74,22 +83,31 @@ GFramework.SourceGenerators 利用 Roslyn 源代码生成器技术,在编译
### NuGet 包安装 ### NuGet 包安装
```xml ```xml
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net6.0</TargetFramework> <TargetFramework>net6.0</TargetFramework>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="GeWuYou.GFramework.SourceGenerators" Version="1.0.0"/> <PackageReference Include="GeWuYou.GFramework.Core.SourceGenerators"
<PackageReference Include="GeWuYou.GFramework.SourceGenerators.Attributes" Version="1.0.0"/> PrivateAssets="all"
ExcludeAssets="runtime" />
<PackageReference Include="GeWuYou.GFramework.Game.SourceGenerators"
PrivateAssets="all"
ExcludeAssets="runtime" />
</ItemGroup> </ItemGroup>
</Project> </Project>
``` ```
如果你只使用 Godot 生成器或 CQRS 处理器注册生成器,请把上面的包替换为对应的
`GeWuYou.GFramework.Godot.SourceGenerators``GeWuYou.GFramework.Cqrs.SourceGenerators`
这些拆分包会同时带上各自需要的 abstractions 程序集,不需要再额外安装单独的 `*.Attributes` 包。
实际接入时请替换为当前发布版本,或与项目中其余 `GeWuYou.GFramework.*` 包保持同一版本。
### Config Schema 文件约定 ### Config Schema 文件约定
当项目引用 `GeWuYou.GFramework.SourceGenerators` 的打包产物时,生成器会默认从 `schemas/**/*.schema.json` 收集配置 schema 当项目引用 `GeWuYou.GFramework.Game.SourceGenerators` 的打包产物时,生成器会默认从 `schemas/**/*.schema.json` 收集配置
schema
文件并作为 `AdditionalFiles` 输入。 文件并作为 `AdditionalFiles` 输入。
这意味着消费者项目通常只需要维护如下结构: 这意味着消费者项目通常只需要维护如下结构:
@ -150,7 +168,7 @@ Config Schema 生成器会扫描 `*.schema.json` 文件,并生成:
### 基础使用 ### 基础使用
```csharp ```csharp
using GFramework.SourceGenerators.Attributes; using GFramework.Core.SourceGenerators.Abstractions.Logging;
[Log] [Log]
public partial class PlayerController public partial class PlayerController
@ -229,7 +247,7 @@ public static partial class MathHelper
```csharp ```csharp
using GFramework.Core.Abstractions.Controller; using GFramework.Core.Abstractions.Controller;
using GFramework.SourceGenerators.Abstractions.Rule; using GFramework.Core.SourceGenerators.Abstractions.Rule;
[ContextAware] [ContextAware]
public partial class PlayerController : IController public partial class PlayerController : IController
@ -337,8 +355,8 @@ public async Task TestPlayerController()
```csharp ```csharp
using GFramework.Core.Abstractions.Controller; using GFramework.Core.Abstractions.Controller;
using GFramework.SourceGenerators.Abstractions.Logging; using GFramework.Core.SourceGenerators.Abstractions.Logging;
using GFramework.SourceGenerators.Abstractions.Rule; using GFramework.Core.SourceGenerators.Abstractions.Rule;
[Log] [Log]
[ContextAware] [ContextAware]
@ -364,7 +382,7 @@ public partial class AdvancedController : IController
### 基础使用 ### 基础使用
```csharp ```csharp
using GFramework.SourceGenerators.Abstractions.Enums; using GFramework.Core.SourceGenerators.Abstractions.Enums;
[GenerateEnumExtensions] [GenerateEnumExtensions]
public enum GameState public enum GameState
@ -543,7 +561,7 @@ AutoRegisterModule 生成器面向 GFramework 模块安装场景,为类上的
### 基础示例 ### 基础示例
```csharp ```csharp
using GFramework.SourceGenerators.Abstractions.Architectures; using GFramework.Core.SourceGenerators.Abstractions.Architectures;
[AutoRegisterModule] [AutoRegisterModule]
[RegisterModel(typeof(RunStateModel))] [RegisterModel(typeof(RunStateModel))]
@ -846,8 +864,8 @@ public class InefficientController : IController
```csharp ```csharp
using GFramework.Core.Abstractions.Controller; using GFramework.Core.Abstractions.Controller;
using GFramework.SourceGenerators.Abstractions.Logging; using GFramework.Core.SourceGenerators.Abstractions.Logging;
using GFramework.SourceGenerators.Abstractions.Rule; using GFramework.Core.SourceGenerators.Abstractions.Rule;
[Log] [Log]
[ContextAware] [ContextAware]
@ -936,8 +954,8 @@ public enum CharacterState
} }
using GFramework.Core.Abstractions.Controller; using GFramework.Core.Abstractions.Controller;
using GFramework.SourceGenerators.Abstractions.Logging; using GFramework.Core.SourceGenerators.Abstractions.Logging;
using GFramework.SourceGenerators.Abstractions.Rule; using GFramework.Core.SourceGenerators.Abstractions.Rule;
[Log] [Log]
[ContextAware] [ContextAware]
@ -1218,11 +1236,14 @@ public partial class ServiceComponent : IService
```mermaid ```mermaid
graph TD graph TD
A[GFramework.SourceGenerators] --> B[GFramework.SourceGenerators.Abstractions] A[GeWuYou.GFramework.Core.SourceGenerators] --> B[GFramework.Core.SourceGenerators.Abstractions]
A --> C[GFramework.SourceGenerators.Common] A --> C[GFramework.SourceGenerators.Common]
A --> D[GFramework.Core.Abstractions] A --> D[GFramework.Core.Abstractions]
A --> E[Microsoft.CodeAnalysis.CSharp] A --> E[Microsoft.CodeAnalysis.CSharp]
A --> F[Microsoft.CodeAnalysis.Analyzers] A --> F[Microsoft.CodeAnalysis.Analyzers]
G[GeWuYou.GFramework.Game.SourceGenerators] --> C
H[GeWuYou.GFramework.Godot.SourceGenerators] --> C
I[GeWuYou.GFramework.Cqrs.SourceGenerators] --> C
``` ```
## 版本兼容性 ## 版本兼容性

View File

@ -74,8 +74,11 @@ dotnet add package GeWuYou.GFramework
# Godot 集成 # Godot 集成
dotnet add package GeWuYou.GFramework.Godot dotnet add package GeWuYou.GFramework.Godot
# 源码生成器(可选,但推荐) # Core 侧源码生成器([Log] / [ContextAware] 等)
dotnet add package GeWuYou.GFramework.SourceGenerators dotnet add package GeWuYou.GFramework.Core.SourceGenerators
# Godot 侧源码生成器([GetNode] / [AutoUiPage] 等)
dotnet add package GeWuYou.GFramework.Godot.SourceGenerators
``` ```
::: details 分包安装(了解即可) ::: details 分包安装(了解即可)
@ -93,8 +96,11 @@ dotnet add package GeWuYou.GFramework.Game.Abstractions
# Godot 集成 # Godot 集成
dotnet add package GeWuYou.GFramework.Godot dotnet add package GeWuYou.GFramework.Godot
# 源码生成器 # Core 侧源码生成器
dotnet add package GeWuYou.GFramework.SourceGenerators dotnet add package GeWuYou.GFramework.Core.SourceGenerators
# Godot 侧源码生成器
dotnet add package GeWuYou.GFramework.Godot.SourceGenerators
``` ```
::: :::
@ -108,7 +114,8 @@ dotnet add package GeWuYou.GFramework.SourceGenerators
3. 安装以下包: 3. 安装以下包:
- `GeWuYou.GFramework` - `GeWuYou.GFramework`
- `GeWuYou.GFramework.Godot` - `GeWuYou.GFramework.Godot`
- `GeWuYou.GFramework.SourceGenerators` - `GeWuYou.GFramework.Core.SourceGenerators`
- `GeWuYou.GFramework.Godot.SourceGenerators`
![NuGet 包管理](../assets/basic/image-20260211211756993.png) ![NuGet 包管理](../assets/basic/image-20260211211756993.png)