Skip to content

Commit

Permalink
add support for generic types (#76)
Browse files Browse the repository at this point in the history
  • Loading branch information
Skleni committed Jul 1, 2022
1 parent 6046abd commit 64599e0
Show file tree
Hide file tree
Showing 3 changed files with 97 additions and 12 deletions.
48 changes: 47 additions & 1 deletion DotNet/Packer.Test/DeclarationTest.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System;
using System;
using System.IO;
using System.Linq;
using Xunit;
Expand Down Expand Up @@ -290,6 +290,52 @@ public void DefinitionIsGeneratedForTypeWithListProperty ()
Contains("Combine(items: Array<n.Item>): n.Container");
}

[Fact]
public void DefinitionIsGeneratedForGenericClass ()
{
AddAssembly(
With("n", "public class GenericClass<T> { public T Value { get; set; } }"),
With("n", "[JSInvokable] public static void Method (GenericClass<string> p) { }"));
Task.Execute();
Matches(@"export class GenericClass<T> {\s*value: T;\s*}");
Contains("Method(p: n.GenericClass<string>): void");
}

[Fact]
public void DefinitionIsGeneratedForGenericInterface()
{
AddAssembly(
With("n", "public interface GenericInterface<T> { public T Value { get; set; } }"),
With("n", "[JSInvokable] public static GenericInterface<string> Method () => default;"));
Task.Execute();
Matches(@"export interface GenericInterface<T> {\s*value: T;\s*}");
Contains("Method(): n.GenericInterface<string>");
}

[Fact]
public void DefinitionIsGeneratedForNestedGenericTypes()
{
AddAssembly(
With("Foo", "public class GenericClass<T> { public T Value { get; set; } }", false),
With("Bar", "public interface GenericInterface<T> { public T Value { get; set; } }", false),
With("n", "[JSInvokable] public static void Method (Foo.GenericClass<Bar.GenericInterface<string>> p) { }"));
Task.Execute();
Matches(@"export namespace Foo {\s*export class GenericClass<T> {\s*value: T;\s*}\s*}");
Matches(@"export namespace Bar {\s*export interface GenericInterface<T> {\s*value: T;\s*}\s*}");
Contains("Method(p: Foo.GenericClass<Bar.GenericInterface<string>>): void");
}

[Fact]
public void DefinitionIsGeneratedForGenericClassWithMultipleTypeArguments()
{
AddAssembly(
With("n", "public class GenericClass<T1, T2> { public T1 Key { get; set; } public T2 Value { get; set; } }"),
With("n", "[JSInvokable] public static void Method (GenericClass<string, int> p) { }"));
Task.Execute();
Matches(@"export class GenericClass<T1, T2> {\s*key: T1;\s*value: T2;\s*}");
Contains("Method(p: n.GenericClass<string, number>): void");
}

[Fact]
public void CanCrawlCustomTypes ()
{
Expand Down
55 changes: 47 additions & 8 deletions DotNet/Packer/DeclarationGenerator/TypeConverter.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using static Packer.TypeUtilities;
Expand All @@ -15,9 +15,12 @@ public TypeConverter (NamespaceBuilder spaceBuilder)
this.spaceBuilder = spaceBuilder;
}

public string ToTypeScript (Type type)
public string ToTypeScript(Type type) => ToTypeScript(type, true);

public string ToTypeScript (Type type, bool withNamespace)
{
if (ShouldConvertToObject(type)) return ConvertToObject(type);
if (type.IsGenericParameter) return type.Name;
if (ShouldConvertToObject(type)) return ConvertToObject(type, withNamespace);
return ConvertToSimple(type);
}

Expand All @@ -27,15 +30,34 @@ private bool ShouldConvertToObject (Type type)
{
type = GetUnderlyingType(type);
return (Type.GetTypeCode(type) == TypeCode.Object || type.IsEnum) &&
!type.IsGenericTypeParameter &&
!ShouldIgnoreAssembly(type.Assembly.FullName!);
}

private string ConvertToObject (Type type)
private string ConvertToObject (Type type, bool withNamespace)
{
if (IsArray(type)) return $"Array<{ConvertToObject(GetArrayElementType(type))}>";
if (IsNullable(type)) return ConvertToObject(GetNullableUnderlyingType(type));
if (IsArray(type)) return $"Array<{ConvertToObject(GetArrayElementType(type), withNamespace)}>";
if (IsNullable(type)) return ConvertToObject(GetNullableUnderlyingType(type), withNamespace);

CrawlObjectType(type);
return $"{spaceBuilder.Build(type)}.{type.Name}";

string typeName = type.IsGenericType
? ConvertGenericType(type)
: type.Name;

return withNamespace
? $"{spaceBuilder.Build(type)}.{typeName}"
: typeName;
}

public string ConvertGenericType (Type type)
{
CrawlObjectType(type);

var genericTypeName = type.Name.Substring(0, type.Name.IndexOf("`"));
var genericTypeArguments = string.Join(", ", type.GetGenericArguments().Select(ToTypeScript));

return $"{genericTypeName}<{genericTypeArguments}>";
}

private string ConvertToSimple (Type type)
Expand Down Expand Up @@ -81,7 +103,24 @@ private string ToPromise (Type type)
private void CrawlObjectType (Type type)
{
type = GetUnderlyingType(type);
if (!objectTypes.Add(type)) return;

if (!type.IsGenericType || type.IsGenericTypeDefinition)
{
if (!objectTypes.Add(type)) return;
}
else
{
// don't add GenericType<string>, only its definition GenericType<T>
foreach (var argument in type.GenericTypeArguments)
{
if (ShouldConvertToObject(argument))
CrawlObjectType(argument);
}

var definition = type.GetGenericTypeDefinition();
CrawlObjectType(definition);
}

CrawlProperties(type);
CrawlBaseType(type);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
Expand Down Expand Up @@ -69,7 +69,7 @@ private void CloseNamespace ()

private void DeclareClass ()
{
AppendLine($"export class {type.Name}", 1);
AppendLine($"export class {converter.ToTypeScript(type, withNamespace: false)}", 1);
AppendBaseType();
AppendInterfaces();
builder.Append(" {");
Expand All @@ -79,7 +79,7 @@ private void DeclareClass ()

private void DeclareInterface ()
{
AppendLine($"export interface {type.Name}", 1);
AppendLine($"export interface {converter.ToTypeScript(type, withNamespace: false)}", 1);
AppendBaseType();
AppendInterfaces();
builder.Append(" {");
Expand Down

0 comments on commit 64599e0

Please sign in to comment.