Skip to content

Commit

Permalink
.
Browse files Browse the repository at this point in the history
  • Loading branch information
mattiasnordqvist committed Nov 9, 2024
1 parent ef21741 commit 559952a
Show file tree
Hide file tree
Showing 9 changed files with 193 additions and 118 deletions.
12 changes: 11 additions & 1 deletion FartingUnicorn.Benchmarks/FartingUnicorn.Benchmarks.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,24 @@
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<PropertyGroup>
<!-- https://andrewlock.net/creating-a-source-generator-part-6-saving-source-generator-output-in-source-control/ -->
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
<CompilerGeneratedFilesOutputPath>Generated</CompilerGeneratedFilesOutputPath>
</PropertyGroup>
<ItemGroup>
<!-- https://andrewlock.net/creating-a-source-generator-part-6-saving-source-generator-output-in-source-control/ -->
<Compile Remove="$(CompilerGeneratedFilesOutputPath)\**\*.cs"></Compile>
</ItemGroup>

<ItemGroup>
<ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.14.0" />
<PackageReference Include="DotNetThoughts.Results" Version="1.5.10" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\FartingUnicorn\FartingUnicorn.csproj" />
<ProjectReference Include="..\MapperGenerator\MapperGenerator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@

namespace DotNetThoughts.FartingUnicorn
{
[System.AttributeUsage(System.AttributeTargets.Class)]
public class CreateMapperAttribute : System.Attribute
{
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using DotNetThoughts.Results;
using System.Text.Json;

namespace FartingUnicorn.Generated;
public static partial class Mappers
{
public static Result<FartingUnicorn.Benchmarks.UserProfile> MapToUserProfile(JsonElement jsonElement)
{
var result = new FartingUnicorn.Benchmarks.UserProfile();
return Result<FartingUnicorn.Benchmarks.UserProfile>.Ok(result);
}
}
45 changes: 28 additions & 17 deletions FartingUnicorn.Benchmarks/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,26 +15,29 @@ public static void Main(string[] args)
}
}

[MemoryDiagnoser]
public class SerializationBenchmarks
[DotNetThoughts.FartingUnicorn.CreateMapper]
public class UserProfile
{
public class UserProfile
{
public string Name { get; set; }
public int Age { get; set; }
public bool IsSubscribed { get; set; }
public string[] Courses { get; set; }
public Pet Pet { get; set; }
public string Name { get; set; }
public int Age { get; set; }
public bool IsSubscribed { get; set; }
public string[] Courses { get; set; }
public Pet Pet { get; set; }

public bool? IsGay { get; set; }
public Pet? FavoritePet { get; set; }
}
public bool? IsGay { get; set; }
public Pet? FavoritePet { get; set; }
}

public class Pet
{
public string Name { get; set; }
public string Type { get; set; }
}
public class Pet
{
public string Name { get; set; }
public string Type { get; set; }
}

