diff --git "a/doc/GenMapper\347\211\210\346\234\254\346\227\245\345\277\227.md" "b/doc/GenMapper\347\211\210\346\234\254\346\227\245\345\277\227.md" index a0647cd..27cd848 100644 --- "a/doc/GenMapper\347\211\210\346\234\254\346\227\245\345\277\227.md" +++ "b/doc/GenMapper\347\211\210\346\234\254\346\227\245\345\277\227.md" @@ -2,6 +2,29 @@ ## v0.1.0 - ⚡️升级`.NET10` +- 🛠优化生成器代码 +- ⚡️支持生成扩展方法 + +```csharp +internal static partial class MapperExtensions +{ + [GenMapper] + [MapBetween([nameof(Product.Name), nameof(Product.Category)], nameof(ProductDto.Name), By = nameof(MapToDtoName))] + [MapBetween(nameof(Product.SplitValue), [nameof(ProductDto.S1), nameof(ProductDto.S2)], By = nameof(MapOneToMultiTest))] + public static partial ProductDto ToDto(this Product product, Action? action = null); + + public static string MapToDtoName(string name, string category) + { + return $"{name}-{category}"; + } + + public static (string, string) MapOneToMultiTest(string value) + { + var val = value.Split(','); + return (val[0], val[1]); + } +} +``` ## v0.0.9 diff --git a/src/AutoGenMapper.Roslyn/AutoMapperExtensionGenerator.cs b/src/AutoGenMapper.Roslyn/AutoMapperExtensionGenerator.cs new file mode 100644 index 0000000..10140fd --- /dev/null +++ b/src/AutoGenMapper.Roslyn/AutoMapperExtensionGenerator.cs @@ -0,0 +1,72 @@ +using Generators.Shared; +using Generators.Shared.Builder; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using System.Collections.Generic; +using System.Linq; +using static AutoGenMapperGenerator.Helper; +namespace AutoGenMapperGenerator; + +[Generator(LanguageNames.CSharp)] +public class AutoMapperExtensionGenerator : IIncrementalGenerator +{ + public void Initialize(IncrementalGeneratorInitializationContext context) + { + var map = context.SyntaxProvider.ForAttributeWithMetadataName(GenMapperAttributeFullName + , static (node, _) => node is MethodDeclarationSyntax + { + ParameterList.Parameters: + { + Count: var psCount + } ps + } && psCount > 0 && ps[0].Type is not null + , static (source, _) => CollectMapperContext(source)); + + context.RegisterSourceOutput(map, static (context, source) => + { + if (source.Item2 is not null) + { + context.ReportDiagnostic(source.Item2); + return; + } + if (source.Item1 is null) return; + var file = CreateCodeFile(source.Item1); +#if DEBUG + var ss = file?.ToString(); +#endif + context.AddSource(file); + }); + } + static (MapperContext?, Diagnostic?) CollectMapperContext(GeneratorAttributeSyntaxContext context) + { + var location = context.TargetNode.GetLocation(); + var mapBetweens = CollectSpecificBetweenInfo(context.TargetSymbol).ToArray(); + var mapperTargets = CollectMapTargets(context.TargetSymbol); + var mapContext = new MapperContext(context.TargetSymbol) + { + Targets = mapperTargets + }; + var error = Helper.CollectMapperContext(mapContext, mapBetweens, location); + return (mapContext, error); + } + static CodeFile? CreateCodeFile(MapperContext context) + { + INamedTypeSymbol classSymbol = context.ContainingType!; + var cb = ClassBuilder.Default.Modifiers("static partial").ClassName(classSymbol.Name) + .AddGeneratedCodeAttribute(typeof(AutoMapperGenerator)); + List methods = []; + foreach (var ctx in context.Targets) + { + var m = BuildAutoMapClass.GenerateExtensionMethod(context, ctx); + methods.Add(m); + } + + cb.AddMembers([.. methods]); + var ns = NamespaceBuilder.Default.Namespace(classSymbol.ContainingNamespace.ToDisplayString()); + return CodeFile.New($"{classSymbol.FormatFileName()}.AutoMap.Ex.g.cs") + //.AddUsings("using System.Linq;") + //.AddUsings("using AutoGenMapperGenerator;") + .AddUsings(classSymbol.GetTargetUsings()) + .AddMembers(ns.AddMembers(cb)); + } +} diff --git a/src/AutoGenMapper.Roslyn/AutoMapperGenerator.cs b/src/AutoGenMapper.Roslyn/AutoMapperGenerator.cs index 4fa150a..0030e89 100644 --- a/src/AutoGenMapper.Roslyn/AutoMapperGenerator.cs +++ b/src/AutoGenMapper.Roslyn/AutoMapperGenerator.cs @@ -6,434 +6,471 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; using Generators.Shared; using System.Diagnostics; +using static AutoGenMapperGenerator.Helper; +namespace AutoGenMapperGenerator; -namespace AutoGenMapperGenerator +[Generator(LanguageNames.CSharp)] +public class AutoMapperGenerator : IIncrementalGenerator { - [Generator(LanguageNames.CSharp)] - public class AutoMapperGenerator : IIncrementalGenerator + public void Initialize(IncrementalGeneratorInitializationContext context) { - internal const string GenMapperAttributeFullName = "AutoGenMapperGenerator.GenMapperAttribute"; - internal const string GenMapFromAttributeFullName = "AutoGenMapperGenerator.MapFromAttribute"; - internal const string GenMapToAttributeFullName = "AutoGenMapperGenerator.MapToAttribute"; - internal const string GenMapableInterface = "AutoGenMapperGenerator.IAutoMap"; - internal const string GenMapIgnoreAttribute = "AutoGenMapperGenerator.MapIgnoreAttribute"; - internal const string GenMapBetweenAttributeFullName = "AutoGenMapperGenerator.MapBetweenAttribute"; - - 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); -#if DEBUG - var ss = file?.ToString(); -#endif - context.AddSource(file); - }); - } - - private static CodeFile? CreateCodeFile(SourceProductionContext context, GeneratorAttributeSyntaxContext gasc) - { - var source = (INamedTypeSymbol)gasc.TargetSymbol; - var hasDefaultCtor = source.Constructors.Any(c => c.Parameters.Length == 0); - if (!hasDefaultCtor) - { - context.ReportDiagnostic(DiagnosticDefinitions.AGM00011(source.Locations.FirstOrDefault())); - } - var ns = NamespaceBuilder.Default.Namespace(source.ContainingNamespace.ToDisplayString()); - - var typeInfos = source.GetAttributes(GenMapperAttributeFullName).Select(a => CollectTypeInfos(source, a)).ToArray(); - var errors = typeInfos.Where(r => r.Item2 is not null).Select(r => r.Item2).ToArray(); - if (errors.Length > 0) - { - foreach (var e in errors) - { - context.ReportDiagnostic(e!); - } - - return null; - } - - GenMapperContext[] ctxs = [.. typeInfos.Where(r => r.Item1 is not null).Select(r => r.Item1)]; - var cb = ClassBuilder.Default.Modifiers("partial").ClassName(source.Name) - .Interface(GenMapableInterface) - .AddGeneratedCodeAttribute(typeof(AutoMapperGenerator)); - List methods = []; - foreach (var ctx in ctxs) - { - var m = BuildAutoMapClass.GenerateMapToMethod(ctx); - methods.Add(m); - var f = BuildAutoMapClass.GenerateMapFromMethod(ctx); - methods.Add(f); - } - - //TODO 添加对Dictionary的支持 - + var map = context.SyntaxProvider.ForAttributeWithMetadataName(GenMapperAttributeFullName + , static (node, _) => node is ClassDeclarationSyntax + , static (source, _) => CollectMapperContext(source)); - var im = BuildAutoMapClass.GenerateInterfaceMethod(ctxs); - methods.AddRange(im); - cb.AddMembers([.. methods]); - - - return CodeFile.New($"{source.FormatFileName()}.AutoMap.g.cs") - //.AddUsings("using System.Linq;") - //.AddUsings("using AutoGenMapperGenerator;") - .AddUsings(source.GetTargetUsings()) - .AddMembers(ns.AddMembers(cb)); - } - - private static (GenMapperContext, Diagnostic?) CollectTypeInfos(INamedTypeSymbol source, AttributeData a) + context.RegisterSourceOutput(map, static (context, source) => { - var context = new GenMapperContext() { SourceType = source }; - if (!(a.GetNamedValue("TargetType", out var t) && t is INamedTypeSymbol target)) - { - target = (a.ConstructorArguments.FirstOrDefault().Value as INamedTypeSymbol) ?? source; - } - - context.TargetType = target; - - var sourceProperties = source.GetAllMembers(_ => true).Where(i => i.Kind == SymbolKind.Property) - .Cast().ToArray(); - var targetProperties = target.GetAllMembers(_ => true).Where(i => i.Kind == SymbolKind.Property) - .Cast().ToArray(); - - var mapInfos = new List(); - - // 定义在类上的MapBetween - var topRules = GetSpecificDatas(source, target); - if (topRules.Error is not null) - { - return (context, topRules.Error); - } - - foreach (var item in topRules.AttrDatas) - { - var mi = new MapInfo() { Position = DeclarePosition.Class }; - var type = CheckMapType(item); - item.GetConstructorValue(0, out var tar); - switch (type) - { - case MappingType.SingleToSingle: - { - item.GetConstructorValue(1, out var sp); - item.GetConstructorValue(2, out var tp); - mi.SourceName = [sp!.ToString()]; - mi.SourceProp = [.. sourceProperties.Where(p => mi.SourceName.Contains(p.Name))]; - mi.TargetName = [tp!.ToString()]; - mi.TargetProp = [.. targetProperties.Where(p => mi.TargetName.Contains(p.Name))]; - mi.MappingType = MappingType.SingleToSingle; - break; - } - case MappingType.SingleToMulti: - { - item.GetConstructorValue(1, out var sp); - item.GetConstructorValues(2, out var tps); - mi.SourceName = [sp!.ToString()]; - mi.SourceProp = [.. sourceProperties.Where(p => mi.SourceName.Contains(p.Name))]; - mi.TargetName = [.. tps.Select(tp => tp!.ToString())]; - mi.TargetProp = [.. targetProperties.Where(p => mi.TargetName.Contains(p.Name))]; - mi.MappingType = MappingType.SingleToMulti; - break; - } - case MappingType.MultiToSingle: - { - item.GetConstructorValues(1, out var sps); - item.GetConstructorValue(2, out var tp); - mi.SourceName = [.. sps.Select(sp => sp!.ToString())]; - mi.SourceProp = [.. sourceProperties.Where(p => mi.SourceName.Contains(p.Name))]; - mi.TargetName = [tp!.ToString()]; - mi.TargetProp = [.. targetProperties.Where(p => mi.TargetName.Contains(p.Name))]; - mi.MappingType = MappingType.MultiToSingle; - break; - } - default: - throw new Exception(); - } - - if (item.GetNamedValue("By", out var by)) - { - var methodName = by!.ToString(); - var error = HandleByMethod(source, methodName, mi); - if (error is not null) - { - return (context, error); - } - } - - if (type != MappingType.SingleToSingle && mi.ForwardBy is null) - { - return (context, DiagnosticDefinitions.AGM00006(source.Locations.FirstOrDefault())); - } - - mapInfos.Add(mi); - } - - foreach (var sp in sourceProperties) - { - if (sp.IsReadOnly || sp.DeclaredAccessibility != Accessibility.Public) - { - continue; - } - - var mi = new MapInfo() - { - SourceName = [sp.Name], - SourceProp = [sp], - MappingType = MappingType.SingleToSingle - }; - // 属性上可能有多个Attribute,只找出跟当前Target一样的Target的Attribute - var AttrData = GetSpecificData(sp, target); - if (AttrData is not null) - { - if (AttrData.ConstructorArguments.Length > 2) - { - return (context, DiagnosticDefinitions.AGM00005(source.Locations.FirstOrDefault())); - } - - AttrData.GetConstructorValue(1, out var tarName); - // 已经在Class上定义了MapBetween,就跳过 - if (mapInfos.Any(m => m.Position == DeclarePosition.Class - && m.SourceName.Contains(sp.Name) - && m.TargetName.Contains(tarName!.ToString()))) - { - continue; - } - - mi.TargetName = [tarName!.ToString()]; - mi.TargetProp = [.. targetProperties.Where(p => mi.TargetName.Contains(p.Name))]; - if (AttrData.GetNamedValue("By", out var by)) - { - var methodName = by!.ToString(); - var error = HandleByMethod(source, methodName, mi); - if (error is not null) - { - return (context, error); - } - } - } - else - { - var tp = targetProperties.FirstOrDefault(tp => tp.Name == sp.Name); - if (tp is null) - { - continue; - } - - mi.TargetName = [tp.Name]; - mi.TargetProp = [tp]; - } - - mapInfos.Add(mi); - } - - if (a.ConstructorArguments.Length > 1) - { - // 指定了构造函数参数 - context.ConstructorParameters = - [.. a.ConstructorArguments[1].Values.Where(c => c.Value != null).Select(c => c.Value!.ToString())]; - } - else - { - // 尝试自动获取构造函数参数 - var ctorSymbol = context.TargetType.GetMethods().FirstOrDefault(m => m.MethodKind == MethodKind.Constructor && m.Parameters.Length > 0); - ctorSymbol ??= context.TargetType.GetMethods().FirstOrDefault(m => m.MethodKind == MethodKind.Constructor); - if (ctorSymbol is null) - { - return (context, DiagnosticDefinitions.AGM00011(target.Locations.FirstOrDefault())); - } - string[] ps = [.. ctorSymbol.Parameters.Select(p => source.GetAllMembers(_ => true).FirstOrDefault(s => string.Equals(p.Name, s.Name, StringComparison.OrdinalIgnoreCase))?.Name).Where(s => !string.IsNullOrEmpty(s))!]; - if (ps.Length != ctorSymbol.Parameters.Length) - { - return (context, DiagnosticDefinitions.AGM00013(source.Locations.FirstOrDefault())); - } - context.ConstructorParameters = ps; - } - - context.Maps = mapInfos; - return (context, null); - - - static AttributeData? GetSpecificData(IPropertySymbol property, INamedTypeSymbol matchSymbol) + if (source.Item2 is not null) { - var attrs = property.GetAttributes(GenMapBetweenAttributeFullName); - foreach (var item in attrs) - { - item.GetConstructorValue(0, out var t); - var symbol = (INamedTypeSymbol)t!; - if (EqualityComparer.Default.Equals(symbol, matchSymbol)) - { - return item; - } - } - - return null; + context.ReportDiagnostic(source.Item2); + return; } - - static (AttributeData[] AttrDatas, Diagnostic? Error) GetSpecificDatas(INamedTypeSymbol type, INamedTypeSymbol matchSymbol) - { - var attrs = type.GetAttributes(GenMapBetweenAttributeFullName); - List result = []; - foreach (var item in attrs) - { - if (item.ConstructorArguments.Length == 2) - { - return ([], DiagnosticDefinitions.AGM00004(type.Locations.FirstOrDefault())); - } - - item.GetConstructorValue(0, out var t); - var symbol = (INamedTypeSymbol)t!; - if (EqualityComparer.Default.Equals(symbol, matchSymbol)) - { - result.Add(item); - } - } - - return ([.. result], null); - } - - static MappingType CheckMapType(AttributeData data) - { - var s = data.ConstructorArguments[1]; - var t = data.ConstructorArguments[2]; - if (s.Type is IArrayTypeSymbol) - { - return MappingType.MultiToSingle; - } - else if (t.Type is IArrayTypeSymbol) - { - return MappingType.SingleToMulti; - } - else - { - return MappingType.SingleToSingle; - } - } - } - - private static bool CheckMappingMethodReturnType(IMethodSymbol? method - , Location? location - , ITypeSymbol[] paramTypes - , ITypeSymbol[] returnTypes - , out Diagnostic? error) + if (source.Item1 is null) return; + var file = CreateCodeFile(source.Item1); +#if DEBUG + var ss = file?.ToString(); +#endif + context.AddSource(file); + }); + } + static (MapperContext?, Diagnostic?) CollectMapperContext(GeneratorAttributeSyntaxContext context) + { + var location = context.TargetNode.GetLocation(); + var mapBetweens = CollectSpecificBetweenInfo(context.TargetSymbol).ToArray(); + var mapperTargets = CollectMapTargets(context.TargetSymbol); + var mapContext = new MapperContext(context.TargetSymbol) { - if (method is null) - { - // 自定义处理方法的参数个数不匹配 - error = DiagnosticDefinitions.AGM00007(location); - return false; - } - location = method.Locations.FirstOrDefault(); - // 检查返回值 - if (returnTypes.Length > 1) - { - // 一对多的情况,返回值需要是object[]类型 - if (!method.ReturnType.IsTupleType) - { - error = DiagnosticDefinitions.AGM00009(location); - return false; - } - if (method.ReturnType is not INamedTypeSymbol tuple) - { - error = DiagnosticDefinitions.AGM00009(location); - return false; - } - var tupleElement = tuple.TupleElements; - if (tupleElement.Length != returnTypes.Length) - { - error = DiagnosticDefinitions.AGM00010(location); - return false; - } - for (int i = 0; i < tupleElement.Length; i++) - { - var te = tupleElement[i].Type; - if (!EqualityComparer.Default.Equals(te, returnTypes[i])) - { - error = DiagnosticDefinitions.AGM00008(location); - return false; - } - } - } - else - { - var returnType = returnTypes[0]; - if (!EqualityComparer.Default.Equals(method.ReturnType, returnType)) - { - error = DiagnosticDefinitions.AGM00009(location); - return false; - } - } - - error = null; - return true; - } + Targets = mapperTargets + }; + var error = Helper.CollectMapperContext(mapContext, mapBetweens, location); + return (mapContext, error); + } - private static bool CheckParameters(IMethodSymbol method, ITypeSymbol[] paramTypes) + static CodeFile? CreateCodeFile(MapperContext context) + { + INamedTypeSymbol source = context.SourceType; + var cb = ClassBuilder.Default.Modifiers("partial").ClassName(source.Name) + .Interface(GenMapableInterface) + .AddGeneratedCodeAttribute(typeof(AutoMapperGenerator)); + List methods = []; + foreach (var ctx in context.Targets) { - // 检查参数类型 - if (method.Parameters.Length != paramTypes.Length) return false; - for (int i = 0; i < method.Parameters.Length; i++) - { - var mpt = method.Parameters[i].Type; - if (!EqualityComparer.Default.Equals(mpt, paramTypes[i])) - { - return false; - } - } - return true; + var m = BuildAutoMapClass.GenerateMapToMethod(source, ctx); + methods.Add(m); + var f = BuildAutoMapClass.GenerateMapFromMethod(source, ctx); + methods.Add(f); } - private static Diagnostic? HandleByMethod(INamedTypeSymbol source - , string methodName - , MapInfo mi) - { - var methods = source.GetAllMembers(_ => true).Where(i => i.Kind == SymbolKind.Method && i.Name == methodName).Cast().ToArray(); - if (methods.Length == 0) - { - return DiagnosticDefinitions.AGM00003(source.Locations.FirstOrDefault()); - } - - var spts = mi.SourceProp.Select(p => p.Type).ToArray(); - var tpts = mi.TargetProp.Select(p => p.Type).ToArray(); - - #region 正向映射 - - var forward = methods.FirstOrDefault(m => CheckParameters(m, spts)); - if (!CheckMappingMethodReturnType(forward - , source.Locations.FirstOrDefault() - , spts - , tpts - , out var error)) - { - return error; - } - - mi.ForwardBy = forward; - - #endregion - - #region 反向映射 - - var reverse = methods.FirstOrDefault(m => CheckParameters(m, tpts)); - if (CheckMappingMethodReturnType(reverse - , source.Locations.FirstOrDefault() - , tpts - , spts - , out error)) - { - mi.CanReverse = true; - mi.ReverseBy = reverse; - } - else if (reverse != null) - { - return error; - } - - #endregion - - return null; - } + var im = BuildAutoMapClass.GenerateInterfaceMethod(context.Targets); + methods.AddRange(im); + cb.AddMembers([.. methods]); + var ns = NamespaceBuilder.Default.Namespace(source.ContainingNamespace.ToDisplayString()); + return CodeFile.New($"{source.FormatFileName()}.AutoMap.g.cs") + //.AddUsings("using System.Linq;") + //.AddUsings("using AutoGenMapperGenerator;") + .AddUsings(source.GetTargetUsings()) + .AddMembers(ns.AddMembers(cb)); } + //[Obsolete] + //private static CodeFile? CreateCodeFile(SourceProductionContext context, GeneratorAttributeSyntaxContext gasc) + //{ + // var source = (INamedTypeSymbol)gasc.TargetSymbol; + // var hasDefaultCtor = source.Constructors.Any(c => c.Parameters.Length == 0); + // if (!hasDefaultCtor) + // { + // context.ReportDiagnostic(DiagnosticDefinitions.AGM00011(source.Locations.FirstOrDefault())); + // } + // var ns = NamespaceBuilder.Default.Namespace(source.ContainingNamespace.ToDisplayString()); + + // var typeInfos = source.GetAttributes(GenMapperAttributeFullName).Select(a => CollectTypeInfos(source, a)).ToArray(); + // var errors = typeInfos.Where(r => r.Item2 is not null).Select(r => r.Item2).ToArray(); + // if (errors.Length > 0) + // { + // foreach (var e in errors) + // { + // context.ReportDiagnostic(e!); + // } + + // return null; + // } + + // GenMapperContext[] ctxs = [.. typeInfos.Where(r => r.Item1 is not null).Select(r => r.Item1)]; + // var cb = ClassBuilder.Default.Modifiers("partial").ClassName(source.Name) + // .Interface(GenMapableInterface) + // .AddGeneratedCodeAttribute(typeof(AutoMapperGenerator)); + // List methods = []; + // foreach (var ctx in ctxs) + // { + // var m = BuildAutoMapClass.GenerateMapToMethod(ctx); + // methods.Add(m); + // var f = BuildAutoMapClass.GenerateMapFromMethod(ctx); + // methods.Add(f); + // } + + // //TODO 添加对Dictionary的支持 + + + // var im = BuildAutoMapClass.GenerateInterfaceMethod(ctxs); + // methods.AddRange(im); + // cb.AddMembers([.. methods]); + + + // return CodeFile.New($"{source.FormatFileName()}.AutoMap.g.cs") + // //.AddUsings("using System.Linq;") + // //.AddUsings("using AutoGenMapperGenerator;") + // .AddUsings(source.GetTargetUsings()) + // .AddMembers(ns.AddMembers(cb)); + //} + + //[Obsolete] + //private static (GenMapperContext, Diagnostic?) CollectTypeInfos(INamedTypeSymbol source, AttributeData a) + //{ + // var context = new GenMapperContext() { SourceType = source }; + // if (!(a.GetNamedValue("TargetType", out var t) && t is INamedTypeSymbol target)) + // { + // target = (a.ConstructorArguments.FirstOrDefault().Value as INamedTypeSymbol) ?? source; + // } + + // context.TargetType = target; + + // var sourceProperties = source.GetAllMembers(_ => true).Where(i => i.Kind == SymbolKind.Property) + // .Cast().ToArray(); + // var targetProperties = target.GetAllMembers(_ => true).Where(i => i.Kind == SymbolKind.Property) + // .Cast().ToArray(); + + // var mapInfos = new List(); + + // // 定义在类上的MapBetween + // var topRules = GetSpecificDatas(source, target); + // if (topRules.Error is not null) + // { + // return (context, topRules.Error); + // } + + // foreach (var item in topRules.AttrDatas) + // { + // var mi = new MapInfo() { Position = DeclarePosition.Class }; + // var type = CheckMapType(item); + // item.GetConstructorValue(0, out var tar); + // switch (type) + // { + // case MappingType.SingleToSingle: + // { + // item.GetConstructorValue(1, out var sp); + // item.GetConstructorValue(2, out var tp); + // mi.SourceName = [sp!.ToString()]; + // mi.SourceProp = [.. sourceProperties.Where(p => mi.SourceName.Contains(p.Name))]; + // mi.TargetName = [tp!.ToString()]; + // mi.TargetProp = [.. targetProperties.Where(p => mi.TargetName.Contains(p.Name))]; + // mi.MappingType = MappingType.SingleToSingle; + // break; + // } + // case MappingType.SingleToMulti: + // { + // item.GetConstructorValue(1, out var sp); + // item.GetConstructorValues(2, out var tps); + // mi.SourceName = [sp!.ToString()]; + // mi.SourceProp = [.. sourceProperties.Where(p => mi.SourceName.Contains(p.Name))]; + // mi.TargetName = [.. tps.Select(tp => tp!.ToString())]; + // mi.TargetProp = [.. targetProperties.Where(p => mi.TargetName.Contains(p.Name))]; + // mi.MappingType = MappingType.SingleToMulti; + // break; + // } + // case MappingType.MultiToSingle: + // { + // item.GetConstructorValues(1, out var sps); + // item.GetConstructorValue(2, out var tp); + // mi.SourceName = [.. sps.Select(sp => sp!.ToString())]; + // mi.SourceProp = [.. sourceProperties.Where(p => mi.SourceName.Contains(p.Name))]; + // mi.TargetName = [tp!.ToString()]; + // mi.TargetProp = [.. targetProperties.Where(p => mi.TargetName.Contains(p.Name))]; + // mi.MappingType = MappingType.MultiToSingle; + // break; + // } + // default: + // throw new Exception(); + // } + + // if (item.GetNamedValue("By", out var by)) + // { + // var methodName = by!.ToString(); + // var error = HandleByMethod(source, methodName, mi); + // if (error is not null) + // { + // return (context, error); + // } + // } + + // if (type != MappingType.SingleToSingle && mi.ForwardBy is null) + // { + // return (context, DiagnosticDefinitions.AGM00006(source.Locations.FirstOrDefault())); + // } + + // mapInfos.Add(mi); + // } + + // foreach (var sp in sourceProperties) + // { + // if (sp.IsReadOnly || sp.DeclaredAccessibility != Accessibility.Public) + // { + // continue; + // } + + // var mi = new MapInfo() + // { + // SourceName = [sp.Name], + // SourceProp = [sp], + // MappingType = MappingType.SingleToSingle + // }; + // // 属性上可能有多个Attribute,只找出跟当前Target一样的Target的Attribute + // var AttrData = GetSpecificData(sp, target); + // if (AttrData is not null) + // { + // if (AttrData.ConstructorArguments.Length > 2) + // { + // return (context, DiagnosticDefinitions.AGM00005(source.Locations.FirstOrDefault())); + // } + + // AttrData.GetConstructorValue(1, out var tarName); + // // 已经在Class上定义了MapBetween,就跳过 + // if (mapInfos.Any(m => m.Position == DeclarePosition.Class + // && m.SourceName.Contains(sp.Name) + // && m.TargetName.Contains(tarName!.ToString()))) + // { + // continue; + // } + + // mi.TargetName = [tarName!.ToString()]; + // mi.TargetProp = [.. targetProperties.Where(p => mi.TargetName.Contains(p.Name))]; + // if (AttrData.GetNamedValue("By", out var by)) + // { + // var methodName = by!.ToString(); + // var error = HandleByMethod(source, methodName, mi); + // if (error is not null) + // { + // return (context, error); + // } + // } + // } + // else + // { + // var tp = targetProperties.FirstOrDefault(tp => tp.Name == sp.Name); + // if (tp is null) + // { + // continue; + // } + + // mi.TargetName = [tp.Name]; + // mi.TargetProp = [tp]; + // } + + // mapInfos.Add(mi); + // } + + // if (a.ConstructorArguments.Length > 1) + // { + // // 指定了构造函数参数 + // context.ConstructorParameters = + // [.. a.ConstructorArguments[1].Values.Where(c => c.Value != null).Select(c => c.Value!.ToString())]; + // } + // else + // { + // // 尝试自动获取构造函数参数 + // var ctorSymbol = context.TargetType.GetMethods().FirstOrDefault(m => m.MethodKind == MethodKind.Constructor && m.Parameters.Length > 0); + // ctorSymbol ??= context.TargetType.GetMethods().FirstOrDefault(m => m.MethodKind == MethodKind.Constructor); + // if (ctorSymbol is null) + // { + // return (context, DiagnosticDefinitions.AGM00011(target.Locations.FirstOrDefault())); + // } + // string[] ps = [.. ctorSymbol.Parameters.Select(p => source.GetAllMembers(_ => true).FirstOrDefault(s => string.Equals(p.Name, s.Name, StringComparison.OrdinalIgnoreCase))?.Name).Where(s => !string.IsNullOrEmpty(s))!]; + // if (ps.Length != ctorSymbol.Parameters.Length) + // { + // return (context, DiagnosticDefinitions.AGM00013(source.Locations.FirstOrDefault())); + // } + // context.ConstructorParameters = ps; + // } + + // context.Maps = mapInfos; + // return (context, null); + + + // static AttributeData? GetSpecificData(IPropertySymbol property, INamedTypeSymbol matchSymbol) + // { + // var attrs = property.GetAttributes(GenMapBetweenAttributeFullName); + // foreach (var item in attrs) + // { + // item.GetConstructorValue(0, out var t); + // var symbol = (INamedTypeSymbol)t!; + // if (EqualityComparer.Default.Equals(symbol, matchSymbol)) + // { + // return item; + // } + // } + + // return null; + // } + + // static (AttributeData[] AttrDatas, Diagnostic? Error) GetSpecificDatas(INamedTypeSymbol type, INamedTypeSymbol matchSymbol) + // { + // var attrs = type.GetAttributes(GenMapBetweenAttributeFullName); + // List result = []; + // foreach (var item in attrs) + // { + // if (item.ConstructorArguments.Length == 2) + // { + // return ([], DiagnosticDefinitions.AGM00004(type.Locations.FirstOrDefault())); + // } + + // item.GetConstructorValue(0, out var t); + // var symbol = (INamedTypeSymbol)t!; + // if (EqualityComparer.Default.Equals(symbol, matchSymbol)) + // { + // result.Add(item); + // } + // } + + // return ([.. result], null); + // } + + // static MappingType CheckMapType(AttributeData data) + // { + // var s = data.ConstructorArguments[1]; + // var t = data.ConstructorArguments[2]; + // if (s.Type is IArrayTypeSymbol) + // { + // return MappingType.MultiToSingle; + // } + // else if (t.Type is IArrayTypeSymbol) + // { + // return MappingType.SingleToMulti; + // } + // else + // { + // return MappingType.SingleToSingle; + // } + // } + //} + + //private static bool CheckMappingMethodReturnType(IMethodSymbol? method + // , Location? location + // , ITypeSymbol[] paramTypes + // , ITypeSymbol[] returnTypes + // , out Diagnostic? error) + //{ + // if (method is null) + // { + // // 自定义处理方法的参数个数不匹配 + // error = DiagnosticDefinitions.AGM00007(location); + // return false; + // } + // location = method.Locations.FirstOrDefault(); + // // 检查返回值 + // if (returnTypes.Length > 1) + // { + // // 一对多的情况,返回值需要是object[]类型 + // if (!method.ReturnType.IsTupleType) + // { + // error = DiagnosticDefinitions.AGM00009(location); + // return false; + // } + // if (method.ReturnType is not INamedTypeSymbol tuple) + // { + // error = DiagnosticDefinitions.AGM00009(location); + // return false; + // } + // var tupleElement = tuple.TupleElements; + // if (tupleElement.Length != returnTypes.Length) + // { + // error = DiagnosticDefinitions.AGM00010(location); + // return false; + // } + // for (int i = 0; i < tupleElement.Length; i++) + // { + // var te = tupleElement[i].Type; + // if (!EqualityComparer.Default.Equals(te, returnTypes[i])) + // { + // error = DiagnosticDefinitions.AGM00008(location); + // return false; + // } + // } + // } + // else + // { + // var returnType = returnTypes[0]; + // if (!EqualityComparer.Default.Equals(method.ReturnType, returnType)) + // { + // error = DiagnosticDefinitions.AGM00009(location); + // return false; + // } + // } + + // error = null; + // return true; + //} + + //private static bool CheckParameters(IMethodSymbol method, ITypeSymbol[] paramTypes) + //{ + // // 检查参数类型 + // if (method.Parameters.Length != paramTypes.Length) return false; + // for (int i = 0; i < method.Parameters.Length; i++) + // { + // var mpt = method.Parameters[i].Type; + // if (!EqualityComparer.Default.Equals(mpt, paramTypes[i])) + // { + // return false; + // } + // } + // return true; + //} + + //private static Diagnostic? HandleByMethod(INamedTypeSymbol source + // , string methodName + // , MapInfo mi) + //{ + // var methods = source.GetAllMembers(_ => true).Where(i => i.Kind == SymbolKind.Method && i.Name == methodName).Cast().ToArray(); + // if (methods.Length == 0) + // { + // return DiagnosticDefinitions.AGM00003(source.Locations.FirstOrDefault()); + // } + + // var spts = mi.SourceProp.Select(p => p.Type).ToArray(); + // var tpts = mi.TargetProp.Select(p => p.Type).ToArray(); + + // #region 正向映射 + + // var forward = methods.FirstOrDefault(m => CheckParameters(m, spts)); + // if (!CheckMappingMethodReturnType(forward + // , source.Locations.FirstOrDefault() + // , spts + // , tpts + // , out var error)) + // { + // return error; + // } + + // mi.ForwardBy = forward; + + // #endregion + + // #region 反向映射 + + // var reverse = methods.FirstOrDefault(m => CheckParameters(m, tpts)); + // if (CheckMappingMethodReturnType(reverse + // , source.Locations.FirstOrDefault() + // , tpts + // , spts + // , out error)) + // { + // mi.CanReverse = true; + // mi.ReverseBy = reverse; + // } + // else if (reverse != null) + // { + // return error; + // } + + // #endregion + + // return null; + //} } \ No newline at end of file diff --git a/src/AutoGenMapper.Roslyn/BuildAutoMapClass.ExtensionMethod.cs b/src/AutoGenMapper.Roslyn/BuildAutoMapClass.ExtensionMethod.cs new file mode 100644 index 0000000..f80dbf8 --- /dev/null +++ b/src/AutoGenMapper.Roslyn/BuildAutoMapClass.ExtensionMethod.cs @@ -0,0 +1,47 @@ +using Generators.Shared; +using Generators.Shared.Builder; +using Microsoft.CodeAnalysis; +using System; +using System.Collections.Generic; + +namespace AutoGenMapperGenerator; + +public static partial class BuildAutoMapClass +{ + internal static MethodBuilder GenerateExtensionMethod(MapperContext mapperContext, MapperTarget context) + { + var method = (IMethodSymbol)mapperContext.TargetSymbol; + var sourceName = method.Parameters[0].Name; + var statements = BuildMapToMethodStatements(TARGET_OBJECT, sourceName, mapperContext.SourceType, context, CustomActionHandler); + + var builder = MethodBuilder.Default + .Partial(method) + .AddGeneratedCodeAttribute(typeof(AutoMapperGenerator)) + .AddBody([.. statements]); + + return builder; + + + IEnumerable CustomActionHandler() + { + if (method.Parameters.Length > 1 && method.Parameters[1].Type.Name.Contains("Action")) + { + var p2 = method.Parameters[1]; + var ext = p2.Type as INamedTypeSymbol; + if (ext?.DelegateInvokeMethod is { } custom && custom.Parameters.Length == 2) + { + if (EqualityComparer.Default.Equals(custom.Parameters[0].Type, mapperContext.SourceType) + && EqualityComparer.Default.Equals(custom.Parameters[1].Type, context.TargetType)) + { + yield return IfStatement.Default.If($"{p2.Name} is not null").AddStatement($"{p2.Name}.Invoke({sourceName}, {TARGET_OBJECT})"); + } + else if (EqualityComparer.Default.Equals(custom.Parameters[1].Type, mapperContext.SourceType) + && EqualityComparer.Default.Equals(custom.Parameters[0].Type, context.TargetType)) + { + yield return IfStatement.Default.If($"{p2.Name} is not null").AddStatement($"{p2.Name}.Invoke({TARGET_OBJECT}, {sourceName})"); + } + } + } + } + } +} diff --git a/src/AutoGenMapper.Roslyn/BuildAutoMapClass.MapFrom.cs b/src/AutoGenMapper.Roslyn/BuildAutoMapClass.MapFrom.cs index 3cc2d67..b117373 100644 --- a/src/AutoGenMapper.Roslyn/BuildAutoMapClass.MapFrom.cs +++ b/src/AutoGenMapper.Roslyn/BuildAutoMapClass.MapFrom.cs @@ -8,11 +8,11 @@ namespace AutoGenMapperGenerator; public static partial class BuildAutoMapClass { - internal static MethodBuilder GenerateMapFromMethod(GenMapperContext context) + internal static MethodBuilder GenerateMapFromMethod(INamedTypeSymbol sourceType, MapperTarget context) { var statements = new List(); - const string SOURCE_OBJECT = "_target_gen"; - + const string SOURCE_OBJECT = "_source_gen"; + //const string TARGET_OBJECT = "_target_gen"; foreach (var item in context.Maps) { if (item.MappingType == MappingType.SingleToSingle) @@ -25,7 +25,7 @@ internal static MethodBuilder GenerateMapFromMethod(GenMapperContext context) else if (item.MappingType == MappingType.MultiToSingle) { if (!item.CanReverse) continue; - var invoker = item.ReverseBy!.TryGetMethodInvoker() ?? "this"; + var invoker = item.ReverseBy!.TryGetMethodInvoker(sourceType); var sourceParam = item.TargetProp[0].Name; var tempArray = $"_{sourceParam}_arr_gen"; var line = $"var {tempArray} = {invoker}.{item.ReverseBy!.Name}({string.Join(", ", $"{SOURCE_OBJECT}.{sourceParam}")})"; @@ -41,7 +41,7 @@ internal static MethodBuilder GenerateMapFromMethod(GenMapperContext context) else if (item.MappingType == MappingType.SingleToMulti) { if (!item.CanReverse) continue; - var invoker = item.ReverseBy!.TryGetMethodInvoker() ?? "this"; + var invoker = item.ReverseBy!.TryGetMethodInvoker(sourceType); var targetName = item.SourceName.First(); var pnames = string.Join(", ", item.TargetName.Select(s => $"{SOURCE_OBJECT}.{s}")); var line = $"this.{targetName} = {invoker}.{item.ReverseBy!.Name}({pnames})"; @@ -80,7 +80,7 @@ Statement HandleReverseSingleToSingle(MapInfo item, out Statement? extra) var sourceElement = sp.Type.GetElementType(); var targetElement = tp.Type.GetElementType(); var fin = sp.Type is IArrayTypeSymbol ? "ToArray()" : "ToList()"; - if (sourceElement.HasAttribute(AutoMapperGenerator.GenMapperAttributeFullName)) + if (sourceElement.HasAttribute(Helper.GenMapperAttributeFullName)) { var na = sp.Type.NullableAnnotation == NullableAnnotation.Annotated ? "?" : ""; var localFuncName = $"Map_{sourceElement.Name}_From_{targetElement.Name}"; @@ -138,7 +138,7 @@ Statement HandleReverseSingleToSingle(MapInfo item, out Statement? extra) else { line = $""" - throw new AutoGenMapperGenerator.AutoGenMapperException("{context.SourceType.ToDisplayString()}.{sp.Name}和{context.TargetType.ToDisplayString()}.{tp.Name}尝试自动类型转换失败,请自定义{sp.Type.ToDisplayString()}和{tp.Type.ToDisplayString()}之间的转换") + throw new AutoGenMapperGenerator.AutoGenMapperException("{sourceType.ToDisplayString()}.{sp.Name}和{context.TargetType.ToDisplayString()}.{tp.Name}尝试自动类型转换失败,请自定义{sp.Type.ToDisplayString()}和{tp.Type.ToDisplayString()}之间的转换") """; } } diff --git a/src/AutoGenMapper.Roslyn/BuildAutoMapClass.cs b/src/AutoGenMapper.Roslyn/BuildAutoMapClass.cs index 022f25f..52f959d 100644 --- a/src/AutoGenMapper.Roslyn/BuildAutoMapClass.cs +++ b/src/AutoGenMapper.Roslyn/BuildAutoMapClass.cs @@ -11,12 +11,12 @@ namespace AutoGenMapperGenerator; public static partial class BuildAutoMapClass { - internal static IEnumerable GenerateInterfaceMethod(GenMapperContext[] contexts) + internal static IEnumerable GenerateInterfaceMethod(List contexts) { #region map to method { var statements = new List(); - if (contexts.Length == 1) + if (contexts.Count == 1) { statements.Add($"return MapTo{contexts[0].TargetType!.Name}()"); } @@ -44,7 +44,7 @@ internal static IEnumerable GenerateInterfaceMethod(GenMapperCont #region map from method { var statements = new List(); - if (contexts.Length == 1) + if (contexts.Count == 1) { var ctx = contexts[0]; statements.Add(IfStatement.Default.If($"value is {ctx.TargetType.ToDisplayString()} source").AddStatement($"MapFrom{contexts[0].TargetType.Name}(source)")); @@ -76,21 +76,25 @@ internal static IEnumerable GenerateInterfaceMethod(GenMapperCont } static bool IsMapableObject(this IPropertySymbol target) { - return target.Type.HasAttribute(AutoMapperGenerator.GenMapperAttributeFullName) == true; + return target.Type.HasAttribute(Helper.GenMapperAttributeFullName) == true; } - static string? TryGetMethodInvoker(this IMethodSymbol method) + static string TryGetMethodInvoker(this IMethodSymbol method, INamedTypeSymbol source) { - return method.IsStatic ? - method.ReceiverType?.ToDisplayString() ?? method.ContainingType?.ToDisplayString() - : null; + //return method.IsStatic ? + // method.ReceiverType?.ToDisplayString() ?? method.ContainingType?.ToDisplayString() + // : null; + return method.ContainingType.ToDisplayString(); } - internal static MethodBuilder GenerateMapToMethod(GenMapperContext context) + private static List BuildMapToMethodStatements(string tarEntityName + , string souEntityName + , INamedTypeSymbol sourceType + , MapperTarget context + , Func>? beforeReturn = null) { - const string TARGET_OBJECT = "_result_gen"; var statements = new List() { - $"var {TARGET_OBJECT} = new {context.TargetType.ToDisplayString()}({string.Join(", ", context.ConstructorParameters)})" + $"var {tarEntityName} = new {context.TargetType.ToDisplayString()}({string.Join(", ", context.ConstructorParameters.Select(n => $"{souEntityName}.{n}"))})" }; foreach (var item in context.Maps) @@ -102,38 +106,36 @@ internal static MethodBuilder GenerateMapToMethod(GenMapperContext context) } else if (item.MappingType == MappingType.MultiToSingle) { - var invoker = item.ForwardBy!.TryGetMethodInvoker() ?? "this"; + var invoker = item.ForwardBy!.TryGetMethodInvoker(sourceType); var targetName = item.TargetName.First(); - var pnames = string.Join(", ", item.SourceName.Select(s => $"this.{s}")); - var line = $"{TARGET_OBJECT}.{targetName} = {invoker}.{item.ForwardBy!.Name}({pnames})"; + var pnames = string.Join(", ", item.SourceName.Select(s => $"{souEntityName}.{s}")); + var line = $"{tarEntityName}.{targetName} = {invoker}.{item.ForwardBy!.Name}({pnames})"; statements.Add(line); } else if (item.MappingType == MappingType.SingleToMulti) { - var invoker = item.ForwardBy!.TryGetMethodInvoker() ?? "this"; + var invoker = item.ForwardBy!.TryGetMethodInvoker(sourceType); var sourceParam = item.SourceName[0]; var tempArray = $"_{sourceParam}_arr_gen"; - var line = $"var {tempArray} = {invoker}.{item.ForwardBy!.Name}({string.Join(", ", $"this.{sourceParam}")})"; + var line = $"var {tempArray} = {invoker}.{item.ForwardBy!.Name}({string.Join(", ", $"{souEntityName}.{sourceParam}")})"; statements.Add(line); //var checkArrResult = IfStatement.Default.If($"{tempArray} is not null"); for (int i = 0; i < item.TargetProp.Count; i++) { var tar = item.TargetProp[i]; - statements.Add($"{TARGET_OBJECT}.{tar.Name} = {tempArray}.Item{i + 1}"); + statements.Add($"{tarEntityName}.{tar.Name} = {tempArray}.Item{i + 1}"); } //statements.Add(checkArrResult); } } + if (beforeReturn is not null) + { + var sss = beforeReturn(); + statements.AddRange(sss); + } + statements.Add($"return {tarEntityName};"); - statements.Add($"return {TARGET_OBJECT};"); - - var builder = MethodBuilder.Default - .MethodName($"MapTo{context.TargetType.Name}") - .ReturnType(context.TargetType.ToDisplayString()) - .AddGeneratedCodeAttribute(typeof(AutoMapperGenerator)) - .AddBody([.. statements]); - - return builder; + return statements; Statement HandleForwardSingleToSingle(MapInfo item) { @@ -142,9 +144,9 @@ Statement HandleForwardSingleToSingle(MapInfo item) IPropertySymbol tp = item.TargetProp.First(); if (item.ForwardBy is not null) { - var invoker = item.ForwardBy.IsStatic ? item.ForwardBy.ReceiverType?.ToDisplayString() ?? item.ForwardBy.ContainingType?.ToDisplayString() : "this"; + var invoker = item.ForwardBy.TryGetMethodInvoker(sourceType); // 使用自定义映射 - line = $"{TARGET_OBJECT}.{tp.Name} = {invoker}.{item.ForwardBy.Name}(this.{sp.Name})"; + line = $"{tarEntityName}.{tp.Name} = {invoker}.{item.ForwardBy.Name}(this.{sp.Name})"; } else { @@ -157,16 +159,16 @@ Statement HandleForwardSingleToSingle(MapInfo item) var sourceElement = sp.Type.GetElementType(); var targetElement = tp.Type.GetElementType(); var fin = sp.Type is IArrayTypeSymbol ? "ToArray()" : "ToList()"; - if (sourceElement.HasAttribute(AutoMapperGenerator.GenMapperAttributeFullName)) + if (sourceElement.HasAttribute(Helper.GenMapperAttributeFullName)) { var na = sp.Type.NullableAnnotation == NullableAnnotation.Annotated ? "?" : ""; line = $""" -{TARGET_OBJECT}.{tp.Name} = this.{sp.Name}{na}.Where(i => i is not null).Select(i => i.MapTo<{targetElement.ToDisplayString()}>("{targetElement.MetadataName}")).{fin} +{tarEntityName}.{tp.Name} = {souEntityName}.{sp.Name}{na}.Where(i => i is not null).Select(i => i.MapTo<{targetElement.ToDisplayString()}>("{targetElement.MetadataName}")).{fin} """; } else { - line = $"{TARGET_OBJECT}.{tp.Name} = this.{sp.Name}"; + line = $"{tarEntityName}.{tp.Name} = {souEntityName}.{sp.Name}"; } } else if (sp.Type.TypeKind == TypeKind.Class && sp.Type.SpecialType == SpecialType.None) @@ -176,37 +178,37 @@ Statement HandleForwardSingleToSingle(MapInfo item) { var na = sp.Type.NullableAnnotation == NullableAnnotation.Annotated ? "?" : ""; line = $""" -{TARGET_OBJECT}.{tp.Name} = this.{sp.Name}{na}.MapTo<{tp.Type.ToDisplayString()}>("{tp.Type.MetadataName}") +{tarEntityName}.{tp.Name} = {souEntityName}.{sp.Name}{na}.MapTo<{tp.Type.ToDisplayString()}>("{tp.Type.MetadataName}") """; } else { - line = $"{TARGET_OBJECT}.{tp.Name} = this.{sp.Name}"; + line = $"{tarEntityName}.{tp.Name} = {souEntityName}.{sp.Name}"; } } else { if (EqualityComparer.Default.Equals(sp.Type, tp.Type)) { - line = $"{TARGET_OBJECT}.{tp.Name} = this.{sp.Name}"; + line = $"{tarEntityName}.{tp.Name} = {souEntityName}.{sp.Name}"; } else { // 处理类型转换 if (tp.Type.SpecialType == SpecialType.System_String) { - line = $"{TARGET_OBJECT}.{tp.Name} = this.{sp.Name}.ToString()"; + line = $"{tarEntityName}.{tp.Name} = {souEntityName}.{sp.Name}.ToString()"; } else if (tp.Type.GetMembers().FirstOrDefault(m => m.Name == "TryParse") is IMethodSymbol tryParse && sp.Type.SpecialType == SpecialType.System_String) { - line = IfStatement.Default.If($"{tp.Type.ToDisplayString()}.{tryParse.Name}(this.{sp.Name}.ToString(), out var _{tp.Name}_out_gen)") - .AddStatement($"{TARGET_OBJECT}.{tp.Name} = _{tp.Name}_out_gen"); + line = IfStatement.Default.If($"{tp.Type.ToDisplayString()}.{tryParse.Name}({souEntityName}.{sp.Name}.ToString(), out var _{tp.Name}_out_gen)") + .AddStatement($"{tarEntityName}.{tp.Name} = _{tp.Name}_out_gen"); } else { line = $""" - throw new AutoGenMapperGenerator.AutoGenMapperException("{context.SourceType.ToDisplayString()}.{sp.Name}和{context.TargetType.ToDisplayString()}.{tp.Name}尝试自动类型转换失败,请自定义{sp.Type.ToDisplayString()}和{tp.Type.ToDisplayString()}之间的转换") + throw new AutoGenMapperGenerator.AutoGenMapperException("{sourceType.ToDisplayString()}.{sp.Name}和{context.TargetType.ToDisplayString()}.{tp.Name}尝试自动类型转换失败,请自定义{sp.Type.ToDisplayString()}和{tp.Type.ToDisplayString()}之间的转换") """; } } @@ -215,5 +217,22 @@ Statement HandleForwardSingleToSingle(MapInfo item) return line; } + + } + + const string TARGET_OBJECT = "_result_gen"; + internal static MethodBuilder GenerateMapToMethod(INamedTypeSymbol sourceType, MapperTarget context) + { + var statements = BuildMapToMethodStatements(TARGET_OBJECT, "this", sourceType, context); + var builder = MethodBuilder.Default + .MethodName($"MapTo{context.TargetType.Name}") + //.Modifiers("private static") + //.AddParameter($"{sourceType.ToDisplayString()} {SOURCE_OBJECT}") + .ReturnType(context.TargetType.ToDisplayString()) + .AddGeneratedCodeAttribute(typeof(AutoMapperGenerator)) + .AddBody([.. statements]); + + return builder; + } } \ No newline at end of file diff --git a/src/AutoGenMapper.Roslyn/DiagnosticDefinitions.cs b/src/AutoGenMapper.Roslyn/DiagnosticDefinitions.cs index 7d3d235..8476852 100644 --- a/src/AutoGenMapper.Roslyn/DiagnosticDefinitions.cs +++ b/src/AutoGenMapper.Roslyn/DiagnosticDefinitions.cs @@ -35,27 +35,27 @@ public static Diagnostic AGM00002(Location? location) => Diagnostic.Create(new D isEnabledByDefault: true), location); /// - /// 无法找到自定义的映射方法,请检查是否定义在自己的类中 + /// 无法找到自定义的映射方法,请检查是否定义在自己的类中,是否是静态方法 /// /// /// public static Diagnostic AGM00003(Location? location) => Diagnostic.Create(new DiagnosticDescriptor( id: "AGM00003", - title: "无法找到自定义的映射方法,请检查是否定义在自己的类中", - messageFormat: "无法找到自定义的映射方法,请检查是否定义在自己的类中", + title: "无法找到自定义的映射方法,请检查是否定义在自己的类中,是否是静态方法", + messageFormat: "无法找到自定义的映射方法,请检查是否定义在自己的类中,是否是静态方法", category: typeof(AutoMapperGenerator).FullName!, defaultSeverity: DiagnosticSeverity.Error, isEnabledByDefault: true), location); /// - /// 该MapBetweenAttribute的构造只能用于属性 + /// 该MapBetweenAttribute的构造只能用于属性/方法 /// /// /// public static Diagnostic AGM00004(Location? location) => Diagnostic.Create(new DiagnosticDescriptor( id: "AGM00004", - title: "该MapBetweenAttribute的构造只能用于属性", - messageFormat: "该MapBetweenAttribute的构造只能用于属性", + title: "该MapBetweenAttribute的构造只能用于属性/方法", + messageFormat: "该MapBetweenAttribute的构造只能用于属性/方法", category: typeof(AutoMapperGenerator).FullName!, defaultSeverity: DiagnosticSeverity.Error, isEnabledByDefault: true), location); @@ -87,14 +87,14 @@ public static Diagnostic AGM00006(Location? location) => Diagnostic.Create(new D isEnabledByDefault: true), location); /// - /// 自定义映射处理方法的参数个数或类型不匹配 + /// 未找到自定义映射处理方法或者参数个数或类型不匹配 /// /// /// public static Diagnostic AGM00007(Location? location) => Diagnostic.Create(new DiagnosticDescriptor( id: "AGM00007", - title: "自定义映射处理方法的参数个数或类型不匹配", - messageFormat: "自定义映射处理方法的参数个数或类型不匹配", + title: "未找到自定义映射处理方法或者参数个数或类型不匹配", + messageFormat: "未找到自定义映射处理方法或者参数个数或类型不匹配", category: typeof(AutoMapperGenerator).FullName!, defaultSeverity: DiagnosticSeverity.Error, isEnabledByDefault: true), location); @@ -161,7 +161,7 @@ public static Diagnostic AGM00012(Location? location) => Diagnostic.Create(new D category: typeof(AutoMapperGenerator).FullName!, defaultSeverity: DiagnosticSeverity.Warning, isEnabledByDefault: true), location); - + /// /// 自动匹配构造函数参数失败 /// diff --git a/src/AutoGenMapper.Roslyn/GenMapperContext.cs b/src/AutoGenMapper.Roslyn/GenMapperContext.cs index 9e01e41..6a17431 100644 --- a/src/AutoGenMapper.Roslyn/GenMapperContext.cs +++ b/src/AutoGenMapper.Roslyn/GenMapperContext.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using Microsoft.CodeAnalysis; namespace AutoGenMapperGenerator; @@ -11,14 +12,15 @@ public enum MappingType public enum DeclarePosition { + Property, Class, - Property + Method, } public class MapInfo { - public List SourceName { get; set; } = []; - public List TargetName { get; set; } = []; + public string[] SourceName { get; set; } = []; + public string[] TargetName { get; set; } = []; public List SourceProp { get; set; } = []; public List TargetProp { get; set; } = []; public IMethodSymbol? ForwardBy { get; set; } @@ -28,6 +30,8 @@ public class MapInfo public DeclarePosition Position { get; set; } public bool CanReverse { get; set; } } + +[Obsolete] public class GenMapperContext { public INamedTypeSymbol SourceType { get; set; } = default!; @@ -38,4 +42,4 @@ public class GenMapperContext //public List Froms { get; set; } = []; //public List Tos { get; set; } = []; public List Maps { get; set; } = []; -} \ No newline at end of file +} diff --git a/src/AutoGenMapper.Roslyn/Helper.Between.cs b/src/AutoGenMapper.Roslyn/Helper.Between.cs new file mode 100644 index 0000000..fa3a6ce --- /dev/null +++ b/src/AutoGenMapper.Roslyn/Helper.Between.cs @@ -0,0 +1,168 @@ +using Generators.Shared; +using Microsoft.CodeAnalysis; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace AutoGenMapperGenerator; + +internal partial class Helper +{ + public static IEnumerable CollectSpecificBetweenInfo(ISymbol symbol) + { + var pos = DeclarePosition.Property; + if (symbol is INamedTypeSymbol) + { + pos = DeclarePosition.Class; + } + else if (symbol is IMethodSymbol) + { + pos = DeclarePosition.Method; + } + foreach (var item in symbol.GetAttributes(GenMapBetweenAttributeFullName)) + { + MapBetweenInfo info; + if (pos == DeclarePosition.Class) + { + info = HandleClassDeclare(item); + } + else if (pos == DeclarePosition.Method && symbol is IMethodSymbol m) + { + info = HandleMethodDeclare(item, m); + } + else if (pos == DeclarePosition.Property && symbol is IPropertySymbol p) + { + info = HandlePropertyDeclare(item, p); + } + else + { + throw new InvalidOperationException(); + } + item.GetNamedValue("By", out var by); + info.By = by?.ToString(); + yield return info; + } + if (symbol is INamedTypeSymbol classSymbol) + { + foreach (var m in HandleClassProperties(classSymbol)) + { + yield return m; + } + } + else if (symbol is IMethodSymbol method) + { + classSymbol = (INamedTypeSymbol)method.Parameters[0].Type; + foreach (var m in HandleClassProperties(classSymbol)) + { + yield return m; + } + } + + + static MapBetweenInfo HandleClassDeclare(AttributeData a) + { + + if (a.ConstructorArguments.Length != 3) + { + throw new InvalidOperationException(); + } + //if (a.GetConstructorValue(0, out var tar) && tar is INamedTypeSymbol symbol1) + //{ + // target = symbol1; + //} + a.GetConstructorValue(0, out var tar); + INamedTypeSymbol target = (INamedTypeSymbol)tar!; + + var mapType = GetMapTypeAndValues(a, 1, out var sources, out var targets); + return new(target) + { + Position = DeclarePosition.Class, + MapType = mapType, + Sources = sources, + Targets = targets, + }; + } + + static IEnumerable HandleClassProperties(INamedTypeSymbol source) + { + var sourceProperties = source.GetAllMembers(_ => true).Where(i => i.Kind == SymbolKind.Property) + .Cast().ToArray(); + foreach (var sourceProperty in sourceProperties) + { + foreach (var item in CollectSpecificBetweenInfo(sourceProperty)) + { + yield return item; + } + } + } + + static MapBetweenInfo HandleMethodDeclare(AttributeData a, IMethodSymbol method) + { + var mapType = GetMapTypeAndValues(a, 0, out var sources, out var targets); + var (IsTask, HasReturn, ReturnType) = method.GetReturnTypeInfo(); + return new((INamedTypeSymbol)ReturnType) + { + Position = DeclarePosition.Method, + MapType = mapType, + Sources = sources, + Targets = targets, + }; + } + + static MapBetweenInfo HandlePropertyDeclare(AttributeData a, IPropertySymbol prop) + { + if (a.ConstructorArguments.Length != 2) + { + throw new InvalidOperationException(); + } + //if (a.GetConstructorValue(0, out var tar) && tar is INamedTypeSymbol symbol1) + //{ + // target = symbol1; + //} + a.GetConstructorValue(0, out var tar); + a.GetConstructorValue(1, out var t); + return new((INamedTypeSymbol)tar!) + { + Position = DeclarePosition.Property, + MapType = MappingType.SingleToSingle, + Sources = [prop.Name], + Targets = [t!.ToString()], + }; + } + + + } + + private static MappingType GetMapTypeAndValues(AttributeData data, int offset + , out string[] sources + , out string[] targets) + { + var s = data.ConstructorArguments[offset + 0]; + var t = data.ConstructorArguments[offset + 1]; + if (s.Type is IArrayTypeSymbol) + { + data.GetConstructorValues(offset + 0, out var sps); + data.GetConstructorValue(offset + 1, out var tp); + sources = [.. sps.Select(sp => sp!.ToString())]; + targets = [tp!.ToString()]; + return MappingType.MultiToSingle; + } + else if (t.Type is IArrayTypeSymbol) + { + data.GetConstructorValue(offset + 0, out var sp); + data.GetConstructorValues(offset + 1, out var tps); + sources = [sp!.ToString()]; + targets = [.. tps.Select(tp => tp!.ToString())]; + return MappingType.SingleToMulti; + } + else + { + data.GetConstructorValue(offset + 0, out var sp); + data.GetConstructorValue(offset + 1, out var tp); + sources = [sp!.ToString()]; + targets = [tp!.ToString()]; + return MappingType.SingleToSingle; + } + } +} diff --git a/src/AutoGenMapper.Roslyn/Helper.Ctor.cs b/src/AutoGenMapper.Roslyn/Helper.Ctor.cs new file mode 100644 index 0000000..972754d --- /dev/null +++ b/src/AutoGenMapper.Roslyn/Helper.Ctor.cs @@ -0,0 +1,143 @@ +using Generators.Shared; +using Microsoft.CodeAnalysis; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace AutoGenMapperGenerator; + +internal partial class Helper +{ + public static List CollectMapTargets(ISymbol symbol) + { + try + { + List targets = []; + if (symbol is INamedTypeSymbol source) + { + foreach (var a in source.GetAttributes(GenMapperAttributeFullName)) + { + // 获取具名的TargetType属性或者构造函数的第一个参数作为目标类型 + if (!(a.GetNamedValue("TargetType", out var t) && t is INamedTypeSymbol target)) + { + target = (a.ConstructorArguments.FirstOrDefault().Value as INamedTypeSymbol) ?? source; + } + var mapTarget = new MapperTarget(target); + // 处理构造函数 + if (a?.ConstructorArguments.Length > 1) + { + // 指定了构造函数参数 + mapTarget.ConstructorParameters = + [.. a.ConstructorArguments[1].Values.Where(c => c.Value != null).Select(c => c.Value!.ToString())]; + } + else + { + var c = GetSpecificAttribute(source, target, GenMapConstructorAttributeFullName); + if (c?.ConstructorArguments.Length == 2) + { + // 指定了构造函数参数 + mapTarget.ConstructorParameters = + [.. c.ConstructorArguments[1].Values.Where(c => c.Value != null).Select(c => c.Value!.ToString())]; + } + else + { + TrySetDefaultCtor(source, mapTarget); + } + } + + + targets.Add(mapTarget); + } + } + else if (symbol is IMethodSymbol method) + { + var (IsTask, HasReturn, ReturnType) = method.GetReturnTypeInfo(); + var methodSource = (INamedTypeSymbol)method.Parameters[0].Type; + var target = (INamedTypeSymbol)ReturnType; + var mapTarget = new MapperTarget(target); + if (method.GetAttribute(GenMapConstructorAttributeFullName, out var c) && c!.ConstructorArguments.Length == 1) + { + mapTarget.ConstructorParameters = + [.. c.ConstructorArguments[0].Values.Where(c => c.Value != null).Select(c => c.Value!.ToString())]; + } + else + { + TrySetDefaultCtor(methodSource, mapTarget); + } + targets.Add(mapTarget); + } + + return targets; + } + catch (Exception) + { + throw; + } + + static void TrySetDefaultCtor(INamedTypeSymbol source, MapperTarget mapTarget) + { + // 尝试自动获取构造函数参数 + var ctorSymbol = mapTarget.TargetType.GetMethods().FirstOrDefault(m => m.MethodKind == MethodKind.Constructor && m.Parameters.Length > 0); + ctorSymbol ??= mapTarget.TargetType.GetMethods().FirstOrDefault(m => m.MethodKind == MethodKind.Constructor); + if (ctorSymbol is null) + { + throw new Exception("AGM00011"); + } + string[] ps = [.. ctorSymbol.Parameters.Select(p => source.GetAllMembers(_ => true).FirstOrDefault(s => string.Equals(p.Name, s.Name, StringComparison.OrdinalIgnoreCase))?.Name).Where(s => !string.IsNullOrEmpty(s))!]; + if (ps.Length != ctorSymbol.Parameters.Length) + { + throw new Exception("AGM00013"); + } + mapTarget.ConstructorParameters = ps; + } + } + + private static void HandleConstructor(AttributeData? a + , ISymbol entry + , INamedTypeSymbol source + , INamedTypeSymbol target + , MapperTarget mapTarget) + { + if (a?.ConstructorArguments.Length > 1) + { + // 指定了构造函数参数 + mapTarget.ConstructorParameters = + [.. a.ConstructorArguments[1].Values.Where(c => c.Value != null).Select(c => c.Value!.ToString())]; + } + else if (entry is INamedTypeSymbol) + { + var c = GetSpecificAttribute(source, target, GenMapConstructorAttributeFullName); + if (c?.ConstructorArguments.Length == 2) + { + // 指定了构造函数参数 + mapTarget.ConstructorParameters = + [.. c.ConstructorArguments[1].Values.Where(c => c.Value != null).Select(c => c.Value!.ToString())]; + } + } + else if (entry is IMethodSymbol) + { + if (entry.GetAttribute(GenMapConstructorAttributeFullName, out var c) && c!.ConstructorArguments.Length == 1) + { + mapTarget.ConstructorParameters = + [.. c.ConstructorArguments[0].Values.Where(c => c.Value != null).Select(c => c.Value!.ToString())]; + } + } + else + { + // 尝试自动获取构造函数参数 + var ctorSymbol = mapTarget.TargetType.GetMethods().FirstOrDefault(m => m.MethodKind == MethodKind.Constructor && m.Parameters.Length > 0); + ctorSymbol ??= mapTarget.TargetType.GetMethods().FirstOrDefault(m => m.MethodKind == MethodKind.Constructor); + if (ctorSymbol is null) + { + throw new Exception("AGM00011"); + } + string[] ps = [.. ctorSymbol.Parameters.Select(p => source.GetAllMembers(_ => true).FirstOrDefault(s => string.Equals(p.Name, s.Name, StringComparison.OrdinalIgnoreCase))?.Name).Where(s => !string.IsNullOrEmpty(s))!]; + if (ps.Length != ctorSymbol.Parameters.Length) + { + throw new Exception("AGM00013"); + } + mapTarget.ConstructorParameters = ps; + } + } +} diff --git a/src/AutoGenMapper.Roslyn/Helper.cs b/src/AutoGenMapper.Roslyn/Helper.cs new file mode 100644 index 0000000..7130b27 --- /dev/null +++ b/src/AutoGenMapper.Roslyn/Helper.cs @@ -0,0 +1,295 @@ +using Generators.Shared; +using Generators.Shared.Builder; +using Microsoft.CodeAnalysis; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +namespace AutoGenMapperGenerator; + +internal partial class Helper +{ + internal const string GenMapperAttributeFullName = "AutoGenMapperGenerator.GenMapperAttribute"; + internal const string GenMapFromAttributeFullName = "AutoGenMapperGenerator.MapFromAttribute"; + internal const string GenMapToAttributeFullName = "AutoGenMapperGenerator.MapToAttribute"; + internal const string GenMapableInterface = "AutoGenMapperGenerator.IAutoMap"; + internal const string GenMapIgnoreAttribute = "AutoGenMapperGenerator.MapIgnoreAttribute"; + internal const string GenMapBetweenAttributeFullName = "AutoGenMapperGenerator.MapBetweenAttribute"; + internal const string GenMapConstructorAttributeFullName = "AutoGenMapperGenerator.MapConstructorAttribute"; + + public static Diagnostic? CollectMapperContext(MapperContext context + , MapBetweenInfo[] mapBetweens + , Location? location) + { + //var hasDefaultCtor = source.Constructors.Any(c => c.Parameters.Length == 0); + //if (!hasDefaultCtor) + //{ + // return (default, DiagnosticDefinitions.AGM00011(location)); + //} + Diagnostic? error = null; + var source = context.SourceType; + var containType = context.ContainingType; + var sourceProperties = source.GetAllMembers(_ => true).Where(i => i.Kind == SymbolKind.Property) + .Cast().ToArray(); + + foreach (var mapTarget in context.Targets) + { + var target = mapTarget.TargetType; + var targetProperties = target.GetAllMembers(_ => true).Where(i => i.Kind == SymbolKind.Property) + .Cast().ToArray(); + var specificBetweens = mapBetweens.Where(t => EqualityComparer.Default.Equals(target, t.Target)).ToList(); + foreach (var map in specificBetweens) + { + var mapInfo = new MapInfo() + { + Position = map.Position, + MappingType = map.MapType, + SourceName = map.Sources, + TargetName = map.Targets + }; + mapInfo.SourceProp = [.. sourceProperties.Where(p => mapInfo.SourceName.Contains(p.Name))]; + mapInfo.TargetProp = [.. targetProperties.Where(p => mapInfo.TargetName.Contains(p.Name))]; + if (map.By is not null) + { + error = HandleByMethod(containType, map.By, mapInfo, location); + if (error is not null) + { + return error; + } + } + mapTarget.Maps.Add(mapInfo); + } + + error = HandleAutoMapProperties(mapTarget.Maps + , source + , target + , sourceProperties + , targetProperties + , location); + if (error is not null) + { + return error; + } + } + return error; + } + + private static Diagnostic? HandleAutoMapProperties(List mapInfos + , INamedTypeSymbol source + , INamedTypeSymbol target + , IPropertySymbol[] sourceProperties + , IPropertySymbol[] targetProperties + , Location? location) + { + foreach (var sp in sourceProperties) + { + if (sp.IsReadOnly || sp.DeclaredAccessibility != Accessibility.Public) + { + continue; + } + + var mi = new MapInfo() + { + SourceName = [sp.Name], + SourceProp = [sp], + MappingType = MappingType.SingleToSingle + }; + // 属性上可能有多个Attribute,只找出跟当前Target一样的Target的Attribute + var AttrData = GetSpecificAttribute(sp, target, GenMapBetweenAttributeFullName); + if (AttrData is not null) + { + if (AttrData.ConstructorArguments.Length > 2) + { + return DiagnosticDefinitions.AGM00005(location); + } + + AttrData.GetConstructorValue(1, out var tarName); + // 已经在Class上定义了MapBetween,就跳过 + if (mapInfos.Any(m => m.Position == DeclarePosition.Class + && m.SourceName.Contains(sp.Name) + && m.TargetName.Contains(tarName!.ToString()))) + { + continue; + } + + mi.TargetName = [tarName!.ToString()]; + mi.TargetProp = [.. targetProperties.Where(p => mi.TargetName.Contains(p.Name))]; + if (AttrData.GetNamedValue("By", out var by)) + { + var methodName = by!.ToString(); + var error = HandleByMethod(source, methodName, mi, location); + if (error is not null) + { + return error; + } + } + } + else + { + if (sp.HasAttribute(GenMapIgnoreAttribute)) + { + continue; + } + var tp = targetProperties.FirstOrDefault(tp => tp.Name == sp.Name); + if (tp is null) + { + continue; + } + + mi.TargetName = [tp.Name]; + mi.TargetProp = [tp]; + } + + mapInfos.Add(mi); + } + return null; + } + + private static AttributeData? GetSpecificAttribute(ISymbol property + , INamedTypeSymbol matchSymbol + , string fullName) + { + var attrs = property.GetAttributes(fullName); + foreach (var item in attrs) + { + item.GetConstructorValue(0, out var t); + var symbol = (INamedTypeSymbol)t!; + if (EqualityComparer.Default.Equals(symbol, matchSymbol)) + { + return item; + } + } + + return null; + } + + private static Diagnostic? HandleByMethod(ISymbol declared + , string methodName + , MapInfo mi + , Location? location) + { + var declaredType = declared switch + { + INamedTypeSymbol nts => nts, + IMethodSymbol ms => ms.ContainingType, + _ => throw new InvalidOperationException("Unsupported symbol type for 'declared'.") + }; + var methods = declaredType.GetAllMembers(_ => true).Where(i => i.Kind == SymbolKind.Method && i.IsStatic && i.Name == methodName).Cast().ToArray(); + if (methods.Length == 0) + { + return DiagnosticDefinitions.AGM00003(location); + } + + var spts = mi.SourceProp.Select(p => p.Type).ToArray(); + var tpts = mi.TargetProp.Select(p => p.Type).ToArray(); + + #region 正向映射 + + var forward = methods.FirstOrDefault(m => CheckParameters(m, spts)); + if (!CheckMappingMethodReturnType(forward + , location + , spts + , tpts + , out var error)) + { + return error; + } + + mi.ForwardBy = forward; + + #endregion + + #region 反向映射 + + var reverse = methods.FirstOrDefault(m => CheckParameters(m, tpts)); + if (CheckMappingMethodReturnType(reverse + , location + , tpts + , spts + , out error)) + { + mi.CanReverse = true; + mi.ReverseBy = reverse; + } + else if (reverse != null) + { + return error; + } + + #endregion + + return null; + } + + private static bool CheckParameters(IMethodSymbol method, ITypeSymbol[] paramTypes) + { + // 检查参数类型 + if (method.Parameters.Length != paramTypes.Length) return false; + for (int i = 0; i < method.Parameters.Length; i++) + { + var mpt = method.Parameters[i].Type; + if (!EqualityComparer.Default.Equals(mpt, paramTypes[i])) + { + return false; + } + } + return true; + } + + private static bool CheckMappingMethodReturnType(IMethodSymbol? method + , Location? location + , ITypeSymbol[] paramTypes + , ITypeSymbol[] returnTypes + , out Diagnostic? error) + { + if (method is null) + { + // 自定义处理方法的参数个数不匹配 + error = DiagnosticDefinitions.AGM00007(location); + return false; + } + location = method.Locations.FirstOrDefault(); + // 检查返回值 + if (returnTypes.Length > 1) + { + // 一对多的情况,返回值需要是object[]类型 + if (!method.ReturnType.IsTupleType) + { + error = DiagnosticDefinitions.AGM00009(location); + return false; + } + if (method.ReturnType is not INamedTypeSymbol tuple) + { + error = DiagnosticDefinitions.AGM00009(location); + return false; + } + var tupleElement = tuple.TupleElements; + if (tupleElement.Length != returnTypes.Length) + { + error = DiagnosticDefinitions.AGM00010(location); + return false; + } + for (int i = 0; i < tupleElement.Length; i++) + { + var te = tupleElement[i].Type; + if (!EqualityComparer.Default.Equals(te, returnTypes[i])) + { + error = DiagnosticDefinitions.AGM00008(location); + return false; + } + } + } + else + { + var returnType = returnTypes[0]; + if (!EqualityComparer.Default.Equals(method.ReturnType, returnType)) + { + error = DiagnosticDefinitions.AGM00009(location); + return false; + } + } + + error = null; + return true; + } +} diff --git a/src/AutoGenMapper.Roslyn/MapperContext.cs b/src/AutoGenMapper.Roslyn/MapperContext.cs new file mode 100644 index 0000000..00a6ca0 --- /dev/null +++ b/src/AutoGenMapper.Roslyn/MapperContext.cs @@ -0,0 +1,51 @@ +using Microsoft.CodeAnalysis; +using System.Collections.Generic; +namespace AutoGenMapperGenerator; + +public class MapperContext +{ + public MapperContext(ISymbol targetSymbol) + { + TargetSymbol = targetSymbol; + if (targetSymbol is INamedTypeSymbol namedTypeSymbol) + { + ContainingType = namedTypeSymbol; + SourceType = namedTypeSymbol; + } + else if (targetSymbol is IMethodSymbol methodSymbol) + { + ContainingType = methodSymbol.ContainingType; + SourceType = (INamedTypeSymbol)methodSymbol.Parameters[0].Type; + } + else + throw new System.InvalidOperationException(); + } + public ISymbol TargetSymbol { get; } + public INamedTypeSymbol ContainingType { get; } + public INamedTypeSymbol SourceType { get; } + public List Targets { get; set; } = []; +} + +public readonly struct MapperGenerateContext(INamedTypeSymbol source, MapperTarget target, string objectName) +{ + public INamedTypeSymbol Source { get; } = source; + public MapperTarget Target { get; } = target; + public string ObjectName { get; } = objectName; +} + +public class MapperTarget(INamedTypeSymbol target) +{ + public INamedTypeSymbol TargetType { get; } = target; + public string[] ConstructorParameters { get; set; } = []; + public List Maps { get; set; } = []; +} + +public class MapBetweenInfo(INamedTypeSymbol target) +{ + public DeclarePosition Position { get; set; } + public MappingType MapType { get; set; } + public INamedTypeSymbol Target { get; } = target; + public string[] Sources { get; set; } = []; + public string[] Targets { get; set; } = []; + public string? By { get; set; } +} \ No newline at end of file diff --git a/src/AutoGenMapperGenerator/Attributes.cs b/src/AutoGenMapperGenerator/Attributes.cs index f419f83..fa708a3 100644 --- a/src/AutoGenMapperGenerator/Attributes.cs +++ b/src/AutoGenMapperGenerator/Attributes.cs @@ -3,56 +3,6 @@ namespace AutoGenMapperGenerator; -/// -/// 生成MapToXXX方法 -/// -[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = true)] -public class GenMapperAttribute : Attribute -{ - private readonly string[]? values; - /// - /// 目标类型,默认是自身 - /// - public Type? TargetType { get; set; } - - /// - /// 默认构造 - /// - public GenMapperAttribute() - { - - } - - /// - /// 指定目标类型 - /// - /// 目标类型 - public GenMapperAttribute(Type targetType) - { - TargetType = targetType; - } - - /// - /// 指定目标类型,并且指定构造函数参数 - /// - /// 目标类型 - /// 构造参数 - public GenMapperAttribute(Type targetType, params string[] values) - { - this.values = values; - TargetType = targetType; - } -} - -/// -/// 忽略映射 -/// -[AttributeUsage(AttributeTargets.Property, Inherited = false, AllowMultiple = false)] -public class MapIgnoreAttribute : Attribute -{ - -} - /// /// /// @@ -98,98 +48,3 @@ public class MapFromAttribute : Attribute /// public string? By { get; set; } } - -/// -/// 自定义映射关系 -/// 定义在类上时,需要使用3个参数的构造函数,并且优先级高于定义在属性上的,如果存在单对多或者多对单的映射,建议定义在类上 -/// 定义在属性上,需要使用2个参数的构造函数,用于单对单的映射 -/// -[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property, Inherited = false, AllowMultiple = true)] -public class MapBetweenAttribute : Attribute -{ - private readonly Type? targetType; - private readonly string[]? sources; - private readonly string[]? targets; - private readonly string? source; - private readonly string? target; - - /// - /// 自定义映射方法 - /// 单对单 - /// - /// - /// - /// 多对单 - /// - /// - /// - /// 单对多 - /// - /// - /// - /// 使用作为参数 - /// - /// context)]]> - /// - /// - public string? By { get; set; } - - /// - /// 自定义属性映射 - /// - /// - public MapBetweenAttribute(Type targetType) - { - this.targetType = targetType; - } - - /// - /// 自定义属性映射 - /// - /// 目标类型 - /// 目标类型的属性 - public MapBetweenAttribute(Type targetType, string target) - { - this.targetType = targetType; - this.target = target; - } - - /// - /// 自定义属性映射 - /// - /// - /// - /// - public MapBetweenAttribute(Type targetType, string source, string target) - { - this.targetType = targetType; - this.source = source; - this.target = target; - } - - /// - /// 指定多对单 - /// - /// 目标类型 - /// 自身的属性 - /// 目标类型的属性 - public MapBetweenAttribute(Type targetType, string[] sources, string target) - { - this.targetType = targetType; - this.sources = sources; - this.target = target; - } - - /// - /// 指定单对多 - /// - /// 目标类型 - /// 自身的属性 - /// 目标类型的属性 - public MapBetweenAttribute(Type targetType, string source, string[] targets) - { - this.targetType = targetType; - this.source = source; - this.targets = targets; - } -} diff --git a/src/AutoGenMapperGenerator/Attributes/GenMapperAttribute.cs b/src/AutoGenMapperGenerator/Attributes/GenMapperAttribute.cs new file mode 100644 index 0000000..f7feb96 --- /dev/null +++ b/src/AutoGenMapperGenerator/Attributes/GenMapperAttribute.cs @@ -0,0 +1,44 @@ +using System; + +namespace AutoGenMapperGenerator; + +/// +/// 实现, 生成MapToXXX方法 +/// +[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = false, AllowMultiple = true)] +public class GenMapperAttribute : Attribute +{ + private readonly string[]? values; + /// + /// 目标类型,默认是自身 + /// + public Type? TargetType { get; set; } + + /// + /// 默认构造 + /// + public GenMapperAttribute() + { + + } + + /// + /// 指定目标类型 + /// + /// 目标类型 + public GenMapperAttribute(Type targetType) + { + TargetType = targetType; + } + + /// + /// 指定目标类型,并且指定构造函数参数 + /// + /// 目标类型 + /// 构造参数 + public GenMapperAttribute(Type targetType, params string[] values) + { + this.values = values; + TargetType = targetType; + } +} diff --git a/src/AutoGenMapperGenerator/Attributes/MapBetweenAttribute.cs b/src/AutoGenMapperGenerator/Attributes/MapBetweenAttribute.cs new file mode 100644 index 0000000..588b6ad --- /dev/null +++ b/src/AutoGenMapperGenerator/Attributes/MapBetweenAttribute.cs @@ -0,0 +1,147 @@ +using System; + +namespace AutoGenMapperGenerator; + +/// +/// 自定义映射关系 +/// 定义在类上时,需要使用3个参数的构造函数,并且优先级高于定义在属性上的,如果存在单对多或者多对单的映射,建议定义在类上 +/// 定义在属性上,需要使用2个参数的构造函数,用于单对单的映射 +/// 定义在方法上,需要使用2个参数的构造函数 +/// +[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Method, Inherited = false, AllowMultiple = true)] +public class MapBetweenAttribute : Attribute +{ + /// + /// 目标类型 + /// + private Type? TargetType { get; set; } + /// + /// 多对单的源属性 + /// + private string[]? Sources { get; set; } + /// + /// 单对多的目标属性 + /// + private string[]? Targets { get; set; } + /// + /// 单对单的源属性 + /// + private string? Source { get; set; } + /// + /// 单对单的目标属性 + /// + private string? Target { get; set; } + + /// + /// 自定义映射方法,必须是静态方法 + /// 单对单 + /// + /// + /// + /// 多对单 + /// + /// + /// + /// 单对多 + /// + /// + /// + /// + // 使用作为参数 + // + // context)]]> + // + public string? By { get; set; } + + /// + /// (属性适用)自定义属性映射 + /// + /// 目标类型 + /// 目标类型的属性 + public MapBetweenAttribute(Type targetType, string target) + { + TargetType = targetType; + Target = target; + } + + ///// + ///// (属性和类适用)自定义属性映射 + ///// + ///// + //public MapBetweenAttribute(Type targetType) + //{ + // TargetType = targetType; + //} + + /// + /// (类适用)自定义属性映射 + /// + /// + /// + /// + public MapBetweenAttribute(Type targetType, string source, string target) + { + TargetType = targetType; + Source = source; + Target = target; + } + + /// + /// (类适用)自定义属性映射, 多对单 + /// + /// 目标类型 + /// 自身的属性 + /// 目标类型的属性 + public MapBetweenAttribute(Type targetType, string[] sources, string target) + { + TargetType = targetType; + Sources = sources; + Target = target; + } + + /// + /// (类适用)自定义属性映射, 单对多 + /// + /// 目标类型 + /// 自身的属性 + /// 目标类型的属性 + public MapBetweenAttribute(Type targetType, string source, string[] targets) + { + TargetType = targetType; + Source = source; + Targets = targets; + } + + /// + /// (方法适用)自定义属性映射 + /// + /// + /// + public MapBetweenAttribute(string source, string target) + { + Source = source; + Target = target; + } + + /// + /// (方法适用)自定义属性映射, 多对单 + /// + /// + /// + public MapBetweenAttribute(string[] sources, string target) + { + Sources = sources; + Target = target; + } + + /// + /// (方法适用)自定义属性映射, 单对多 + /// + /// + /// + public MapBetweenAttribute(string source, string[] targets) + { + Source = source; + Targets = targets; + } +} diff --git a/src/AutoGenMapperGenerator/Attributes/MapConstructorAttribute.cs b/src/AutoGenMapperGenerator/Attributes/MapConstructorAttribute.cs new file mode 100644 index 0000000..db39e0f --- /dev/null +++ b/src/AutoGenMapperGenerator/Attributes/MapConstructorAttribute.cs @@ -0,0 +1,32 @@ +using System; + +namespace AutoGenMapperGenerator; + +/// +/// 指定构造函数参数 +/// +[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] +public class MapConstructorAttribute : Attribute +{ + private Type? targetType; + private string[] parameters; + /// + /// 用在类上的 + /// + /// + /// + public MapConstructorAttribute(Type targetType, params string[] parameters) + { + this.targetType = targetType; + this.parameters = parameters; + } + + /// + /// 用在方法上的 + /// + /// + public MapConstructorAttribute(params string[] parameters) + { + this.parameters = parameters; + } +} \ No newline at end of file diff --git a/src/AutoGenMapperGenerator/Attributes/MapIgnoreAttribute.cs b/src/AutoGenMapperGenerator/Attributes/MapIgnoreAttribute.cs new file mode 100644 index 0000000..d6d1ee6 --- /dev/null +++ b/src/AutoGenMapperGenerator/Attributes/MapIgnoreAttribute.cs @@ -0,0 +1,12 @@ +using System; + +namespace AutoGenMapperGenerator; + +/// +/// 忽略映射 +/// +[AttributeUsage(AttributeTargets.Property, Inherited = false, AllowMultiple = false)] +public class MapIgnoreAttribute : Attribute +{ + +} diff --git a/src/AutoGenMapperGenerator/AutoGenMapperGenerator.csproj b/src/AutoGenMapperGenerator/AutoGenMapperGenerator.csproj index 63d7814..f8a1212 100644 --- a/src/AutoGenMapperGenerator/AutoGenMapperGenerator.csproj +++ b/src/AutoGenMapperGenerator/AutoGenMapperGenerator.csproj @@ -15,7 +15,7 @@ True true - + diff --git a/src/AutoGenMapperGenerator/GMapper.cs b/src/AutoGenMapperGenerator/GMapper.cs new file mode 100644 index 0000000..6a19609 --- /dev/null +++ b/src/AutoGenMapperGenerator/GMapper.cs @@ -0,0 +1,33 @@ +namespace AutoGenMapperGenerator; + +/// +/// G -> Generator +/// +public static class GMapper +{ + /// + /// 首先检查是否,如果是,则直接调用接口 + /// 否侧,回退到表达式动态创建映射 + /// + /// + /// + /// + /// + public static TTarget Map(TSource source) + { + if (source is IAutoMap m) + { + return m.MapTo(); + } + return ExpressionMapper.Map(source); + } +} + +internal static class ExpressionMapper +{ + public static TTarget Map(TSource source) + { + // TODO + throw new System.NotImplementedException(); + } +} diff --git a/src/AutoGenMapperGenerator/IAutoMap.cs b/src/AutoGenMapperGenerator/IAutoMap.cs index b779b62..6c89dc9 100644 --- a/src/AutoGenMapperGenerator/IAutoMap.cs +++ b/src/AutoGenMapperGenerator/IAutoMap.cs @@ -1,7 +1,7 @@ namespace AutoGenMapperGenerator; /// -/// +/// 由生成器动态实现 /// public interface IAutoMap { diff --git a/src/AutoGenMapperGenerator/MappingContext.cs b/src/AutoGenMapperGenerator/MappingContext.cs new file mode 100644 index 0000000..e7c7ca1 --- /dev/null +++ b/src/AutoGenMapperGenerator/MappingContext.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace AutoGenMapperGenerator; + +/// +/// +/// +/// +/// +public readonly struct MappingContext(TSource source, TTarget target) + where TSource : class + where TTarget : class +{ + /// + /// 源数据 + /// + public TSource Source { get; } = source; + /// + /// 目标数据 + /// + public TTarget Target { get; } = target; +} +//internal class MyClass +//{ +// public int Age { get; set; } +// public string Name { get; set; } = string.Empty; +//} +//internal class Test +//{ +// public void MapTest(MappingContext context) +// { +// context.Target.Age = context.Source.Age + 1; +// context.Target.Name = context.Source.Name + "!"; +// } +//} diff --git a/src/AutoGenMapperGenerator/readme.md b/src/AutoGenMapperGenerator/readme.md index 5a5bcfa..bc9fc2e 100644 --- a/src/AutoGenMapperGenerator/readme.md +++ b/src/AutoGenMapperGenerator/readme.md @@ -3,7 +3,7 @@ ### `GenMapperAttribute`标注了类型需要生成映射方法,同时实现`IAutoMap`接口(由生成器实现接口, 类型转换时可以用`obj is IAutoMap map`检查) ### 构造函数可选参数为目标类型,默认是自身 -### `MapBetweenAttribute`用于自定义转换动作,支持定义在类上或者属性上,两者的有一下区别 +### `MapBetweenAttribute`用于自定义转换动作,支持定义在类上或者属性上或者方法上,两者的有一下区别 - 定义在类上时,必须使用3个参数的构造函数,并且支持单对多,多对单的映射 - 定义在属性上,必须使用2个参数的构造函数,并且只支持单对单映射 diff --git a/src/MT.Generators.slnx b/src/MT.Generators.slnx new file mode 100644 index 0000000..0213966 --- /dev/null +++ b/src/MT.Generators.slnx @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Shared b/src/Shared index 1e3126c..753bbc6 160000 --- a/src/Shared +++ b/src/Shared @@ -1 +1 @@ -Subproject commit 1e3126cf6d98e7c31d438e7cdd3d41116f2f7a46 +Subproject commit 753bbc69e3ec48241e30ab9edc0d2ef97cabfadd diff --git a/src/TestProject1/Models/MapperExtensions.cs b/src/TestProject1/Models/MapperExtensions.cs new file mode 100644 index 0000000..f276d32 --- /dev/null +++ b/src/TestProject1/Models/MapperExtensions.cs @@ -0,0 +1,31 @@ +using AutoGenMapperGenerator; +using System.Xml.Linq; + +namespace TestProject1.Models; + +internal static partial class MapperExtensions +{ + [GenMapper] + [MapBetween([nameof(Product.Name), nameof(Product.Category)], nameof(ProductDto.Name), By = nameof(MapToDtoName))] + [MapBetween(nameof(Product.SplitValue), [nameof(ProductDto.S1), nameof(ProductDto.S2)], By = nameof(MapOneToMultiTest))] + public static partial ProductDto ToDto(this Product product, Action? action = null); + + public static string MapToDtoName(string name, string category) + { + return $"{name}-{category}"; + } + + public static (string, string) MapOneToMultiTest(string value) + { + var val = value.Split(','); + return (val[0], val[1]); + } +} + +//static partial class MapperExtensions +//{ +// static partial ProductDto ToDto(Product product) +// { +// throw new NotImplementedException(); +// } +//} \ No newline at end of file