mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-05-14 06:34:30 +08:00
Compare commits
No commits in common. "428b932f66faca85255c2c8210019bbd33dabf23" and "e692c721e92ae2230c0c6713f441dad806936b2d" have entirely different histories.
428b932f66
...
e692c721e9
@ -1,4 +1,7 @@
|
|||||||
namespace GFramework.Godot.SourceGenerators.Tests.Core;
|
using Microsoft.CodeAnalysis.CSharp.Testing;
|
||||||
|
using Microsoft.CodeAnalysis.Testing;
|
||||||
|
|
||||||
|
namespace GFramework.Godot.SourceGenerators.Tests.Core;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 提供源代码生成器测试的通用功能。
|
/// 提供源代码生成器测试的通用功能。
|
||||||
@ -27,21 +30,8 @@ public static class GeneratorTest<TGenerator>
|
|||||||
|
|
||||||
foreach (var (filename, content) in generatedSources)
|
foreach (var (filename, content) in generatedSources)
|
||||||
test.TestState.GeneratedSources.Add(
|
test.TestState.GeneratedSources.Add(
|
||||||
(typeof(TGenerator), filename, NormalizeLineEndings(content)));
|
(typeof(TGenerator), filename, content));
|
||||||
|
|
||||||
await test.RunAsync();
|
await test.RunAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 将测试内联快照统一为当前平台换行符,避免不同系统上的源生成输出比较出现伪差异。
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="content">原始快照内容。</param>
|
|
||||||
/// <returns>使用当前平台换行符的快照内容。</returns>
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@ -15,8 +15,4 @@ global using System;
|
|||||||
global using System.Collections.Generic;
|
global using System.Collections.Generic;
|
||||||
global using System.Linq;
|
global using System.Linq;
|
||||||
global using System.Threading;
|
global using System.Threading;
|
||||||
global using System.Threading.Tasks;
|
global using System.Threading.Tasks;
|
||||||
global using Microsoft.CodeAnalysis;
|
|
||||||
global using Microsoft.CodeAnalysis.CSharp.Testing;
|
|
||||||
global using Microsoft.CodeAnalysis.Testing;
|
|
||||||
global using NUnit.Framework;
|
|
||||||
@ -1,4 +1,7 @@
|
|||||||
namespace GFramework.SourceGenerators.Tests.Core;
|
using Microsoft.CodeAnalysis.CSharp.Testing;
|
||||||
|
using Microsoft.CodeAnalysis.Testing;
|
||||||
|
|
||||||
|
namespace GFramework.SourceGenerators.Tests.Core;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 提供源代码生成器测试的通用功能
|
/// 提供源代码生成器测试的通用功能
|
||||||
@ -29,21 +32,8 @@ public static class GeneratorTest<TGenerator>
|
|||||||
// 添加期望的生成源文件到测试状态中
|
// 添加期望的生成源文件到测试状态中
|
||||||
foreach (var (filename, content) in generatedSources)
|
foreach (var (filename, content) in generatedSources)
|
||||||
test.TestState.GeneratedSources.Add(
|
test.TestState.GeneratedSources.Add(
|
||||||
(typeof(TGenerator), filename, NormalizeLineEndings(content)));
|
(typeof(TGenerator), filename, content));
|
||||||
|
|
||||||
await test.RunAsync();
|
await test.RunAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 将测试快照统一为当前平台换行符,避免不同系统上的源生成输出比较出现伪差异。
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="content">原始快照内容。</param>
|
|
||||||
/// <returns>使用当前平台换行符的快照内容。</returns>
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@ -15,8 +15,4 @@ global using System;
|
|||||||
global using System.Collections.Generic;
|
global using System.Collections.Generic;
|
||||||
global using System.Linq;
|
global using System.Linq;
|
||||||
global using System.Threading;
|
global using System.Threading;
|
||||||
global using System.Threading.Tasks;
|
global using System.Threading.Tasks;
|
||||||
global using Microsoft.CodeAnalysis;
|
|
||||||
global using Microsoft.CodeAnalysis.CSharp.Testing;
|
|
||||||
global using Microsoft.CodeAnalysis.Testing;
|
|
||||||
global using NUnit.Framework;
|
|
||||||
@ -1,5 +1,9 @@
|
|||||||
using GFramework.SourceGenerators.Rule;
|
using GFramework.SourceGenerators.Rule;
|
||||||
using GFramework.SourceGenerators.Tests.Core;
|
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;
|
namespace GFramework.SourceGenerators.Tests.Rule;
|
||||||
|
|
||||||
@ -230,6 +234,7 @@ public class ContextGetGeneratorTests
|
|||||||
public static IReadOnlyList<T> GetModels<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 GetSystem<T>(this object contextAware) => default!;
|
||||||
public static T GetUtility<T>(this object contextAware) => default!;
|
public static T GetUtility<T>(this object contextAware) => default!;
|
||||||
|
public static T GetService<T>(this object contextAware) => default!;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -253,7 +258,6 @@ public class ContextGetGeneratorTests
|
|||||||
private ICombatSystem _system = null!;
|
private ICombatSystem _system = null!;
|
||||||
private IUiUtility _utility = null!;
|
private IUiUtility _utility = null!;
|
||||||
private IStrategy _service = null!;
|
private IStrategy _service = null!;
|
||||||
private IReadOnlyList<IStrategy> _services = null!;
|
|
||||||
private Godot.Node _node = null!;
|
private Godot.Node _node = null!;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -275,96 +279,6 @@ public class ContextGetGeneratorTests
|
|||||||
_models = this.GetModels<global::TestApp.IInventoryModel>();
|
_models = this.GetModels<global::TestApp.IInventoryModel>();
|
||||||
_system = this.GetSystem<global::TestApp.ICombatSystem>();
|
_system = this.GetSystem<global::TestApp.ICombatSystem>();
|
||||||
_utility = this.GetUtility<global::TestApp.IUiUtility>();
|
_utility = this.GetUtility<global::TestApp.IUiUtility>();
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
""";
|
|
||||||
|
|
||||||
await GeneratorTest<ContextGetGenerator>.RunAsync(
|
|
||||||
source,
|
|
||||||
("TestApp_BattlePanel.ContextGet.g.cs", expected));
|
|
||||||
Assert.Pass();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public async Task Generates_Explicit_Service_Binding_For_GetAll_Class()
|
|
||||||
{
|
|
||||||
var source = """
|
|
||||||
using System;
|
|
||||||
using GFramework.SourceGenerators.Abstractions.Rule;
|
|
||||||
|
|
||||||
namespace GFramework.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<T>(this object contextAware) => default!;
|
|
||||||
public static T GetService<T>(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 = """
|
|
||||||
// <auto-generated />
|
|
||||||
#nullable enable
|
|
||||||
|
|
||||||
using GFramework.Core.Extensions;
|
|
||||||
|
|
||||||
namespace TestApp;
|
|
||||||
|
|
||||||
partial class BattlePanel
|
|
||||||
{
|
|
||||||
private void __InjectContextBindings_Generated()
|
|
||||||
{
|
|
||||||
_model = this.GetModel<global::TestApp.IInventoryModel>();
|
|
||||||
_service = this.GetService<global::TestApp.IStrategy>();
|
_service = this.GetService<global::TestApp.IStrategy>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -377,98 +291,6 @@ public class ContextGetGeneratorTests
|
|||||||
Assert.Pass();
|
Assert.Pass();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
|
||||||
public async Task Skips_Nullable_Service_Like_Field_For_ContextAware_GetAll_Class()
|
|
||||||
{
|
|
||||||
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.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<T>(this object contextAware) => default!;
|
|
||||||
public static T GetSystem<T>(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 = """
|
|
||||||
// <auto-generated />
|
|
||||||
#nullable enable
|
|
||||||
|
|
||||||
using GFramework.Core.Extensions;
|
|
||||||
|
|
||||||
namespace TestApp;
|
|
||||||
|
|
||||||
partial class GameplayHud
|
|
||||||
{
|
|
||||||
private void __InjectContextBindings_Generated()
|
|
||||||
{
|
|
||||||
_gridModel = this.GetModel<global::TestApp.IGridModel>();
|
|
||||||
_runLoopSystem = this.GetSystem<global::TestApp.IRunLoopSystem>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
""";
|
|
||||||
|
|
||||||
await GeneratorTest<ContextGetGenerator>.RunAsync(
|
|
||||||
source,
|
|
||||||
("TestApp_GameplayHud.ContextGet.g.cs", expected));
|
|
||||||
Assert.Pass();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public async Task Generates_Bindings_For_IContextAware_Class()
|
public async Task Generates_Bindings_For_IContextAware_Class()
|
||||||
{
|
{
|
||||||
|
|||||||
@ -15,6 +15,4 @@ global using System;
|
|||||||
global using System.Collections.Generic;
|
global using System.Collections.Generic;
|
||||||
global using System.Linq;
|
global using System.Linq;
|
||||||
global using System.Threading;
|
global using System.Threading;
|
||||||
global using System.Threading.Tasks;
|
global using System.Threading.Tasks;
|
||||||
global using Microsoft.CodeAnalysis;
|
|
||||||
global using Microsoft.CodeAnalysis.CSharp.Syntax;
|
|
||||||
@ -43,7 +43,4 @@ public partial class InventoryPanel
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
`[GetAll]` 作用于类本身,会自动扫描字段并推断 `Model`、`System`、`Utility` 相关的 `GetX` 调用;已显式标记字段的优先级更高。
|
`[GetAll]` 作用于类本身,会自动扫描字段并推断对应的 `GetX` 调用;已显式标记字段的优先级更高。
|
||||||
|
|
||||||
`Service` 和 `Services` 绑定不会在 `[GetAll]` 下自动推断。对于普通引用类型字段,请显式使用 `[GetService]` 或
|
|
||||||
`[GetServices]`,避免将非上下文服务字段误判为服务依赖。
|
|
||||||
|
|||||||
@ -5,6 +5,9 @@ using GFramework.SourceGenerators.Common.Diagnostics;
|
|||||||
using GFramework.SourceGenerators.Common.Extensions;
|
using GFramework.SourceGenerators.Common.Extensions;
|
||||||
using GFramework.SourceGenerators.Common.Info;
|
using GFramework.SourceGenerators.Common.Info;
|
||||||
using GFramework.SourceGenerators.Diagnostics;
|
using GFramework.SourceGenerators.Diagnostics;
|
||||||
|
using Microsoft.CodeAnalysis;
|
||||||
|
using Microsoft.CodeAnalysis.CSharp;
|
||||||
|
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||||
|
|
||||||
namespace GFramework.SourceGenerators.Rule;
|
namespace GFramework.SourceGenerators.Rule;
|
||||||
|
|
||||||
@ -579,7 +582,12 @@ public sealed class ContextGetGenerator : IIncrementalGenerator
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Service collections stay opt-in for the same reason as single services.
|
if (elementType.IsReferenceType)
|
||||||
|
{
|
||||||
|
binding = new BindingInfo(fieldSymbol, BindingKind.Services, elementType);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -601,7 +609,12 @@ public sealed class ContextGetGenerator : IIncrementalGenerator
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Service bindings stay opt-in because arbitrary reference types are too ambiguous to infer safely.
|
if (fieldSymbol.Type.IsReferenceType)
|
||||||
|
{
|
||||||
|
binding = new BindingInfo(fieldSymbol, BindingKind.Service, fieldSymbol.Type);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user