Skip to content

Commit

Permalink
Merge pull request #143 from dragonfruitnetwork/switch-source-generator
Browse files Browse the repository at this point in the history
Replace source generator
  • Loading branch information
aspriddell authored Aug 9, 2024
2 parents e49dd76 + 9c2224d commit ad601a5
Show file tree
Hide file tree
Showing 60 changed files with 4,302 additions and 296 deletions.
31 changes: 0 additions & 31 deletions DragonFruit.Data.Roslyn.Tests/ApiRequestTemplateTests.cs

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Analyzer.Testing" Version="1.1.1"/>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.CodeFix.Testing" Version="1.1.1"/>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageReference Include="Scriban" Version="5.9.0" />
<PackageReference Include="xunit" Version="2.6.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.5">
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Analyzer.Testing" Version="1.1.2" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.CodeFix.Testing" Version="1.1.2" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0" />

<PackageReference Include="xunit" Version="2.9.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
Expand Down
292 changes: 292 additions & 0 deletions DragonFruit.Data.Roslyn/ApiRequestSourceBuilder.cs

Large diffs are not rendered by default.

66 changes: 6 additions & 60 deletions DragonFruit.Data.Roslyn/ApiRequestSourceGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,24 +8,19 @@
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Text;
using DragonFruit.Data.Converters;
using DragonFruit.Data.Requests;
using DragonFruit.Data.Roslyn.Entities;
using DragonFruit.Data.Roslyn.Enums;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;
using Scriban;

