-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
494d500
commit 1cc8dcc
Showing
22 changed files
with
672 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; } = []; | ||
} |
Oops, something went wrong.