mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-03-28 16:13:28 +08:00
- 移除ContextAwareServiceExtensions中GetService/GetSystem/GetModel/GetUtility方法的可空返回值 - 添加ContextGetGenerator源码生成器,支持通过特性自动生成上下文注入代码 - 新增GetService/GetServices/GetSystem/GetSystems/GetModel/GetModels/GetUtility/GetUtilities/GetAll特性 - 添加ContextGetDiagnostics提供注入相关的编译时诊断检查 - 实现INamedTypeSymbol扩展方法AreAllDeclarationsPartial用于检查partial类声明 - 添加ITypeSymbol扩展方法IsAssignableTo用于类型兼容性判断 - 创建FieldCandidateInfo和TypeCandidateInfo记录类型用于存储生成器候选信息 - 添加IsExternalInit内部类型支持低版本.NET框架的init-only setter功能 - 更新AnalyzerReleases.Unshipped.md添加新的诊断规则条目 - 创建完整的单元测试验证生成器功能和各种边界情况
425 lines
16 KiB
C#
425 lines
16 KiB
C#
using GFramework.SourceGenerators.Rule;
|
|
using GFramework.SourceGenerators.Tests.Core;
|
|
using Microsoft.CodeAnalysis;
|
|
using Microsoft.CodeAnalysis.CSharp.Testing;
|
|
using Microsoft.CodeAnalysis.Testing;
|
|
using NUnit.Framework;
|
|
|
|
namespace GFramework.SourceGenerators.Tests.Rule;
|
|
|
|
[TestFixture]
|
|
public class ContextGetGeneratorTests
|
|
{
|
|
[Test]
|
|
public async Task Generates_Bindings_For_ContextAwareAttribute_Class()
|
|
{
|
|
var source = """
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using GFramework.SourceGenerators.Abstractions.Rule;
|
|
|
|
namespace GFramework.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<T>(this object contextAware) => default!;
|
|
public static IReadOnlyList<T> GetServices<T>(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<IInventoryStrategy> _strategies = null!;
|
|
}
|
|
}
|
|
""";
|
|
|
|
const string expected = """
|
|
// <auto-generated />
|
|
#nullable enable
|
|
|
|
using GFramework.Core.Extensions;
|
|
|
|
namespace TestApp;
|
|
|
|
partial class InventoryPanel
|
|
{
|
|
private void __InjectContextBindings_Generated()
|
|
{
|
|
_model = this.GetModel<global::TestApp.IInventoryModel>();
|
|
_strategies = this.GetServices<global::TestApp.IInventoryStrategy>();
|
|
}
|
|
}
|
|
|
|
""";
|
|
|
|
await GeneratorTest<ContextGetGenerator>.RunAsync(
|
|
source,
|
|
("TestApp_InventoryPanel.ContextGet.g.cs", expected));
|
|
Assert.Pass();
|
|
}
|
|
|
|
[Test]
|
|
public async Task Generates_Inferred_Bindings_For_GetAll_Class()
|
|
{
|
|
var source = """
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using GFramework.SourceGenerators.Abstractions.Rule;
|
|
|
|
namespace GFramework.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<T>(this object contextAware) => default!;
|
|
public static IReadOnlyList<T> GetModels<T>(this object contextAware) => default!;
|
|
public static T GetSystem<T>(this object contextAware) => default!;
|
|
public static T GetUtility<T>(this object contextAware) => default!;
|
|
public static T GetService<T>(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<IInventoryModel> _models = null!;
|
|
private ICombatSystem _system = null!;
|
|
private IUiUtility _utility = null!;
|
|
private IStrategy _service = null!;
|
|
private Godot.Node _node = null!;
|
|
}
|
|
}
|
|
""";
|
|
|
|
const string expected = """
|
|
// <auto-generated />
|
|
#nullable enable
|
|
|
|
using GFramework.Core.Extensions;
|
|
|
|
namespace TestApp;
|
|
|
|
partial class BattlePanel
|
|
{
|
|
private void __InjectContextBindings_Generated()
|
|
{
|
|
_model = this.GetModel<global::TestApp.IInventoryModel>();
|
|
_models = this.GetModels<global::TestApp.IInventoryModel>();
|
|
_system = this.GetSystem<global::TestApp.ICombatSystem>();
|
|
_utility = this.GetUtility<global::TestApp.IUiUtility>();
|
|
_service = this.GetService<global::TestApp.IStrategy>();
|
|
}
|
|
}
|
|
|
|
""";
|
|
|
|
await GeneratorTest<ContextGetGenerator>.RunAsync(
|
|
source,
|
|
("TestApp_BattlePanel.ContextGet.g.cs", expected));
|
|
Assert.Pass();
|
|
}
|
|
|
|
[Test]
|
|
public async Task Generates_Bindings_For_IContextAware_Class()
|
|
{
|
|
var source = """
|
|
using System;
|
|
using GFramework.SourceGenerators.Abstractions.Rule;
|
|
|
|
namespace GFramework.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<T>(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 = """
|
|
// <auto-generated />
|
|
#nullable enable
|
|
|
|
using GFramework.Core.Extensions;
|
|
|
|
namespace TestApp;
|
|
|
|
partial class StrategyHost
|
|
{
|
|
private void __InjectContextBindings_Generated()
|
|
{
|
|
_strategy = this.GetService<global::TestApp.IStrategy>();
|
|
}
|
|
}
|
|
|
|
""";
|
|
|
|
await GeneratorTest<ContextGetGenerator>.RunAsync(
|
|
source,
|
|
("TestApp_StrategyHost.ContextGet.g.cs", expected));
|
|
Assert.Pass();
|
|
}
|
|
|
|
[Test]
|
|
public async Task Reports_Diagnostic_When_Class_Is_Not_ContextAware()
|
|
{
|
|
var source = """
|
|
using System;
|
|
using GFramework.SourceGenerators.Abstractions.Rule;
|
|
|
|
namespace GFramework.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<T>(this object contextAware) => default!;
|
|
}
|
|
}
|
|
|
|
namespace TestApp
|
|
{
|
|
public interface IInventoryModel : GFramework.Core.Abstractions.Model.IModel { }
|
|
|
|
public partial class InventoryPanel
|
|
{
|
|
[GetModel]
|
|
private IInventoryModel _model = null!;
|
|
}
|
|
}
|
|
""";
|
|
|
|
var test = new CSharpSourceGeneratorTest<ContextGetGenerator, DefaultVerifier>
|
|
{
|
|
TestState =
|
|
{
|
|
Sources = { source }
|
|
},
|
|
DisabledDiagnostics = { "GF_Common_Trace_001" }
|
|
};
|
|
|
|
test.ExpectedDiagnostics.Add(new DiagnosticResult("GF_ContextGet_005", DiagnosticSeverity.Error)
|
|
.WithSpan(31, 30, 31, 45)
|
|
.WithArguments("InventoryPanel"));
|
|
|
|
await test.RunAsync();
|
|
Assert.Pass();
|
|
}
|
|
|
|
[Test]
|
|
public async Task Reports_Diagnostic_When_GetModels_Field_Is_Not_IReadOnlyList()
|
|
{
|
|
var source = """
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using GFramework.SourceGenerators.Abstractions.Rule;
|
|
|
|
namespace GFramework.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<T> GetModels<T>(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<IInventoryModel> _models = new();
|
|
}
|
|
}
|
|
""";
|
|
|
|
var test = new CSharpSourceGeneratorTest<ContextGetGenerator, DefaultVerifier>
|
|
{
|
|
TestState =
|
|
{
|
|
Sources = { source }
|
|
},
|
|
DisabledDiagnostics = { "GF_Common_Trace_001" }
|
|
};
|
|
|
|
test.ExpectedDiagnostics.Add(new DiagnosticResult("GF_ContextGet_004", DiagnosticSeverity.Error)
|
|
.WithSpan(40, 40, 40, 47)
|
|
.WithArguments("_models", "System.Collections.Generic.List<TestApp.IInventoryModel>", "GetModels"));
|
|
|
|
await test.RunAsync();
|
|
Assert.Pass();
|
|
}
|
|
} |