Skip to content

Commit

Permalink
Fix nullability of generated fields (#1724)
Browse files Browse the repository at this point in the history
  • Loading branch information
pekkah authored Jan 15, 2024
1 parent 69eb84b commit f397fa8
Show file tree
Hide file tree
Showing 38 changed files with 541 additions and 42 deletions.
2 changes: 1 addition & 1 deletion benchmarks/GraphQL.Benchmarks/GraphQL.Benchmarks.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.13.11" />
<PackageReference Include="BenchmarkDotNet" Version="0.13.12" />
<PackageReference Include="GraphQL-Parser" Version="9.3.1" />
</ItemGroup>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,11 @@
<NoWarn>NU5100</NoWarn>
<IncludeSymbols>false</IncludeSymbols>
<IncludeBuildOutput>false</IncludeBuildOutput>
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Polyfill" Version="1.34.0">
<PackageReference Include="Polyfill" Version="2.1.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
Expand All @@ -22,7 +23,7 @@
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.8.0" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" PrivateAssets="all" />
<PackageReference Include="System.Text.Json" Version="8.0.0" PrivateAssets="all" GeneratePathProperty="true" />
<PackageReference Include="System.Text.Json" Version="8.0.1" PrivateAssets="all" GeneratePathProperty="true" />
</ItemGroup>

<ItemGroup>
Expand Down
6 changes: 1 addition & 5 deletions src/GraphQL.Server.SourceGenerators/InputTypeDefinition.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,7 @@ namespace Tanka.GraphQL.Server.SourceGenerators;

public class InputTypeDefinition: TypeDefinition, IEquatable<InputTypeDefinition>
{
public string? Namespace { get; init; }

public string TargetType { get; init; }

public List<ObjectPropertyDefinition> Properties { get; set; } = new List<ObjectPropertyDefinition>();
public List<ObjectPropertyDefinition> Properties { get; set; } = new();

public ParentClass? ParentClass { get; set; }

Expand Down
2 changes: 2 additions & 0 deletions src/GraphQL.Server.SourceGenerators/InputTypeEmitter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ public static class {{name}}InputTypeExtensions
return builder;
}
}
""";

public SourceProductionContext Context { get; }
Expand Down Expand Up @@ -69,6 +70,7 @@ private string BuildTypeSdl(InputTypeDefinition definition)
{
var fieldName = JsonNamingPolicy.CamelCase.ConvertName(field.Name);
var fieldType = field.ClosestMatchingGraphQLTypeName;

builder.AppendLine($"{fieldName}: {fieldType}");
}
}
Expand Down
19 changes: 16 additions & 3 deletions src/GraphQL.Server.SourceGenerators/InputTypeParser.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
Expand Down Expand Up @@ -54,13 +55,25 @@ private List<ObjectPropertyDefinition> ParseMembers(ClassDeclarationSyntax clas
return properties;
}



private string GetClosestMatchingGraphQLTypeName(TypeSyntax typeSyntax)
{
var typeSymbol = Context.SemanticModel.GetTypeInfo(typeSyntax).Type;
TypeInfo typeInfo = Context.SemanticModel.GetTypeInfo(typeSyntax);

var typeSymbol = typeInfo.Type;

if (typeSymbol is null)
return typeSyntax.ToString();

return TypeHelper.GetGraphQLTypeName(typeSymbol);
var typeName = TypeHelper.GetGraphQLTypeName(typeSymbol);

// dirty hack until we have a better way to handle this
if (typeSyntax is NullableTypeSyntax && typeName.AsSpan().EndsWith("!"))
{
return typeName.AsSpan().Slice(0, typeName.Length - 1).ToString();
}

return typeName;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,6 @@ namespace Tanka.GraphQL.Server.SourceGenerators;

public class ObjectControllerDefinition: TypeDefinition, IEquatable<ObjectControllerDefinition>
{
public string? Namespace { get; init; }

public string TargetType { get; init; }

public List<ObjectPropertyDefinition> Properties { get; set; } = new List<ObjectPropertyDefinition>();

public List<ObjectMethodDefinition> Methods { get; set; } = new List<ObjectMethodDefinition>();
Expand All @@ -18,7 +14,7 @@ public class ObjectControllerDefinition: TypeDefinition, IEquatable<ObjectContro

public bool IsStatic { get; init; }

public IReadOnlyList<string> Usings { get; init; }
public IReadOnlyList<string> Usings { get; init; } = [];

public virtual bool Equals(ObjectControllerDefinition? other)
{
Expand Down
11 changes: 10 additions & 1 deletion src/GraphQL.Server.SourceGenerators/ObjectTypeParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using System;

namespace Tanka.GraphQL.Server.SourceGenerators
{
Expand Down Expand Up @@ -94,7 +95,15 @@ private static string GetClosestMatchingGraphQLTypeName(SemanticModel model, Typ
if (typeSymbol is null)
return typeSyntax.ToString();

return TypeHelper.GetGraphQLTypeName(typeSymbol);
var typeName = TypeHelper.GetGraphQLTypeName(typeSymbol);

// dirty hack until we have a better way to handle this
if (typeSyntax is NullableTypeSyntax && typeName.AsSpan().EndsWith("!"))
{
return typeName.AsSpan().Slice(0, typeName.Length - 1).ToString();
}

return typeName;
}
}
}
30 changes: 26 additions & 4 deletions src/GraphQL.Server.SourceGenerators/TypeHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ namespace Tanka.GraphQL.Server.SourceGenerators;

public class TypeHelper
{
public static string GetGraphQLTypeName(TypeSyntax typeSyntax)
{
return null;

Check warning on line 15 in src/GraphQL.Server.SourceGenerators/TypeHelper.cs

View workflow job for this annotation

GitHub Actions / build

Possible null reference return.
}

public static string GetGraphQLTypeName(ITypeSymbol typeSymbol)
{
// Handle arrays
Expand All @@ -18,10 +23,24 @@ public static string GetGraphQLTypeName(ITypeSymbol typeSymbol)
return $"[{GetGraphQLTypeName(arrayTypeSymbol.ElementType)}]";
}

// Handle IEnumerable<T>
if (typeSymbol is INamedTypeSymbol { IsGenericType: true, ConstructedFrom.Name: "IEnumerable" } namedType)
if (typeSymbol is not { SpecialType: SpecialType.System_String })
{
return $"[{GetGraphQLTypeName(namedType.TypeArguments[0])}]";
var ienumerableT = typeSymbol
.AllInterfaces
.FirstOrDefault(i =>
i.OriginalDefinition.SpecialType == SpecialType.System_Collections_Generic_IEnumerable_T);

if (ienumerableT is not null)
{
var innerType = ienumerableT.TypeArguments[0];
return $"[{GetGraphQLTypeName(innerType)}]";
}

// Handle IEnumerable<T>
if (typeSymbol is INamedTypeSymbol { IsGenericType: true, ConstructedFrom.Name: "IEnumerable" } namedType)
{
return $"[{GetGraphQLTypeName(namedType.TypeArguments[0])}]";
}
}

bool isNullable = IsNullable(typeSymbol, out typeSymbol);
Expand Down Expand Up @@ -74,6 +93,7 @@ public static string GetGraphQLTypeName(ITypeSymbol typeSymbol)

public static bool IsNullable(ITypeSymbol typeSymbol, out ITypeSymbol innerType)
{

if (typeSymbol is INamedTypeSymbol
{
OriginalDefinition.SpecialType: SpecialType.System_Nullable_T
Expand All @@ -82,7 +102,7 @@ public static bool IsNullable(ITypeSymbol typeSymbol, out ITypeSymbol innerType)
innerType = namedTypeSymbol.TypeArguments[0];
return true;
}

innerType = typeSymbol;
return typeSymbol.NullableAnnotation == NullableAnnotation.Annotated;
}
Expand Down Expand Up @@ -412,4 +432,6 @@ public static IReadOnlyList<string> GetUsings(ClassDeclarationSyntax classDeclar
.Select(u => u.ToString())
.ToList();
}


}
2 changes: 1 addition & 1 deletion src/GraphQL.Server/GraphQL.Server.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
<PackageReference Include="Microsoft.Extensions.Options" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Options" Version="8.0.1" />
<PackageReference Include="System.IO.Pipelines" Version="8.0.0" />
<PackageReference Include="System.Net.WebSockets" Version="4.3.0" />
</ItemGroup>
Expand Down
4 changes: 2 additions & 2 deletions src/GraphQL/GraphQL.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Features" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Features" Version="8.0.1" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="8.0.0" />
<PackageReference Include="System.Text.Json" Version="8.0.0" />
<PackageReference Include="System.Text.Json" Version="8.0.1" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageReference Include="xunit" Version="2.6.4" />
<PackageReference Include="xunit" Version="2.6.5" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.6">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.0" />
<PackageReference Include="xunit" Version="2.4.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageReference Include="xunit" Version="2.6.5" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.6">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageReference Include="xunit" Version="2.6.4" />
<PackageReference Include="xunit" Version="2.6.5" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.6">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
Expand Down
2 changes: 1 addition & 1 deletion tests/GraphQL.Language.Tests/GraphQL.Language.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageReference Include="NSubstitute" Version="5.1.0" />
<PackageReference Include="xunit" Version="2.6.4" />
<PackageReference Include="xunit" Version="2.6.5" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.6">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageReference Include="xunit" Version="2.6.4" />
<PackageReference Include="xunit" Version="2.6.5" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.6">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
Expand All @@ -19,7 +19,7 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Verify.XUnit" Version="22.7.1" />
<PackageReference Include="Verify.XUnit" Version="22.11.5" />
<PackageReference Include="Verify.SourceGenerators" Version="2.2.0" />
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.8.0" PrivateAssets="all" />
Expand Down
52 changes: 52 additions & 0 deletions tests/GraphQL.Server.SourceGenerators.Tests/InputTypeFacts.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,56 @@ public class InputMessage
return TestHelper<ObjectTypeGenerator>.Verify(source);
}

[Fact]
public Task Generate_InputType_with_nullable_string()
{
var source = """
using Tanka.GraphQL.Server;
[InputType]
public class InputMessage
{
public string? Content { get; set; }
}
""";

return TestHelper<ObjectTypeGenerator>.Verify(source);
}

[Fact]
public Task Generate_InputType_with_nullable_int()
{
var source = """
using Tanka.GraphQL.Server;
[InputType]
public class InputMessage
{
public int? Content { get; set; }
}
""";

return TestHelper<ObjectTypeGenerator>.Verify(source);
}

[Fact]
public Task Generate_InputType_with_listof_nullable_class()
{
var source = """
using Tanka.GraphQL.Server;
using System.Collections.Generic;
[InputType]
public class InputMessage
{
public List<Person?> Content { get; set; }
}
public class Person
{
}
""";

return TestHelper<ObjectTypeGenerator>.Verify(source);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,4 @@ input InputMessage
return builder;
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
//HintName: Global.SourceGeneratedTypesExtensions.cs
using System;
using System.Threading.Tasks;
using Microsoft.Extensions.Options;
using Tanka.GraphQL.Server;
using Tanka.GraphQL.Executable;
using Tanka.GraphQL.ValueResolution;
using Tanka.GraphQL.Fields;

public static class GlobalSourceGeneratedTypesExtensions
{
public static SourceGeneratedTypesBuilder AddGlobalTypes(this SourceGeneratedTypesBuilder builder)
{
builder.AddInputMessageInputType();
return builder;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
//HintName: InputMessageInputType.g.cs
using System;
using System.Threading.Tasks;
using Microsoft.Extensions.Options;
using Tanka.GraphQL.Server;
using Tanka.GraphQL.Executable;
using Tanka.GraphQL.ValueResolution;
using Tanka.GraphQL.Fields;



public static class InputMessageInputTypeExtensions
{
public static SourceGeneratedTypesBuilder AddInputMessageInputType(
this SourceGeneratedTypesBuilder builder)
{
builder.Builder.Configure(options => options.Builder.Add(
"""
input InputMessage
{
content: [Person]
}
"""
)
);

return builder;
}
}

Loading

0 comments on commit f397fa8

Please sign in to comment.