Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix naming issues with generic types #6461

Merged
merged 7 commits into from
Aug 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 36 additions & 15 deletions src/HotChocolate/Core/src/Abstractions/NameFormattingHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using HotChocolate.Properties;
using HotChocolate.Utilities;
Expand All @@ -17,7 +18,7 @@ internal static class NameFormattingHelpers
{
private const string _get = "Get";
private const string _async = "Async";
private const string _typePostfix = "`1";
private const char _genericTypeDelimiter = '`';

public static string GetGraphQLName(this Type type)
{
Expand All @@ -26,10 +27,7 @@ public static string GetGraphQLName(this Type type)
throw new ArgumentNullException(nameof(type));
}

var typeInfo = type.GetTypeInfo();
var name = typeInfo.IsDefined(typeof(GraphQLNameAttribute), false)
? typeInfo.GetCustomAttribute<GraphQLNameAttribute>()!.Name
: GetFromType(typeInfo);
var name = GetFromType(type);

return NameUtils.MakeValidGraphQLName(name)!;
}
Expand Down Expand Up @@ -184,21 +182,44 @@ public static bool IsDeprecated(

private static string GetFromType(Type type)
{
if (type.GetTypeInfo().IsGenericType)
var typeName = type.IsDefined(typeof(GraphQLNameAttribute), false)
? type.GetCustomAttribute<GraphQLNameAttribute>()!.Name
: null;

if (type.IsGenericType)
{
var name = type.GetTypeInfo()
.GetGenericTypeDefinition()
.Name;
if (typeName == null)
{
typeName = type.GetGenericTypeDefinition().Name;

var nameSpan = typeName.AsSpan();
var index = nameSpan.LastIndexOf(_genericTypeDelimiter);

if (index >= 0)
{
nameSpan = nameSpan.Slice(0, index);
}

typeName = nameSpan.ToString();
}

name = name.Substring(0, name.Length - _typePostfix.Length);
var arguments = type.GetGenericArguments();
var stringBuilder = new StringBuilder(typeName).Append("Of");

var arguments = type
.GetTypeInfo().GenericTypeArguments
.Select(GetFromType);
for (var i = 0; i < arguments.Length; i++)
{
if (i > 0)
{
stringBuilder.Append("And");
}

return $"{name}Of{string.Join("And", arguments)}";
stringBuilder.Append(GetFromType(arguments[i]));
}

return stringBuilder.ToString();
}
return type.Name;

return typeName ?? type.Name;
}

public static unsafe string FormatFieldName(string fieldName)
Expand Down
84 changes: 84 additions & 0 deletions src/HotChocolate/Core/test/Types.Tests/GenericTypeNamingTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
using System;
using System.Threading.Tasks;
using HotChocolate.Execution;
using HotChocolate.Tests;
using Microsoft.Extensions.DependencyInjection;

#nullable enable

namespace HotChocolate;

public class GenericTypesNamingTests
{
[Fact]
public async Task NamingResolution()
{
await new ServiceCollection()
.AddGraphQL()
.AddQueryType<Query>()
.BuildSchemaAsync()
.MatchSnapshotAsync();
}

public class Query
{
public Tuple<int> OneGenericType => default!;
public Tuple<int, int> TwoGenericsType => default!;
public Tuple<int, int, int> ThreeGenericsType => default!;
public Tuple<int, int, int, int> FourGenericsType => default!;
public Tuple<int, int, int, int, int> FiveGenericTypes => default!;
public Tuple<int, int, int, int, int, int> SixGenericTypes => default!;
public Tuple<int, int, int, int, int, int, int> SevenGenericTypes => default!;
public EightElementsTuple<int, int, int, int, int, int, int, int> EightGenericTypes => default!;
public NineElementsTuple<int, int, int, int, int, int, int, int, int> NineGenericTypes => default!;
public TenElementsTuple<int, int, int, int, int, int, int, int, int, int> TenGenericTypes => default!;
public Foo<int> IntBar => default!;
public Foo<string> StringBar => default!;
public Foo<Bar> CustomNameBar => default!;
}

public class EightElementsTuple<T1, T2, T3, T4, T5, T6, T7, T8> : Tuple<T1, T2, T3, T4, T5, T6, T7>
{
public T8 Item8 { get; set; } = default!;

public EightElementsTuple(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5, T6 item6, T7 item7, T8 item8)
: base(item1, item2, item3, item4, item5, item6, item7)
{
Item8 = item8;
}
}

public class NineElementsTuple<T1, T2, T3, T4, T5, T6, T7, T8, T9> : EightElementsTuple<T1, T2, T3, T4, T5, T6, T7, T8>
{
public T9 Item9 { get; set; } = default!;

public NineElementsTuple(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5, T6 item6, T7 item7, T8 item8, T9 item9)
: base(item1, item2, item3, item4, item5, item6, item7, item8)
{
Item9 = item9;
}
}

public class TenElementsTuple<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10> : NineElementsTuple<T1, T2, T3, T4, T5, T6, T7, T8, T9>
{
public T10 Item10 { get; set; } = default!;

public TenElementsTuple(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5, T6 item6, T7 item7, T8 item8, T9 item9, T10 item10)
: base(item1, item2, item3, item4, item5, item6, item7, item8, item9)
{
Item10 = item10;
}
}

[GraphQLName("Bar")]
public class Foo<T>
{
public T Test { get; init; }
}

[GraphQLName("MyType")]
public class Bar
{
public int Test { get; init; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
schema {
query: Query
}

type BarOfInt32 {
test: Int!
}

type BarOfMyType {
test: MyType!
}

type BarOfString {
test: String!
}

type EightElementsTupleOfInt32AndInt32AndInt32AndInt32AndInt32AndInt32AndInt32AndInt32 {
item8: Int!
item1: Int!
item2: Int!
item3: Int!
item4: Int!
item5: Int!
item6: Int!
item7: Int!
}

type MyType {
test: Int!
}

type NineElementsTupleOfInt32AndInt32AndInt32AndInt32AndInt32AndInt32AndInt32AndInt32AndInt32 {
item9: Int!
item8: Int!
item1: Int!
item2: Int!
item3: Int!
item4: Int!
item5: Int!
item6: Int!
item7: Int!
}

type Query {
oneGenericType: TupleOfInt32!
twoGenericsType: TupleOfInt32AndInt32!
threeGenericsType: TupleOfInt32AndInt32AndInt32!
fourGenericsType: TupleOfInt32AndInt32AndInt32AndInt32!
fiveGenericTypes: TupleOfInt32AndInt32AndInt32AndInt32AndInt32!
sixGenericTypes: TupleOfInt32AndInt32AndInt32AndInt32AndInt32AndInt32!
sevenGenericTypes: TupleOfInt32AndInt32AndInt32AndInt32AndInt32AndInt32AndInt32!
eightGenericTypes: EightElementsTupleOfInt32AndInt32AndInt32AndInt32AndInt32AndInt32AndInt32AndInt32!
nineGenericTypes: NineElementsTupleOfInt32AndInt32AndInt32AndInt32AndInt32AndInt32AndInt32AndInt32AndInt32!
tenGenericTypes: TenElementsTupleOfInt32AndInt32AndInt32AndInt32AndInt32AndInt32AndInt32AndInt32AndInt32AndInt32!
intBar: BarOfInt32!
stringBar: BarOfString!
customNameBar: BarOfMyType!
}

type TenElementsTupleOfInt32AndInt32AndInt32AndInt32AndInt32AndInt32AndInt32AndInt32AndInt32AndInt32 {
item10: Int!
item9: Int!
item8: Int!
item1: Int!
item2: Int!
item3: Int!
item4: Int!
item5: Int!
item6: Int!
item7: Int!
}

type TupleOfInt32 {
item1: Int!
}

type TupleOfInt32AndInt32 {
item1: Int!
item2: Int!
}

type TupleOfInt32AndInt32AndInt32 {
item1: Int!
item2: Int!
item3: Int!
}

type TupleOfInt32AndInt32AndInt32AndInt32 {
item1: Int!
item2: Int!
item3: Int!
item4: Int!
}

type TupleOfInt32AndInt32AndInt32AndInt32AndInt32 {
item1: Int!
item2: Int!
item3: Int!
item4: Int!
item5: Int!
}

type TupleOfInt32AndInt32AndInt32AndInt32AndInt32AndInt32 {
item1: Int!
item2: Int!
item3: Int!
item4: Int!
item5: Int!
item6: Int!
}

type TupleOfInt32AndInt32AndInt32AndInt32AndInt32AndInt32AndInt32 {
item1: Int!
item2: Int!
item3: Int!
item4: Int!
item5: Int!
item6: Int!
item7: Int!
}