From 8cd492506d8bd1c004a10d491e3e854c3a37392d Mon Sep 17 00:00:00 2001
From: gewuyou <95328647+GeWuYou@users.noreply.github.com>
Date: Thu, 23 Apr 2026 13:17:07 +0800
Subject: [PATCH] =?UTF-8?q?refactor(source-generators-tests):=20=E6=94=B6?=
=?UTF-8?q?=E5=8F=A3=20ContextGetGeneratorTests=20=E7=9A=84=20MA0051?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 重构 ContextGetGeneratorTests 的长测试方法为场景常量与验证 helper,保持生成输入和断言语义不变
- 补充测试类与 helper 的 XML 文档,并统一生成源码与诊断断言的复用路径
- 验证 GFramework.SourceGenerators.Tests Release build 与 ContextGetGeneratorTests 过滤测试通过
---
.../Rule/ContextGetGeneratorTests.cs | 2527 +++++++++--------
1 file changed, 1304 insertions(+), 1223 deletions(-)
diff --git a/GFramework.SourceGenerators.Tests/Rule/ContextGetGeneratorTests.cs b/GFramework.SourceGenerators.Tests/Rule/ContextGetGeneratorTests.cs
index bae2d00c..1d015e12 100644
--- a/GFramework.SourceGenerators.Tests/Rule/ContextGetGeneratorTests.cs
+++ b/GFramework.SourceGenerators.Tests/Rule/ContextGetGeneratorTests.cs
@@ -3,743 +3,1351 @@ using GFramework.SourceGenerators.Tests.Core;
namespace GFramework.SourceGenerators.Tests.Rule;
+///
+/// 验证 在显式特性、GetAll 推断与诊断场景下的生成契约。
+///
[TestFixture]
public class ContextGetGeneratorTests
{
+ private const string InventoryPanelGeneratedFileName = "TestApp_InventoryPanel.ContextGet.g.cs";
+ private const string BattlePanelGeneratedFileName = "TestApp_BattlePanel.ContextGet.g.cs";
+ private const string GameplayHudGeneratedFileName = "TestApp_GameplayHud.ContextGet.g.cs";
+ private const string StrategyHostGeneratedFileName = "TestApp_StrategyHost.ContextGet.g.cs";
+
+ private const string ContextAwareAttributeClassSource = """
+ using System;
+ using System.Collections.Generic;
+ using GFramework.Core.SourceGenerators.Abstractions.Rule;
+
+ namespace GFramework.Core.SourceGenerators.Abstractions.Rule
+ {
+ [AttributeUsage(AttributeTargets.Class, Inherited = false)]
+ public sealed class ContextAwareAttribute : Attribute { }
+
+ [AttributeUsage(AttributeTargets.Field, Inherited = false)]
+ public sealed class GetModelAttribute : Attribute { }
+
+ [AttributeUsage(AttributeTargets.Field, Inherited = false)]
+ public sealed class GetServicesAttribute : Attribute { }
+ }
+
+ namespace GFramework.Core.Abstractions.Rule
+ {
+ public interface IContextAware { }
+ }
+
+ namespace GFramework.Core.Abstractions.Model
+ {
+ public interface IModel { }
+ }
+
+ namespace GFramework.Core.Abstractions.Systems
+ {
+ public interface ISystem { }
+ }
+
+ namespace GFramework.Core.Abstractions.Utility
+ {
+ public interface IUtility { }
+ }
+
+ namespace GFramework.Core.Extensions
+ {
+ public static class ContextAwareServiceExtensions
+ {
+ public static T GetModel(this object contextAware) => default!;
+ public static IReadOnlyList GetServices(this object contextAware) => default!;
+ }
+ }
+
+ namespace TestApp
+ {
+ public interface IInventoryModel : GFramework.Core.Abstractions.Model.IModel { }
+ public interface IInventoryStrategy { }
+
+ [ContextAware]
+ public partial class InventoryPanel
+ {
+ [GetModel]
+ private IInventoryModel _model = null!;
+
+ [GetServices]
+ private IReadOnlyList _strategies = null!;
+ }
+ }
+ """;
+
+ private const string ContextAwareAttributeClassExpected = """
+ //
+ #nullable enable
+
+ using GFramework.Core.Extensions;
+
+ namespace TestApp;
+
+ partial class InventoryPanel
+ {
+ private void __InjectContextBindings_Generated()
+ {
+ _model = this.GetModel();
+ _strategies = this.GetServices();
+ }
+ }
+
+ """;
+
+ private const string FullyQualifiedFieldAttributesSource = """
+ using System;
+ using GFramework.Core.SourceGenerators.Abstractions.Rule;
+
+ namespace GFramework.Core.SourceGenerators.Abstractions.Rule
+ {
+ [AttributeUsage(AttributeTargets.Class, Inherited = false)]
+ public sealed class ContextAwareAttribute : Attribute { }
+
+ [AttributeUsage(AttributeTargets.Field, Inherited = false)]
+ public sealed class GetModelAttribute : Attribute { }
+ }
+
+ namespace GFramework.Core.Abstractions.Rule
+ {
+ public interface IContextAware { }
+ }
+
+ namespace GFramework.Core.Abstractions.Model
+ {
+ public interface IModel { }
+ }
+
+ namespace GFramework.Core.Abstractions.Systems
+ {
+ public interface ISystem { }
+ }
+
+ namespace GFramework.Core.Abstractions.Utility
+ {
+ public interface IUtility { }
+ }
+
+ namespace GFramework.Core.Extensions
+ {
+ public static class ContextAwareServiceExtensions
+ {
+ public static T GetModel(this object contextAware) => default!;
+ }
+ }
+
+ namespace TestApp
+ {
+ public interface IInventoryModel : GFramework.Core.Abstractions.Model.IModel { }
+
+ [ContextAware]
+ public partial class InventoryPanel
+ {
+ [global::GFramework.Core.SourceGenerators.Abstractions.Rule.GetModel]
+ private IInventoryModel _model = null!;
+ }
+ }
+ """;
+
+ private const string FullyQualifiedFieldAttributesExpected = """
+ //
+ #nullable enable
+
+ using GFramework.Core.Extensions;
+
+ namespace TestApp;
+
+ partial class InventoryPanel
+ {
+ private void __InjectContextBindings_Generated()
+ {
+ _model = this.GetModel();
+ }
+ }
+
+ """;
+
+ private const string InferredGetAllClassSource = """
+ using System;
+ using System.Collections.Generic;
+ using GFramework.Core.SourceGenerators.Abstractions.Rule;
+
+ namespace GFramework.Core.SourceGenerators.Abstractions.Rule
+ {
+ [AttributeUsage(AttributeTargets.Class, Inherited = false)]
+ public sealed class GetAllAttribute : Attribute { }
+ }
+
+ namespace GFramework.Core.Abstractions.Rule
+ {
+ public interface IContextAware { }
+ }
+
+ namespace GFramework.Core.Abstractions.Architectures
+ {
+ public interface IArchitectureContext { }
+ }
+
+ namespace GFramework.Core.Rule
+ {
+ public abstract class ContextAwareBase : GFramework.Core.Abstractions.Rule.IContextAware { }
+ }
+
+ namespace GFramework.Core.Abstractions.Model
+ {
+ public interface IModel { }
+ }
+
+ namespace GFramework.Core.Abstractions.Systems
+ {
+ public interface ISystem { }
+ }
+
+ namespace GFramework.Core.Abstractions.Utility
+ {
+ public interface IUtility { }
+ }
+
+ namespace GFramework.Core.Extensions
+ {
+ public static class ContextAwareServiceExtensions
+ {
+ public static T GetModel(this object contextAware) => default!;
+ public static IReadOnlyList GetModels(this object contextAware) => default!;
+ public static T GetSystem(this object contextAware) => default!;
+ public static T GetUtility(this object contextAware) => default!;
+ }
+ }
+
+ namespace Godot
+ {
+ public class Node { }
+ }
+
+ namespace TestApp
+ {
+ public interface IInventoryModel : GFramework.Core.Abstractions.Model.IModel { }
+ public interface ICombatSystem : GFramework.Core.Abstractions.Systems.ISystem { }
+ public interface IUiUtility : GFramework.Core.Abstractions.Utility.IUtility { }
+ public interface IStrategy { }
+
+ [GetAll]
+ public partial class BattlePanel : GFramework.Core.Rule.ContextAwareBase
+ {
+ private IInventoryModel _model = null!;
+ private IReadOnlyList _models = null!;
+ private ICombatSystem _system = null!;
+ private IUiUtility _utility = null!;
+ private IStrategy _service = null!;
+ private IReadOnlyList _services = null!;
+ private Godot.Node _node = null!;
+ }
+ }
+ """;
+
+ private const string InferredGetAllClassExpected = """
+ //
+ #nullable enable
+
+ using GFramework.Core.Extensions;
+
+ namespace TestApp;
+
+ partial class BattlePanel
+ {
+ private void __InjectContextBindings_Generated()
+ {
+ _model = this.GetModel();
+ _models = this.GetModels();
+ _system = this.GetSystem();
+ _utility = this.GetUtility();
+ }
+ }
+
+ """;
+
+ private const string ExplicitServiceGetAllClassSource = """
+ using System;
+ using GFramework.Core.SourceGenerators.Abstractions.Rule;
+
+ namespace GFramework.Core.SourceGenerators.Abstractions.Rule
+ {
+ [AttributeUsage(AttributeTargets.Class, Inherited = false)]
+ public sealed class GetAllAttribute : Attribute { }
+
+ [AttributeUsage(AttributeTargets.Field, Inherited = false)]
+ public sealed class GetServiceAttribute : Attribute { }
+ }
+
+ namespace GFramework.Core.Abstractions.Rule
+ {
+ public interface IContextAware { }
+ }
+
+ namespace GFramework.Core.Rule
+ {
+ public abstract class ContextAwareBase : GFramework.Core.Abstractions.Rule.IContextAware { }
+ }
+
+ namespace GFramework.Core.Abstractions.Model
+ {
+ public interface IModel { }
+ }
+
+ namespace GFramework.Core.Abstractions.Systems
+ {
+ public interface ISystem { }
+ }
+
+ namespace GFramework.Core.Abstractions.Utility
+ {
+ public interface IUtility { }
+ }
+
+ namespace GFramework.Core.Extensions
+ {
+ public static class ContextAwareServiceExtensions
+ {
+ public static T GetModel(this object contextAware) => default!;
+ public static T GetService(this object contextAware) => default!;
+ }
+ }
+
+ namespace TestApp
+ {
+ public interface IInventoryModel : GFramework.Core.Abstractions.Model.IModel { }
+ public interface IStrategy { }
+
+ [GetAll]
+ public partial class BattlePanel : GFramework.Core.Rule.ContextAwareBase
+ {
+ private IInventoryModel _model = null!;
+
+ [GetService]
+ private IStrategy _service = null!;
+ }
+ }
+ """;
+
+ private const string ExplicitServiceGetAllClassExpected = """
+ //
+ #nullable enable
+
+ using GFramework.Core.Extensions;
+
+ namespace TestApp;
+
+ partial class BattlePanel
+ {
+ private void __InjectContextBindings_Generated()
+ {
+ _model = this.GetModel();
+ _service = this.GetService();
+ }
+ }
+
+ """;
+
+ private const string GeneratedInjectionMethodAlreadyExistsMarkupSource = """
+ using System;
+ using GFramework.Core.SourceGenerators.Abstractions.Rule;
+
+ namespace GFramework.Core.SourceGenerators.Abstractions.Rule
+ {
+ [AttributeUsage(AttributeTargets.Class, Inherited = false)]
+ public sealed class ContextAwareAttribute : Attribute { }
+
+ [AttributeUsage(AttributeTargets.Field, Inherited = false)]
+ public sealed class GetModelAttribute : Attribute { }
+ }
+
+ namespace GFramework.Core.Abstractions.Rule
+ {
+ public interface IContextAware { }
+ }
+
+ namespace GFramework.Core.Abstractions.Model
+ {
+ public interface IModel { }
+ }
+
+ namespace GFramework.Core.Abstractions.Systems
+ {
+ public interface ISystem { }
+ }
+
+ namespace GFramework.Core.Abstractions.Utility
+ {
+ public interface IUtility { }
+ }
+
+ namespace GFramework.Core.Extensions
+ {
+ public static class ContextAwareServiceExtensions
+ {
+ public static T GetModel(this object contextAware) => default!;
+ }
+ }
+
+ namespace TestApp
+ {
+ public interface IInventoryModel : GFramework.Core.Abstractions.Model.IModel { }
+
+ [ContextAware]
+ public partial class InventoryPanel
+ {
+ [GetModel]
+ private IInventoryModel _model = null!;
+
+ private void {|#0:__InjectContextBindings_Generated|}()
+ {
+ }
+ }
+ }
+ """;
+
+ private const string IgnoreConstFieldGetAllSource = """
+ using System;
+ using GFramework.Core.SourceGenerators.Abstractions.Rule;
+
+ namespace GFramework.Core.SourceGenerators.Abstractions.Rule
+ {
+ [AttributeUsage(AttributeTargets.Class, Inherited = false)]
+ public sealed class GetAllAttribute : Attribute { }
+ }
+
+ namespace GFramework.Core.Abstractions.Rule
+ {
+ public interface IContextAware { }
+ }
+
+ namespace GFramework.Core.Rule
+ {
+ public abstract class ContextAwareBase : GFramework.Core.Abstractions.Rule.IContextAware { }
+ }
+
+ namespace GFramework.Core.Abstractions.Model
+ {
+ public interface IModel { }
+ }
+
+ namespace GFramework.Core.Abstractions.Systems
+ {
+ public interface ISystem { }
+ }
+
+ namespace GFramework.Core.Abstractions.Utility
+ {
+ public interface IUtility { }
+ }
+
+ namespace GFramework.Core.Extensions
+ {
+ public static class ContextAwareServiceExtensions
+ {
+ public static T GetModel(this object contextAware) => default!;
+ }
+ }
+
+ namespace TestApp
+ {
+ public interface IInventoryModel : GFramework.Core.Abstractions.Model.IModel { }
+
+ [GetAll]
+ public partial class BattlePanel : GFramework.Core.Rule.ContextAwareBase
+ {
+ private const double LogicStep = 0.2;
+ private IInventoryModel _model = null!;
+ }
+ }
+ """;
+
+ private const string IgnoreConstFieldGetAllExpected = """
+ //
+ #nullable enable
+
+ using GFramework.Core.Extensions;
+
+ namespace TestApp;
+
+ partial class BattlePanel
+ {
+ private void __InjectContextBindings_Generated()
+ {
+ _model = this.GetModel();
+ }
+ }
+
+ """;
+
+ private const string ReadonlyInferredFieldGetAllMarkupSource = """
+ using System;
+ using GFramework.Core.SourceGenerators.Abstractions.Rule;
+
+ namespace GFramework.Core.SourceGenerators.Abstractions.Rule
+ {
+ [AttributeUsage(AttributeTargets.Class, Inherited = false)]
+ public sealed class GetAllAttribute : Attribute { }
+ }
+
+ namespace GFramework.Core.Abstractions.Rule
+ {
+ public interface IContextAware { }
+ }
+
+ namespace GFramework.Core.Rule
+ {
+ public abstract class ContextAwareBase : GFramework.Core.Abstractions.Rule.IContextAware { }
+ }
+
+ namespace GFramework.Core.Abstractions.Model
+ {
+ public interface IModel { }
+ }
+
+ namespace GFramework.Core.Abstractions.Systems
+ {
+ public interface ISystem { }
+ }
+
+ namespace GFramework.Core.Abstractions.Utility
+ {
+ public interface IUtility { }
+ }
+
+ namespace GFramework.Core.Extensions
+ {
+ public static class ContextAwareServiceExtensions
+ {
+ public static T GetModel(this object contextAware) => default!;
+ public static T GetSystem(this object contextAware) => default!;
+ }
+ }
+
+ namespace TestApp
+ {
+ public interface IInventoryModel : GFramework.Core.Abstractions.Model.IModel { }
+ public interface ICombatSystem : GFramework.Core.Abstractions.Systems.ISystem { }
+
+ [GetAll]
+ public partial class BattlePanel : GFramework.Core.Rule.ContextAwareBase
+ {
+ private readonly IInventoryModel {|#0:_model|} = null!;
+ private ICombatSystem _system = null!;
+ }
+ }
+ """;
+
+ private const string SkipInvalidGetAllFieldExpected = """
+ //
+ #nullable enable
+
+ using GFramework.Core.Extensions;
+
+ namespace TestApp;
+
+ partial class BattlePanel
+ {
+ private void __InjectContextBindings_Generated()
+ {
+ _system = this.GetSystem();
+ }
+ }
+
+ """;
+
+ private const string StaticInferredFieldGetAllMarkupSource = """
+ using System;
+ using GFramework.Core.SourceGenerators.Abstractions.Rule;
+
+ namespace GFramework.Core.SourceGenerators.Abstractions.Rule
+ {
+ [AttributeUsage(AttributeTargets.Class, Inherited = false)]
+ public sealed class GetAllAttribute : Attribute { }
+ }
+
+ namespace GFramework.Core.Abstractions.Rule
+ {
+ public interface IContextAware { }
+ }
+
+ namespace GFramework.Core.Rule
+ {
+ public abstract class ContextAwareBase : GFramework.Core.Abstractions.Rule.IContextAware { }
+ }
+
+ namespace GFramework.Core.Abstractions.Model
+ {
+ public interface IModel { }
+ }
+
+ namespace GFramework.Core.Abstractions.Systems
+ {
+ public interface ISystem { }
+ }
+
+ namespace GFramework.Core.Abstractions.Utility
+ {
+ public interface IUtility { }
+ }
+
+ namespace GFramework.Core.Extensions
+ {
+ public static class ContextAwareServiceExtensions
+ {
+ public static T GetModel(this object contextAware) => default!;
+ public static T GetSystem(this object contextAware) => default!;
+ }
+ }
+
+ namespace TestApp
+ {
+ public interface IInventoryModel : GFramework.Core.Abstractions.Model.IModel { }
+ public interface ICombatSystem : GFramework.Core.Abstractions.Systems.ISystem { }
+
+ [GetAll]
+ public partial class BattlePanel : GFramework.Core.Rule.ContextAwareBase
+ {
+ private static IInventoryModel {|#0:_model|} = null!;
+ private ICombatSystem _system = null!;
+ }
+ }
+ """;
+
+ private const string SkipNullableServiceLikeFieldSource = """
+ using System;
+ using GFramework.Core.SourceGenerators.Abstractions.Rule;
+
+ namespace GFramework.Core.SourceGenerators.Abstractions.Rule
+ {
+ [AttributeUsage(AttributeTargets.Class, Inherited = false)]
+ public sealed class ContextAwareAttribute : Attribute { }
+
+ [AttributeUsage(AttributeTargets.Class, Inherited = false)]
+ public sealed class GetAllAttribute : Attribute { }
+ }
+
+ namespace GFramework.Core.Abstractions.Rule
+ {
+ public interface IContextAware { }
+ }
+
+ namespace GFramework.Core.Abstractions.Model
+ {
+ public interface IModel { }
+ }
+
+ namespace GFramework.Core.Abstractions.Systems
+ {
+ public interface ISystem { }
+ }
+
+ namespace GFramework.Core.Abstractions.Utility
+ {
+ public interface IUtility { }
+ }
+
+ namespace GFramework.Core.Extensions
+ {
+ public static class ContextAwareServiceExtensions
+ {
+ public static T GetModel(this object contextAware) => default!;
+ public static T GetSystem(this object contextAware) => default!;
+ }
+ }
+
+ namespace Godot
+ {
+ public class Control { }
+ }
+
+ namespace TestApp
+ {
+ public interface IGridModel : GFramework.Core.Abstractions.Model.IModel { }
+ public interface IRunLoopSystem : GFramework.Core.Abstractions.Systems.ISystem { }
+ public interface IUiPageBehavior { }
+
+ [ContextAware]
+ [GetAll]
+ public partial class GameplayHud : Godot.Control
+ {
+ private IGridModel _gridModel = null!;
+ private IUiPageBehavior? _page;
+ private IRunLoopSystem _runLoopSystem = null!;
+ }
+ }
+ """;
+
+ private const string SkipNullableServiceLikeFieldExpected = """
+ //
+ #nullable enable
+
+ using GFramework.Core.Extensions;
+
+ namespace TestApp;
+
+ partial class GameplayHud
+ {
+ private void __InjectContextBindings_Generated()
+ {
+ _gridModel = this.GetModel();
+ _runLoopSystem = this.GetSystem();
+ }
+ }
+
+ """;
+
+ private const string IContextAwareClassSource = """
+ using System;
+ using GFramework.Core.SourceGenerators.Abstractions.Rule;
+
+ namespace GFramework.Core.SourceGenerators.Abstractions.Rule
+ {
+ [AttributeUsage(AttributeTargets.Field, Inherited = false)]
+ public sealed class GetServiceAttribute : Attribute { }
+ }
+
+ namespace GFramework.Core.Abstractions.Rule
+ {
+ public interface IContextAware { }
+ }
+
+ namespace GFramework.Core.Abstractions.Model
+ {
+ public interface IModel { }
+ }
+
+ namespace GFramework.Core.Abstractions.Systems
+ {
+ public interface ISystem { }
+ }
+
+ namespace GFramework.Core.Abstractions.Utility
+ {
+ public interface IUtility { }
+ }
+
+ namespace GFramework.Core.Extensions
+ {
+ public static class ContextAwareServiceExtensions
+ {
+ public static T GetService(this object contextAware) => default!;
+ }
+ }
+
+ namespace TestApp
+ {
+ public interface IStrategy { }
+
+ public partial class StrategyHost : GFramework.Core.Abstractions.Rule.IContextAware
+ {
+ [GetService]
+ private IStrategy _strategy = null!;
+ }
+ }
+ """;
+
+ private const string IContextAwareClassExpected = """
+ //
+ #nullable enable
+
+ using GFramework.Core.Extensions;
+
+ namespace TestApp;
+
+ partial class StrategyHost
+ {
+ private void __InjectContextBindings_Generated()
+ {
+ _strategy = this.GetService();
+ }
+ }
+
+ """;
+
+ private const string NonContextAwareClassMarkupSource = """
+ using System;
+ using GFramework.Core.SourceGenerators.Abstractions.Rule;
+
+ namespace GFramework.Core.SourceGenerators.Abstractions.Rule
+ {
+ [AttributeUsage(AttributeTargets.Field, Inherited = false)]
+ public sealed class GetModelAttribute : Attribute { }
+ }
+
+ namespace GFramework.Core.Abstractions.Model
+ {
+ public interface IModel { }
+ }
+
+ namespace GFramework.Core.Abstractions.Systems
+ {
+ public interface ISystem { }
+ }
+
+ namespace GFramework.Core.Abstractions.Utility
+ {
+ public interface IUtility { }
+ }
+
+ namespace GFramework.Core.Extensions
+ {
+ public static class ContextAwareServiceExtensions
+ {
+ public static T GetModel(this object contextAware) => default!;
+ }
+ }
+
+ namespace TestApp
+ {
+ public interface IInventoryModel : GFramework.Core.Abstractions.Model.IModel { }
+
+ public partial class InventoryPanel
+ {
+ [GetModel]
+ private IInventoryModel {|#0:_model|} = null!;
+ }
+ }
+ """;
+
+ private const string GetModelsFieldNotIReadOnlyListMarkupSource = """
+ using System;
+ using System.Collections.Generic;
+ using GFramework.Core.SourceGenerators.Abstractions.Rule;
+
+ namespace GFramework.Core.SourceGenerators.Abstractions.Rule
+ {
+ [AttributeUsage(AttributeTargets.Field, Inherited = false)]
+ public sealed class GetModelsAttribute : Attribute { }
+ }
+
+ namespace GFramework.Core.Abstractions.Rule
+ {
+ public interface IContextAware { }
+ }
+
+ namespace GFramework.Core.Abstractions.Model
+ {
+ public interface IModel { }
+ }
+
+ namespace GFramework.Core.Abstractions.Systems
+ {
+ public interface ISystem { }
+ }
+
+ namespace GFramework.Core.Abstractions.Utility
+ {
+ public interface IUtility { }
+ }
+
+ namespace GFramework.Core.Extensions
+ {
+ public static class ContextAwareServiceExtensions
+ {
+ public static IReadOnlyList GetModels(this object contextAware) => default!;
+ }
+ }
+
+ namespace TestApp
+ {
+ public interface IInventoryModel : GFramework.Core.Abstractions.Model.IModel { }
+
+ public partial class InventoryPanel : GFramework.Core.Abstractions.Rule.IContextAware
+ {
+ [GetModels]
+ private List {|#0:_models|} = new();
+ }
+ }
+ """;
+
+ private const string ReadonlyExplicitGetModelFieldMarkupSource = """
+ using System;
+ using GFramework.Core.SourceGenerators.Abstractions.Rule;
+
+ namespace GFramework.Core.SourceGenerators.Abstractions.Rule
+ {
+ [AttributeUsage(AttributeTargets.Field, Inherited = false)]
+ public sealed class GetModelAttribute : Attribute { }
+ }
+
+ namespace GFramework.Core.Abstractions.Rule
+ {
+ public interface IContextAware { }
+ }
+
+ namespace GFramework.Core.Abstractions.Model
+ {
+ public interface IModel { }
+ }
+
+ namespace GFramework.Core.Abstractions.Systems
+ {
+ public interface ISystem { }
+ }
+
+ namespace GFramework.Core.Abstractions.Utility
+ {
+ public interface IUtility { }
+ }
+
+ namespace GFramework.Core.Extensions
+ {
+ public static class ContextAwareServiceExtensions
+ {
+ public static T GetModel(this object contextAware) => default!;
+ }
+ }
+
+ namespace TestApp
+ {
+ public interface IInventoryModel : GFramework.Core.Abstractions.Model.IModel { }
+
+ public partial class InventoryPanel : GFramework.Core.Abstractions.Rule.IContextAware
+ {
+ [GetModel]
+ private readonly IInventoryModel {|#0:_model|} = null!;
+ }
+ }
+ """;
+
+ private const string StaticExplicitGetModelFieldMarkupSource = """
+ using System;
+ using GFramework.Core.SourceGenerators.Abstractions.Rule;
+
+ namespace GFramework.Core.SourceGenerators.Abstractions.Rule
+ {
+ [AttributeUsage(AttributeTargets.Field, Inherited = false)]
+ public sealed class GetModelAttribute : Attribute { }
+ }
+
+ namespace GFramework.Core.Abstractions.Rule
+ {
+ public interface IContextAware { }
+ }
+
+ namespace GFramework.Core.Abstractions.Model
+ {
+ public interface IModel { }
+ }
+
+ namespace GFramework.Core.Abstractions.Systems
+ {
+ public interface ISystem { }
+ }
+
+ namespace GFramework.Core.Abstractions.Utility
+ {
+ public interface IUtility { }
+ }
+
+ namespace GFramework.Core.Extensions
+ {
+ public static class ContextAwareServiceExtensions
+ {
+ public static T GetModel(this object contextAware) => default!;
+ }
+ }
+
+ namespace TestApp
+ {
+ public interface IInventoryModel : GFramework.Core.Abstractions.Model.IModel { }
+
+ public partial class InventoryPanel : GFramework.Core.Abstractions.Rule.IContextAware
+ {
+ [GetModel]
+ private static IInventoryModel {|#0:_model|} = null!;
+ }
+ }
+ """;
+
+ private const string GetModelsAssignableSource = """
+ using System;
+ using System.Collections.Generic;
+ using GFramework.Core.SourceGenerators.Abstractions.Rule;
+
+ namespace GFramework.Core.SourceGenerators.Abstractions.Rule
+ {
+ [AttributeUsage(AttributeTargets.Field, Inherited = false)]
+ public sealed class GetModelsAttribute : Attribute { }
+ }
+
+ namespace GFramework.Core.Abstractions.Rule
+ {
+ public interface IContextAware { }
+ }
+
+ namespace GFramework.Core.Abstractions.Model
+ {
+ public interface IModel { }
+ }
+
+ namespace GFramework.Core.Abstractions.Systems
+ {
+ public interface ISystem { }
+ }
+
+ namespace GFramework.Core.Abstractions.Utility
+ {
+ public interface IUtility { }
+ }
+
+ namespace GFramework.Core.Extensions
+ {
+ public static class ContextAwareServiceExtensions
+ {
+ public static IReadOnlyList GetModels(this object contextAware) => default!;
+ }
+ }
+
+ namespace TestApp
+ {
+ public interface IInventoryModel : GFramework.Core.Abstractions.Model.IModel { }
+
+ public partial class InventoryPanel : GFramework.Core.Abstractions.Rule.IContextAware
+ {
+ [GetModels]
+ private IEnumerable _models = null!;
+ }
+ }
+ """;
+
+ private const string GetModelsAssignableExpected = """
+ //
+ #nullable enable
+
+ using GFramework.Core.Extensions;
+
+ namespace TestApp;
+
+ partial class InventoryPanel
+ {
+ private void __InjectContextBindings_Generated()
+ {
+ _models = this.GetModels();
+ }
+ }
+
+ """;
+
+ ///
+ /// 验证 [ContextAware] 类上的显式字段特性会生成模型与服务绑定。
+ ///
[Test]
- public async Task Generates_Bindings_For_ContextAwareAttribute_Class()
+ public Task Generates_Bindings_For_ContextAwareAttribute_Class()
{
- var source = """
- using System;
- using System.Collections.Generic;
- using GFramework.Core.SourceGenerators.Abstractions.Rule;
-
- namespace GFramework.Core.SourceGenerators.Abstractions.Rule
- {
- [AttributeUsage(AttributeTargets.Class, Inherited = false)]
- public sealed class ContextAwareAttribute : Attribute { }
-
- [AttributeUsage(AttributeTargets.Field, Inherited = false)]
- public sealed class GetModelAttribute : Attribute { }
-
- [AttributeUsage(AttributeTargets.Field, Inherited = false)]
- public sealed class GetServicesAttribute : Attribute { }
- }
-
- namespace GFramework.Core.Abstractions.Rule
- {
- public interface IContextAware { }
- }
-
- namespace GFramework.Core.Abstractions.Model
- {
- public interface IModel { }
- }
-
- namespace GFramework.Core.Abstractions.Systems
- {
- public interface ISystem { }
- }
-
- namespace GFramework.Core.Abstractions.Utility
- {
- public interface IUtility { }
- }
-
- namespace GFramework.Core.Extensions
- {
- public static class ContextAwareServiceExtensions
- {
- public static T GetModel(this object contextAware) => default!;
- public static IReadOnlyList GetServices(this object contextAware) => default!;
- }
- }
-
- namespace TestApp
- {
- public interface IInventoryModel : GFramework.Core.Abstractions.Model.IModel { }
- public interface IInventoryStrategy { }
-
- [ContextAware]
- public partial class InventoryPanel
- {
- [GetModel]
- private IInventoryModel _model = null!;
-
- [GetServices]
- private IReadOnlyList _strategies = null!;
- }
- }
- """;
-
- const string expected = """
- //
- #nullable enable
-
- using GFramework.Core.Extensions;
-
- namespace TestApp;
-
- partial class InventoryPanel
- {
- private void __InjectContextBindings_Generated()
- {
- _model = this.GetModel();
- _strategies = this.GetServices();
- }
- }
-
- """;
-
- await GeneratorTest.RunAsync(
- source,
- ("TestApp_InventoryPanel.ContextGet.g.cs", expected));
- Assert.Pass();
+ return VerifyGeneratedSourceAsync(
+ ContextAwareAttributeClassSource,
+ InventoryPanelGeneratedFileName,
+ ContextAwareAttributeClassExpected);
}
+ ///
+ /// 验证字段使用 fully-qualified 特性名时仍能生成绑定。
+ ///
[Test]
- public async Task Generates_Bindings_For_Fully_Qualified_Field_Attributes()
+ public Task Generates_Bindings_For_Fully_Qualified_Field_Attributes()
{
- var source = """
- using System;
- using GFramework.Core.SourceGenerators.Abstractions.Rule;
-
- namespace GFramework.Core.SourceGenerators.Abstractions.Rule
- {
- [AttributeUsage(AttributeTargets.Class, Inherited = false)]
- public sealed class ContextAwareAttribute : Attribute { }
-
- [AttributeUsage(AttributeTargets.Field, Inherited = false)]
- public sealed class GetModelAttribute : Attribute { }
- }
-
- namespace GFramework.Core.Abstractions.Rule
- {
- public interface IContextAware { }
- }
-
- namespace GFramework.Core.Abstractions.Model
- {
- public interface IModel { }
- }
-
- namespace GFramework.Core.Abstractions.Systems
- {
- public interface ISystem { }
- }
-
- namespace GFramework.Core.Abstractions.Utility
- {
- public interface IUtility { }
- }
-
- namespace GFramework.Core.Extensions
- {
- public static class ContextAwareServiceExtensions
- {
- public static T GetModel(this object contextAware) => default!;
- }
- }
-
- namespace TestApp
- {
- public interface IInventoryModel : GFramework.Core.Abstractions.Model.IModel { }
-
- [ContextAware]
- public partial class InventoryPanel
- {
- [global::GFramework.Core.SourceGenerators.Abstractions.Rule.GetModel]
- private IInventoryModel _model = null!;
- }
- }
- """;
-
- const string expected = """
- //
- #nullable enable
-
- using GFramework.Core.Extensions;
-
- namespace TestApp;
-
- partial class InventoryPanel
- {
- private void __InjectContextBindings_Generated()
- {
- _model = this.GetModel();
- }
- }
-
- """;
-
- await GeneratorTest.RunAsync(
- source,
- ("TestApp_InventoryPanel.ContextGet.g.cs", expected));
- Assert.Pass();
+ return VerifyGeneratedSourceAsync(
+ FullyQualifiedFieldAttributesSource,
+ InventoryPanelGeneratedFileName,
+ FullyQualifiedFieldAttributesExpected);
}
+ ///
+ /// 验证 GetAll 会仅为可推断的 model、models、system 与 utility 字段生成绑定。
+ ///
[Test]
- public async Task Generates_Inferred_Bindings_For_GetAll_Class()
+ public Task Generates_Inferred_Bindings_For_GetAll_Class()
{
- var source = """
- using System;
- using System.Collections.Generic;
- using GFramework.Core.SourceGenerators.Abstractions.Rule;
-
- namespace GFramework.Core.SourceGenerators.Abstractions.Rule
- {
- [AttributeUsage(AttributeTargets.Class, Inherited = false)]
- public sealed class GetAllAttribute : Attribute { }
- }
-
- namespace GFramework.Core.Abstractions.Rule
- {
- public interface IContextAware { }
- }
-
- namespace GFramework.Core.Abstractions.Architectures
- {
- public interface IArchitectureContext { }
- }
-
- namespace GFramework.Core.Rule
- {
- public abstract class ContextAwareBase : GFramework.Core.Abstractions.Rule.IContextAware { }
- }
-
- namespace GFramework.Core.Abstractions.Model
- {
- public interface IModel { }
- }
-
- namespace GFramework.Core.Abstractions.Systems
- {
- public interface ISystem { }
- }
-
- namespace GFramework.Core.Abstractions.Utility
- {
- public interface IUtility { }
- }
-
- namespace GFramework.Core.Extensions
- {
- public static class ContextAwareServiceExtensions
- {
- public static T GetModel(this object contextAware) => default!;
- public static IReadOnlyList GetModels(this object contextAware) => default!;
- public static T GetSystem(this object contextAware) => default!;
- public static T GetUtility(this object contextAware) => default!;
- }
- }
-
- namespace Godot
- {
- public class Node { }
- }
-
- namespace TestApp
- {
- public interface IInventoryModel : GFramework.Core.Abstractions.Model.IModel { }
- public interface ICombatSystem : GFramework.Core.Abstractions.Systems.ISystem { }
- public interface IUiUtility : GFramework.Core.Abstractions.Utility.IUtility { }
- public interface IStrategy { }
-
- [GetAll]
- public partial class BattlePanel : GFramework.Core.Rule.ContextAwareBase
- {
- private IInventoryModel _model = null!;
- private IReadOnlyList _models = null!;
- private ICombatSystem _system = null!;
- private IUiUtility _utility = null!;
- private IStrategy _service = null!;
- private IReadOnlyList _services = null!;
- private Godot.Node _node = null!;
- }
- }
- """;
-
- const string expected = """
- //
- #nullable enable
-
- using GFramework.Core.Extensions;
-
- namespace TestApp;
-
- partial class BattlePanel
- {
- private void __InjectContextBindings_Generated()
- {
- _model = this.GetModel();
- _models = this.GetModels();
- _system = this.GetSystem();
- _utility = this.GetUtility();
- }
- }
-
- """;
-
- await GeneratorTest.RunAsync(
- source,
- ("TestApp_BattlePanel.ContextGet.g.cs", expected));
- Assert.Pass();
+ return VerifyGeneratedSourceAsync(
+ InferredGetAllClassSource,
+ BattlePanelGeneratedFileName,
+ InferredGetAllClassExpected);
}
+ ///
+ /// 验证 GetAll 与显式 [GetService] 可以组合生成绑定。
+ ///
[Test]
- public async Task Generates_Explicit_Service_Binding_For_GetAll_Class()
+ public Task Generates_Explicit_Service_Binding_For_GetAll_Class()
{
- var source = """
- using System;
- using GFramework.Core.SourceGenerators.Abstractions.Rule;
-
- namespace GFramework.Core.SourceGenerators.Abstractions.Rule
- {
- [AttributeUsage(AttributeTargets.Class, Inherited = false)]
- public sealed class GetAllAttribute : Attribute { }
-
- [AttributeUsage(AttributeTargets.Field, Inherited = false)]
- public sealed class GetServiceAttribute : Attribute { }
- }
-
- namespace GFramework.Core.Abstractions.Rule
- {
- public interface IContextAware { }
- }
-
- namespace GFramework.Core.Rule
- {
- public abstract class ContextAwareBase : GFramework.Core.Abstractions.Rule.IContextAware { }
- }
-
- namespace GFramework.Core.Abstractions.Model
- {
- public interface IModel { }
- }
-
- namespace GFramework.Core.Abstractions.Systems
- {
- public interface ISystem { }
- }
-
- namespace GFramework.Core.Abstractions.Utility
- {
- public interface IUtility { }
- }
-
- namespace GFramework.Core.Extensions
- {
- public static class ContextAwareServiceExtensions
- {
- public static T GetModel(this object contextAware) => default!;
- public static T GetService(this object contextAware) => default!;
- }
- }
-
- namespace TestApp
- {
- public interface IInventoryModel : GFramework.Core.Abstractions.Model.IModel { }
- public interface IStrategy { }
-
- [GetAll]
- public partial class BattlePanel : GFramework.Core.Rule.ContextAwareBase
- {
- private IInventoryModel _model = null!;
-
- [GetService]
- private IStrategy _service = null!;
- }
- }
- """;
-
- const string expected = """
- //
- #nullable enable
-
- using GFramework.Core.Extensions;
-
- namespace TestApp;
-
- partial class BattlePanel
- {
- private void __InjectContextBindings_Generated()
- {
- _model = this.GetModel();
- _service = this.GetService();
- }
- }
-
- """;
-
- await GeneratorTest.RunAsync(
- source,
- ("TestApp_BattlePanel.ContextGet.g.cs", expected));
- Assert.Pass();
+ return VerifyGeneratedSourceAsync(
+ ExplicitServiceGetAllClassSource,
+ BattlePanelGeneratedFileName,
+ ExplicitServiceGetAllClassExpected);
}
+ ///
+ /// 验证目标类已声明注入方法时会报告冲突诊断。
+ ///
[Test]
- public async Task Reports_Diagnostic_When_Generated_Injection_Method_Name_Already_Exists()
+ public Task Reports_Diagnostic_When_Generated_Injection_Method_Name_Already_Exists()
{
- var source = """
- using System;
- using GFramework.Core.SourceGenerators.Abstractions.Rule;
+ var source = MarkupTestSource.Parse(GeneratedInjectionMethodAlreadyExistsMarkupSource);
- namespace GFramework.Core.SourceGenerators.Abstractions.Rule
- {
- [AttributeUsage(AttributeTargets.Class, Inherited = false)]
- public sealed class ContextAwareAttribute : Attribute { }
+ return VerifyDiagnosticAsync(
+ source,
+ CreateSingleSpanDiagnostic(
+ source,
+ "GF_Common_Class_002",
+ DiagnosticSeverity.Error,
+ "InventoryPanel",
+ "__InjectContextBindings_Generated"));
+ }
- [AttributeUsage(AttributeTargets.Field, Inherited = false)]
- public sealed class GetModelAttribute : Attribute { }
- }
+ ///
+ /// 验证 GetAll 会忽略不可推断的常量字段且不报告诊断。
+ ///
+ [Test]
+ public Task Ignores_NonInferable_Const_Field_For_GetAll_Class_Without_Diagnostic()
+ {
+ return VerifyGeneratedSourceAsync(
+ IgnoreConstFieldGetAllSource,
+ BattlePanelGeneratedFileName,
+ IgnoreConstFieldGetAllExpected);
+ }
- namespace GFramework.Core.Abstractions.Rule
- {
- public interface IContextAware { }
- }
+ ///
+ /// 验证只读推断字段会被跳过并报告 warning,同时其他字段保持生成。
+ ///
+ [Test]
+ public Task Warns_And_Skips_Readonly_Inferred_Field_For_GetAll_Class()
+ {
+ var source = MarkupTestSource.Parse(ReadonlyInferredFieldGetAllMarkupSource);
- namespace GFramework.Core.Abstractions.Model
- {
- public interface IModel { }
- }
+ return VerifyDiagnosticAndGeneratedSourceAsync(
+ source,
+ BattlePanelGeneratedFileName,
+ SkipInvalidGetAllFieldExpected,
+ CreateSingleSpanDiagnostic(
+ source,
+ "GF_ContextGet_008",
+ DiagnosticSeverity.Warning,
+ "_model"));
+ }
- namespace GFramework.Core.Abstractions.Systems
- {
- public interface ISystem { }
- }
+ ///
+ /// 验证静态推断字段会被跳过并报告 warning,同时其他字段保持生成。
+ ///
+ [Test]
+ public Task Warns_And_Skips_Static_Inferred_Field_For_GetAll_Class()
+ {
+ var source = MarkupTestSource.Parse(StaticInferredFieldGetAllMarkupSource);
- namespace GFramework.Core.Abstractions.Utility
- {
- public interface IUtility { }
- }
+ return VerifyDiagnosticAndGeneratedSourceAsync(
+ source,
+ BattlePanelGeneratedFileName,
+ SkipInvalidGetAllFieldExpected,
+ CreateSingleSpanDiagnostic(
+ source,
+ "GF_ContextGet_007",
+ DiagnosticSeverity.Warning,
+ "_model"));
+ }
- namespace GFramework.Core.Extensions
- {
- public static class ContextAwareServiceExtensions
- {
- public static T GetModel(this object contextAware) => default!;
- }
- }
+ ///
+ /// 验证 nullable 的服务样字段不会被 GetAll 误判为可注入字段。
+ ///
+ [Test]
+ public Task Skips_Nullable_Service_Like_Field_For_ContextAware_GetAll_Class()
+ {
+ return VerifyGeneratedSourceAsync(
+ SkipNullableServiceLikeFieldSource,
+ GameplayHudGeneratedFileName,
+ SkipNullableServiceLikeFieldExpected);
+ }
- namespace TestApp
- {
- public interface IInventoryModel : GFramework.Core.Abstractions.Model.IModel { }
+ ///
+ /// 验证实现 IContextAware 的类型无需 [ContextAware] 也能生成显式绑定。
+ ///
+ [Test]
+ public Task Generates_Bindings_For_IContextAware_Class()
+ {
+ return VerifyGeneratedSourceAsync(
+ IContextAwareClassSource,
+ StrategyHostGeneratedFileName,
+ IContextAwareClassExpected);
+ }
- [ContextAware]
- public partial class InventoryPanel
- {
- [GetModel]
- private IInventoryModel _model = null!;
+ ///
+ /// 验证缺少上下文感知契约的类型会报告错误诊断。
+ ///
+ [Test]
+ public Task Reports_Diagnostic_When_Class_Is_Not_ContextAware()
+ {
+ var source = MarkupTestSource.Parse(NonContextAwareClassMarkupSource);
- private void {|#0:__InjectContextBindings_Generated|}()
- {
- }
- }
- }
- """;
+ return VerifyDiagnosticAsync(
+ source,
+ CreateSingleSpanDiagnostic(
+ source,
+ "GF_ContextGet_005",
+ DiagnosticSeverity.Error,
+ "InventoryPanel"));
+ }
- var test = new CSharpSourceGeneratorTest
+ ///
+ /// 验证 [GetModels] 字段若不是可赋值自 IReadOnlyList<T> 会报告错误。
+ ///
+ [Test]
+ public Task Reports_Diagnostic_When_GetModels_Field_Is_Not_IReadOnlyList()
+ {
+ var source = MarkupTestSource.Parse(GetModelsFieldNotIReadOnlyListMarkupSource);
+
+ return VerifyDiagnosticAsync(
+ source,
+ CreateSingleSpanDiagnostic(
+ source,
+ "GF_ContextGet_004",
+ DiagnosticSeverity.Error,
+ "_models",
+ "System.Collections.Generic.List",
+ "GetModels"));
+ }
+
+ ///
+ /// 验证显式 [GetModel] 作用于只读字段时会报告错误。
+ ///
+ [Test]
+ public Task Reports_Diagnostic_For_Readonly_Explicit_GetModel_Field()
+ {
+ var source = MarkupTestSource.Parse(ReadonlyExplicitGetModelFieldMarkupSource);
+
+ return VerifyDiagnosticAsync(
+ source,
+ CreateSingleSpanDiagnostic(
+ source,
+ "GF_ContextGet_003",
+ DiagnosticSeverity.Error,
+ "_model"));
+ }
+
+ ///
+ /// 验证显式 [GetModel] 作用于静态字段时会报告错误。
+ ///
+ [Test]
+ public Task Reports_Diagnostic_For_Static_Explicit_GetModel_Field()
+ {
+ var source = MarkupTestSource.Parse(StaticExplicitGetModelFieldMarkupSource);
+
+ return VerifyDiagnosticAsync(
+ source,
+ CreateSingleSpanDiagnostic(
+ source,
+ "GF_ContextGet_002",
+ DiagnosticSeverity.Error,
+ "_model"));
+ }
+
+ ///
+ /// 验证 [GetModels] 字段可以赋值到更宽的可枚举接口上。
+ ///
+ [Test]
+ public Task Generates_Bindings_For_GetModels_Field_Assignable_From_IReadOnlyList()
+ {
+ return VerifyGeneratedSourceAsync(
+ GetModelsAssignableSource,
+ InventoryPanelGeneratedFileName,
+ GetModelsAssignableExpected);
+ }
+
+ ///
+ /// 运行单个生成源码断言,保持文件名与文本快照语义不变。
+ ///
+ /// 测试输入源码。
+ /// 期望生成文件名。
+ /// 期望生成源码。
+ /// 表示测试执行的异步任务。
+ private static Task VerifyGeneratedSourceAsync(
+ string source,
+ string generatedFileName,
+ string expectedGeneratedSource)
+ {
+ return GeneratorTest.RunAsync(
+ source,
+ (generatedFileName, expectedGeneratedSource));
+ }
+
+ ///
+ /// 运行仅关注诊断输出的生成器测试。
+ ///
+ /// 包含 markup span 的测试源码。
+ /// 期望诊断。
+ /// 表示测试执行的异步任务。
+ private static Task VerifyDiagnosticAsync(
+ MarkupTestSource source,
+ DiagnosticResult expectedDiagnostic)
+ {
+ var test = CreateGeneratorTest(source.Source);
+ test.ExpectedDiagnostics.Add(expectedDiagnostic);
+ return test.RunAsync();
+ }
+
+ ///
+ /// 运行同时断言诊断与部分生成输出的生成器测试。
+ ///
+ /// 包含 markup span 的测试源码。
+ /// 期望生成文件名。
+ /// 期望生成源码。
+ /// 期望诊断。
+ /// 表示测试执行的异步任务。
+ private static Task VerifyDiagnosticAndGeneratedSourceAsync(
+ MarkupTestSource source,
+ string generatedFileName,
+ string expectedGeneratedSource,
+ DiagnosticResult expectedDiagnostic)
+ {
+ var test = CreateGeneratorTest(source.Source);
+ test.TestState.GeneratedSources.Add(
+ (typeof(ContextGetGenerator), generatedFileName, NormalizeLineEndings(expectedGeneratedSource)));
+ test.ExpectedDiagnostics.Add(expectedDiagnostic);
+ return test.RunAsync();
+ }
+
+ ///
+ /// 为单一 markup span 场景构造诊断结果,统一保持定位键与参数组装方式。
+ ///
+ /// 包含 markup span 的测试源码。
+ /// 诊断 ID。
+ /// 诊断严重级别。
+ /// 诊断参数。
+ /// 绑定到 markup key 0 的期望诊断。
+ private static DiagnosticResult CreateSingleSpanDiagnostic(
+ MarkupTestSource source,
+ string diagnosticId,
+ DiagnosticSeverity severity,
+ params string[] arguments)
+ {
+ return source.WithSpan(
+ new DiagnosticResult(diagnosticId, severity),
+ "0")
+ .WithArguments(arguments);
+ }
+
+ ///
+ /// 创建禁用 trace 诊断的通用源生成器测试实例,避免各场景重复样板配置。
+ ///
+ /// 测试输入源码。
+ /// 配置完成的生成器测试对象。
+ private static CSharpSourceGeneratorTest CreateGeneratorTest(string source)
+ {
+ return new CSharpSourceGeneratorTest
{
TestState =
{
Sources = { source }
},
- DisabledDiagnostics = { "GF_Common_Trace_001" },
- TestBehaviors = TestBehaviors.SkipGeneratedSourcesCheck
- };
-
- test.ExpectedDiagnostics.Add(new DiagnosticResult("GF_Common_Class_002", DiagnosticSeverity.Error)
- .WithLocation(0)
- .WithArguments("InventoryPanel", "__InjectContextBindings_Generated"));
-
- await test.RunAsync();
- }
-
- [Test]
- public async Task Ignores_NonInferable_Const_Field_For_GetAll_Class_Without_Diagnostic()
- {
- var source = """
- using System;
- using GFramework.Core.SourceGenerators.Abstractions.Rule;
-
- namespace GFramework.Core.SourceGenerators.Abstractions.Rule
- {
- [AttributeUsage(AttributeTargets.Class, Inherited = false)]
- public sealed class GetAllAttribute : Attribute { }
- }
-
- namespace GFramework.Core.Abstractions.Rule
- {
- public interface IContextAware { }
- }
-
- namespace GFramework.Core.Rule
- {
- public abstract class ContextAwareBase : GFramework.Core.Abstractions.Rule.IContextAware { }
- }
-
- namespace GFramework.Core.Abstractions.Model
- {
- public interface IModel { }
- }
-
- namespace GFramework.Core.Abstractions.Systems
- {
- public interface ISystem { }
- }
-
- namespace GFramework.Core.Abstractions.Utility
- {
- public interface IUtility { }
- }
-
- namespace GFramework.Core.Extensions
- {
- public static class ContextAwareServiceExtensions
- {
- public static T GetModel(this object contextAware) => default!;
- }
- }
-
- namespace TestApp
- {
- public interface IInventoryModel : GFramework.Core.Abstractions.Model.IModel { }
-
- [GetAll]
- public partial class BattlePanel : GFramework.Core.Rule.ContextAwareBase
- {
- private const double LogicStep = 0.2;
- private IInventoryModel _model = null!;
- }
- }
- """;
-
- const string expected = """
- //
- #nullable enable
-
- using GFramework.Core.Extensions;
-
- namespace TestApp;
-
- partial class BattlePanel
- {
- private void __InjectContextBindings_Generated()
- {
- _model = this.GetModel();
- }
- }
-
- """;
-
- await GeneratorTest.RunAsync(
- source,
- ("TestApp_BattlePanel.ContextGet.g.cs", expected));
- Assert.Pass();
- }
-
- [Test]
- public async Task Warns_And_Skips_Readonly_Inferred_Field_For_GetAll_Class()
- {
- var source = MarkupTestSource.Parse("""
- using System;
- using GFramework.Core.SourceGenerators.Abstractions.Rule;
-
- namespace GFramework.Core.SourceGenerators.Abstractions.Rule
- {
- [AttributeUsage(AttributeTargets.Class, Inherited = false)]
- public sealed class GetAllAttribute : Attribute { }
- }
-
- namespace GFramework.Core.Abstractions.Rule
- {
- public interface IContextAware { }
- }
-
- namespace GFramework.Core.Rule
- {
- public abstract class ContextAwareBase : GFramework.Core.Abstractions.Rule.IContextAware { }
- }
-
- namespace GFramework.Core.Abstractions.Model
- {
- public interface IModel { }
- }
-
- namespace GFramework.Core.Abstractions.Systems
- {
- public interface ISystem { }
- }
-
- namespace GFramework.Core.Abstractions.Utility
- {
- public interface IUtility { }
- }
-
- namespace GFramework.Core.Extensions
- {
- public static class ContextAwareServiceExtensions
- {
- public static T GetModel(this object contextAware) => default!;
- public static T GetSystem(this object contextAware) => default!;
- }
- }
-
- namespace TestApp
- {
- public interface IInventoryModel : GFramework.Core.Abstractions.Model.IModel { }
- public interface ICombatSystem : GFramework.Core.Abstractions.Systems.ISystem { }
-
- [GetAll]
- public partial class BattlePanel : GFramework.Core.Rule.ContextAwareBase
- {
- private readonly IInventoryModel {|#0:_model|} = null!;
- private ICombatSystem _system = null!;
- }
- }
- """);
-
- const string expected = """
- //
- #nullable enable
-
- using GFramework.Core.Extensions;
-
- namespace TestApp;
-
- partial class BattlePanel
- {
- private void __InjectContextBindings_Generated()
- {
- _system = this.GetSystem();
- }
- }
-
- """;
-
- var test = new CSharpSourceGeneratorTest
- {
- TestState =
- {
- Sources = { source.Source },
- GeneratedSources =
- {
- (typeof(ContextGetGenerator), "TestApp_BattlePanel.ContextGet.g.cs", NormalizeLineEndings(expected))
- }
- },
DisabledDiagnostics = { "GF_Common_Trace_001" }
};
-
- test.ExpectedDiagnostics.Add(source.WithSpan(
- new DiagnosticResult("GF_ContextGet_008", DiagnosticSeverity.Warning),
- "0")
- .WithArguments("_model"));
-
- await test.RunAsync();
- Assert.Pass();
- }
-
- [Test]
- public async Task Warns_And_Skips_Static_Inferred_Field_For_GetAll_Class()
- {
- var source = MarkupTestSource.Parse("""
- using System;
- using GFramework.Core.SourceGenerators.Abstractions.Rule;
-
- namespace GFramework.Core.SourceGenerators.Abstractions.Rule
- {
- [AttributeUsage(AttributeTargets.Class, Inherited = false)]
- public sealed class GetAllAttribute : Attribute { }
- }
-
- namespace GFramework.Core.Abstractions.Rule
- {
- public interface IContextAware { }
- }
-
- namespace GFramework.Core.Rule
- {
- public abstract class ContextAwareBase : GFramework.Core.Abstractions.Rule.IContextAware { }
- }
-
- namespace GFramework.Core.Abstractions.Model
- {
- public interface IModel { }
- }
-
- namespace GFramework.Core.Abstractions.Systems
- {
- public interface ISystem { }
- }
-
- namespace GFramework.Core.Abstractions.Utility
- {
- public interface IUtility { }
- }
-
- namespace GFramework.Core.Extensions
- {
- public static class ContextAwareServiceExtensions
- {
- public static T GetModel(this object contextAware) => default!;
- public static T GetSystem(this object contextAware) => default!;
- }
- }
-
- namespace TestApp
- {
- public interface IInventoryModel : GFramework.Core.Abstractions.Model.IModel { }
- public interface ICombatSystem : GFramework.Core.Abstractions.Systems.ISystem { }
-
- [GetAll]
- public partial class BattlePanel : GFramework.Core.Rule.ContextAwareBase
- {
- private static IInventoryModel {|#0:_model|} = null!;
- private ICombatSystem _system = null!;
- }
- }
- """);
-
- const string expected = """
- //
- #nullable enable
-
- using GFramework.Core.Extensions;
-
- namespace TestApp;
-
- partial class BattlePanel
- {
- private void __InjectContextBindings_Generated()
- {
- _system = this.GetSystem();
- }
- }
-
- """;
-
- var test = new CSharpSourceGeneratorTest
- {
- TestState =
- {
- Sources = { source.Source },
- GeneratedSources =
- {
- (typeof(ContextGetGenerator), "TestApp_BattlePanel.ContextGet.g.cs", NormalizeLineEndings(expected))
- }
- },
- DisabledDiagnostics = { "GF_Common_Trace_001" }
- };
-
- test.ExpectedDiagnostics.Add(source.WithSpan(
- new DiagnosticResult("GF_ContextGet_007", DiagnosticSeverity.Warning),
- "0")
- .WithArguments("_model"));
-
- await test.RunAsync();
- Assert.Pass();
}
+ ///
+ /// 将手工声明的期望生成源码归一化到当前平台换行符,避免不同宿主上的伪差异。
+ ///
+ /// 原始期望源码。
+ /// 已按当前平台换行符归一化的源码文本。
private static string NormalizeLineEndings(string content)
{
return content
@@ -747,531 +1355,4 @@ public class ContextGetGeneratorTests
.Replace("\r", "\n", StringComparison.Ordinal)
.Replace("\n", Environment.NewLine, StringComparison.Ordinal);
}
-
- [Test]
- public async Task Skips_Nullable_Service_Like_Field_For_ContextAware_GetAll_Class()
- {
- var source = """
- using System;
- using GFramework.Core.SourceGenerators.Abstractions.Rule;
-
- namespace GFramework.Core.SourceGenerators.Abstractions.Rule
- {
- [AttributeUsage(AttributeTargets.Class, Inherited = false)]
- public sealed class ContextAwareAttribute : Attribute { }
-
- [AttributeUsage(AttributeTargets.Class, Inherited = false)]
- public sealed class GetAllAttribute : Attribute { }
- }
-
- namespace GFramework.Core.Abstractions.Rule
- {
- public interface IContextAware { }
- }
-
- namespace GFramework.Core.Abstractions.Model
- {
- public interface IModel { }
- }
-
- namespace GFramework.Core.Abstractions.Systems
- {
- public interface ISystem { }
- }
-
- namespace GFramework.Core.Abstractions.Utility
- {
- public interface IUtility { }
- }
-
- namespace GFramework.Core.Extensions
- {
- public static class ContextAwareServiceExtensions
- {
- public static T GetModel(this object contextAware) => default!;
- public static T GetSystem(this object contextAware) => default!;
- }
- }
-
- namespace Godot
- {
- public class Control { }
- }
-
- namespace TestApp
- {
- public interface IGridModel : GFramework.Core.Abstractions.Model.IModel { }
- public interface IRunLoopSystem : GFramework.Core.Abstractions.Systems.ISystem { }
- public interface IUiPageBehavior { }
-
- [ContextAware]
- [GetAll]
- public partial class GameplayHud : Godot.Control
- {
- private IGridModel _gridModel = null!;
- private IUiPageBehavior? _page;
- private IRunLoopSystem _runLoopSystem = null!;
- }
- }
- """;
-
- const string expected = """
- //
- #nullable enable
-
- using GFramework.Core.Extensions;
-
- namespace TestApp;
-
- partial class GameplayHud
- {
- private void __InjectContextBindings_Generated()
- {
- _gridModel = this.GetModel();
- _runLoopSystem = this.GetSystem();
- }
- }
-
- """;
-
- await GeneratorTest.RunAsync(
- source,
- ("TestApp_GameplayHud.ContextGet.g.cs", expected));
- Assert.Pass();
- }
-
- [Test]
- public async Task Generates_Bindings_For_IContextAware_Class()
- {
- var source = """
- using System;
- using GFramework.Core.SourceGenerators.Abstractions.Rule;
-
- namespace GFramework.Core.SourceGenerators.Abstractions.Rule
- {
- [AttributeUsage(AttributeTargets.Field, Inherited = false)]
- public sealed class GetServiceAttribute : Attribute { }
- }
-
- namespace GFramework.Core.Abstractions.Rule
- {
- public interface IContextAware { }
- }
-
- namespace GFramework.Core.Abstractions.Model
- {
- public interface IModel { }
- }
-
- namespace GFramework.Core.Abstractions.Systems
- {
- public interface ISystem { }
- }
-
- namespace GFramework.Core.Abstractions.Utility
- {
- public interface IUtility { }
- }
-
- namespace GFramework.Core.Extensions
- {
- public static class ContextAwareServiceExtensions
- {
- public static T GetService(this object contextAware) => default!;
- }
- }
-
- namespace TestApp
- {
- public interface IStrategy { }
-
- public partial class StrategyHost : GFramework.Core.Abstractions.Rule.IContextAware
- {
- [GetService]
- private IStrategy _strategy = null!;
- }
- }
- """;
-
- const string expected = """
- //
- #nullable enable
-
- using GFramework.Core.Extensions;
-
- namespace TestApp;
-
- partial class StrategyHost
- {
- private void __InjectContextBindings_Generated()
- {
- _strategy = this.GetService();
- }
- }
-
- """;
-
- await GeneratorTest.RunAsync(
- source,
- ("TestApp_StrategyHost.ContextGet.g.cs", expected));
- Assert.Pass();
- }
-
- [Test]
- public async Task Reports_Diagnostic_When_Class_Is_Not_ContextAware()
- {
- var source = MarkupTestSource.Parse("""
- using System;
- using GFramework.Core.SourceGenerators.Abstractions.Rule;
-
- namespace GFramework.Core.SourceGenerators.Abstractions.Rule
- {
- [AttributeUsage(AttributeTargets.Field, Inherited = false)]
- public sealed class GetModelAttribute : Attribute { }
- }
-
- namespace GFramework.Core.Abstractions.Model
- {
- public interface IModel { }
- }
-
- namespace GFramework.Core.Abstractions.Systems
- {
- public interface ISystem { }
- }
-
- namespace GFramework.Core.Abstractions.Utility
- {
- public interface IUtility { }
- }
-
- namespace GFramework.Core.Extensions
- {
- public static class ContextAwareServiceExtensions
- {
- public static T GetModel(this object contextAware) => default!;
- }
- }
-
- namespace TestApp
- {
- public interface IInventoryModel : GFramework.Core.Abstractions.Model.IModel { }
-
- public partial class InventoryPanel
- {
- [GetModel]
- private IInventoryModel {|#0:_model|} = null!;
- }
- }
- """);
-
- var test = new CSharpSourceGeneratorTest
- {
- TestState =
- {
- Sources = { source.Source }
- },
- DisabledDiagnostics = { "GF_Common_Trace_001" }
- };
-
- test.ExpectedDiagnostics.Add(source.WithSpan(
- new DiagnosticResult("GF_ContextGet_005", DiagnosticSeverity.Error),
- "0")
- .WithArguments("InventoryPanel"));
-
- await test.RunAsync();
- Assert.Pass();
- }
-
- [Test]
- public async Task Reports_Diagnostic_When_GetModels_Field_Is_Not_IReadOnlyList()
- {
- var source = MarkupTestSource.Parse("""
- using System;
- using System.Collections.Generic;
- using GFramework.Core.SourceGenerators.Abstractions.Rule;
-
- namespace GFramework.Core.SourceGenerators.Abstractions.Rule
- {
- [AttributeUsage(AttributeTargets.Field, Inherited = false)]
- public sealed class GetModelsAttribute : Attribute { }
- }
-
- namespace GFramework.Core.Abstractions.Rule
- {
- public interface IContextAware { }
- }
-
- namespace GFramework.Core.Abstractions.Model
- {
- public interface IModel { }
- }
-
- namespace GFramework.Core.Abstractions.Systems
- {
- public interface ISystem { }
- }
-
- namespace GFramework.Core.Abstractions.Utility
- {
- public interface IUtility { }
- }
-
- namespace GFramework.Core.Extensions
- {
- public static class ContextAwareServiceExtensions
- {
- public static IReadOnlyList GetModels(this object contextAware) => default!;
- }
- }
-
- namespace TestApp
- {
- public interface IInventoryModel : GFramework.Core.Abstractions.Model.IModel { }
-
- public partial class InventoryPanel : GFramework.Core.Abstractions.Rule.IContextAware
- {
- [GetModels]
- private List {|#0:_models|} = new();
- }
- }
- """);
-
- var test = new CSharpSourceGeneratorTest
- {
- TestState =
- {
- Sources = { source.Source }
- },
- DisabledDiagnostics = { "GF_Common_Trace_001" }
- };
-
- test.ExpectedDiagnostics.Add(source.WithSpan(
- new DiagnosticResult("GF_ContextGet_004", DiagnosticSeverity.Error),
- "0")
- .WithArguments("_models", "System.Collections.Generic.List", "GetModels"));
-
- await test.RunAsync();
- Assert.Pass();
- }
-
- [Test]
- public async Task Reports_Diagnostic_For_Readonly_Explicit_GetModel_Field()
- {
- var source = MarkupTestSource.Parse("""
- using System;
- using GFramework.Core.SourceGenerators.Abstractions.Rule;
-
- namespace GFramework.Core.SourceGenerators.Abstractions.Rule
- {
- [AttributeUsage(AttributeTargets.Field, Inherited = false)]
- public sealed class GetModelAttribute : Attribute { }
- }
-
- namespace GFramework.Core.Abstractions.Rule
- {
- public interface IContextAware { }
- }
-
- namespace GFramework.Core.Abstractions.Model
- {
- public interface IModel { }
- }
-
- namespace GFramework.Core.Abstractions.Systems
- {
- public interface ISystem { }
- }
-
- namespace GFramework.Core.Abstractions.Utility
- {
- public interface IUtility { }
- }
-
- namespace GFramework.Core.Extensions
- {
- public static class ContextAwareServiceExtensions
- {
- public static T GetModel(this object contextAware) => default!;
- }
- }
-
- namespace TestApp
- {
- public interface IInventoryModel : GFramework.Core.Abstractions.Model.IModel { }
-
- public partial class InventoryPanel : GFramework.Core.Abstractions.Rule.IContextAware
- {
- [GetModel]
- private readonly IInventoryModel {|#0:_model|} = null!;
- }
- }
- """);
-
- var test = new CSharpSourceGeneratorTest
- {
- TestState =
- {
- Sources = { source.Source }
- },
- DisabledDiagnostics = { "GF_Common_Trace_001" }
- };
-
- test.ExpectedDiagnostics.Add(source.WithSpan(
- new DiagnosticResult("GF_ContextGet_003", DiagnosticSeverity.Error),
- "0")
- .WithArguments("_model"));
-
- await test.RunAsync();
- Assert.Pass();
- }
-
- [Test]
- public async Task Reports_Diagnostic_For_Static_Explicit_GetModel_Field()
- {
- var source = MarkupTestSource.Parse("""
- using System;
- using GFramework.Core.SourceGenerators.Abstractions.Rule;
-
- namespace GFramework.Core.SourceGenerators.Abstractions.Rule
- {
- [AttributeUsage(AttributeTargets.Field, Inherited = false)]
- public sealed class GetModelAttribute : Attribute { }
- }
-
- namespace GFramework.Core.Abstractions.Rule
- {
- public interface IContextAware { }
- }
-
- namespace GFramework.Core.Abstractions.Model
- {
- public interface IModel { }
- }
-
- namespace GFramework.Core.Abstractions.Systems
- {
- public interface ISystem { }
- }
-
- namespace GFramework.Core.Abstractions.Utility
- {
- public interface IUtility { }
- }
-
- namespace GFramework.Core.Extensions
- {
- public static class ContextAwareServiceExtensions
- {
- public static T GetModel(this object contextAware) => default!;
- }
- }
-
- namespace TestApp
- {
- public interface IInventoryModel : GFramework.Core.Abstractions.Model.IModel { }
-
- public partial class InventoryPanel : GFramework.Core.Abstractions.Rule.IContextAware
- {
- [GetModel]
- private static IInventoryModel {|#0:_model|} = null!;
- }
- }
- """);
-
- var test = new CSharpSourceGeneratorTest
- {
- TestState =
- {
- Sources = { source.Source }
- },
- DisabledDiagnostics = { "GF_Common_Trace_001" }
- };
-
- test.ExpectedDiagnostics.Add(source.WithSpan(
- new DiagnosticResult("GF_ContextGet_002", DiagnosticSeverity.Error),
- "0")
- .WithArguments("_model"));
-
- await test.RunAsync();
- Assert.Pass();
- }
-
- [Test]
- public async Task Generates_Bindings_For_GetModels_Field_Assignable_From_IReadOnlyList()
- {
- var source = """
- using System;
- using System.Collections.Generic;
- using GFramework.Core.SourceGenerators.Abstractions.Rule;
-
- namespace GFramework.Core.SourceGenerators.Abstractions.Rule
- {
- [AttributeUsage(AttributeTargets.Field, Inherited = false)]
- public sealed class GetModelsAttribute : Attribute { }
- }
-
- namespace GFramework.Core.Abstractions.Rule
- {
- public interface IContextAware { }
- }
-
- namespace GFramework.Core.Abstractions.Model
- {
- public interface IModel { }
- }
-
- namespace GFramework.Core.Abstractions.Systems
- {
- public interface ISystem { }
- }
-
- namespace GFramework.Core.Abstractions.Utility
- {
- public interface IUtility { }
- }
-
- namespace GFramework.Core.Extensions
- {
- public static class ContextAwareServiceExtensions
- {
- public static IReadOnlyList GetModels(this object contextAware) => default!;
- }
- }
-
- namespace TestApp
- {
- public interface IInventoryModel : GFramework.Core.Abstractions.Model.IModel { }
-
- public partial class InventoryPanel : GFramework.Core.Abstractions.Rule.IContextAware
- {
- [GetModels]
- private IEnumerable _models = null!;
- }
- }
- """;
-
- const string expected = """
- //
- #nullable enable
-
- using GFramework.Core.Extensions;
-
- namespace TestApp;
-
- partial class InventoryPanel
- {
- private void __InjectContextBindings_Generated()
- {
- _models = this.GetModels();
- }
- }
-
- """;
-
- await GeneratorTest.RunAsync(
- source,
- ("TestApp_InventoryPanel.ContextGet.g.cs", expected));
- Assert.Pass();
- }
}