Skip to content
This repository has been archived by the owner on Jun 10, 2024. It is now read-only.

Commit

Permalink
feat: allow generic scripts (#12)
Browse files Browse the repository at this point in the history
  • Loading branch information
jolexxa authored Sep 9, 2023
1 parent e1339ce commit af0cfb6
Show file tree
Hide file tree
Showing 13 changed files with 118 additions and 7 deletions.
2 changes: 2 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -467,3 +467,5 @@ dotnet_diagnostic.RCS1160.severity = none
dotnet_diagnostic.RCS1175.severity = none
# Don't make me change if statements to conditional expressions.
dotnet_diagnostic.IDE0046.severity = none
# Let me use old-school constructors if I want.
dotnet_diagnostic.IDE0290.severity = none
35 changes: 35 additions & 0 deletions SuperNodes.TestCases/test/test_cases/GenericScriptTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
namespace GenericScript;

using Chickensoft.GoDotTest;
using Godot;
using Shouldly;
using SuperNodes.Types;

[SuperNode(typeof(MyPowerUp))]
public abstract partial class MySuperNodeBase<T> : Node2D {
public override partial void _Notification(int what);

public bool IsReady { get; private set; }

public T? Item { get; set; }

public void OnReady() => IsReady = true;
}

public partial class MySuperNode : MySuperNodeBase<string> { }

[PowerUp]
public abstract partial class MyPowerUp : Node2D {
public void OnMyPowerUp(int what) { }
}

public class GenericScriptTest : TestClass {
public GenericScriptTest(Node testScene) : base(testScene) { }

[Test]
public void Test() {
var mySuperNode = new MySuperNode();
mySuperNode._Notification((int)Node.NotificationReady);
mySuperNode.IsReady.ShouldBeTrue();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ public void OnTestPowerUp(int what) {
var superNode = new SuperNode(
Namespace: "Tests",
Name: "TestSuperNode",
NameWithoutGenerics: "TestSuperNode",
Location: new Mock<Location>().Object,
BaseClasses: new string[] { "global::Godot.Node" }.ToImmutableArray(),
LifecycleHooks: ImmutableArray<IGodotNodeLifecycleHook>.Empty,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ public void GeneratesSuperNode() {
SuperNode: new SuperNode(
Namespace: "global::Tests",
Name: "TestSuperNode",
NameWithoutGenerics: "TestSuperNode",
Location: new Mock<Location>().Object,
BaseClasses: new string[] { "global::Godot.Node" }.ToImmutableArray(),
LifecycleHooks: lifecycleHooks,
Expand Down Expand Up @@ -184,6 +185,7 @@ public void GeneratesSuperNodeStaticReflectionTables() {
var superNode = new SuperNode(
Namespace: "global::Tests",
Name: "TestSuperNode",
NameWithoutGenerics: "TestSuperNode",
Location: new Mock<Location>().Object,
BaseClasses: new string[] { "global::Godot.Node" }.ToImmutableArray(),
LifecycleHooks: ImmutableArray<IGodotNodeLifecycleHook>.Empty,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ public void OnReady() { }
code, out var symbol
);

codeService.Setup(cs => cs.GetName(symbol, node))
codeService.Setup(cs => cs.GetNameWithGenerics(symbol, node))
.Returns(symbol.Name);
codeService.Setup(cs => cs.GetContainingNamespace(symbol))
.Returns("Tests");
Expand Down
1 change: 1 addition & 0 deletions SuperNodes.Tests/tests/common/models/GenerationItemTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ public void Initializes() {
var superNode = new SuperNode(
Namespace: "global::Tests",
Name: "SuperNode",
NameWithoutGenerics: "SuperNode",
Location: new Mock<Location>().Object,
BaseClasses: new string[] { "global::Godot.Node" }.ToImmutableArray(),
LifecycleHooks: ImmutableArray<IGodotNodeLifecycleHook>.Empty,
Expand Down
2 changes: 2 additions & 0 deletions SuperNodes.Tests/tests/common/models/SuperNodeTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ public void Initializes() {
var superNode = new SuperNode(
Namespace: "global::Tests",
Name: "SuperNode",
NameWithoutGenerics: "SuperNode",
Location: new Mock<Location>().Object,
BaseClasses: new string[] { "global::Godot.Node" }.ToImmutableArray(),
LifecycleHooks: ImmutableArray<IGodotNodeLifecycleHook>.Empty,
Expand All @@ -33,6 +34,7 @@ public void FilenamePrefixHandlesEmptyNamespace() {
var superNode = new SuperNode(
Namespace: "",
Name: "SuperNode",
NameWithoutGenerics: "SuperNode",
Location: new Mock<Location>().Object,
BaseClasses: new string[] { "global::Godot.Node" }.ToImmutableArray(),
LifecycleHooks: ImmutableArray<IGodotNodeLifecycleHook>.Empty,
Expand Down
31 changes: 31 additions & 0 deletions SuperNodes.Tests/tests/common/services/CodeServiceTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -551,6 +551,37 @@ public class TestClass { }
codeService.GetName(null, node).ShouldBe(node.Identifier.ValueText);
}

[Fact]
public void GetNameWithFallbackAndGenerics() {
var code = """
namespace Tests {
public class TestClass<TA, TB> { }
}
""";

var node = Tester.Parse<ClassDeclarationSyntax>(code);

var symbol = new Mock<INamedTypeSymbol>();

var ta = new Mock<ITypeParameterSymbol>();
ta.Setup(s => s.Name).Returns("TA");

var tb = new Mock<ITypeParameterSymbol>();
tb.Setup(s => s.Name).Returns("TB");

symbol.Setup(s => s.Name).Returns("TestClass");
symbol.Setup(s => s.TypeParameters).Returns(
new[] { ta.Object, tb.Object }.ToImmutableArray()
);

var codeService = new CodeService();

codeService.GetNameWithGenerics(symbol.Object, node)
.ShouldBe("TestClass<TA, TB>");
codeService.GetNameWithGenerics(null, node)
.ShouldBe(node.Identifier.ValueText);
}

[Fact]
public void HasOnNotificationMethodHandlerFindsHandler() {
var code = """
Expand Down
2 changes: 1 addition & 1 deletion SuperNodes.Types/Chickensoft.SuperNodes.Types.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
<DebugType>portable</DebugType>

<Title>SuperNodes Types</Title>
<Version>1.5.0</Version>
<Version>1.5.1</Version>
<Description>Runtime types for SuperNodes.</Description>
<Copyright>© 2023 Chickensoft</Copyright>
<Authors>Chickensoft</Authors>
Expand Down
2 changes: 1 addition & 1 deletion SuperNodes/Chickensoft.SuperNodes.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
<NoWarn>NU5128</NoWarn>

<Title>SuperNodes</Title>
<Version>1.5.0</Version>
<Version>1.5.1</Version>
<Description>Supercharge your Godot nodes with lifecycle-aware mixins, third party source generators, script introspection, and dynamic property manipulation — all without runtime reflection!</Description>
<Copyright>© 2023 Chickensoft Games</Copyright>
<Authors>Chickensoft</Authors>
Expand Down
4 changes: 3 additions & 1 deletion SuperNodes/src/SuperNodesFeature/SuperNodesRepo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,8 @@ public SuperNode GetSuperNode(
ClassDeclarationSyntax classDeclaration,
INamedTypeSymbol? symbol
) {
var name = CodeService.GetName(symbol, classDeclaration);
var name = CodeService.GetNameWithGenerics(symbol, classDeclaration);
var nameWithoutGenerics = CodeService.GetName(symbol, classDeclaration);
var @namespace = CodeService.GetContainingNamespace(symbol);
var baseClasses = CodeService.GetBaseClassHierarchy(symbol);

Expand Down Expand Up @@ -110,6 +111,7 @@ var hasOnNotificationMethodHandler
return new SuperNode(
Namespace: @namespace,
Name: name,
NameWithoutGenerics: nameWithoutGenerics,
Location: classDeclaration.GetLocation(),
BaseClasses: baseClasses,
LifecycleHooks: lifecycleHooksResponse.LifecycleHooks,
Expand Down
11 changes: 8 additions & 3 deletions SuperNodes/src/common/models/SuperNode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,12 @@ namespace SuperNodes.Common.Models;
/// </summary>
/// <param name="Namespace">Fully qualified namespace containing the node
/// (without the <c>global::</c> prefix).</param>
/// <param name="Name">Name of the Godot Node (not fully qualified). Combine
/// <param name="Name">Name of the Godot Node (not fully qualified, but includes
/// generic parameter syntax). Combine
/// with <paramref name="Namespace" /> to determine the fully resolved name.
/// </param>
/// <param name="NameWithoutGenerics">Name of the Godot node, without any
/// generic parameter syntax.</param>
/// <param name="Location">The location of the class declaration syntax node
/// that corresponds to the SuperNode.</param>
/// <param name="BaseClasses">Array of fully qualified base types (base class
Expand Down Expand Up @@ -50,6 +53,7 @@ namespace SuperNodes.Common.Models;
public record SuperNode(
string? Namespace,
string Name,
string NameWithoutGenerics,
Location Location,
ImmutableArray<string> BaseClasses,
ImmutableArray<IGodotNodeLifecycleHook> LifecycleHooks,
Expand All @@ -64,6 +68,7 @@ IImmutableSet<string> Usings
/// Filename prefix to use when generating the SuperNode's related
/// implementation files.
/// </summary>
public string FilenamePrefix
=> Namespace is not "" ? $"{Namespace}.{Name}" : Name;
public string FilenamePrefix => Namespace is not ""
? $"{Namespace}.{NameWithoutGenerics}"
: NameWithoutGenerics;
}
30 changes: 30 additions & 0 deletions SuperNodes/src/common/services/CodeService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,19 @@ string GetName(
INamedTypeSymbol? symbol, TypeDeclarationSyntax fallbackType
);

/// <summary>
/// Returns the name of the symbol, or the name of the identifier for the
/// given type declaration syntax node if the symbol is null.
/// <br />
/// If the type has generics, this appends the generic parameter syntax
/// to the name.
/// </summary>
/// <param name="symbol">Named type symbol.</param>
/// <param name="fallbackType">Fallback type declaration syntax node.</param>
string GetNameWithGenerics(
INamedTypeSymbol? symbol, TypeDeclarationSyntax fallbackType
);

/// <summary>
/// Gets the name of the symbol, or null if the symbol is null.
/// </summary>
Expand Down Expand Up @@ -458,6 +471,23 @@ public string GetName(
INamedTypeSymbol? symbol, TypeDeclarationSyntax fallbackType
) => symbol?.Name ?? fallbackType.Identifier.ValueText;

public string GetNameWithGenerics(
INamedTypeSymbol? symbol, TypeDeclarationSyntax fallbackType
) {
var name = GetName(symbol, fallbackType);

if (symbol?.TypeParameters.Length > 0) {
name += "<" + string.Join(
", ",
symbol.TypeParameters.Select(
typeParameter => typeParameter.Name
)
) + ">";
}

return name;
}

public string? GetName(ISymbol? symbol) => symbol?.Name;

public ImmutableArray<ISymbol> GetMembers(INamedTypeSymbol? symbol)
Expand Down

0 comments on commit af0cfb6

Please sign in to comment.