Skip to content

Commit

Permalink
gen map
Browse files Browse the repository at this point in the history
  • Loading branch information
MarvelTiter committed Aug 22, 2024
1 parent 494d500 commit 1cc8dcc
Show file tree
Hide file tree
Showing 22 changed files with 672 additions and 7 deletions.
11 changes: 11 additions & 0 deletions .github/workflows/push_nuget.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,22 +21,33 @@ jobs:
- name: Build
run: |
dotnet build ./src -c Release
# AutoInjectGenerator
- name: Pack
run: |
dotnet pack ./src/AutoInjectGenerator/AutoInjectGenerator.csproj -c Release -o publish/autoinject
- name: Push
run: |
dotnet nuget push 'publish/autoinject/*.nupkg' -s https://api.nuget.org/v3/index.json -k ${{secrets.AUTOINJECT}} --skip-duplicate
# AutoWasmApiGenerator
- name: Pack
run: |
dotnet pack ./src/AutoWasmApiGenerator/AutoWasmApiGenerator.csproj -c Release -o publish/autowasmapi
- name: Push
run: |
dotnet nuget push 'publish/autowasmapi/*.nupkg' -s https://api.nuget.org/v3/index.json -k ${{secrets.AUTOWASMAPI}} --skip-duplicate
# MT.Generators.Abstraction
- name: Pack
run: |
dotnet pack ./src/MT.Generators.Abstraction/MT.Generators.Abstraction.csproj -c Release -o publish/genabs
- name: Push
run: |
dotnet nuget push 'publish/genabs/*.nupkg' -s https://api.nuget.org/v3/index.json -k ${{secrets.GEN_ABSTRACTION}} --skip-duplicate
# AutoGenMapperGenerator
- name: Pack
run: |
dotnet pack ./src/AutoGenMapperGenerator/AutoGenMapperGenerator.csproj -c Release -o publish/genmapper
- name: Push
run: |
dotnet nuget push 'publish/genmapper/*.nupkg' -s https://api.nuget.org/v3/index.json -k ${{secrets.GEN_MAPPER}} --skip-duplicate
8 changes: 8 additions & 0 deletions src/AutoGenMapper.Roslyn/AutoGenMapper.Roslyn.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<RootNamespace>AutoGenMapperGenerator</RootNamespace>
</PropertyGroup>
<Import Project="..\Generators.Shared\Generators.Shared.projitems" Label="Shared" />
<Import Project="..\Generators.Shared\MT.Generators.props" />
</Project>
150 changes: 150 additions & 0 deletions src/AutoGenMapper.Roslyn/AutoMapperGenerator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Generators.Shared.Builder;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Generators.Shared;
using System.Diagnostics;

