diff --git a/src/Riok.Mapperly/AnalyzerReleases.Shipped.md b/src/Riok.Mapperly/AnalyzerReleases.Shipped.md index 81ba862e17..05dfbeaad4 100644 --- a/src/Riok.Mapperly/AnalyzerReleases.Shipped.md +++ b/src/Riok.Mapperly/AnalyzerReleases.Shipped.md @@ -114,4 +114,5 @@ RMG047 | Mapper | Error | Cannot map to member path due to modifying a tem Rule ID | Category | Severity | Notes --------|----------|----------|------- RMG048 | Mapper | Error | Used mapper members cannot be nullable - +RMG049 | Mapper | Warning | Source member is ignored and also explicitly mapped +RMG050 | Mapper | Warning | Target member is ignored and also explicitly mapped diff --git a/src/Riok.Mapperly/Descriptors/MappingBodyBuilders/BuilderContext/MembersMappingBuilderContext.cs b/src/Riok.Mapperly/Descriptors/MappingBodyBuilders/BuilderContext/MembersMappingBuilderContext.cs index c635e30708..d91d570023 100644 --- a/src/Riok.Mapperly/Descriptors/MappingBodyBuilders/BuilderContext/MembersMappingBuilderContext.cs +++ b/src/Riok.Mapperly/Descriptors/MappingBodyBuilders/BuilderContext/MembersMappingBuilderContext.cs @@ -15,6 +15,8 @@ public abstract class MembersMappingBuilderContext : IMembersBuilderContext _unmappedSourceMemberNames; + private readonly HashSet _mappedAndIgnoredTargetMemberNames; + private readonly HashSet _mappedAndIgnoredSourceMemberNames; private readonly IReadOnlyCollection _ignoredUnmatchedTargetMemberNames; private readonly IReadOnlyCollection _ignoredUnmatchedSourceMemberNames; @@ -44,9 +46,19 @@ protected MembersMappingBuilderContext(MappingBuilderContext builderContext, T m MemberConfigsByRootTargetName = GetMemberConfigurations(); + // source and target properties may have been ignored and mapped explicitly + _mappedAndIgnoredSourceMemberNames = MemberConfigsByRootTargetName.Values + .SelectMany(v => v.Select(s => s.Source.Path.First())) + .ToHashSet(); + _mappedAndIgnoredSourceMemberNames.IntersectWith(IgnoredSourceMemberNames); + + _mappedAndIgnoredTargetMemberNames = new HashSet(ignoredTargetMemberNames); + _mappedAndIgnoredTargetMemberNames.IntersectWith(MemberConfigsByRootTargetName.Keys); + // remove explicitly mapped ignored targets from ignoredTargetMemberNames // then remove all ignored targets from TargetMembers, leaving unignored and explicitly mapped ignored members - ignoredTargetMemberNames.ExceptWith(MemberConfigsByRootTargetName.Keys); + ignoredTargetMemberNames.ExceptWith(_mappedAndIgnoredTargetMemberNames); + TargetMembers.RemoveRange(ignoredTargetMemberNames); } @@ -66,6 +78,8 @@ public void AddDiagnostics() AddUnmatchedIgnoredSourceMembersDiagnostics(); AddUnmatchedTargetMembersDiagnostics(); AddUnmatchedSourceMembersDiagnostics(); + AddMappedAndIgnoredSourceMembersDiagnostics(); + AddMappedAndIgnoredTargetMembersDiagnostics(); } protected void SetSourceMemberMapped(MemberPath sourcePath) => _unmappedSourceMemberNames.Remove(sourcePath.Path.First().Name); @@ -162,4 +176,28 @@ private void AddUnmatchedSourceMembersDiagnostics() ); } } + + private void AddMappedAndIgnoredTargetMembersDiagnostics() + { + foreach (var targetMemberName in _mappedAndIgnoredTargetMemberNames) + { + BuilderContext.ReportDiagnostic( + DiagnosticDescriptors.IgnoredTargetMemberExplicitlyMapped, + targetMemberName, + Mapping.TargetType + ); + } + } + + private void AddMappedAndIgnoredSourceMembersDiagnostics() + { + foreach (var sourceMemberName in _mappedAndIgnoredSourceMemberNames) + { + BuilderContext.ReportDiagnostic( + DiagnosticDescriptors.IgnoredSourceMemberExplicitlyMapped, + sourceMemberName, + Mapping.SourceType + ); + } + } } diff --git a/src/Riok.Mapperly/Diagnostics/DiagnosticDescriptors.cs b/src/Riok.Mapperly/Diagnostics/DiagnosticDescriptors.cs index e2b6f36d24..7d4d8ed160 100644 --- a/src/Riok.Mapperly/Diagnostics/DiagnosticDescriptors.cs +++ b/src/Riok.Mapperly/Diagnostics/DiagnosticDescriptors.cs @@ -428,4 +428,22 @@ public static class DiagnosticDescriptors DiagnosticSeverity.Error, true ); + + public static readonly DiagnosticDescriptor IgnoredSourceMemberExplicitlyMapped = new DiagnosticDescriptor( + "RMG049", + "Source member is ignored and also explicitly mapped", + "The source member {0} on {1} is ignored, but is also mapped by the " + nameof(MapPropertyAttribute), + DiagnosticCategories.Mapper, + DiagnosticSeverity.Warning, + true + ); + + public static readonly DiagnosticDescriptor IgnoredTargetMemberExplicitlyMapped = new DiagnosticDescriptor( + "RMG050", + "Target member is ignored and also explicitly mapped", + "The target member {0} on {1} is ignored, but is also mapped by the " + nameof(MapPropertyAttribute), + DiagnosticCategories.Mapper, + DiagnosticSeverity.Warning, + true + ); } diff --git a/test/Riok.Mapperly.Tests/Mapping/IgnoreObsoleteTest.cs b/test/Riok.Mapperly.Tests/Mapping/IgnoreObsoleteTest.cs index c32bf50b8e..923a9539c5 100644 --- a/test/Riok.Mapperly.Tests/Mapping/IgnoreObsoleteTest.cs +++ b/test/Riok.Mapperly.Tests/Mapping/IgnoreObsoleteTest.cs @@ -1,4 +1,4 @@ -using Riok.Mapperly.Abstractions; +using Riok.Mapperly.Abstractions; using Riok.Mapperly.Diagnostics; namespace Riok.Mapperly.Tests.Mapping; @@ -245,7 +245,7 @@ public void MapPropertyOverridesIgnoreObsoleteBoth() ); TestHelper - .GenerateMapper(source, TestHelperOptions.AllowInfoDiagnostics) + .GenerateMapper(source, TestHelperOptions.AllowDiagnostics) .Should() .HaveSingleMethodBody( """ @@ -254,7 +254,16 @@ public void MapPropertyOverridesIgnoreObsoleteBoth() target.Ignored = source.Ignored; return target; """ - ); + ) + .HaveDiagnostic( + DiagnosticDescriptors.IgnoredSourceMemberExplicitlyMapped, + "The source member Ignored on A is ignored, but is also mapped by the MapPropertyAttribute" + ) + .HaveDiagnostic( + DiagnosticDescriptors.IgnoredTargetMemberExplicitlyMapped, + "The target member Ignored on B is ignored, but is also mapped by the MapPropertyAttribute" + ) + .HaveAssertedAllDiagnostics(); } [Fact] @@ -271,7 +280,7 @@ public void MapPropertyOverridesIgnoreObsoleteSource() ); TestHelper - .GenerateMapper(source, TestHelperOptions.AllowInfoDiagnostics) + .GenerateMapper(source, TestHelperOptions.AllowDiagnostics) .Should() .HaveSingleMethodBody( """ @@ -280,7 +289,12 @@ public void MapPropertyOverridesIgnoreObsoleteSource() target.Ignored = source.Ignored; return target; """ - ); + ) + .HaveDiagnostic( + DiagnosticDescriptors.IgnoredSourceMemberExplicitlyMapped, + "The source member Ignored on A is ignored, but is also mapped by the MapPropertyAttribute" + ) + .HaveAssertedAllDiagnostics(); } [Fact] @@ -297,7 +311,7 @@ public void MapPropertyOverridesIgnoreObsoleteTarget() ); TestHelper - .GenerateMapper(source) + .GenerateMapper(source, TestHelperOptions.AllowDiagnostics) .Should() .HaveSingleMethodBody( """ @@ -306,7 +320,12 @@ public void MapPropertyOverridesIgnoreObsoleteTarget() target.Ignored = source.Ignored; return target; """ - ); + ) + .HaveDiagnostic( + DiagnosticDescriptors.IgnoredTargetMemberExplicitlyMapped, + "The target member Ignored on B is ignored, but is also mapped by the MapPropertyAttribute" + ) + .HaveAssertedAllDiagnostics(); } [Fact] @@ -331,7 +350,7 @@ class B ); TestHelper - .GenerateMapper(source) + .GenerateMapper(source, TestHelperOptions.AllowDiagnostics) .Should() .HaveSingleMethodBody( """ @@ -342,7 +361,12 @@ class B target.Value = source.Value; return target; """ - ); + ) + .HaveDiagnostic( + DiagnosticDescriptors.IgnoredTargetMemberExplicitlyMapped, + "The target member Ignored on B is ignored, but is also mapped by the MapPropertyAttribute" + ) + .HaveAssertedAllDiagnostics(); } [Fact] @@ -367,7 +391,7 @@ class B ); TestHelper - .GenerateMapper(source) + .GenerateMapper(source, TestHelperOptions.AllowDiagnostics) .Should() .HaveSingleMethodBody( """ @@ -378,6 +402,11 @@ class B target.Value = source.Value; return target; """ - ); + ) + .HaveDiagnostic( + DiagnosticDescriptors.IgnoredTargetMemberExplicitlyMapped, + "The target member Ignored on B is ignored, but is also mapped by the MapPropertyAttribute" + ) + .HaveAssertedAllDiagnostics(); } } diff --git a/test/Riok.Mapperly.Tests/Mapping/ObjectPropertyInitPropertyTest.cs b/test/Riok.Mapperly.Tests/Mapping/ObjectPropertyInitPropertyTest.cs index 1177e77a21..787bcd9f92 100644 --- a/test/Riok.Mapperly.Tests/Mapping/ObjectPropertyInitPropertyTest.cs +++ b/test/Riok.Mapperly.Tests/Mapping/ObjectPropertyInitPropertyTest.cs @@ -328,7 +328,7 @@ public void IgnoredTargetRequiredPropertyWithConfiguration() ); TestHelper - .GenerateMapper(source) + .GenerateMapper(source, TestHelperOptions.AllowDiagnostics) .Should() .HaveSingleMethodBody( """ @@ -339,7 +339,12 @@ public void IgnoredTargetRequiredPropertyWithConfiguration() target.IntValue = source.IntValue; return target; """ - ); + ) + .HaveDiagnostic( + DiagnosticDescriptors.IgnoredTargetMemberExplicitlyMapped, + "The target member StringValue on B is ignored, but is also mapped by the MapPropertyAttribute" + ) + .HaveAssertedAllDiagnostics(); } [Fact] @@ -358,7 +363,7 @@ public void IgnoredTargetInitPropertyWithConfiguration() ); TestHelper - .GenerateMapper(source) + .GenerateMapper(source, TestHelperOptions.AllowDiagnostics) .Should() .HaveSingleMethodBody( """ @@ -369,7 +374,12 @@ public void IgnoredTargetInitPropertyWithConfiguration() target.IntValue = source.IntValue; return target; """ - ); + ) + .HaveDiagnostic( + DiagnosticDescriptors.IgnoredTargetMemberExplicitlyMapped, + "The target member StringValue on B is ignored, but is also mapped by the MapPropertyAttribute" + ) + .HaveAssertedAllDiagnostics(); } [Fact]