From 617fcd0e282bf75101ff949a743ade4cfa5067f7 Mon Sep 17 00:00:00 2001 From: Timothy Makkison Date: Sun, 21 May 2023 21:04:46 +0100 Subject: [PATCH] feat: add `MapProperty` override, add tests and fix #450 --- .../BuilderContext/IMembersBuilderContext.cs | 1 + .../MembersMappingBuilderContext.cs | 8 +- ...wInstanceObjectMemberMappingBodyBuilder.cs | 13 ++++ .../ObjectMemberMappingBodyBuilder.cs | 14 ++++ .../Helpers/DictionaryExtensions.cs | 15 ++++ .../Mapping/IgnoreObsoleteTest.cs | 78 +++++++++++++++++++ 6 files changed, 126 insertions(+), 3 deletions(-) diff --git a/src/Riok.Mapperly/Descriptors/MappingBodyBuilders/BuilderContext/IMembersBuilderContext.cs b/src/Riok.Mapperly/Descriptors/MappingBodyBuilders/BuilderContext/IMembersBuilderContext.cs index 9a9fde25c1..117622fc60 100644 --- a/src/Riok.Mapperly/Descriptors/MappingBodyBuilders/BuilderContext/IMembersBuilderContext.cs +++ b/src/Riok.Mapperly/Descriptors/MappingBodyBuilders/BuilderContext/IMembersBuilderContext.cs @@ -20,6 +20,7 @@ public interface IMembersBuilderContext IReadOnlyCollection IgnoredSourceMemberNames { get; } Dictionary TargetMembers { get; } + Dictionary IgnoredTargetMembers { get; } Dictionary> MemberConfigsByRootTargetName { get; } } diff --git a/src/Riok.Mapperly/Descriptors/MappingBodyBuilders/BuilderContext/MembersMappingBuilderContext.cs b/src/Riok.Mapperly/Descriptors/MappingBodyBuilders/BuilderContext/MembersMappingBuilderContext.cs index 2a71d5d9d2..4872877c81 100644 --- a/src/Riok.Mapperly/Descriptors/MappingBodyBuilders/BuilderContext/MembersMappingBuilderContext.cs +++ b/src/Riok.Mapperly/Descriptors/MappingBodyBuilders/BuilderContext/MembersMappingBuilderContext.cs @@ -26,14 +26,14 @@ protected MembersMappingBuilderContext(MappingBuilderContext builderContext, T m _unmappedSourceMemberNames = GetSourceMemberNames(); TargetMembers = GetTargetMembers(); - var ignoredTargetMemberNames = GetIgnoredTargetMembers(); IgnoredSourceMemberNames = GetIgnoredSourceMembers(); + var ignoredTargetMemberNames = GetIgnoredTargetMembers(); _ignoredUnmatchedSourceMemberNames = InitIgnoredUnmatchedProperties(IgnoredSourceMemberNames, _unmappedSourceMemberNames); _ignoredUnmatchedTargetMemberNames = InitIgnoredUnmatchedProperties(ignoredTargetMemberNames, TargetMembers.Keys); _unmappedSourceMemberNames.ExceptWith(IgnoredSourceMemberNames); - TargetMembers.RemoveRange(ignoredTargetMemberNames); + IgnoredTargetMembers = TargetMembers.ExtractRange(ignoredTargetMemberNames); } public MappingBuilderContext BuilderContext { get; } @@ -44,6 +44,8 @@ protected MembersMappingBuilderContext(MappingBuilderContext builderContext, T m public Dictionary TargetMembers { get; } + public Dictionary IgnoredTargetMembers { get; } + public Dictionary> MemberConfigsByRootTargetName { get; } public void AddDiagnostics() @@ -84,7 +86,7 @@ private IEnumerable GetIgnoredObsoleteTargetMembers() if (obsoleteAttribute.IgnoreObsoleteStrategy.HasFlag(IgnoreObsoleteMembersStrategy.Target)) { - return Mapping.SourceType + return Mapping.TargetType .GetAccessibleMappableMembers() .Where(x => x.MemberSymbol.HasAttribute(BuilderContext.Types.ObsoleteAttribute)) .Select(x => x.Name); diff --git a/src/Riok.Mapperly/Descriptors/MappingBodyBuilders/NewInstanceObjectMemberMappingBodyBuilder.cs b/src/Riok.Mapperly/Descriptors/MappingBodyBuilders/NewInstanceObjectMemberMappingBodyBuilder.cs index a4ce81e624..156bee2700 100644 --- a/src/Riok.Mapperly/Descriptors/MappingBodyBuilders/NewInstanceObjectMemberMappingBodyBuilder.cs +++ b/src/Riok.Mapperly/Descriptors/MappingBodyBuilders/NewInstanceObjectMemberMappingBodyBuilder.cs @@ -66,6 +66,19 @@ out var sourceMemberPath BuildInitMemberMapping(ctx, targetMember, sourceMemberPath); } + + var initOnlyIgnoredTargetMembers = includeAllMembers + ? ctx.IgnoredTargetMembers.Values.ToArray() + : ctx.IgnoredTargetMembers.Values.Where(x => x.CanOnlySetViaInitializer()).ToArray(); + foreach (var targetMember in initOnlyIgnoredTargetMembers) + { + ctx.IgnoredTargetMembers.Remove(targetMember.Name); + + if (ctx.MemberConfigsByRootTargetName.Remove(targetMember.Name, out var memberConfigs)) + { + BuildInitMemberMapping(ctx, targetMember, memberConfigs); + } + } } private static void BuildInitMemberMapping( diff --git a/src/Riok.Mapperly/Descriptors/MappingBodyBuilders/ObjectMemberMappingBodyBuilder.cs b/src/Riok.Mapperly/Descriptors/MappingBodyBuilders/ObjectMemberMappingBodyBuilder.cs index f683f0a730..7a825de887 100644 --- a/src/Riok.Mapperly/Descriptors/MappingBodyBuilders/ObjectMemberMappingBodyBuilder.cs +++ b/src/Riok.Mapperly/Descriptors/MappingBodyBuilders/ObjectMemberMappingBodyBuilder.cs @@ -66,6 +66,20 @@ out var sourceMemberPath } } + foreach (var targetMember in ctx.IgnoredTargetMembers.Values) + { + if (ctx.MemberConfigsByRootTargetName.Remove(targetMember.Name, out var memberConfigs)) + { + // add all configured mappings + // order by target path count to map less nested items first (otherwise they would overwrite all others) + // eg. target.A = source.B should be mapped before target.A.Id = source.B.Id + foreach (var config in memberConfigs.OrderBy(x => x.Target.Count)) + { + BuildMemberAssignmentMapping(ctx, config); + } + } + } + ctx.AddDiagnostics(); } diff --git a/src/Riok.Mapperly/Helpers/DictionaryExtensions.cs b/src/Riok.Mapperly/Helpers/DictionaryExtensions.cs index 7092280e7a..2ebcc4d657 100644 --- a/src/Riok.Mapperly/Helpers/DictionaryExtensions.cs +++ b/src/Riok.Mapperly/Helpers/DictionaryExtensions.cs @@ -19,6 +19,21 @@ public static void RemoveRange(this IDictionary dict } } + public static Dictionary ExtractRange(this IDictionary dict, IEnumerable keys) + { + var removedItems = new Dictionary(); + foreach (var key in keys) + { + if (dict.TryGetValue(key, out var value)) + { + dict.Remove(key); + removedItems.Add(key, value); + } + } + + return removedItems; + } + public static TValue? GetValueOrDefault(this IDictionary dict, TKey key) { dict.TryGetValue(key, out var value); diff --git a/test/Riok.Mapperly.Tests/Mapping/IgnoreObsoleteTest.cs b/test/Riok.Mapperly.Tests/Mapping/IgnoreObsoleteTest.cs index f94f562ecf..d2d01f8478 100644 --- a/test/Riok.Mapperly.Tests/Mapping/IgnoreObsoleteTest.cs +++ b/test/Riok.Mapperly.Tests/Mapping/IgnoreObsoleteTest.cs @@ -225,4 +225,82 @@ public void MethodAttributeOverridesClassAttribute() """ ); } + + [Fact] + public void MapPropertyOverridesIgnoreObsoleteBoth() + { + var source = TestSourceBuilder.MapperWithBodyAndTypes( + """ + [MapProperty("Ignored", "Ignored")] + [MapperIgnoreObsoleteMembers] + partial B Map(A source); + """, + _classA, + _classB + ); + + TestHelper + .GenerateMapper(source, TestHelperOptions.AllowInfoDiagnostics) + .Should() + .HaveSingleMethodBody( + """ + var target = new global::B(); + target.Value = source.Value; + target.Ignored = source.Ignored; + return target; + """ + ); + } + + [Fact] + public void MapPropertyOverridesIgnoreObsoleteSource() + { + var source = TestSourceBuilder.MapperWithBodyAndTypes( + """ + [MapProperty("Ignored", "Ignored")] + [MapperIgnoreObsoleteMembers(IgnoreObsoleteMembersStrategy.Source)] + partial B Map(A source); + """, + _classA, + _classB + ); + + TestHelper + .GenerateMapper(source, TestHelperOptions.AllowInfoDiagnostics) + .Should() + .HaveSingleMethodBody( + """ + var target = new global::B(); + target.Value = source.Value; + target.Ignored = source.Ignored; + return target; + """ + ); + } + + [Fact] + public void MapPropertyOverridesIgnoreObsoleteTarget() + { + var source = TestSourceBuilder.MapperWithBodyAndTypes( + """ + [MapProperty("Ignored", "Ignored")] + [MapperIgnoreObsoleteMembers(IgnoreObsoleteMembersStrategy.Target)] + partial B Map(A source); + """, + _classA, + _classB + ); + + TestHelper + .GenerateMapper(source, TestHelperOptions.AllowInfoDiagnostics) + .Should() + .HaveSingleMethodBody( + """ + var target = new global::B(); + target.Value = source.Value; + target.Ignored = source.Ignored; + return target; + """ + ); + } }