namespace AutoGenMapperGenerator
{
[Generator(LanguageNames.CSharp)]
public class AutoMapperGenerator : IIncrementalGenerator
{
const string GenMapperAttributeFullName = "AutoGenMapperGenerator.GenMapperAttribute";
const string GenMapFromAttributeFullName = "AutoGenMapperGenerator.MapFromAttribute";
const string GenMapToAttributeFullName = "AutoGenMapperGenerator.MapToAttribute";
const string GenMapableInterface = "AutoGenMapperGenerator.IAutoMap";

public void Initialize(IncrementalGeneratorInitializationContext context)
{
var map = context.SyntaxProvider.ForAttributeWithMetadataName(GenMapperAttributeFullName
, static (node, _) => node is ClassDeclarationSyntax
, static (source, _) => source);
context.RegisterSourceOutput(map, static (context, source) =>
{
var file = CreateCodeFile(context, source);
context.AddSource(file);
});
}

private static CodeFile? CreateCodeFile(SourceProductionContext context, GeneratorAttributeSyntaxContext source)
{
var origin = (INamedTypeSymbol)source.TargetSymbol;
if (!origin.HasInterface(GenMapableInterface))
{
context.ReportDiagnostic(DiagnosticDefinitions.AGM00001(source.TargetNode.GetLocation()));
return null;
}

var ctxs = source.TargetSymbol.GetAttributes(GenMapperAttributeFullName).Select(s => CollectTypeInfos(origin, s)).ToArray();
var cb = ClassBuilder.Default.Modifiers("partial").ClassName(origin.Name)
.AddGeneratedCodeAttribute(typeof(AutoMapperGenerator));
List<MethodBuilder> methods = [];
foreach (var ctx in ctxs)
{
var m = BuildAutoMapClass.GenerateMethod(ctx);
methods.Add(m);
}

var im = BuildAutoMapClass.GenerateInterfaceMethod(ctxs);
methods.Add(im);
cb.AddMembers([.. methods]);

var cla = cb.ToString();
return CodeFile.New($"{origin.FormatFileName()}.AutoMap.g.cs")
.AddMembers(NamespaceBuilder.Default.Namespace(origin.ContainingNamespace.ToDisplayString())
.AddMembers(cb));
}

private static GenMapperContext CollectTypeInfos(INamedTypeSymbol source, AttributeData a)
{
var context = new GenMapperContext() { SourceType = source };
context.SourceProperties = source.GetMembers().Where(i => i.Kind == SymbolKind.Property)
.Cast<IPropertySymbol>().ToArray();
if (!(a.GetNamedValue("TargetType", out var t) && t is INamedTypeSymbol target))
{
target = (a.ConstructorArguments.FirstOrDefault().Value as INamedTypeSymbol) ?? source;
}

if (a.ConstructorArguments.Length > 1)
{
context.ConstructorParameters =
a.ConstructorArguments[1].Values.Where(c => c.Value != null).Select(c => c.Value!.ToString()).ToArray();
}

context.TargetType = target;

context.TargetProperties = target.GetMembers().Where(i => i.Kind == SymbolKind.Property)
.Cast<IPropertySymbol>().ToArray();
//Debugger.Launch();
foreach (var (froms, tos) in context.TargetProperties.Select(GetMapInfo))
{
context.Froms.AddRange(froms);
context.Tos.AddRange(tos);
}
foreach (var (froms, tos) in context.SourceProperties.Select(GetMapInfo))
{
context.Froms.AddRange(froms);
context.Tos.AddRange(tos);
}

return context;
}

private static (MapInfo[] Froms, MapInfo[] Tos) GetMapInfo(ISymbol symbol)
{
if (symbol is not IPropertySymbol prop)
{
return ([], []);
}

var from = prop.GetAttributes(GenMapFromAttributeFullName).Select(a =>
{
var mapInfo = new MapInfo();
if (a.GetNamedValue("Source", out var t) && t is INamedTypeSymbol target)
{
mapInfo.Target = target;
}

if (a.GetNamedValue("Name", out var n))
{
mapInfo.From = n!.ToString();
}

if (a.GetNamedValue("By", out var b))
{
mapInfo.By = prop.ContainingType.GetMembers().FirstOrDefault(s => s.Kind == SymbolKind.Method && s.Name == b?.ToString()) as IMethodSymbol;
}
mapInfo.To = prop.Name;

return mapInfo;
}).ToArray();

var to = prop.GetAttributes(GenMapToAttributeFullName).Select(a =>
{
var mapInfo = new MapInfo();
if (a.GetNamedValue("Target", out var t) && t is INamedTypeSymbol target)
{
mapInfo.Target = target;
}

if (a.GetNamedValue("Name", out var n))
{
mapInfo.To = n!.ToString();
}

if (a.GetNamedValue("By", out var b))
{
mapInfo.By = prop.ContainingType.GetMembers().FirstOrDefault(s => s.Kind == SymbolKind.Method && s.Name == b?.ToString()) as IMethodSymbol;
}
mapInfo.From = prop.Name;

return mapInfo;
}).ToArray();

return (from, to);
}
}
}
124 changes: 124 additions & 0 deletions src/AutoGenMapper.Roslyn/BuildAutoMapClass.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using Generators.Shared;
using Generators.Shared.Builder;
using Microsoft.CodeAnalysis;

namespace AutoGenMapperGenerator;

