mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-03-28 16:13:28 +08:00
- 修改GetService方法文档,将返回值描述从"返回null"改为"抛出异常" - 为GetService方法添加InvalidOperationException异常说明 - 删除冗余的IsExternalInit类文件 - 重构属性匹配逻辑,使用预定义集合进行候选属性名称验证 - 添加辅助方法HasCandidateAttribute、TryGetAttributeSimpleName等提升代码可读性 - 改进集合类型推断逻辑,支持接口类型的遍历匹配 - 更新单元测试以验证完全限定名属性和只读列表类型的支持 - 修正诊断错误位置信息的准确性
584 lines
22 KiB
C#
584 lines
22 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_Bindings_For_Fully_Qualified_Field_Attributes()
|
|
{
|
|
var source = """
|
|
using System;
|
|
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 { }
|
|
}
|
|
|
|
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!;
|
|
}
|
|
}
|
|
|
|
namespace TestApp
|
|
{
|
|
public interface IInventoryModel : GFramework.Core.Abstractions.Model.IModel { }
|
|
|
|
[ContextAware]
|
|
public partial class InventoryPanel
|
|
{
|
|
[global::GFramework.SourceGenerators.Abstractions.Rule.GetModel]
|
|
private IInventoryModel _model = 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>();
|
|
}
|
|
}
|
|
|
|
""";
|
|
|
|
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(40, 33, 40, 39)
|
|
.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(46, 39, 46, 46)
|
|
.WithArguments("_models", "System.Collections.Generic.List<TestApp.IInventoryModel>", "GetModels"));
|
|
|
|
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.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 IEnumerable<IInventoryModel> _models = null!;
|
|
}
|
|
}
|
|
""";
|
|
|
|
const string expected = """
|
|
// <auto-generated />
|
|
#nullable enable
|
|
|
|
using GFramework.Core.Extensions;
|
|
|
|
namespace TestApp;
|
|
|
|
partial class InventoryPanel
|
|
{
|
|
private void __InjectContextBindings_Generated()
|
|
{
|
|
_models = this.GetModels<global::TestApp.IInventoryModel>();
|
|
}
|
|
}
|
|
|
|
""";
|
|
|
|
await GeneratorTest<ContextGetGenerator>.RunAsync(
|
|
source,
|
|
("TestApp_InventoryPanel.ContextGet.g.cs", expected));
|
|
Assert.Pass();
|
|
}
|
|
} |