Skip to content

Commit

Permalink
Added DataLoader group generator. (#7528)
Browse files Browse the repository at this point in the history
(cherry picked from commit c174e86)
  • Loading branch information
michaelstaib committed Sep 29, 2024
1 parent 6bb769c commit b7c55f5
Show file tree
Hide file tree
Showing 13 changed files with 440 additions and 10 deletions.
20 changes: 20 additions & 0 deletions src/GreenDonut/src/Core/Attributes/DataLoaderGroupAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
namespace GreenDonut;

/// <summary>
/// Allows to group multiple DataLoaders together into a context class
/// that can be used to inject multiple DataLoader at once into classes.
/// </summary>
/// <param name="groupNames">
/// The group names that are used to group multiple DataLoaders together.
/// </param>
[AttributeUsage(
AttributeTargets.Method
| AttributeTargets.Class,
AllowMultiple = true)]
public class DataLoaderGroupAttribute(params string[] groupNames) : Attribute
{
/// <summary>
/// Gets the group names that are used to group multiple DataLoaders together.
/// </summary>
public string[] GroupNames { get; } = groupNames;
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Collections.Immutable;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using HotChocolate.Types.Analyzers.Helpers;
using HotChocolate.Types.Analyzers.Inspectors;
Expand Down Expand Up @@ -225,8 +226,8 @@ public void WriteDataLoaderLoadMethod(
value.ToFullyQualified(),
kind is DataLoaderKind.Group ? "[]" : string.Empty,
value.IsValueType ? string.Empty : "?");
_writer.WriteIndentedLine(
"global::{0}<{1}{2}> context,",
_writer.WriteIndentedLine(
"global::{0}<{1}{2}> context,",
WellKnownTypes.DataLoaderFetchContext,
value.ToFullyQualified(),
kind is DataLoaderKind.Group ? "[]" : string.Empty);
Expand Down Expand Up @@ -277,7 +278,6 @@ public void WriteDataLoaderLoadMethod(
parameter.Type.PrintNullRefQualifier(),
parameter.StateKey,
defaultValueString);

}
else if (parameter.Type.IsNullableType())
{
Expand Down Expand Up @@ -484,6 +484,87 @@ kind is DataLoaderKind.Cache
_writer.WriteLine(").ConfigureAwait(false);");
}

public void WriteDataLoaderGroupClass(
string groupClassName,
IReadOnlyList<GroupedDataLoaderInfo> dataLoaders)
{
_writer.WriteIndentedLine("public interface I{0}", groupClassName);
_writer.WriteIndentedLine("{");
_writer.IncreaseIndent();

foreach (var dataLoader in dataLoaders)
{
_writer.WriteIndentedLine("{0} {1} {{ get; }}", dataLoader.InterfaceName, dataLoader.Name);
}

_writer.DecreaseIndent();
_writer.WriteIndentedLine("}");

_writer.WriteIndentedLine("public sealed class {0} : I{0}", groupClassName);
_writer.WriteIndentedLine("{");
_writer.IncreaseIndent();

_writer.WriteIndentedLine("private readonly IServiceProvider _services;");

foreach (var dataLoader in dataLoaders)
{
_writer.WriteIndentedLine("private {0}? {1};", dataLoader.InterfaceName, dataLoader.FieldName);
}

_writer.WriteLine();
_writer.WriteIndentedLine("public {0}(IServiceProvider services)", groupClassName);
_writer.WriteIndentedLine("{");
_writer.IncreaseIndent();
_writer.WriteIndentedLine("_services = services");
_writer.IncreaseIndent();
_writer.WriteIndentedLine("?? throw new ArgumentNullException(nameof(services));");
_writer.DecreaseIndent();
_writer.DecreaseIndent();
_writer.WriteIndentedLine("}");

foreach (var dataLoader in dataLoaders)
{
_writer.WriteIndentedLine(
"public {0} {1}",
dataLoader.InterfaceName,
dataLoader.Name);

_writer.WriteIndentedLine("{");
_writer.IncreaseIndent();

_writer.WriteIndentedLine("get");

_writer.WriteIndentedLine("{");
_writer.IncreaseIndent();

_writer.WriteIndentedLine(
"if ({0} is null)",
dataLoader.FieldName);

_writer.WriteIndentedLine("{");
_writer.IncreaseIndent();

_writer.WriteIndentedLine(
"{0} = _services.GetRequiredService<{1}>();",
dataLoader.FieldName,
dataLoader.InterfaceName);

_writer.DecreaseIndent();
_writer.WriteIndentedLine("}");
_writer.WriteLine();
_writer.WriteIndentedLine("return {0}!;", dataLoader.FieldName);

_writer.DecreaseIndent();
_writer.WriteIndentedLine("}");

_writer.DecreaseIndent();
_writer.WriteIndentedLine("}");
}

_writer.DecreaseIndent();
_writer.WriteIndentedLine("}");
}

public void WriteLine() => _writer.WriteLine();

private static ITypeSymbol ExtractMapType(ITypeSymbol returnType)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,14 @@ public void WriteAddDataLoader(string dataLoaderType, string dataLoaderInterface
dataLoaderType);
}

public void WriteAddDataLoaderGroup(string groupType, string groupInterfaceType)
{
_writer.WriteIndentedLine(
"services.AddScoped<global::{0}, global::{1}>();",
groupInterfaceType,
groupType);
}

public override string ToString()
=> _sb.ToString();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,12 @@ public void WriteRegisterDataLoader(string typeName)
public void WriteRegisterDataLoader(string typeName, string interfaceTypeName)
=> _writer.WriteIndentedLine("builder.AddDataLoader<global::{0}, global::{1}>();", interfaceTypeName, typeName);

public void WriteRegisterDataLoaderGroup(string typeName, string interfaceTypeName)
=> _writer.WriteIndentedLine(
"builder.Services.AddScoped<global::{0}, global::{1}>();",
interfaceTypeName,
typeName);

public void WriteTryAddOperationType(OperationType type)
{
_writer.WriteIndentedLine("builder.ConfigureSchema(");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,26 @@ private static void WriteDataLoader(
hasDataLoaders = true;
}

List<GroupedDataLoaderInfo>? buffer = null;
foreach (var dataLoaderGroup in group
.Where(d => d.Groups.Count > 0)
.SelectMany(d => d.Groups, (d, g) => new { DataLoader = d, Group = g })
.GroupBy(t => t.Group, t => t.DataLoader, StringComparer.Ordinal)
.OrderBy(t => t.Key, StringComparer.Ordinal))
{
var isPublic = defaults.IsInterfacePublic ?? true;
var groups = group.Select(
t => new GroupedDataLoaderInfo(
t.NameWithoutSuffix,
t.InterfaceName,
t.IsInterfacePublic ?? isPublic));

buffer ??= new();
buffer.Clear();
buffer.AddRange(groups);
generator.WriteDataLoaderGroupClass(dataLoaderGroup.Key, buffer);
}

generator.WriteEndNamespace();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ public void Generate(
return;
}

HashSet<(string InterfaceName, string ClassName)>? groups = null;
var generator = new DataLoaderModuleFileBuilder(module.ModuleName);

generator.WriteHeader();
Expand All @@ -43,10 +44,27 @@ public void Generate(
var typeName = $"{dataLoader.Namespace}.{dataLoader.Name}";
var interfaceTypeName = $"{dataLoader.Namespace}.{dataLoader.InterfaceName}";
generator.WriteAddDataLoader(typeName, interfaceTypeName);

if(dataLoader.Groups.Count > 0)
{
groups ??= [];
foreach (var groupName in dataLoader.Groups)
{
groups.Add(($"{dataLoader.Namespace}.I{groupName}", $"{dataLoader.Namespace}.{groupName}"));
}
}
break;
}
}

if (groups is not null)
{
foreach (var (interfaceName, className) in groups.OrderBy(t => t.ClassName))
{
generator.WriteAddDataLoaderGroup(className, interfaceName);
}
}

generator.WriteEndRegistrationMethod();
generator.WriteEndClass();
generator.WriteEndNamespace();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ private static void WriteConfiguration(
List<SyntaxInfo> syntaxInfos,
ModuleInfo module)
{
HashSet<(string InterfaceName, string ClassName)>? groups = null;
using var generator = new ModuleFileBuilder(module.ModuleName, "Microsoft.Extensions.DependencyInjection");

generator.WriteHeader();
Expand Down Expand Up @@ -110,6 +111,15 @@ private static void WriteConfiguration(

generator.WriteRegisterDataLoader(typeName, interfaceTypeName);
hasConfigurations = true;

if(dataLoader.Groups.Count > 0)
{
groups ??= [];
foreach (var groupName in dataLoader.Groups)
{
groups.Add(($"{dataLoader.Namespace}.I{groupName}", $"{dataLoader.Namespace}.{groupName}"));
}
}
}

break;
Expand Down Expand Up @@ -174,6 +184,14 @@ private static void WriteConfiguration(
hasConfigurations = true;
}

if (groups is not null)
{
foreach (var (interfaceName, className) in groups.OrderBy(t => t.ClassName))
{
generator.WriteRegisterDataLoaderGroup(className, interfaceName);
}
}

generator.WriteEndRegistrationMethod();

if (hasObjectTypeExtensions)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
Expand All @@ -14,6 +15,51 @@ public static AttributeData GetDataLoaderAttribute(
"DataLoaderAttribute",
StringComparison.Ordinal));

public static ImmutableHashSet<string> GetDataLoaderGroupKeys(this IMethodSymbol methodSymbol)
{
var groupNamesBuilder = ImmutableHashSet.CreateBuilder<string>(StringComparer.Ordinal);
AddGroupNames(groupNamesBuilder, methodSymbol.GetAttributes());
AddGroupNames(groupNamesBuilder, methodSymbol.ContainingType.GetAttributes());
return groupNamesBuilder.Count == 0 ? ImmutableHashSet<string>.Empty : groupNamesBuilder.ToImmutable();

static void AddGroupNames(ImmutableHashSet<string>.Builder builder, IEnumerable<AttributeData> attributes)
{
foreach (var attribute in attributes)
{
if (IsDataLoaderGroupAttribute(attribute.AttributeClass))
{
foreach (var arg in attribute.ConstructorArguments.FirstOrDefault().Values)
{
if (arg.Value is string groupName)
{
builder.Add(groupName);
}
}
}
}
}

static bool IsDataLoaderGroupAttribute(INamedTypeSymbol? attributeClass)
{
if (attributeClass == null)
{
return false;
}

while (attributeClass != null)
{
if (attributeClass.Name == "DataLoaderGroupAttribute")
{
return true;
}

attributeClass = attributeClass.BaseType;
}

return false;
}
}

public static string? GetDataLoaderStateKey(
this IParameterSymbol parameter)
{
Expand Down
17 changes: 10 additions & 7 deletions src/HotChocolate/Core/src/Types.Analyzers/Models/DataLoaderInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ public DataLoaderInfo(
_lookups = attribute.GetLookups();
var declaringType = methodSymbol.ContainingType;

Name = GetDataLoaderName(methodSymbol.Name, attribute);
NameWithoutSuffix = GetDataLoaderName(methodSymbol.Name, attribute);
Name = NameWithoutSuffix + "DataLoader";
InterfaceName = $"I{Name}";
Namespace = methodSymbol.ContainingNamespace.ToDisplayString();
FullName = $"{Namespace}.{Name}";
Expand All @@ -38,12 +39,17 @@ public DataLoaderInfo(
KeyParameter = MethodSymbol.Parameters[0];
ContainingType = declaringType.ToDisplayString();
Parameters = CreateParameters(methodSymbol);
Groups = methodSymbol.GetDataLoaderGroupKeys();
}

public string Name { get; }

public string NameWithoutSuffix { get; }

public string FullName { get; }

public ImmutableHashSet<string> Groups { get; }

public string Namespace { get; }

public string InterfaceName { get; }
Expand Down Expand Up @@ -274,11 +280,8 @@ private static string GetDataLoaderName(string name, AttributeData attribute)
name = name.Substring(0, name.Length - 5);
}

if (name.EndsWith("DataLoader"))
{
return name;
}

return name + "DataLoader";
return name.EndsWith("DataLoader")
? name.Substring(0, name.Length - 10)
: name;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
namespace HotChocolate.Types.Analyzers.FileBuilders;

public class GroupedDataLoaderInfo
{
public GroupedDataLoaderInfo(string name, string interfaceName, bool isPublic)
{
Name = name;
InterfaceName = interfaceName;
IsPublic = isPublic;
FieldName = "_" + name.Substring(0, 1).ToLowerInvariant() + name.Substring(1);
}

public string Name { get; }

public string InterfaceName { get; }

public string FieldName { get; }

public bool IsPublic { get; }
}
Loading

0 comments on commit b7c55f5

Please sign in to comment.