public static class BuildAutoMapClass
{
internal static MethodBuilder GenerateInterfaceMethod(GenMapperContext[] mapToMethods)
{
var statements = new List<Statement>();
if (mapToMethods.Length == 1)
{
statements.Add($"return MapTo{mapToMethods[0].TargetType!.Name}()");
}
else
{
statements.Add($"if (string.IsNullOrEmpty(target))");
statements.Add(""" throw new ArgumentNullException(nameof(target), "存在多个目标类型,请指定目标类型,推荐使用nameof(TargetType)")""");
foreach (var method in mapToMethods)
{
statements.Add($"if (target == nameof({method.TargetType!.Name}))");
statements.Add($" return MapTo{method.TargetType.Name}();");
}
statements.Add("""throw new ArgumentException("未找到指定目标类型的映射方法");""");
}
var m = MethodBuilder.Default
.MethodName("MapTo")
.ReturnType("object?")
.AddParameter("string? target = null")
.AddGeneratedCodeAttribute(typeof(AutoMapperGenerator))
.AddBody([.. statements]);
return m;
}
internal static MethodBuilder GenerateMethod(GenMapperContext context)
{
var statements = new List<Statement>()
{
$"var result = new {context.TargetType.ToDisplayString()}({string.Join(", ", context.ConstructorParameters)})"
};
//Debugger.Launch();
List<string> solved = [];
foreach (var mapTo in context.Tos)
{
if (!EqualityComparer<INamedTypeSymbol>.Default.Equals(context.TargetType, mapTo.Target))
{
continue;
}

if (mapTo.By != null)
{
statements.Add($"this.{mapTo.By.Name}(result)");
}
else if (!mapTo.To.IsNullOrEmpty())
{
statements.Add($"result.{mapTo.To} = this.{mapTo.From}");
solved.Add(mapTo.To!);
}
}

foreach (var prop in context.TargetProperties)
{
if (solved.Contains(prop.Name))
{
continue;
}
if (GetPropertyValue(context, prop, out var value))
{
statements.Add($"result.{prop.Name} = {value}");
}
}

statements.Add($"return result;");

var builder = MethodBuilder.Default
.MethodName($"MapTo{context.TargetType.Name}")
.ReturnType(context.TargetType.ToDisplayString())
.AddGeneratedCodeAttribute(typeof(AutoMapperGenerator))
.AddBody([.. statements]);

return builder;
}

private static bool GetPropertyValue(GenMapperContext context, IPropertySymbol prop, out string? value)
{
var customTrans = context.Froms.FirstOrDefault(f =>
EqualityComparer<INamedTypeSymbol>.Default.Equals(f.Target, context.SourceType)
&& f.To == prop.Name);
if (customTrans != null)
{
var tranMethod = customTrans.By;
if (tranMethod != null)
{
if (tranMethod.IsStatic)
{
value = $"{tranMethod.ReceiverType?.ToDisplayString() ?? tranMethod.ContainingType?.ToDisplayString()}.{tranMethod.Name}(this)";
}
else
{
value = $"result.{tranMethod.Name}(this)";
}
return true;
}
else if (!customTrans.From.IsNullOrEmpty())
{
value = $"this.{customTrans.From}";
return true;
}
}

var p = context.SourceProperties.FirstOrDefault(p => p.Name == prop.Name);
if (p != null)
{
value = $"this.{p.Name}";
return true;
}
value = null;
return false;
}
}
24 changes: 24 additions & 0 deletions src/AutoGenMapper.Roslyn/DiagnosticDefinitions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using Microsoft.CodeAnalysis;
using System;
using System.Collections.Generic;
using System.Text;
using AutoGenMapperGenerator;

namespace AutoGenMapperGenerator
{
internal class DiagnosticDefinitions
{
/// <summary>
/// 需要实现 IAutoMap 接口
/// </summary>
/// <param name="location"></param>
/// <returns></returns>
public static Diagnostic AGM00001(Location? location) => Diagnostic.Create(new DiagnosticDescriptor(
id: "AGM00001",
title: "需要实现 IAutoMap 接口",
messageFormat: "需要实现 IAutoMap 接口",
category: typeof(AutoMapperGenerator).FullName!,
defaultSeverity: DiagnosticSeverity.Error,
isEnabledByDefault: true), location);
}
}
21 changes: 21 additions & 0 deletions src/AutoGenMapper.Roslyn/GenMapperContext.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using System.Collections.Generic;
using Microsoft.CodeAnalysis;
namespace AutoGenMapperGenerator;

public class MapInfo
{
public INamedTypeSymbol Target { get; set; } = default!;
public string? From { get; set; }
public string? To { get; set; }
public IMethodSymbol? By { get; set; }
}
public class GenMapperContext
{
public INamedTypeSymbol SourceType { get; set; } = default!;
public INamedTypeSymbol TargetType { get; set; } = default!;
public IPropertySymbol[] SourceProperties { get; set; } = [];
public IPropertySymbol[] TargetProperties { get; set; } = [];
public string[] ConstructorParameters { get; set; } = [];
public List<MapInfo> Froms { get; set; } = [];
public List<MapInfo> Tos { get; set; } = [];
}
Loading

0 comments on commit 1cc8dcc

Please sign in to comment.