using GFramework.Core.SourceGenerators.Rule;
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 Task Generates_Bindings_For_ContextAwareAttribute_Class()
{
return VerifyGeneratedSourceAsync(
ContextAwareAttributeClassSource,
InventoryPanelGeneratedFileName,
ContextAwareAttributeClassExpected);
}
///
/// 验证字段使用 fully-qualified 特性名时仍能生成绑定。
///
[Test]
public Task Generates_Bindings_For_Fully_Qualified_Field_Attributes()
{
return VerifyGeneratedSourceAsync(
FullyQualifiedFieldAttributesSource,
InventoryPanelGeneratedFileName,
FullyQualifiedFieldAttributesExpected);
}
///
/// 验证 GetAll 会仅为可推断的 model、models、system 与 utility 字段生成绑定。
///
[Test]
public Task Generates_Inferred_Bindings_For_GetAll_Class()
{
return VerifyGeneratedSourceAsync(
InferredGetAllClassSource,
BattlePanelGeneratedFileName,
InferredGetAllClassExpected);
}
///
/// 验证 GetAll 与显式 [GetService] 可以组合生成绑定。
///
[Test]
public Task Generates_Explicit_Service_Binding_For_GetAll_Class()
{
return VerifyGeneratedSourceAsync(
ExplicitServiceGetAllClassSource,
BattlePanelGeneratedFileName,
ExplicitServiceGetAllClassExpected);
}
///
/// 验证目标类已声明注入方法时会报告冲突诊断。
///
[Test]
public Task Reports_Diagnostic_When_Generated_Injection_Method_Name_Already_Exists()
{
var source = MarkupTestSource.Parse(GeneratedInjectionMethodAlreadyExistsMarkupSource);
return VerifyDiagnosticAsync(
source,
CreateSingleSpanDiagnostic(
source,
"GF_Common_Class_002",
DiagnosticSeverity.Error,
"InventoryPanel",
"__InjectContextBindings_Generated"));
}
///
/// 验证 GetAll 会忽略不可推断的常量字段且不报告诊断。
///
[Test]
public Task Ignores_NonInferable_Const_Field_For_GetAll_Class_Without_Diagnostic()
{
return VerifyGeneratedSourceAsync(
IgnoreConstFieldGetAllSource,
BattlePanelGeneratedFileName,
IgnoreConstFieldGetAllExpected);
}
///
/// 验证只读推断字段会被跳过并报告 warning,同时其他字段保持生成。
///
[Test]
public Task Warns_And_Skips_Readonly_Inferred_Field_For_GetAll_Class()
{
var source = MarkupTestSource.Parse(ReadonlyInferredFieldGetAllMarkupSource);
return VerifyDiagnosticAndGeneratedSourceAsync(
source,
BattlePanelGeneratedFileName,
SkipInvalidGetAllFieldExpected,
CreateSingleSpanDiagnostic(
source,
"GF_ContextGet_008",
DiagnosticSeverity.Warning,
"_model"));
}
///
/// 验证静态推断字段会被跳过并报告 warning,同时其他字段保持生成。
///
[Test]
public Task Warns_And_Skips_Static_Inferred_Field_For_GetAll_Class()
{
var source = MarkupTestSource.Parse(StaticInferredFieldGetAllMarkupSource);
return VerifyDiagnosticAndGeneratedSourceAsync(
source,
BattlePanelGeneratedFileName,
SkipInvalidGetAllFieldExpected,
CreateSingleSpanDiagnostic(
source,
"GF_ContextGet_007",
DiagnosticSeverity.Warning,
"_model"));
}
///
/// 验证 nullable 的服务样字段不会被 GetAll 误判为可注入字段。
///
[Test]
public Task Skips_Nullable_Service_Like_Field_For_ContextAware_GetAll_Class()
{
return VerifyGeneratedSourceAsync(
SkipNullableServiceLikeFieldSource,
GameplayHudGeneratedFileName,
SkipNullableServiceLikeFieldExpected);
}
///
/// 验证实现 IContextAware 的类型无需 [ContextAware] 也能生成显式绑定。
///
[Test]
public Task Generates_Bindings_For_IContextAware_Class()
{
return VerifyGeneratedSourceAsync(
IContextAwareClassSource,
StrategyHostGeneratedFileName,
IContextAwareClassExpected);
}
///
/// 验证缺少上下文感知契约的类型会报告错误诊断。
///
[Test]
public Task Reports_Diagnostic_When_Class_Is_Not_ContextAware()
{
var source = MarkupTestSource.Parse(NonContextAwareClassMarkupSource);
return VerifyDiagnosticAsync(
source,
CreateSingleSpanDiagnostic(
source,
"GF_ContextGet_005",
DiagnosticSeverity.Error,
"InventoryPanel"));
}
///
/// 验证 [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" }
};
}
///
/// 将手工声明的期望生成源码归一化到当前平台换行符,避免不同宿主上的伪差异。
///
/// 原始期望源码。
/// 已按当前平台换行符归一化的源码文本。
private static string NormalizeLineEndings(string content)
{
return content
.Replace("\r\n", "\n", StringComparison.Ordinal)
.Replace("\r", "\n", StringComparison.Ordinal)
.Replace("\n", Environment.NewLine, StringComparison.Ordinal);
}
}