Skip to content

Commit

Permalink
Reworked Types Source Generator (#5040)
Browse files Browse the repository at this point in the history
  • Loading branch information
michaelstaib committed May 8, 2022
1 parent f9dd6a6 commit 1096ec7
Show file tree
Hide file tree
Showing 21 changed files with 657 additions and 262 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using HotChocolate.Types.Analyzers.Inspectors;
using Microsoft.CodeAnalysis;

namespace HotChocolate.Types.Analyzers.Generators;

/// <summary>
/// A syntax generator produces C# code from the consumed syntax infos.
/// </summary>
public interface ISyntaxGenerator
{
/// <summary>
/// Specifies if the given <paramref name="syntaxInfo"/> will be consumed by this generator.
/// </summary>
bool Consume(ISyntaxInfo syntaxInfo);

/// <summary>
/// Generates the C# source code for the consumed syntax infos.
/// </summary>
void Generate(
SourceProductionContext context,
Compilation compilation,
IReadOnlyCollection<ISyntaxInfo> consumed);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
using System.Text;
using HotChocolate.Types.Analyzers.Inspectors;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Text;
using static HotChocolate.Types.Analyzers.StringConstants;
using static HotChocolate.Types.Analyzers.WellKnownFileNames;
using TypeInfo = HotChocolate.Types.Analyzers.Inspectors.TypeInfo;

namespace HotChocolate.Types.Analyzers.Generators;

public class ModuleGenerator : ISyntaxGenerator
{
public bool Consume(ISyntaxInfo syntaxInfo)
=> syntaxInfo is TypeInfo or TypeExtensionInfo or DataLoaderInfo or ModuleInfo;

public void Generate(
SourceProductionContext context,
Compilation compilation,
IReadOnlyCollection<ISyntaxInfo> syntaxInfos)
{
ModuleInfo module =
syntaxInfos.OfType<ModuleInfo>().FirstOrDefault() ??
new ModuleInfo(
compilation.AssemblyName is null
? "AssemblyTypes"
: compilation.AssemblyName?.Split('.').Last() + "Types",
ModuleOptions.Default);

var batch = new List<ISyntaxInfo>(syntaxInfos.Where(static t => t is not ModuleInfo));
if (batch.Count == 0)
{
return;
}

var code = new StringBuilder();
code.AppendLine("using System;");
code.AppendLine("using HotChocolate.Execution.Configuration;");

code.AppendLine();
code.AppendLine("namespace Microsoft.Extensions.DependencyInjection");
code.AppendLine("{");

code.Append(Indent)
.Append("public static class ")
.Append(module.ModuleName)
.AppendLine("RequestExecutorBuilderExtensions");

code.Append(Indent)
.AppendLine("{");

code.Append(Indent)
.Append(Indent)
.Append("public static IRequestExecutorBuilder Add")
.Append(module.ModuleName)
.AppendLine("(this IRequestExecutorBuilder builder)");

code.Append(Indent).Append(Indent).AppendLine("{");

foreach (ISyntaxInfo syntaxInfo in batch.Distinct())
{
switch (syntaxInfo)
{
case TypeInfo type:
if ((module.Options & ModuleOptions.RegisterTypes) ==
ModuleOptions.RegisterTypes)
{
code.Append(Indent)
.Append(Indent)
.Append(Indent)
.Append("builder.AddType<")
.Append(type.Name)
.AppendLine(">();");
}
break;

case TypeExtensionInfo extension:
if ((module.Options & ModuleOptions.RegisterTypes) ==
ModuleOptions.RegisterTypes)
{
code.Append(Indent)
.Append(Indent)
.Append(Indent)
.Append("builder.AddTypeExtension<")
.Append(extension.Name)
.AppendLine(">();");
}
break;

case DataLoaderInfo dataLoader:
if ((module.Options & ModuleOptions.RegisterDataLoader) ==
ModuleOptions.RegisterDataLoader)
{
code.Append(Indent)
.Append(Indent)
.Append(Indent)
.Append("builder.AddDataLoader<")
.Append(dataLoader.Name)
.AppendLine(">();");
}
break;
}
}
code.Append(Indent).Append(Indent).Append(Indent).AppendLine("return builder;");
code.Append(Indent).Append(Indent).AppendLine("}");
code.Append(Indent).AppendLine("}");
code.AppendLine("}");

context.AddSource(TypeModuleFile, SourceText.From(code.ToString(), Encoding.UTF8));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
namespace HotChocolate.Types.Analyzers.Inspectors;

public sealed class AggregateInfo : ISyntaxInfo, IEquatable<AggregateInfo>
{
private readonly IReadOnlyList<ISyntaxInfo> _syntaxInfos;

public AggregateInfo(IReadOnlyList<ISyntaxInfo> syntaxInfos)
{
_syntaxInfos = syntaxInfos;
}

public IReadOnlyList<ISyntaxInfo> Items => _syntaxInfos;

public bool Equals(AggregateInfo? other)
{
if (ReferenceEquals(null, other))
{
return false;
}

if (ReferenceEquals(this, other))
{
return true;
}

return _syntaxInfos == other._syntaxInfos;
}

public override bool Equals(object? obj)
=> ReferenceEquals(this, obj) ||
obj is AggregateInfo other && Equals(other);

public override int GetHashCode()
=> _syntaxInfos.GetHashCode();

public static bool operator ==(AggregateInfo? left, AggregateInfo? right)
=> Equals(left, right);

public static bool operator !=(AggregateInfo? left, AggregateInfo? right)
=> !Equals(left, right);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
using System.Diagnostics.CodeAnalysis;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;

namespace HotChocolate.Types.Analyzers.Inspectors;

public class ClassBaseClassInspector : ISyntaxInspector
{
public bool TryHandle(
GeneratorSyntaxContext context,
[NotNullWhen(true)] out ISyntaxInfo? syntaxInfo)
{
if (context.Node is ClassDeclarationSyntax { BaseList.Types.Count: > 0 } possibleType)
{
var model = context.SemanticModel.GetDeclaredSymbol(possibleType);
if (model is { IsAbstract: false } type)
{
var typeDisplayString = type.ToDisplayString();
var processing = new Queue<INamedTypeSymbol>();
processing.Enqueue(type);

var current = type.BaseType;

while (current is not null)
{
processing.Enqueue(current);

var displayString = current.ToDisplayString();

if (displayString.Equals(WellKnownTypes.SystemObject, StringComparison.Ordinal))
{
break;
}

if (WellKnownTypes.TypeClass.Contains(displayString))
{
syntaxInfo = new TypeInfo(typeDisplayString);
return true;
}

if (WellKnownTypes.TypeExtensionClass.Contains(displayString))
{
syntaxInfo = new TypeExtensionInfo(typeDisplayString);
return true;
}

current = current.BaseType;
}

while (processing.Count > 0)
{
current = processing.Dequeue();

var displayString = current.ToDisplayString();

if (displayString.Equals(WellKnownTypes.DataLoader, StringComparison.Ordinal))
{
syntaxInfo = new DataLoaderInfo(typeDisplayString);
return true;
}

foreach (var interfaceType in current.Interfaces)
{
processing.Enqueue(interfaceType);
}
}
}
}

syntaxInfo = null;
return false;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
namespace HotChocolate.Types.Analyzers.Inspectors;

public sealed class DataLoaderInfo : ISyntaxInfo, IEquatable<DataLoaderInfo>
{
public DataLoaderInfo(string name)
{
Name = name;
}

public string Name { get; }

public bool Equals(DataLoaderInfo? other)
{
if (ReferenceEquals(null, other))
{
return false;
}

if (ReferenceEquals(this, other))
{
return true;
}

return Name == other.Name;
}

public override bool Equals(object? obj)
=> ReferenceEquals(this, obj) ||
obj is DataLoaderInfo other && Equals(other);

public override int GetHashCode()
=> Name.GetHashCode();

public static bool operator ==(DataLoaderInfo? left, DataLoaderInfo? right)
=> Equals(left, right);

public static bool operator !=(DataLoaderInfo? left, DataLoaderInfo? right)
=> !Equals(left, right);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
namespace HotChocolate.Types.Analyzers.Inspectors;

public interface ISyntaxInfo
{
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using System.Diagnostics.CodeAnalysis;
using Microsoft.CodeAnalysis;

namespace HotChocolate.Types.Analyzers.Inspectors;

/// <summary>
/// The syntax inspector will analyze a syntax node and try to reason out the semantics in a
/// Hot Chocolate server context.
/// </summary>
public interface ISyntaxInspector
{
/// <summary>
/// <para>
/// Inspects the current syntax node and if the current inspector can handle
/// the syntax will produce a syntax info.
/// </para>
/// <para>The syntax info is used by a syntax generator to produce source code.</para>
/// </summary>
bool TryHandle(
GeneratorSyntaxContext context,
[NotNullWhen(true)] out ISyntaxInfo? syntaxInfo);
}
47 changes: 47 additions & 0 deletions src/HotChocolate/Core/src/Types.Analyzers/Inspectors/ModuleInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
namespace HotChocolate.Types.Analyzers.Inspectors;

public sealed class ModuleInfo : ISyntaxInfo, IEquatable<ModuleInfo>
{
public ModuleInfo(string moduleName, ModuleOptions options)
{
ModuleName = moduleName;
Options = options;
}

public string ModuleName { get; }

public ModuleOptions Options { get; }

public bool Equals(ModuleInfo? other)
{
if (ReferenceEquals(null, other))
{
return false;
}

if (ReferenceEquals(this, other))
{
return true;
}

return ModuleName == other.ModuleName && Options == other.Options;
}

public override bool Equals(object? obj)
=> ReferenceEquals(this, obj) ||
(obj is ModuleInfo other && Equals(other));

public override int GetHashCode()
{
unchecked
{
return (ModuleName.GetHashCode() * 397) ^ (int)Options;
}
}

public static bool operator ==(ModuleInfo? left, ModuleInfo? right)
=> Equals(left, right);

public static bool operator !=(ModuleInfo? left, ModuleInfo? right)
=> !Equals(left, right);
}

0 comments on commit 1096ec7

Please sign in to comment.