GFramework/GFramework.SourceGenerators.Tests/Rule/ContextGetGeneratorTests.cs
GeWuYou 796408539e refactor(generators): 优化ContextGetGenerator代码结构并改进异常处理
- 修改GetService方法文档,将返回值描述从"返回null"改为"抛出异常"
- 为GetService方法添加InvalidOperationException异常说明
- 删除冗余的IsExternalInit类文件
- 重构属性匹配逻辑,使用预定义集合进行候选属性名称验证
- 添加辅助方法HasCandidateAttribute、TryGetAttributeSimpleName等提升代码可读性
- 改进集合类型推断逻辑,支持接口类型的遍历匹配
- 更新单元测试以验证完全限定名属性和只读列表类型的支持
- 修正诊断错误位置信息的准确性
2026-03-28 12:54:17 +08:00

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();
}
}