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

Replace source generator #143

Merged
merged 15 commits into from
Aug 9, 2024
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
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
Loading