namespace DragonFruit.Data.Roslyn
{
[Generator(LanguageNames.CSharp)]
public class ApiRequestSourceGenerator : IIncrementalGenerator
{
public static readonly string TemplateName = "DragonFruit.Data.Roslyn.Templates.ApiRequest.liquid";

private static readonly HashSet<SpecialType> SupportedCollectionTypes =
[
..new[]
Expand All @@ -40,25 +35,8 @@ public class ApiRequestSourceGenerator : IIncrementalGenerator
}
];

private Template _partialRequestTemplate;

static ApiRequestSourceGenerator()
{
ExternalDependencyLoader.RegisterDependencyLoader();
}

public void Initialize(IncrementalGeneratorInitializationContext context)
{
using var templateStream = typeof(ApiRequestSourceGenerator).Assembly.GetManifestResourceStream(TemplateName);

if (templateStream == null)
{
throw new NullReferenceException("Could not find template");
}

using var reader = new StreamReader(templateStream);
_partialRequestTemplate = Template.ParseLiquid(reader.ReadToEnd());

var apiRequestDerivedClasses = context.SyntaxProvider.CreateSyntaxProvider(
predicate: (syntaxNode, _) => syntaxNode is ClassDeclarationSyntax classDecl && classDecl.Modifiers.Any(x => x.IsKind(SyntaxKind.PartialKeyword)),
transform: (generatorSyntaxContext, _) => GetSemanticTarget(generatorSyntaxContext));
Expand All @@ -81,7 +59,6 @@ private void Execute(Compilation compilation, ImmutableArray<ClassDeclarationSyn
var model = compilation.GetSemanticModel(classDeclaration.SyntaxTree, true);
var classSymbol = model.GetDeclaredSymbol(classDeclaration)!;

var sourceBuilder = new StringBuilder("// <auto-generated />");
var metadata = GetRequestSymbolMetadata(compilation, classSymbol);

// check if body is derived from httpcontent
Expand All @@ -97,39 +74,8 @@ private void Execute(Compilation compilation, ImmutableArray<ClassDeclarationSyn
requestBodyType = metadata.FormBodyType == FormBodyType.Multipart ? RequestBodyType.FormMultipart : RequestBodyType.FormUriEncoded;
}

// build class name (adding generics if they're present)
var className = classSymbol.Name;

if (classSymbol.IsGenericType)
{
var genericNameBuilder = new StringBuilder(classSymbol.Name);

genericNameBuilder.Append("<");
genericNameBuilder.Append(string.Join(", ", classSymbol.TypeArguments.Select(x => x.Name)));
genericNameBuilder.Append(">");

className = genericNameBuilder.ToString();
}

// create template info object
var parameterInfo = new
{
ClassName = className,
Namespace = classSymbol.ContainingNamespace.ToDisplayString(),

RequireNewKeyword = WillHideOtherMembers(classSymbol, compilation.GetTypeByMetadataName(typeof(ApiRequest).FullName)),

RequestBodyType = requestBodyType,
RequestBodySymbol = metadata.Properties[ParameterType.Form].Any() ? null : metadata.BodyProperty, // prefer using forms over body - this to match reflection-based behaviour

QueryParameters = metadata.Properties[ParameterType.Query],
HeaderParameters = metadata.Properties[ParameterType.Header],
FormBodyParameters = metadata.Properties[ParameterType.Form],
};

sourceBuilder.Append("\n\n");
sourceBuilder.Append(_partialRequestTemplate.Render(parameterInfo));
context.AddSource($"{classSymbol.Name}.g.cs", SourceText.From(sourceBuilder.ToString(), Encoding.UTF8));
var requireNewKeyword = WillHideOtherMembers(classSymbol, compilation.GetTypeByMetadataName(typeof(ApiRequest).FullName));
context.AddSource($"{classSymbol.Name}.dragonfruit.g.cs", ApiRequestSourceBuilder.Build(classSymbol, metadata, requestBodyType, requireNewKeyword));
}
}

Expand Down Expand Up @@ -286,12 +232,12 @@ private static RequestSymbolMetadata GetRequestSymbolMetadata(Compilation compil
// string (IEnumerable<char>)
else if (returnType.SpecialType == SpecialType.System_String)
{
symbolMetadata = new PropertySymbolMetadata(candidate, returnType, parameterName);
symbolMetadata = new ParameterSymbolMetadata(candidate, returnType, parameterName);
}
// Stream
else if (streamTypeSymbol.Equals(returnType, SymbolEqualityComparer.Default) || DerivesFrom(returnType, streamTypeSymbol))
{
symbolMetadata = new PropertySymbolMetadata(candidate, returnType, parameterName, RequestSymbolType.Stream);
symbolMetadata = new ParameterSymbolMetadata(candidate, returnType, parameterName, RequestSymbolType.Stream);
}
else
{
Expand All @@ -300,7 +246,7 @@ private static RequestSymbolMetadata GetRequestSymbolMetadata(Compilation compil
{
// byte[]
case true when returnType is IArrayTypeSymbol { ElementType.SpecialType: SpecialType.System_Byte }:
symbolMetadata = new PropertySymbolMetadata(candidate, returnType, parameterName, RequestSymbolType.ByteArray);
symbolMetadata = new ParameterSymbolMetadata(candidate, returnType, parameterName, RequestSymbolType.ByteArray);
break;

// IEnumerable<KeyValuePair<string, string>>
Expand All @@ -322,7 +268,7 @@ private static RequestSymbolMetadata GetRequestSymbolMetadata(Compilation compil
}

default:
symbolMetadata = new PropertySymbolMetadata(candidate, returnType, parameterName);
symbolMetadata = new ParameterSymbolMetadata(candidate, returnType, parameterName);
break;
}
}
Expand Down
5 changes: 5 additions & 0 deletions DragonFruit.Data.Roslyn/CodeGeneration/.editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
root = true

[*.cs]
generated_code = true
dotnet_analyzer_diagnostic.severity = none
9 changes: 9 additions & 0 deletions DragonFruit.Data.Roslyn/CodeGeneration/AccessModifier.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace StrawberryShake.CodeGeneration;

public enum AccessModifier
{
Public = 0,
Internal = 1,
Protected = 2,
Private = 3,
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using System;
using System.Collections.Generic;

namespace StrawberryShake.CodeGeneration.CSharp.Builders;

public abstract class AbstractTypeBuilder : ITypeBuilder
{
protected List<PropertyBuilder> Properties { get; } = [];

protected string? Name { get; private set; }

protected List<string> Implements { get; } = [];

public abstract void Build(CodeWriter writer);

protected void SetName(string name)
{
Name = name;
}

public void AddProperty(PropertyBuilder property)
{
if (property is null)
{
throw new ArgumentNullException(nameof(property));
}

Properties.Add(property);
}

public void AddImplements(string value)
{
if (string.IsNullOrEmpty(value))
{
throw new ArgumentException(nameof(value));
}

Implements.Add(value);
}
}
98 changes: 98 additions & 0 deletions DragonFruit.Data.Roslyn/CodeGeneration/Builders/ArrayBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
using System;
using System.Collections.Generic;

namespace StrawberryShake.CodeGeneration.CSharp.Builders;

public class ArrayBuilder : ICode
{
private string? _prefix;
private string? _type;
private bool _determineStatement = true;
private bool _setReturn;
private readonly List<ICode> _assignment = [];

private ArrayBuilder()
{
}

public ArrayBuilder SetType(string type)
{
_type = type;
return this;
}

public ArrayBuilder AddAssignment(ICode code)
{
_assignment.Add(code);
return this;
}

public ArrayBuilder SetDetermineStatement(bool value)
{
_determineStatement = value;
return this;
}

public ArrayBuilder SetPrefix(string prefix)
{
_prefix = prefix;
return this;
}

public ArrayBuilder SetReturn(bool value = true)
{
_setReturn = value;
return this;
}

public void Build(CodeWriter writer)
{
if (_type is null)
{
throw new ArgumentNullException(nameof(_type));
}

if (_determineStatement)
{
writer.WriteIndent();
}

if (_setReturn)
{
writer.Write("return ");
}

writer.Write(_prefix);

writer.Write("new ");
writer.Write(_type);
writer.Write("[] {");
writer.WriteLine();

using (writer.IncreaseIndent())
{
for (var i = 0; i < _assignment.Count; i++)
{
writer.WriteIndent();
_assignment[i].Build(writer);
if (i != _assignment.Count - 1)
{
writer.Write(",");
writer.WriteLine();
}
}
}

writer.WriteLine();
writer.WriteIndent();
writer.Write("}");

if (_determineStatement)
{
writer.Write(";");
writer.WriteLine();
}
}

public static ArrayBuilder New() => new ArrayBuilder();
}
Loading

0 comments on commit ad601a5

Please sign in to comment.