[MemoryDiagnoser]
public class SerializationBenchmarks
{


private static string _json = """
{
Expand Down Expand Up @@ -66,4 +69,12 @@ public async Task<UserProfile> FartingDeserialization()
using var json = await JsonDocument.ParseAsync(_jsonStream);
return Mapper.Map<UserProfile>(json.RootElement).ValueOrThrow();
}

[Benchmark]
public async Task<UserProfile> FartingGeneratedDeserialization()
{
_jsonStream.Seek(0, SeekOrigin.Begin);
using var json = await JsonDocument.ParseAsync(_jsonStream);
return FartingUnicorn.Generated.Mappers.MapToUserProfile(json.RootElement).ValueOrThrow();
}
}
106 changes: 30 additions & 76 deletions MapperGenerator/MapperGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,82 +21,37 @@ public class CreateMapperAttribute : System.Attribute
{
}
}";
public static string GenerateExtensionClass(ClassToGenerateMapperFor classToGenerateMapperFor)
public static SourceText GenerateExtensionClass(ClassToGenerateMapperFor classToGenerateMapperFor)
{
var sb = new StringBuilder();
var sb = new SourceBuilder();
sb.AppendLine("using DotNetThoughts.Results;");
sb.AppendLine("using System.Text.Json;");
sb.AppendLine("namespace DotNetThoughts.FartingUnicorn;");
sb.AppendLine();
sb.AppendLine("namespace FartingUnicorn.Generated;");
sb.AppendLine($"public static partial class Mappers");
sb.AppendLine("{");
sb.AppendLine($" public static Result<{classToGenerateMapperFor.Name}> Map(JsonElement jsonElement)");
sb.AppendLine(" {");
sb.AppendLine($" var result = new {classToGenerateMapperFor.Name}();");
foreach (var value in classToGenerateMapperFor.Values)
using (var _ = sb.Indent())
{
sb.AppendLine($" result.{value} = jsonElement.GetProperty(\"{value}\").GetString();");
sb.AppendLine($"public static Result<{classToGenerateMapperFor.FullName}> MapTo{classToGenerateMapperFor.Name}(JsonElement jsonElement)");
sb.AppendLine("{");
using (var __ = sb.Indent())
{
sb.AppendLine($"var result = new {classToGenerateMapperFor.FullName}();");
foreach (var value in classToGenerateMapperFor.Values)
{
sb.AppendLine($"result.{value} = jsonElement.GetProperty(\"{value}\").GetString();");
}
sb.AppendLine($"return Result<{classToGenerateMapperFor.FullName}>.Ok(result);");

}
sb.AppendLine("}");
}
sb.AppendLine($" return Result<{classToGenerateMapperFor.Name}>.Ok(result);");
sb.AppendLine(" }");
sb.AppendLine("}");
return sb.ToString();
return sb.ToSourceText();
}
}

//public static Result<T> Map<T>(JsonElement json)
//{
// Result<Unit> result = UnitResult.Ok;
// var userProfile = new UserProfile();


// // required property in all senses. It must be present in the JSON and must have a value (not null in json)
// if (!json.TryGetProperty("name", out var nameProperty))
// {
// // This handles the case when the property is completely missing
// result = result.Or(Result<Unit>.Error(new RequiredPropertyMissingError("name")));
// }
// else
// {
// if (nameProperty.ValueKind == JsonValueKind.Null)
// {
// result = result.Or(Result<Unit>.Error(new RequiredValueMissingError("name")));
// }
// userProfile.Name = nameProperty.GetString();
// }
// userProfile.Age = json.GetProperty("age").GetInt32();
// userProfile.IsSubscribed = json.GetProperty("isSubscribed").GetBoolean();
// userProfile.Courses = json.GetProperty("courses").EnumerateArray().Select(course => course.GetString()).ToArray();

// if (json.GetProperty("pet").ValueKind == JsonValueKind.Null)
// {
// userProfile.Pet = new None<Pet>();
// }
// else
// {
// userProfile.Pet = new Some<Pet>(new Pet
// {
// Name = json.GetProperty("pet").GetProperty("name").GetString(),
// Type = json.GetProperty("pet").GetProperty("type").GetString()
// });
// }

// if (json.TryGetProperty("isGay", out var isGay))
// {
// userProfile.IsGay = isGay.GetBoolean();
// }

// if (json.TryGetProperty("favoritePet", out var favoritePet))
// {
// userProfile.FavoritePet = new Pet
// {
// Name = favoritePet.GetProperty("name").GetString(),
// Type = favoritePet.GetProperty("type").GetString()
// };
// }

// return result.Map(() => userProfile);
//}


public void Initialize(IncrementalGeneratorInitializationContext context)
{
// Add the marker attribute to the compilation
Expand All @@ -113,18 +68,14 @@ public void Initialize(IncrementalGeneratorInitializationContext context)

context.RegisterSourceOutput(classesToGenerate,
static (spc, source) => Execute(source, spc));
//context.RegisterSourceOutput(context.CompilationProvider, (sourceProductionContext, c) =>
//{
// sourceProductionContext.AddSource("Mapper.g", @"public class Mapper {}");
//});
static void Execute(ClassToGenerateMapperFor? classToGenerateMapperFor, SourceProductionContext context)
{
if (classToGenerateMapperFor is { } value)
{
// generate the source code and add it to the output
string result = SourceGenerationHelper.GenerateExtensionClass(value);
var sourceText = SourceGenerationHelper.GenerateExtensionClass(value);
// Create a separate partial class file for each enum
context.AddSource($"Mapper.{value.Name}.g.cs", SourceText.From(result, Encoding.UTF8));
context.AddSource($"Mapper.{value.FullName}.g.cs", sourceText);
}
}
static bool IsSyntaxTargetForGeneration(SyntaxNode node)
Expand Down Expand Up @@ -165,18 +116,19 @@ static bool IsSyntaxTargetForGeneration(SyntaxNode node)
static ClassToGenerateMapperFor? GetClassToGenerate(SemanticModel semanticModel, SyntaxNode enumDeclarationSyntax)
{
// Get the semantic representation of the enum syntax
if (semanticModel.GetDeclaredSymbol(enumDeclarationSyntax) is not INamedTypeSymbol enumSymbol)
if (semanticModel.GetDeclaredSymbol(enumDeclarationSyntax) is not INamedTypeSymbol classSymbol)
{
// something went wrong
return null;
}

// Get the full type name of the enum e.g. Colour,
// or OuterClass<T>.Colour if it was nested in a generic type (for example)
string className = enumSymbol.ToString();
string className = classSymbol.ToString();
string name = classSymbol.Name.ToString();

// Get all the members in the enum
ImmutableArray<ISymbol> enumMembers = enumSymbol.GetMembers();
ImmutableArray<ISymbol> enumMembers = classSymbol.GetMembers();
var members = new List<string>(enumMembers.Length);

// Get all the fields from the enum, and add their name to the list
Expand All @@ -196,18 +148,20 @@ static bool IsSyntaxTargetForGeneration(SyntaxNode node)
}
}

return new ClassToGenerateMapperFor(className, members);
return new ClassToGenerateMapperFor(className, name, members);
}
}
}

public readonly record struct ClassToGenerateMapperFor
{
public readonly string FullName;
public readonly string Name;
public readonly EquatableArray<string> Values;

public ClassToGenerateMapperFor(string name, List<string> values)
public ClassToGenerateMapperFor(string fullName, string name, List<string> values)
{
FullName = fullName;
Name = name;
Values = new(values.ToArray());
}
Expand Down
70 changes: 70 additions & 0 deletions MapperGenerator/SourceBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
using Microsoft.CodeAnalysis.Text;

using System.Text;

namespace MapperGenerator;

public class SourceBuilder
{
private int _indentLevel;
private readonly int _indentSize;
private readonly List<string> _indentStrings;
private readonly StringBuilder _stringBuilder;

public SourceBuilder()
{
_indentLevel = 0;
_indentSize = 4;
_indentStrings = new List<string> { "" };
_stringBuilder = new StringBuilder();
}
public void AppendLine(string line)
{
if (_stringBuilder != null)
{
_stringBuilder
.Append(_indentStrings[_indentLevel])
.AppendLine(line);
}
}

public IDisposable Indent()
{
_indentLevel++;
if (_indentLevel == _indentStrings.Count)
{
_indentStrings.Add(new string(' ', _indentSize * _indentLevel));
}
return new Unindent(() => _indentLevel--);
}

public override string? ToString()
{
return _stringBuilder.ToString();
}

public SourceText ToSourceText()
{
return SourceText.From(_stringBuilder.ToString(), Encoding.UTF8);
}

internal void AppendLine()
{
_stringBuilder.AppendLine();
}

private class Unindent : IDisposable
{
private Func<int> _unindent;

public Unindent(Func<int> unindent)
{
_unindent = unindent;
}

public void Dispose()
{
_unindent();
}
}
}
Loading

0 comments on commit 559952a

Please sign in to comment.