diff --git a/src/Riok.Mapperly.Abstractions/PublicAPI.Shipped.txt b/src/Riok.Mapperly.Abstractions/PublicAPI.Shipped.txt index f68ab9fc72..94add9f456 100644 --- a/src/Riok.Mapperly.Abstractions/PublicAPI.Shipped.txt +++ b/src/Riok.Mapperly.Abstractions/PublicAPI.Shipped.txt @@ -105,3 +105,9 @@ Riok.Mapperly.Abstractions.MapperIgnoreSourceValueAttribute.SourceValue.get -> S Riok.Mapperly.Abstractions.MapperIgnoreTargetValueAttribute.TargetValue.get -> System.Enum? Riok.Mapperly.Abstractions.MapperAttribute.AllowNullPropertyAssignment.get -> bool Riok.Mapperly.Abstractions.MapperAttribute.AllowNullPropertyAssignment.set -> void +Riok.Mapperly.Abstractions.UseMapperAttribute +Riok.Mapperly.Abstractions.UseMapperAttribute.UseMapperAttribute() -> void +Riok.Mapperly.Abstractions.UseStaticMapperAttribute +Riok.Mapperly.Abstractions.UseStaticMapperAttribute.UseStaticMapperAttribute(System.Type! mapperType) -> void +Riok.Mapperly.Abstractions.UseStaticMapperAttribute +Riok.Mapperly.Abstractions.UseStaticMapperAttribute.UseStaticMapperAttribute() -> void diff --git a/src/Riok.Mapperly.Abstractions/UseMapperAttribute.cs b/src/Riok.Mapperly.Abstractions/UseMapperAttribute.cs new file mode 100644 index 0000000000..52cd9af661 --- /dev/null +++ b/src/Riok.Mapperly.Abstractions/UseMapperAttribute.cs @@ -0,0 +1,7 @@ +namespace Riok.Mapperly.Abstractions; + +/// +/// Uses mapping methods provided by the type of this member to map types. +/// +[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] +public sealed class UseMapperAttribute : Attribute { } diff --git a/src/Riok.Mapperly.Abstractions/UseStaticMapperAttribute.cs b/src/Riok.Mapperly.Abstractions/UseStaticMapperAttribute.cs new file mode 100644 index 0000000000..a80d94e7bd --- /dev/null +++ b/src/Riok.Mapperly.Abstractions/UseStaticMapperAttribute.cs @@ -0,0 +1,21 @@ +namespace Riok.Mapperly.Abstractions; + +/// +/// Uses static mapping methods provided by the provided type to map types. +/// +[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] +public sealed class UseStaticMapperAttribute : Attribute +{ + /// + /// Uses static mapping methods provided by to map types. + /// + /// The type of which mapping methods will be included. + public UseStaticMapperAttribute(Type mapperType) { } +} + +/// +/// Uses static mapping methods provided by the provided type to map types. +/// +/// The type of which mapping methods will be included. +[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] +public sealed class UseStaticMapperAttribute : Attribute { } diff --git a/src/Riok.Mapperly/Configuration/UseStaticMapperConfiguration.cs b/src/Riok.Mapperly/Configuration/UseStaticMapperConfiguration.cs new file mode 100644 index 0000000000..6e84c03549 --- /dev/null +++ b/src/Riok.Mapperly/Configuration/UseStaticMapperConfiguration.cs @@ -0,0 +1,5 @@ +using Microsoft.CodeAnalysis; + +namespace Riok.Mapperly.Configuration; + +public record UseStaticMapperConfiguration(ITypeSymbol MapperType); diff --git a/src/Riok.Mapperly/Descriptors/DescriptorBuilder.cs b/src/Riok.Mapperly/Descriptors/DescriptorBuilder.cs index 1dfe294ed1..01c7800ef8 100644 --- a/src/Riok.Mapperly/Descriptors/DescriptorBuilder.cs +++ b/src/Riok.Mapperly/Descriptors/DescriptorBuilder.cs @@ -2,6 +2,7 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; using Riok.Mapperly.Abstractions.ReferenceHandling; using Riok.Mapperly.Configuration; +using Riok.Mapperly.Descriptors.ExternalMappings; using Riok.Mapperly.Descriptors.MappingBodyBuilders; using Riok.Mapperly.Descriptors.MappingBuilders; using Riok.Mapperly.Descriptors.ObjectFactories; @@ -49,6 +50,7 @@ SymbolAccessor symbolAccessor { ReserveMethodNames(); ExtractObjectFactories(); + ExtractExternalMappings(); ExtractUserMappings(); _mappingBodyBuilder.BuildMappingBodies(); BuildMappingMethodNames(); @@ -79,6 +81,14 @@ private void ExtractUserMappings() } } + private void ExtractExternalMappings() + { + foreach (var externalMapping in ExternalMappingsExtractor.ExtractExternalMappings(_builderContext, _mapperDescriptor.Symbol)) + { + _mappings.Add(externalMapping); + } + } + private void ReserveMethodNames() { foreach (var methodSymbol in _symbolAccessor.GetAllMembers(_mapperDescriptor.Symbol)) diff --git a/src/Riok.Mapperly/Descriptors/ExternalMappings/ExternalMappingsExtractor.cs b/src/Riok.Mapperly/Descriptors/ExternalMappings/ExternalMappingsExtractor.cs new file mode 100644 index 0000000000..6c90088560 --- /dev/null +++ b/src/Riok.Mapperly/Descriptors/ExternalMappings/ExternalMappingsExtractor.cs @@ -0,0 +1,39 @@ +using Microsoft.CodeAnalysis; +using Riok.Mapperly.Abstractions; +using Riok.Mapperly.Configuration; +using Riok.Mapperly.Descriptors.Mappings.UserMappings; +using Riok.Mapperly.Emit; + +namespace Riok.Mapperly.Descriptors.ExternalMappings; + +internal static class ExternalMappingsExtractor +{ + // TODO move to user extractor + public static IEnumerable ExtractExternalMappings(SimpleMappingBuilderContext ctx, INamedTypeSymbol mapperSymbol) + { + // TODO extract attribute data accessor + var accessor = new AttributeDataAccessor(ctx.SymbolAccessor); + + // TODO should not access syntax factory helper here + var staticExternalMappers = accessor + .Access(mapperSymbol) + .Concat(accessor.Access, UseStaticMapperConfiguration>(mapperSymbol)) + .SelectMany( + x => + UserMethodMappingExtractor.ExtractUserImplementedMappings( + ctx, + x.MapperType, + SyntaxFactoryHelper.FullyQualifiedIdentifierName(x.MapperType), + true + ) + ); + + var externalInstanceMappers = ctx.SymbolAccessor + .GetAllMembers(mapperSymbol) + .Select(x => new { x.Name, Type = (x as IFieldSymbol)?.Type ?? (x as IPropertySymbol)?.Type }) + .Where(x => x.Type != null) + .SelectMany(x => UserMethodMappingExtractor.ExtractUserImplementedMappings(ctx, x.Type!, x.Name, false)); + + return staticExternalMappers.Concat(externalInstanceMappers); + } +} diff --git a/src/Riok.Mapperly/Descriptors/Mappings/UserMappings/UserImplementedExistingTargetMethodMapping.cs b/src/Riok.Mapperly/Descriptors/Mappings/UserMappings/UserImplementedExistingTargetMethodMapping.cs index 5d57e6644d..801061f9c3 100644 --- a/src/Riok.Mapperly/Descriptors/Mappings/UserMappings/UserImplementedExistingTargetMethodMapping.cs +++ b/src/Riok.Mapperly/Descriptors/Mappings/UserMappings/UserImplementedExistingTargetMethodMapping.cs @@ -1,10 +1,10 @@ using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Riok.Mapperly.Descriptors.Mappings.ExistingTarget; -using Riok.Mapperly.Emit; using Riok.Mapperly.Helpers; using Riok.Mapperly.Symbols; +using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; +using static Riok.Mapperly.Emit.SyntaxFactoryHelper; namespace Riok.Mapperly.Descriptors.Mappings.UserMappings; @@ -16,8 +16,10 @@ public class UserImplementedExistingTargetMethodMapping : ExistingTargetMapping, private readonly MethodParameter _sourceParameter; private readonly MethodParameter _targetParameter; private readonly MethodParameter? _referenceHandlerParameter; + private readonly string? _receiver; public UserImplementedExistingTargetMethodMapping( + string? receiver, IMethodSymbol method, MethodParameter sourceParameter, MethodParameter targetParameter, @@ -29,24 +31,20 @@ public UserImplementedExistingTargetMethodMapping( _sourceParameter = sourceParameter; _targetParameter = targetParameter; _referenceHandlerParameter = referenceHandlerParameter; + _receiver = receiver; } public IMethodSymbol Method { get; } - public ExpressionSyntax Build(TypeMappingBuildContext ctx) => - throw new InvalidOperationException( - $"{nameof(UserImplementedExistingTargetMethodMapping)} {ctx.Source}, {ctx.ReferenceHandler} does not support {nameof(Build)}" - ); - public override IEnumerable Build(TypeMappingBuildContext ctx, ExpressionSyntax target) { // if the user implemented method is on an interface, // we explicitly cast to be able to use the default interface implementation or explicit implementations if (Method.ReceiverType?.TypeKind != TypeKind.Interface) { - yield return SyntaxFactory.ExpressionStatement( - SyntaxFactoryHelper.Invocation( - Method.Name, + yield return ExpressionStatement( + Invocation( + _receiver == null ? IdentifierName(Method.Name) : MemberAccess(_receiver, Method.Name), _sourceParameter.WithArgument(ctx.Source), _targetParameter.WithArgument(target), _referenceHandlerParameter?.WithArgument(ctx.ReferenceHandler) @@ -55,13 +53,13 @@ public override IEnumerable Build(TypeMappingBuildContext ctx, yield break; } - var castedThis = SyntaxFactory.CastExpression( - SyntaxFactoryHelper.FullyQualifiedIdentifier(Method.ReceiverType!), - SyntaxFactory.ThisExpression() + var castedThis = CastExpression( + FullyQualifiedIdentifier(Method.ReceiverType!), + _receiver != null ? IdentifierName(_receiver) : ThisExpression() ); - var method = SyntaxFactoryHelper.MemberAccess(SyntaxFactory.ParenthesizedExpression(castedThis), Method.Name); - yield return SyntaxFactory.ExpressionStatement( - SyntaxFactoryHelper.Invocation( + var method = MemberAccess(ParenthesizedExpression(castedThis), Method.Name); + yield return ExpressionStatement( + Invocation( method, _sourceParameter.WithArgument(ctx.Source), _targetParameter.WithArgument(target), diff --git a/src/Riok.Mapperly/Descriptors/Mappings/UserMappings/UserImplementedMethodMapping.cs b/src/Riok.Mapperly/Descriptors/Mappings/UserMappings/UserImplementedMethodMapping.cs index c600fb45f8..99deced51b 100644 --- a/src/Riok.Mapperly/Descriptors/Mappings/UserMappings/UserImplementedMethodMapping.cs +++ b/src/Riok.Mapperly/Descriptors/Mappings/UserMappings/UserImplementedMethodMapping.cs @@ -12,13 +12,20 @@ namespace Riok.Mapperly.Descriptors.Mappings.UserMappings; /// public class UserImplementedMethodMapping : NewInstanceMapping, IUserMapping { + private readonly string? _receiver; private readonly MethodParameter _sourceParameter; private readonly MethodParameter? _referenceHandlerParameter; - public UserImplementedMethodMapping(IMethodSymbol method, MethodParameter sourceParameter, MethodParameter? referenceHandlerParameter) + public UserImplementedMethodMapping( + string? receiver, + IMethodSymbol method, + MethodParameter sourceParameter, + MethodParameter? referenceHandlerParameter + ) : base(method.Parameters[0].Type.UpgradeNullable(), method.ReturnType.UpgradeNullable()) { Method = method; + _receiver = receiver; _sourceParameter = sourceParameter; _referenceHandlerParameter = referenceHandlerParameter; } @@ -31,13 +38,16 @@ public override ExpressionSyntax Build(TypeMappingBuildContext ctx) // we explicitly cast to be able to use the default interface implementation or explicit implementations if (Method.ReceiverType?.TypeKind != TypeKind.Interface) return Invocation( - Method.Name, + _receiver == null ? IdentifierName(Method.Name) : MemberAccess(_receiver, Method.Name), _sourceParameter.WithArgument(ctx.Source), _referenceHandlerParameter?.WithArgument(ctx.ReferenceHandler) ); - var castedThis = CastExpression(FullyQualifiedIdentifier(Method.ReceiverType!), ThisExpression()); - var method = MemberAccess(ParenthesizedExpression(castedThis), Method.Name); + var castedReceiver = CastExpression( + FullyQualifiedIdentifier(Method.ReceiverType!), + _receiver == null ? ThisExpression() : IdentifierName(_receiver) + ); + var method = MemberAccess(ParenthesizedExpression(castedReceiver), Method.Name); return Invocation( method, _sourceParameter.WithArgument(ctx.Source), diff --git a/src/Riok.Mapperly/Descriptors/UserMethodMappingExtractor.cs b/src/Riok.Mapperly/Descriptors/UserMethodMappingExtractor.cs index f48f1501fe..4a2ceeaf4d 100644 --- a/src/Riok.Mapperly/Descriptors/UserMethodMappingExtractor.cs +++ b/src/Riok.Mapperly/Descriptors/UserMethodMappingExtractor.cs @@ -18,7 +18,7 @@ public static IEnumerable ExtractUserMappings(SimpleMappingBuilder { var mapping = BuilderUserDefinedMapping(ctx, methodSymbol, mapperSymbol.IsStatic) - ?? BuildUserImplementedMapping(ctx, methodSymbol, false, mapperSymbol.IsStatic); + ?? BuildUserImplementedMapping(ctx, methodSymbol, null, false, mapperSymbol.IsStatic); if (mapping != null) yield return mapping; } @@ -28,40 +28,65 @@ public static IEnumerable ExtractUserMappings(SimpleMappingBuilder yield break; // extract user implemented mappings from base methods - foreach (var method in ExtractBaseMethods(ctx, mapperSymbol)) + var methods = mapperSymbol.AllInterfaces.SelectMany(ctx.SymbolAccessor.GetAllMethods); + if (mapperSymbol.BaseType is { } mapperBaseSymbol) { + methods = methods.Concat(ctx.SymbolAccessor.GetAllMethods(mapperBaseSymbol)); + } + + foreach (var mapping in BuildUserImplementedMappings(ctx, methods, null, false)) + { + yield return mapping; + } + } + + public static IEnumerable ExtractUserImplementedMappings( + SimpleMappingBuilderContext ctx, + ITypeSymbol type, + string? receiver, + bool isStatic + ) + { + var methods = ctx.SymbolAccessor.GetAllMethods(type).Concat(type.AllInterfaces.SelectMany(ctx.SymbolAccessor.GetAllMethods)); + return BuildUserImplementedMappings(ctx, methods, receiver, isStatic); + } + + private static IEnumerable ExtractMethods(ITypeSymbol mapperSymbol) => mapperSymbol.GetMembers().OfType(); + + private static IEnumerable BuildUserImplementedMappings( + SimpleMappingBuilderContext ctx, + IEnumerable methods, + string? receiver, + bool isStatic + ) + { + foreach (var method in methods) + { + if (!IsMappingMethodCandidate(ctx, method)) + continue; + // Partial method declarations are allowed for base classes, // but still treated as user implemented methods, // since the user should provide an implementation elsewhere. // This is the case if a partial mapper class is extended. - var mapping = BuildUserImplementedMapping(ctx, method, true, mapperSymbol.IsStatic); + var mapping = BuildUserImplementedMapping(ctx, method, receiver, true, isStatic); if (mapping != null) yield return mapping; } } - private static IEnumerable ExtractMethods(ITypeSymbol mapperSymbol) => mapperSymbol.GetMembers().OfType(); - - private static IEnumerable ExtractBaseMethods(SimpleMappingBuilderContext ctx, ITypeSymbol mapperSymbol) + private static bool IsMappingMethodCandidate(SimpleMappingBuilderContext ctx, IMethodSymbol method) { - var baseMethods = - mapperSymbol.BaseType != null ? ctx.SymbolAccessor.GetAllMethods(mapperSymbol.BaseType!) : Enumerable.Empty(); - var intfMethods = mapperSymbol.AllInterfaces.SelectMany(ctx.SymbolAccessor.GetAllMethods); - return baseMethods - .Concat(intfMethods) - .OfType() - // ignore all non ordinary methods (eg. ctor, operators, etc.) and methods declared on the object type (eg. ToString) - .Where( - x => - x.MethodKind == MethodKind.Ordinary - && ctx.SymbolAccessor.IsAccessible(x) - && !SymbolEqualityComparer.Default.Equals(x.ReceiverType, ctx.Compilation.ObjectType) - ); + // ignore all non ordinary methods (eg. ctor, operators, etc.) and methods declared on the object type (eg. ToString) + return method.MethodKind == MethodKind.Ordinary + && ctx.SymbolAccessor.IsAccessible(method) + && !SymbolEqualityComparer.Default.Equals(method.ReceiverType, ctx.Compilation.ObjectType); } private static IUserMapping? BuildUserImplementedMapping( SimpleMappingBuilderContext ctx, IMethodSymbol method, + string? receiver, bool allowPartial, bool isStatic ) @@ -75,12 +100,13 @@ bool isStatic return method.ReturnsVoid ? new UserImplementedExistingTargetMethodMapping( + receiver, method, parameters.Source, parameters.Target!.Value, parameters.ReferenceHandler ) - : new UserImplementedMethodMapping(method, parameters.Source, parameters.ReferenceHandler); + : new UserImplementedMethodMapping(receiver, method, parameters.Source, parameters.ReferenceHandler); } private static IUserMapping? BuilderUserDefinedMapping(SimpleMappingBuilderContext ctx, IMethodSymbol methodSymbol, bool isStatic) diff --git a/test/Riok.Mapperly.IntegrationTests/_snapshots/Roslyn_4_5/ProjectionMapperTest.SnapshotGeneratedSource.verified.cs b/test/Riok.Mapperly.IntegrationTests/_snapshots/Roslyn_4_5/ProjectionMapperTest.SnapshotGeneratedSource.verified.cs index 15094d8ac8..c0f4c0470a 100644 --- a/test/Riok.Mapperly.IntegrationTests/_snapshots/Roslyn_4_5/ProjectionMapperTest.SnapshotGeneratedSource.verified.cs +++ b/test/Riok.Mapperly.IntegrationTests/_snapshots/Roslyn_4_5/ProjectionMapperTest.SnapshotGeneratedSource.verified.cs @@ -7,7 +7,7 @@ public static partial class ProjectionMapper public static partial global::System.Linq.IQueryable ProjectToDto(this global::System.Linq.IQueryable q) { #nullable disable - return System.Linq.Queryable.Select(q, x => new global::Riok.Mapperly.IntegrationTests.Dto.TestObjectDtoProjection(x.CtorValue) { IntValue = x.IntValue, IntInitOnlyValue = x.IntInitOnlyValue, RequiredValue = x.RequiredValue, StringValue = x.StringValue, RenamedStringValue2 = x.RenamedStringValue, FlatteningIdValue = x.Flattening.IdValue, NullableFlatteningIdValue = x.NullableFlattening != null ? x.NullableFlattening.IdValue : default, NestedNullableIntValue = x.NestedNullable != null ? x.NestedNullable.IntValue : default, NestedNullable = x.NestedNullable != null ? new global::Riok.Mapperly.IntegrationTests.Dto.TestObjectNestedDto() { IntValue = x.NestedNullable.IntValue } : default, NestedNullableTargetNotNullable = x.NestedNullableTargetNotNullable != null ? new global::Riok.Mapperly.IntegrationTests.Dto.TestObjectNestedDto() { IntValue = x.NestedNullableTargetNotNullable.IntValue } : new global::Riok.Mapperly.IntegrationTests.Dto.TestObjectNestedDto(), StringNullableTargetNotNullable = x.StringNullableTargetNotNullable ?? "", SourceTargetSameObjectType = x.SourceTargetSameObjectType, NullableReadOnlyObjectCollection = x.NullableReadOnlyObjectCollection != null ? global::System.Linq.Enumerable.ToArray(global::System.Linq.Enumerable.Select(x.NullableReadOnlyObjectCollection, x1 => new global::Riok.Mapperly.IntegrationTests.Dto.TestObjectNestedDto() { IntValue = x1.IntValue })) : default, EnumValue = (global::Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByValue)x.EnumValue, EnumName = (global::Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByName)x.EnumName, EnumRawValue = (byte)x.EnumRawValue, EnumStringValue = (string)x.EnumStringValue.ToString(), EnumReverseStringValue = System.Enum.Parse(x.EnumReverseStringValue, false), SubObject = x.SubObject != null ? new global::Riok.Mapperly.IntegrationTests.Dto.InheritanceSubObjectDto() { SubIntValue = x.SubObject.SubIntValue, BaseIntValue = x.SubObject.BaseIntValue } : default, DateTimeValueTargetDateOnly = global::System.DateOnly.FromDateTime(x.DateTimeValueTargetDateOnly), DateTimeValueTargetTimeOnly = global::System.TimeOnly.FromDateTime(x.DateTimeValueTargetTimeOnly), ManuallyMapped = MapManual(x.ManuallyMapped) }); + return System.Linq.Queryable.Select(q, x => new global::Riok.Mapperly.IntegrationTests.Dto.TestObjectDtoProjection(x.CtorValue) { IntValue = x.IntValue, IntInitOnlyValue = x.IntInitOnlyValue, RequiredValue = x.RequiredValue, StringValue = x.StringValue, RenamedStringValue2 = x.RenamedStringValue, FlatteningIdValue = x.Flattening.IdValue, NullableFlatteningIdValue = x.NullableFlattening != null ? x.NullableFlattening.IdValue : default, NestedNullableIntValue = x.NestedNullable != null ? x.NestedNullable.IntValue : default, NestedNullable = x.NestedNullable != null ? new global::Riok.Mapperly.IntegrationTests.Dto.TestObjectNestedDto() { IntValue = x.NestedNullable.IntValue } : default, NestedNullableTargetNotNullable = x.NestedNullableTargetNotNullable != null ? new global::Riok.Mapperly.IntegrationTests.Dto.TestObjectNestedDto() { IntValue = x.NestedNullableTargetNotNullable.IntValue } : new global::Riok.Mapperly.IntegrationTests.Dto.TestObjectNestedDto(), StringNullableTargetNotNullable = x.StringNullableTargetNotNullable ?? "", SourceTargetSameObjectType = x.SourceTargetSameObjectType, NullableReadOnlyObjectCollection = x.NullableReadOnlyObjectCollection != null ? global::System.Linq.Enumerable.ToArray(global::System.Linq.Enumerable.Select(x.NullableReadOnlyObjectCollection, x1 => new global::Riok.Mapperly.IntegrationTests.Dto.TestObjectNestedDto() { IntValue = x1.IntValue })) : default, EnumValue = (global::Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByValue)x.EnumValue, EnumName = (global::Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByName)x.EnumName, EnumRawValue = (byte)x.EnumRawValue, EnumStringValue = (string)x.EnumStringValue.ToString(), EnumReverseStringValue = System.Enum.Parse(x.EnumReverseStringValue, false), SubObject = x.SubObject != null ? new global::Riok.Mapperly.IntegrationTests.Dto.InheritanceSubObjectDto() { SubIntValue = x.SubObject.SubIntValue, BaseIntValue = x.SubObject.BaseIntValue } : default, DateTimeValueTargetDateOnly = global::System.DateOnly.FromDateTime(x.DateTimeValueTargetDateOnly), DateTimeValueTargetTimeOnly = global::System.TimeOnly.FromDateTime(x.DateTimeValueTargetTimeOnly), ManuallyMapped = global::Riok.Mapperly.IntegrationTests.Mapper.ProjectionMapper.MapManual(x.ManuallyMapped) }); #nullable enable } @@ -64,7 +64,7 @@ public static partial class ProjectionMapper target.EnumReverseStringValue = MapToTestEnumDtoByName(testObject.EnumReverseStringValue); target.DateTimeValueTargetDateOnly = global::System.DateOnly.FromDateTime(testObject.DateTimeValueTargetDateOnly); target.DateTimeValueTargetTimeOnly = global::System.TimeOnly.FromDateTime(testObject.DateTimeValueTargetTimeOnly); - target.ManuallyMapped = MapManual(testObject.ManuallyMapped); + target.ManuallyMapped = global::Riok.Mapperly.IntegrationTests.Mapper.ProjectionMapper.MapManual(testObject.ManuallyMapped); return target; } @@ -146,4 +146,4 @@ private static string MapToString(global::Riok.Mapperly.IntegrationTests.Models. return target; } } -} +} \ No newline at end of file diff --git a/test/Riok.Mapperly.IntegrationTests/_snapshots/Roslyn_4_5/StaticMapperTest.SnapshotGeneratedSource.verified.cs b/test/Riok.Mapperly.IntegrationTests/_snapshots/Roslyn_4_5/StaticMapperTest.SnapshotGeneratedSource.verified.cs index f7bac3307e..073a4d1dd2 100644 --- a/test/Riok.Mapperly.IntegrationTests/_snapshots/Roslyn_4_5/StaticMapperTest.SnapshotGeneratedSource.verified.cs +++ b/test/Riok.Mapperly.IntegrationTests/_snapshots/Roslyn_4_5/StaticMapperTest.SnapshotGeneratedSource.verified.cs @@ -129,7 +129,7 @@ public static partial int ParseableInt(string value) target.ExistingSortedSet.Add(ParseableInt(item2)); } - MapExistingList(src.ExistingList, target.ExistingList); + global::Riok.Mapperly.IntegrationTests.Mapper.StaticTestMapper.MapExistingList(src.ExistingList, target.ExistingList); target.ISet = global::System.Linq.Enumerable.ToHashSet(global::System.Linq.Enumerable.Select(src.ISet, x => ParseableInt(x))); target.IReadOnlySet = global::System.Linq.Enumerable.ToHashSet(global::System.Linq.Enumerable.Select(src.IReadOnlySet, x => ParseableInt(x))); target.HashSet = global::System.Linq.Enumerable.ToHashSet(global::System.Linq.Enumerable.Select(src.HashSet, x => ParseableInt(x))); @@ -233,7 +233,7 @@ public static partial int ParseableInt(string value) target.ExistingSortedSet.Add(ParseableInt(item2)); } - MapExistingList(testObject.ExistingList, target.ExistingList); + global::Riok.Mapperly.IntegrationTests.Mapper.StaticTestMapper.MapExistingList(testObject.ExistingList, target.ExistingList); target.ISet = global::System.Linq.Enumerable.ToHashSet(global::System.Linq.Enumerable.Select(testObject.ISet, x => ParseableInt(x))); target.IReadOnlySet = global::System.Linq.Enumerable.ToHashSet(global::System.Linq.Enumerable.Select(testObject.IReadOnlySet, x => ParseableInt(x))); target.HashSet = global::System.Linq.Enumerable.ToHashSet(global::System.Linq.Enumerable.Select(testObject.HashSet, x => ParseableInt(x))); @@ -415,7 +415,7 @@ public static partial void UpdateDto(global::Riok.Mapperly.IntegrationTests.Mode target.ExistingSortedSet.Add(ParseableInt(item2)); } - MapExistingList(source.ExistingList, target.ExistingList); + global::Riok.Mapperly.IntegrationTests.Mapper.StaticTestMapper.MapExistingList(source.ExistingList, target.ExistingList); target.ISet = global::System.Linq.Enumerable.ToHashSet(global::System.Linq.Enumerable.Select(source.ISet, x => ParseableInt(x))); target.IReadOnlySet = global::System.Linq.Enumerable.ToHashSet(global::System.Linq.Enumerable.Select(source.IReadOnlySet, x => ParseableInt(x))); target.HashSet = global::System.Linq.Enumerable.ToHashSet(global::System.Linq.Enumerable.Select(source.HashSet, x => ParseableInt(x))); diff --git a/test/Riok.Mapperly.Tests/Mapping/UseMapperTest.cs b/test/Riok.Mapperly.Tests/Mapping/UseMapperTest.cs new file mode 100644 index 0000000000..94476e986f --- /dev/null +++ b/test/Riok.Mapperly.Tests/Mapping/UseMapperTest.cs @@ -0,0 +1,30 @@ +namespace Riok.Mapperly.Tests.Mapping; + +public class UseMapperTest +{ + [Fact] + public void UseOrdinalMapperMethod() + { + var source = TestSourceBuilder.MapperWithBodyAndTypes( + """ + [UseMapper] + private readonly OtherMapper _otherMapper; + partial B Map(A source); + """, + "record A(A1 Value);", + "record B(B1 Value);", + "record A1();", + "record B1();", + "class OtherMapper { public B1 ToB(A1 source) => new B1(); }" + ); + TestHelper + .GenerateMapper(source) + .Should() + .HaveSingleMethodBody( + """ + var target = new global::B(_otherMapper.ToB(source.Value)); + return target; + """ + ); + } +} diff --git a/test/Riok.Mapperly.Tests/Mapping/UseStaticMapperTest.cs b/test/Riok.Mapperly.Tests/Mapping/UseStaticMapperTest.cs new file mode 100644 index 0000000000..309f785e35 --- /dev/null +++ b/test/Riok.Mapperly.Tests/Mapping/UseStaticMapperTest.cs @@ -0,0 +1,37 @@ +namespace Riok.Mapperly.Tests.Mapping; + +public class UseStaticMapperTest +{ + [Fact] + public void UseStaticGenericMapperStaticMethod() + { + var source = TestSourceBuilder.CSharp( + """ + using Riok.Mapperly.Abstractions; + + record A(A1 Value); + record B(B1 Value); + record A1(); + record B1(); + + class OtherMapper { public static B1 ToB(A1 source) => new B1(); } + + [Mapper] + [UseStaticMapper] + public partial class Mapper + { + partial B Map(A source); + } + """ + ); + TestHelper + .GenerateMapper(source) + .Should() + .HaveMapMethodBody( + """ + var target = new global::B(global::OtherMapper.ToB(source.Value)); + return target; + """ + ); + } +}