mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-03-31 18:39:00 +08:00
- 实现 BindNodeSignalGenerator 用于生成节点信号绑定与解绑逻辑 - 实现 GetNodeGenerator 用于生成 Godot 节点获取注入逻辑 - 添加 BindNodeSignalDiagnostics 提供详细的诊断错误信息 - 集成到 AnalyzerReleases.Unshipped.md 追踪新的分析规则 - 支持 [BindNodeSignal] 属性的方法自动生成事件绑定代码 - 支持 [GetNode] 属性的字段自动生成节点获取代码 - 提供生命周期方法集成的智能提示和验证功能
306 lines
13 KiB
C#
306 lines
13 KiB
C#
using GFramework.Godot.SourceGenerators.Tests.Core;
|
|
|
|
namespace GFramework.Godot.SourceGenerators.Tests.GetNode;
|
|
|
|
[TestFixture]
|
|
public class GetNodeGeneratorTests
|
|
{
|
|
[Test]
|
|
public async Task Generates_InferredUniqueNameBindings_And_ReadyHook_WhenReadyIsMissing()
|
|
{
|
|
const string source = """
|
|
using System;
|
|
using GFramework.Godot.SourceGenerators.Abstractions;
|
|
using Godot;
|
|
|
|
namespace GFramework.Godot.SourceGenerators.Abstractions
|
|
{
|
|
[AttributeUsage(AttributeTargets.Field, Inherited = false, AllowMultiple = false)]
|
|
public sealed class GetNodeAttribute : Attribute
|
|
{
|
|
public GetNodeAttribute() {}
|
|
public GetNodeAttribute(string path) { Path = path; }
|
|
public string? Path { get; set; }
|
|
public bool Required { get; set; } = true;
|
|
public NodeLookupMode Lookup { get; set; } = NodeLookupMode.Auto;
|
|
}
|
|
|
|
public enum NodeLookupMode
|
|
{
|
|
Auto = 0,
|
|
UniqueName = 1,
|
|
RelativePath = 2,
|
|
AbsolutePath = 3
|
|
}
|
|
}
|
|
|
|
namespace Godot
|
|
{
|
|
public class Node
|
|
{
|
|
public virtual void _Ready() {}
|
|
public T GetNode<T>(string path) where T : Node => throw new InvalidOperationException(path);
|
|
public T? GetNodeOrNull<T>(string path) where T : Node => default;
|
|
}
|
|
|
|
public class HBoxContainer : Node
|
|
{
|
|
}
|
|
}
|
|
|
|
namespace TestApp
|
|
{
|
|
public partial class TopBar : HBoxContainer
|
|
{
|
|
[GetNode]
|
|
private HBoxContainer _leftContainer = null!;
|
|
|
|
[GetNode]
|
|
private HBoxContainer m_rightContainer = null!;
|
|
}
|
|
}
|
|
""";
|
|
|
|
const string expected = """
|
|
// <auto-generated />
|
|
#nullable enable
|
|
|
|
namespace TestApp;
|
|
|
|
partial class TopBar
|
|
{
|
|
private void __InjectGetNodes_Generated()
|
|
{
|
|
_leftContainer = GetNode<global::Godot.HBoxContainer>("%LeftContainer");
|
|
m_rightContainer = GetNode<global::Godot.HBoxContainer>("%RightContainer");
|
|
}
|
|
|
|
partial void OnGetNodeReadyGenerated();
|
|
|
|
public override void _Ready()
|
|
{
|
|
__InjectGetNodes_Generated();
|
|
OnGetNodeReadyGenerated();
|
|
}
|
|
}
|
|
|
|
""";
|
|
|
|
await GeneratorTest<GetNodeGenerator>.RunAsync(
|
|
source,
|
|
("TestApp_TopBar.GetNode.g.cs", expected));
|
|
}
|
|
|
|
[Test]
|
|
public async Task Generates_ManualInjectionOnly_WhenReadyAlreadyExists()
|
|
{
|
|
const string source = """
|
|
using System;
|
|
using GFramework.Godot.SourceGenerators.Abstractions;
|
|
using Godot;
|
|
|
|
namespace GFramework.Godot.SourceGenerators.Abstractions
|
|
{
|
|
[AttributeUsage(AttributeTargets.Field, Inherited = false, AllowMultiple = false)]
|
|
public sealed class GetNodeAttribute : Attribute
|
|
{
|
|
public GetNodeAttribute() {}
|
|
public GetNodeAttribute(string path) { Path = path; }
|
|
public string? Path { get; set; }
|
|
public bool Required { get; set; } = true;
|
|
public NodeLookupMode Lookup { get; set; } = NodeLookupMode.Auto;
|
|
}
|
|
|
|
public enum NodeLookupMode
|
|
{
|
|
Auto = 0,
|
|
UniqueName = 1,
|
|
RelativePath = 2,
|
|
AbsolutePath = 3
|
|
}
|
|
}
|
|
|
|
namespace Godot
|
|
{
|
|
public class Node
|
|
{
|
|
public virtual void _Ready() {}
|
|
public T GetNode<T>(string path) where T : Node => throw new InvalidOperationException(path);
|
|
public T? GetNodeOrNull<T>(string path) where T : Node => default;
|
|
}
|
|
|
|
public class HBoxContainer : Node
|
|
{
|
|
}
|
|
}
|
|
|
|
namespace TestApp
|
|
{
|
|
public partial class TopBar : HBoxContainer
|
|
{
|
|
[GetNode("%LeftContainer")]
|
|
private HBoxContainer _leftContainer = null!;
|
|
|
|
[GetNode(Required = false, Lookup = NodeLookupMode.RelativePath)]
|
|
private HBoxContainer? _rightContainer;
|
|
|
|
public override void _Ready()
|
|
{
|
|
__InjectGetNodes_Generated();
|
|
}
|
|
}
|
|
}
|
|
""";
|
|
|
|
const string expected = """
|
|
// <auto-generated />
|
|
#nullable enable
|
|
|
|
namespace TestApp;
|
|
|
|
partial class TopBar
|
|
{
|
|
private void __InjectGetNodes_Generated()
|
|
{
|
|
_leftContainer = GetNode<global::Godot.HBoxContainer>("%LeftContainer");
|
|
_rightContainer = GetNodeOrNull<global::Godot.HBoxContainer>("RightContainer");
|
|
}
|
|
}
|
|
|
|
""";
|
|
|
|
await GeneratorTest<GetNodeGenerator>.RunAsync(
|
|
source,
|
|
("TestApp_TopBar.GetNode.g.cs", expected));
|
|
}
|
|
|
|
[Test]
|
|
public async Task Reports_Diagnostic_When_FieldType_IsNotGodotNode()
|
|
{
|
|
const string source = """
|
|
using System;
|
|
using GFramework.Godot.SourceGenerators.Abstractions;
|
|
using Godot;
|
|
|
|
namespace GFramework.Godot.SourceGenerators.Abstractions
|
|
{
|
|
[AttributeUsage(AttributeTargets.Field, Inherited = false, AllowMultiple = false)]
|
|
public sealed class GetNodeAttribute : Attribute
|
|
{
|
|
public string? Path { get; set; }
|
|
public bool Required { get; set; } = true;
|
|
public NodeLookupMode Lookup { get; set; } = NodeLookupMode.Auto;
|
|
}
|
|
|
|
public enum NodeLookupMode
|
|
{
|
|
Auto = 0,
|
|
UniqueName = 1,
|
|
RelativePath = 2,
|
|
AbsolutePath = 3
|
|
}
|
|
}
|
|
|
|
namespace Godot
|
|
{
|
|
public class Node
|
|
{
|
|
public virtual void _Ready() {}
|
|
public T GetNode<T>(string path) where T : Node => throw new InvalidOperationException(path);
|
|
public T? GetNodeOrNull<T>(string path) where T : Node => default;
|
|
}
|
|
}
|
|
|
|
namespace TestApp
|
|
{
|
|
public partial class TopBar : Node
|
|
{
|
|
[GetNode]
|
|
private string _leftContainer = string.Empty;
|
|
}
|
|
}
|
|
""";
|
|
|
|
var test = new CSharpSourceGeneratorTest<GetNodeGenerator, DefaultVerifier>
|
|
{
|
|
TestState =
|
|
{
|
|
Sources = { source }
|
|
},
|
|
DisabledDiagnostics = { "GF_Common_Trace_001" }
|
|
};
|
|
|
|
test.ExpectedDiagnostics.Add(new DiagnosticResult("GF_Godot_GetNode_004", DiagnosticSeverity.Error)
|
|
.WithSpan(39, 24, 39, 38)
|
|
.WithArguments("_leftContainer"));
|
|
|
|
await test.RunAsync();
|
|
}
|
|
|
|
[Test]
|
|
public async Task Reports_Diagnostic_When_Generated_Injection_Method_Name_Already_Exists()
|
|
{
|
|
const string source = """
|
|
using System;
|
|
using GFramework.Godot.SourceGenerators.Abstractions;
|
|
using Godot;
|
|
|
|
namespace GFramework.Godot.SourceGenerators.Abstractions
|
|
{
|
|
[AttributeUsage(AttributeTargets.Field, Inherited = false, AllowMultiple = false)]
|
|
public sealed class GetNodeAttribute : Attribute
|
|
{
|
|
public GetNodeAttribute() {}
|
|
}
|
|
|
|
public enum NodeLookupMode
|
|
{
|
|
Auto = 0
|
|
}
|
|
}
|
|
|
|
namespace Godot
|
|
{
|
|
public class Node
|
|
{
|
|
public virtual void _Ready() {}
|
|
public T GetNode<T>(string path) where T : Node => throw new InvalidOperationException(path);
|
|
public T? GetNodeOrNull<T>(string path) where T : Node => default;
|
|
}
|
|
|
|
public class HBoxContainer : Node
|
|
{
|
|
}
|
|
}
|
|
|
|
namespace TestApp
|
|
{
|
|
public partial class TopBar : HBoxContainer
|
|
{
|
|
[GetNode]
|
|
private HBoxContainer _leftContainer = null!;
|
|
|
|
private void {|#0:__InjectGetNodes_Generated|}()
|
|
{
|
|
}
|
|
}
|
|
}
|
|
""";
|
|
|
|
var test = new CSharpSourceGeneratorTest<GetNodeGenerator, DefaultVerifier>
|
|
{
|
|
TestState =
|
|
{
|
|
Sources = { source }
|
|
},
|
|
DisabledDiagnostics = { "GF_Common_Trace_001" },
|
|
TestBehaviors = TestBehaviors.SkipGeneratedSourcesCheck
|
|
};
|
|
|
|
test.ExpectedDiagnostics.Add(new DiagnosticResult("GF_Common_Class_002", DiagnosticSeverity.Error)
|
|
.WithLocation(0)
|
|
.WithArguments("TopBar", "__InjectGetNodes_Generated"));
|
|
|
|
await test.RunAsync();
|
|
}
|
|
} |