diff --git a/docs/docs/configuration/mapper.md b/docs/docs/configuration/mapper.mdx
similarity index 58%
rename from docs/docs/configuration/mapper.md
rename to docs/docs/configuration/mapper.mdx
index 3a95a795c9..2e0febe4df 100644
--- a/docs/docs/configuration/mapper.md
+++ b/docs/docs/configuration/mapper.mdx
@@ -3,11 +3,14 @@ sidebar_position: 0
description: Define a mapper with Mapperly.
---
+import Tabs from '@theme/Tabs';
+import TabItem from '@theme/TabItem';
+
# Mapper configuration
The `MapperAttribute` provides options to customize the generated mapper class.
-## Copy behaviour
+## Copy behavior
By default, Mapperly does not create deep copies of objects to improve performance.
If an object can be directly assigned to the target, it will do so
@@ -56,6 +59,51 @@ public partial class CarMapper
}
```
+### Ignore obsolete members stratgey
+
+By default, mapperly will map source/target members marked with `ObsoleteAttribute`. This can be changed by setting the `IgnoreObsoleteMembersStrategy` of a method with `MapperIgnoreObsoleteMembersAttribute`, or by setting the `IgnoreObsoleteMembersStrategy` option of the `MapperAttribute`.
+
+| Name | Description |
+| ------ | ------------------------------------------------------------------------------- |
+| None | Will map members marked with the `Obsolete` attribute (default) |
+| Both | Ignores source and target members that are mapped with the `Obsolete` attribute |
+| Source | Ignores source members that are mapped with the `Obsolete` attribute |
+| Target | Ignores target members that are mapped with the `Obsolete` attribute |
+
+
+
+
+Sets the `IgnoreObsoleteMembersStrategy` for all methods within the mapper, by default it is `None` allowing obsolete source and target members to be mapped. This can be overriden by individual mapping methods using `MapperIgnoreObsoleteMembersAttribute`.
+
+```csharp
+// highlight-start
+[Mapper(IgnoreObsoleteMembersStrategy = IgnoreObsoleteMembersStrategy.Both)]
+// highlight-end
+public partial class CarMapper
+{
+ ...
+}
+```
+
+
+
+
+Method will use the provided ignore obsolete mapping strategy, otherwise the `MapperAttribute` property `IgnoreObsoleteMembersStrategy` will be used.
+
+```csharp
+[Mapper]
+public partial class CarMapper
+{
+ // highlight-start
+ [MapperIgnoreObsoleteMembers(IgnoreObsoleteMembersStrategy.Both)]
+ // highlight-end
+ public partial CarMakeDto MapMake(CarMake make);
+}
+```
+
+
+
+
### Property name mapping strategy
By default, property and field names are matched using a case sensitive strategy.
diff --git a/src/Riok.Mapperly.Abstractions/IgnoreObsoleteMembersStrategy.cs b/src/Riok.Mapperly.Abstractions/IgnoreObsoleteMembersStrategy.cs
new file mode 100644
index 0000000000..ce9d0fbe0d
--- /dev/null
+++ b/src/Riok.Mapperly.Abstractions/IgnoreObsoleteMembersStrategy.cs
@@ -0,0 +1,30 @@
+namespace Riok.Mapperly.Abstractions;
+
+///
+/// Defines the strategy to use when mapping members marked with .
+/// Note that will always map marked members,
+/// even if they are ignored.
+///
+[Flags]
+public enum IgnoreObsoleteMembersStrategy
+{
+ ///
+ /// Maps marked members.
+ ///
+ None = 0,
+
+ ///
+ /// Will not map marked source or target members.
+ ///
+ Both = ~None,
+
+ ///
+ /// Ignores source marked members.
+ ///
+ Source = 1 << 0,
+
+ ///
+ /// Ignores target marked members.
+ ///
+ Target = 1 << 1,
+}
diff --git a/src/Riok.Mapperly.Abstractions/MapperAttribute.cs b/src/Riok.Mapperly.Abstractions/MapperAttribute.cs
index 21cb2f386c..6aa1f5e792 100644
--- a/src/Riok.Mapperly.Abstractions/MapperAttribute.cs
+++ b/src/Riok.Mapperly.Abstractions/MapperAttribute.cs
@@ -70,4 +70,10 @@ public sealed class MapperAttribute : Attribute
/// to keep track of and reuse existing target object instances.
///
public bool UseReferenceHandling { get; set; }
+
+ ///
+ /// The ignore obsolete attribute strategy. Determines how marked members are mapped.
+ /// Defaults to .
+ ///
+ public IgnoreObsoleteMembersStrategy IgnoreObsoleteMembersStrategy { get; set; } = IgnoreObsoleteMembersStrategy.None;
}
diff --git a/src/Riok.Mapperly.Abstractions/MapperIgnoreObsoleteMembersAttribute.cs b/src/Riok.Mapperly.Abstractions/MapperIgnoreObsoleteMembersAttribute.cs
new file mode 100644
index 0000000000..e798fb3956
--- /dev/null
+++ b/src/Riok.Mapperly.Abstractions/MapperIgnoreObsoleteMembersAttribute.cs
@@ -0,0 +1,22 @@
+namespace Riok.Mapperly.Abstractions;
+
+///
+/// Specifies options for obsolete ignoring strategy.
+///
+[AttributeUsage(AttributeTargets.Method)]
+public sealed class MapperIgnoreObsoleteMembersAttribute : Attribute
+{
+ ///
+ /// Specifies options for obsolete ignoring strategy.
+ ///
+ /// The strategy to be used to map marked members. Defaults to .
+ public MapperIgnoreObsoleteMembersAttribute(IgnoreObsoleteMembersStrategy ignoreObsoleteStrategy = IgnoreObsoleteMembersStrategy.Both)
+ {
+ IgnoreObsoleteStrategy = ignoreObsoleteStrategy;
+ }
+
+ ///
+ /// The strategy used to map marked members.
+ ///
+ public IgnoreObsoleteMembersStrategy IgnoreObsoleteStrategy { get; }
+}
diff --git a/src/Riok.Mapperly.Abstractions/PublicAPI.Shipped.txt b/src/Riok.Mapperly.Abstractions/PublicAPI.Shipped.txt
index 2c2d6862f5..0bf47f01ee 100644
--- a/src/Riok.Mapperly.Abstractions/PublicAPI.Shipped.txt
+++ b/src/Riok.Mapperly.Abstractions/PublicAPI.Shipped.txt
@@ -3,6 +3,11 @@
Riok.Mapperly.Abstractions.EnumMappingStrategy
Riok.Mapperly.Abstractions.EnumMappingStrategy.ByName = 1 -> Riok.Mapperly.Abstractions.EnumMappingStrategy
Riok.Mapperly.Abstractions.EnumMappingStrategy.ByValue = 0 -> Riok.Mapperly.Abstractions.EnumMappingStrategy
+Riok.Mapperly.Abstractions.IgnoreObsoleteMembersStrategy
+Riok.Mapperly.Abstractions.IgnoreObsoleteMembersStrategy.Both = -1 -> Riok.Mapperly.Abstractions.IgnoreObsoleteMembersStrategy
+Riok.Mapperly.Abstractions.IgnoreObsoleteMembersStrategy.None = 0 -> Riok.Mapperly.Abstractions.IgnoreObsoleteMembersStrategy
+Riok.Mapperly.Abstractions.IgnoreObsoleteMembersStrategy.Source = 1 -> Riok.Mapperly.Abstractions.IgnoreObsoleteMembersStrategy
+Riok.Mapperly.Abstractions.IgnoreObsoleteMembersStrategy.Target = 2 -> Riok.Mapperly.Abstractions.IgnoreObsoleteMembersStrategy
Riok.Mapperly.Abstractions.MapEnumAttribute
Riok.Mapperly.Abstractions.MapEnumAttribute.IgnoreCase.get -> bool
Riok.Mapperly.Abstractions.MapEnumAttribute.IgnoreCase.set -> void
@@ -19,6 +24,8 @@ Riok.Mapperly.Abstractions.MapperAttribute.EnumMappingIgnoreCase.get -> bool
Riok.Mapperly.Abstractions.MapperAttribute.EnumMappingIgnoreCase.set -> void
Riok.Mapperly.Abstractions.MapperAttribute.EnumMappingStrategy.get -> Riok.Mapperly.Abstractions.EnumMappingStrategy
Riok.Mapperly.Abstractions.MapperAttribute.EnumMappingStrategy.set -> void
+Riok.Mapperly.Abstractions.MapperAttribute.IgnoreObsoleteMembersStrategy.get -> Riok.Mapperly.Abstractions.IgnoreObsoleteMembersStrategy
+Riok.Mapperly.Abstractions.MapperAttribute.IgnoreObsoleteMembersStrategy.set -> void
Riok.Mapperly.Abstractions.MapperAttribute.MapperAttribute() -> void
Riok.Mapperly.Abstractions.MapperAttribute.PropertyNameMappingStrategy.get -> Riok.Mapperly.Abstractions.PropertyNameMappingStrategy
Riok.Mapperly.Abstractions.MapperAttribute.PropertyNameMappingStrategy.set -> void
@@ -33,6 +40,9 @@ Riok.Mapperly.Abstractions.MapperConstructorAttribute.MapperConstructorAttribute
Riok.Mapperly.Abstractions.MapperIgnoreAttribute
Riok.Mapperly.Abstractions.MapperIgnoreAttribute.MapperIgnoreAttribute(string! target) -> void
Riok.Mapperly.Abstractions.MapperIgnoreAttribute.Target.get -> string!
+Riok.Mapperly.Abstractions.MapperIgnoreObsoleteMembersAttribute
+Riok.Mapperly.Abstractions.MapperIgnoreObsoleteMembersAttribute.IgnoreObsoleteStrategy.get -> Riok.Mapperly.Abstractions.IgnoreObsoleteMembersStrategy
+Riok.Mapperly.Abstractions.MapperIgnoreObsoleteMembersAttribute.MapperIgnoreObsoleteMembersAttribute(Riok.Mapperly.Abstractions.IgnoreObsoleteMembersStrategy ignoreObsoleteStrategy = (Riok.Mapperly.Abstractions.IgnoreObsoleteMembersStrategy)-1) -> void
Riok.Mapperly.Abstractions.MapperIgnoreSourceAttribute
Riok.Mapperly.Abstractions.MapperIgnoreSourceAttribute.MapperIgnoreSourceAttribute(string! source) -> void
Riok.Mapperly.Abstractions.MapperIgnoreSourceAttribute.Source.get -> string!
diff --git a/src/Riok.Mapperly/Configuration/MapperConfiguration.cs b/src/Riok.Mapperly/Configuration/MapperConfiguration.cs
index 5bab50920f..bd5f65c54d 100644
--- a/src/Riok.Mapperly/Configuration/MapperConfiguration.cs
+++ b/src/Riok.Mapperly/Configuration/MapperConfiguration.cs
@@ -23,7 +23,12 @@ public MapperConfiguration(SymbolAccessor symbolAccessor, ISymbol mapperSymbol)
Array.Empty(),
Array.Empty()
),
- new PropertiesMappingConfiguration(Array.Empty(), Array.Empty(), Array.Empty()),
+ new PropertiesMappingConfiguration(
+ Array.Empty(),
+ Array.Empty(),
+ Array.Empty(),
+ Mapper.IgnoreObsoleteMembersStrategy
+ ),
Array.Empty()
);
}
@@ -66,7 +71,11 @@ private PropertiesMappingConfiguration BuildPropertiesConfig(IMethodSymbol metho
.WhereNotNull()
.ToList();
var explicitMappings = _dataAccessor.Access(method).ToList();
- return new PropertiesMappingConfiguration(ignoredSourceProperties, ignoredTargetProperties, explicitMappings);
+ var ignoreObsolete = _dataAccessor.Access(method).FirstOrDefault() is not { } methodIgnore
+ ? _defaultConfiguration.Properties.IgnoreObsoleteMembersStrategy
+ : methodIgnore.IgnoreObsoleteStrategy;
+
+ return new PropertiesMappingConfiguration(ignoredSourceProperties, ignoredTargetProperties, explicitMappings, ignoreObsolete);
}
private EnumMappingConfiguration BuildEnumConfig(MappingConfigurationReference configRef)
diff --git a/src/Riok.Mapperly/Configuration/PropertiesMappingConfiguration.cs b/src/Riok.Mapperly/Configuration/PropertiesMappingConfiguration.cs
index ccd3bca757..f991cc536a 100644
--- a/src/Riok.Mapperly/Configuration/PropertiesMappingConfiguration.cs
+++ b/src/Riok.Mapperly/Configuration/PropertiesMappingConfiguration.cs
@@ -1,7 +1,10 @@
+using Riok.Mapperly.Abstractions;
+
namespace Riok.Mapperly.Configuration;
public record PropertiesMappingConfiguration(
IReadOnlyCollection IgnoredSources,
IReadOnlyCollection IgnoredTargets,
- IReadOnlyCollection ExplicitMappings
+ IReadOnlyCollection ExplicitMappings,
+ IgnoreObsoleteMembersStrategy IgnoreObsoleteMembersStrategy
);
diff --git a/src/Riok.Mapperly/Descriptors/MappingBodyBuilders/BuilderContext/MembersMappingBuilderContext.cs b/src/Riok.Mapperly/Descriptors/MappingBodyBuilders/BuilderContext/MembersMappingBuilderContext.cs
index cb09e44455..c635e30708 100644
--- a/src/Riok.Mapperly/Descriptors/MappingBodyBuilders/BuilderContext/MembersMappingBuilderContext.cs
+++ b/src/Riok.Mapperly/Descriptors/MappingBodyBuilders/BuilderContext/MembersMappingBuilderContext.cs
@@ -1,3 +1,4 @@
+using Riok.Mapperly.Abstractions;
using Riok.Mapperly.Configuration;
using Riok.Mapperly.Descriptors.Mappings;
using Riok.Mapperly.Diagnostics;
@@ -21,11 +22,17 @@ protected MembersMappingBuilderContext(MappingBuilderContext builderContext, T m
{
BuilderContext = builderContext;
Mapping = mapping;
+ MemberConfigsByRootTargetName = GetMemberConfigurations();
_unmappedSourceMemberNames = GetSourceMemberNames();
TargetMembers = GetTargetMembers();
- IgnoredSourceMemberNames = builderContext.Configuration.Properties.IgnoredSources;
+ IgnoredSourceMemberNames = builderContext.Configuration.Properties.IgnoredSources
+ .Concat(GetIgnoredObsoleteSourceMembers())
+ .ToHashSet();
+ var ignoredTargetMemberNames = builderContext.Configuration.Properties.IgnoredTargets
+ .Concat(GetIgnoredObsoleteTargetMembers())
+ .ToHashSet();
_ignoredUnmatchedSourceMemberNames = InitIgnoredUnmatchedProperties(IgnoredSourceMemberNames, _unmappedSourceMemberNames);
_ignoredUnmatchedTargetMemberNames = InitIgnoredUnmatchedProperties(
@@ -34,9 +41,13 @@ protected MembersMappingBuilderContext(MappingBuilderContext builderContext, T m
);
_unmappedSourceMemberNames.ExceptWith(IgnoredSourceMemberNames);
- TargetMembers.RemoveRange(builderContext.Configuration.Properties.IgnoredTargets);
MemberConfigsByRootTargetName = GetMemberConfigurations();
+
+ // 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);
+ TargetMembers.RemoveRange(ignoredTargetMemberNames);
}
public MappingBuilderContext BuilderContext { get; }
@@ -66,6 +77,32 @@ private HashSet InitIgnoredUnmatchedProperties(IEnumerable allPr
return unmatched;
}
+ private IEnumerable GetIgnoredObsoleteTargetMembers()
+ {
+ var obsoleteStrategy = BuilderContext.Configuration.Properties.IgnoreObsoleteMembersStrategy;
+
+ if (!obsoleteStrategy.HasFlag(IgnoreObsoleteMembersStrategy.Target))
+ return Enumerable.Empty();
+
+ return BuilderContext.SymbolAccessor
+ .GetAllAccessibleMappableMembers(Mapping.TargetType)
+ .Where(x => BuilderContext.SymbolAccessor.HasAttribute(x.MemberSymbol))
+ .Select(x => x.Name);
+ }
+
+ private IEnumerable GetIgnoredObsoleteSourceMembers()
+ {
+ var obsoleteStrategy = BuilderContext.Configuration.Properties.IgnoreObsoleteMembersStrategy;
+
+ if (!obsoleteStrategy.HasFlag(IgnoreObsoleteMembersStrategy.Source))
+ return Enumerable.Empty();
+
+ return BuilderContext.SymbolAccessor
+ .GetAllAccessibleMappableMembers(Mapping.SourceType)
+ .Where(x => BuilderContext.SymbolAccessor.HasAttribute(x.MemberSymbol))
+ .Select(x => x.Name);
+ }
+
private HashSet GetSourceMemberNames()
{
return BuilderContext.SymbolAccessor.GetAllAccessibleMappableMembers(Mapping.SourceType).Select(x => x.Name).ToHashSet();
diff --git a/src/Riok.Mapperly/Symbols/FieldMember.cs b/src/Riok.Mapperly/Symbols/FieldMember.cs
index a2c5bc755c..67065063b2 100644
--- a/src/Riok.Mapperly/Symbols/FieldMember.cs
+++ b/src/Riok.Mapperly/Symbols/FieldMember.cs
@@ -14,6 +14,7 @@ public FieldMember(IFieldSymbol fieldSymbol)
public string Name => _fieldSymbol.Name;
public ITypeSymbol Type => _fieldSymbol.Type;
+ public ISymbol MemberSymbol => _fieldSymbol;
public bool IsNullable => _fieldSymbol.NullableAnnotation == NullableAnnotation.Annotated || Type.IsNullable();
public bool IsIndexer => false;
public bool CanGet => !_fieldSymbol.IsReadOnly && _fieldSymbol.IsAccessible();
diff --git a/src/Riok.Mapperly/Symbols/IMappableMember.cs b/src/Riok.Mapperly/Symbols/IMappableMember.cs
index bebdf5fee5..9eb6a873ee 100644
--- a/src/Riok.Mapperly/Symbols/IMappableMember.cs
+++ b/src/Riok.Mapperly/Symbols/IMappableMember.cs
@@ -12,6 +12,8 @@ public interface IMappableMember
ITypeSymbol Type { get; }
+ ISymbol MemberSymbol { get; }
+
bool IsNullable { get; }
bool IsIndexer { get; }
diff --git a/src/Riok.Mapperly/Symbols/PropertyMember.cs b/src/Riok.Mapperly/Symbols/PropertyMember.cs
index 1a825ef584..4180059569 100644
--- a/src/Riok.Mapperly/Symbols/PropertyMember.cs
+++ b/src/Riok.Mapperly/Symbols/PropertyMember.cs
@@ -14,6 +14,7 @@ internal PropertyMember(IPropertySymbol propertySymbol)
public string Name => _propertySymbol.Name;
public ITypeSymbol Type => _propertySymbol.Type;
+ public ISymbol MemberSymbol => _propertySymbol;
public bool IsNullable => _propertySymbol.NullableAnnotation == NullableAnnotation.Annotated || Type.IsNullable();
public bool IsIndexer => _propertySymbol.IsIndexer;
public bool CanGet => !_propertySymbol.IsWriteOnly && _propertySymbol.GetMethod?.IsAccessible() != false;
diff --git a/test/Riok.Mapperly.IntegrationTests/Dto/TestObjectDto.cs b/test/Riok.Mapperly.IntegrationTests/Dto/TestObjectDto.cs
index 2e0c554ea2..574d0fcb92 100644
--- a/test/Riok.Mapperly.IntegrationTests/Dto/TestObjectDto.cs
+++ b/test/Riok.Mapperly.IntegrationTests/Dto/TestObjectDto.cs
@@ -111,6 +111,9 @@ public TestObjectDto(int ctorValue, int unknownValue = 10, int ctorValue2 = 100)
public int IgnoredIntValue { get; set; }
+ [Obsolete]
+ public int IgnoredObsoleteValue { get; set; }
+
public DateOnly DateTimeValueTargetDateOnly { get; set; }
public TimeOnly DateTimeValueTargetTimeOnly { get; set; }
diff --git a/test/Riok.Mapperly.IntegrationTests/Mapper/DeepCloningMapper.cs b/test/Riok.Mapperly.IntegrationTests/Mapper/DeepCloningMapper.cs
index 937db5f544..b7573d4e79 100644
--- a/test/Riok.Mapperly.IntegrationTests/Mapper/DeepCloningMapper.cs
+++ b/test/Riok.Mapperly.IntegrationTests/Mapper/DeepCloningMapper.cs
@@ -11,6 +11,7 @@ public static partial class DeepCloningMapper
[MapperIgnoreSource(nameof(TestObject.IgnoredIntValue))]
[MapperIgnoreSource(nameof(TestObject.IgnoredStringValue))]
[MapperIgnoreSource(nameof(TestObject.ImmutableHashSetValue))]
+ [MapperIgnoreObsoleteMembers]
public static partial TestObject Copy(TestObject src);
}
}
diff --git a/test/Riok.Mapperly.IntegrationTests/Mapper/ProjectionMapper.cs b/test/Riok.Mapperly.IntegrationTests/Mapper/ProjectionMapper.cs
index 723a75792c..6469e0066f 100644
--- a/test/Riok.Mapperly.IntegrationTests/Mapper/ProjectionMapper.cs
+++ b/test/Riok.Mapperly.IntegrationTests/Mapper/ProjectionMapper.cs
@@ -19,6 +19,7 @@ public static partial class ProjectionMapper
[MapperIgnoreTarget(nameof(TestObjectDtoProjection.IgnoredIntValue))]
[MapperIgnoreSource(nameof(TestObjectProjection.IgnoredStringValue))]
[MapProperty(nameof(TestObjectProjection.RenamedStringValue), nameof(TestObjectDtoProjection.RenamedStringValue2))]
+ [MapperIgnoreObsoleteMembers]
private static partial TestObjectDtoProjection ProjectToDto(this TestObjectProjection testObject);
private static TestObjectDtoManuallyMappedProjection? MapManual(string str)
diff --git a/test/Riok.Mapperly.IntegrationTests/Mapper/StaticTestMapper.cs b/test/Riok.Mapperly.IntegrationTests/Mapper/StaticTestMapper.cs
index 84c41bbf12..f4d3d458cb 100644
--- a/test/Riok.Mapperly.IntegrationTests/Mapper/StaticTestMapper.cs
+++ b/test/Riok.Mapperly.IntegrationTests/Mapper/StaticTestMapper.cs
@@ -29,6 +29,7 @@ public static partial class StaticTestMapper
[MapperIgnoreSource(nameof(TestObject.IgnoredIntValue))]
[MapperIgnoreTarget(nameof(TestObjectDto.IgnoredStringValue))]
+ [MapperIgnoreObsoleteMembers]
public static partial TestObjectDto MapToDtoExt(this TestObject src);
public static TestObjectDto MapToDto(TestObject src)
@@ -53,6 +54,7 @@ public static TestObjectDto MapToDto(TestObject src)
)]
[MapperIgnoreSource(nameof(TestObject.IgnoredIntValue))]
[MapperIgnoreTarget(nameof(TestObjectDto.IgnoredIntValue))]
+ [MapperIgnoreObsoleteMembers]
private static partial TestObjectDto MapToDtoInternal(TestObject testObject);
// disable obsolete warning, as the obsolete attribute should still be tested.
diff --git a/test/Riok.Mapperly.IntegrationTests/Mapper/TestMapper.cs b/test/Riok.Mapperly.IntegrationTests/Mapper/TestMapper.cs
index 05aa8f2b9c..d913d5a542 100644
--- a/test/Riok.Mapperly.IntegrationTests/Mapper/TestMapper.cs
+++ b/test/Riok.Mapperly.IntegrationTests/Mapper/TestMapper.cs
@@ -47,6 +47,7 @@ public TestObjectDto MapToDto(TestObject src)
nameof(TestObject.NullableUnflatteningIdValue),
nameof(TestObjectDto.NullableUnflattening) + "." + nameof(TestObjectDto.NullableUnflattening.IdValue)
)]
+ [MapperIgnoreObsoleteMembers]
private partial TestObjectDto MapToDtoInternal(TestObject testObject);
// disable obsolete warning, as the obsolete attribute should still be tested.
diff --git a/test/Riok.Mapperly.Tests/Mapping/IgnoreObsoleteTest.cs b/test/Riok.Mapperly.Tests/Mapping/IgnoreObsoleteTest.cs
new file mode 100644
index 0000000000..1dd5768588
--- /dev/null
+++ b/test/Riok.Mapperly.Tests/Mapping/IgnoreObsoleteTest.cs
@@ -0,0 +1,379 @@
+using Riok.Mapperly.Abstractions;
+using Riok.Mapperly.Diagnostics;
+
+namespace Riok.Mapperly.Tests.Mapping;
+
+[UsesVerify]
+public class IgnoreObsoleteTest
+{
+ private const string _classA = """
+ class A
+ {
+ public int Value { get; set; }
+
+ [Obsolete]
+ public int Ignored { get; set; }
+ }
+ """;
+
+ private const string _classB = """
+ class B
+ {
+ public int Value { get; set; }
+
+ [Obsolete]
+ public int Ignored { get; set; }
+ }
+ """;
+
+ [Fact]
+ public void ClassAttributeIgnoreObsoleteNone()
+ {
+ var source = TestSourceBuilder.Mapping(
+ "A",
+ "B",
+ TestSourceBuilderOptions.WithIgnoreObsolete(IgnoreObsoleteMembersStrategy.None),
+ _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 ClassAttributeIgnoreObsoleteBoth()
+ {
+ var source = TestSourceBuilder.Mapping(
+ "A",
+ "B",
+ TestSourceBuilderOptions.WithIgnoreObsolete(IgnoreObsoleteMembersStrategy.Both),
+ _classA,
+ _classB
+ );
+
+ TestHelper
+ .GenerateMapper(source, TestHelperOptions.AllowInfoDiagnostics)
+ .Should()
+ .HaveSingleMethodBody(
+ """
+ var target = new global::B();
+ target.Value = source.Value;
+ return target;
+ """
+ );
+ }
+
+ [Fact]
+ public void ClassAttributeIgnoreSourceShouldDiagnostic()
+ {
+ var source = TestSourceBuilder.Mapping(
+ "A",
+ "B",
+ TestSourceBuilderOptions.WithIgnoreObsolete(IgnoreObsoleteMembersStrategy.Source),
+ _classA,
+ _classB
+ );
+
+ TestHelper
+ .GenerateMapper(source, TestHelperOptions.AllowInfoDiagnostics)
+ .Should()
+ .HaveSingleMethodBody(
+ """
+ var target = new global::B();
+ target.Value = source.Value;
+ return target;
+ """
+ )
+ .HaveDiagnostic(DiagnosticDescriptors.SourceMemberNotFound);
+ }
+
+ [Fact]
+ public void ClassAttributeIgnoreTargetShouldDiagnostic()
+ {
+ var source = TestSourceBuilder.Mapping(
+ "A",
+ "B",
+ TestSourceBuilderOptions.WithIgnoreObsolete(IgnoreObsoleteMembersStrategy.Target),
+ _classA,
+ _classB
+ );
+
+ TestHelper
+ .GenerateMapper(source, TestHelperOptions.AllowInfoDiagnostics)
+ .Should()
+ .HaveSingleMethodBody(
+ """
+ var target = new global::B();
+ target.Value = source.Value;
+ return target;
+ """
+ )
+ .HaveDiagnostic(DiagnosticDescriptors.SourceMemberNotMapped);
+ }
+
+ [Fact]
+ public void MethodAttributeIgnoreObsoleteNone()
+ {
+ var source = TestSourceBuilder.MapperWithBodyAndTypes(
+ "[MapperIgnoreObsoleteMembers(IgnoreObsoleteMembersStrategy.None)] 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 MethodAttributeIgnoreObsoleteBoth()
+ {
+ var source = TestSourceBuilder.MapperWithBodyAndTypes("[MapperIgnoreObsoleteMembers] partial B Map(A source);", _classA, _classB);
+
+ TestHelper
+ .GenerateMapper(source, TestHelperOptions.AllowInfoDiagnostics)
+ .Should()
+ .HaveSingleMethodBody(
+ """
+ var target = new global::B();
+ target.Value = source.Value;
+ return target;
+ """
+ );
+ }
+
+ [Fact]
+ public void MethodAttributeIgnoreObsoleteSourceShouldDiagnostic()
+ {
+ var source = TestSourceBuilder.MapperWithBodyAndTypes(
+ "[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;
+ return target;
+ """
+ )
+ .HaveDiagnostic(DiagnosticDescriptors.SourceMemberNotFound);
+ }
+
+ [Fact]
+ public void MethodAttributeIgnoreObsoleteTargetShouldDiagnostic()
+ {
+ var source = TestSourceBuilder.MapperWithBodyAndTypes(
+ "[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;
+ return target;
+ """
+ )
+ .HaveDiagnostic(DiagnosticDescriptors.SourceMemberNotMapped);
+ }
+
+ [Fact]
+ public void MethodAttributeOverridesClassAttribute()
+ {
+ var source = TestSourceBuilder.MapperWithBodyAndTypes(
+ "[MapperIgnoreObsoleteMembers(IgnoreObsoleteMembersStrategy.None)] partial B Map(A source);",
+ TestSourceBuilderOptions.WithIgnoreObsolete(IgnoreObsoleteMembersStrategy.Both),
+ _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 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)
+ .Should()
+ .HaveSingleMethodBody(
+ """
+ var target = new global::B();
+ target.Value = source.Value;
+ target.Ignored = source.Ignored;
+ return target;
+ """
+ );
+ }
+
+ [Fact]
+ public void MapInitPropertyWhenIgnoreObsoleteTarget()
+ {
+ var source = TestSourceBuilder.MapperWithBodyAndTypes(
+ """
+ [MapProperty("Ignored", "Ignored")]
+ [MapperIgnoreObsoleteMembers(IgnoreObsoleteMembersStrategy.Target)]
+ partial B Map(A source);
+ """,
+ _classA,
+ """
+ class B
+ {
+ public int Value { get; set; }
+
+ [Obsolete]
+ public int Ignored { get; init; }
+ }
+ """
+ );
+
+ TestHelper
+ .GenerateMapper(source)
+ .Should()
+ .HaveSingleMethodBody(
+ """
+ var target = new global::B()
+ {
+ Ignored = source.Ignored
+ };
+ target.Value = source.Value;
+ return target;
+ """
+ );
+ }
+
+ [Fact]
+ public void MapRequiredPropertyWhenIgnoreObsoleteTarget()
+ {
+ var source = TestSourceBuilder.MapperWithBodyAndTypes(
+ """
+ [MapProperty("Ignored", "Ignored")]
+ [MapperIgnoreObsoleteMembers(IgnoreObsoleteMembersStrategy.Target)]
+ partial B Map(A source);
+ """,
+ _classA,
+ """
+ class B
+ {
+ public int Value { get; set; }
+
+ [Obsolete]
+ public required int Ignored { get; set; }
+ }
+ """
+ );
+
+ TestHelper
+ .GenerateMapper(source)
+ .Should()
+ .HaveSingleMethodBody(
+ """
+ var target = new global::B()
+ {
+ Ignored = source.Ignored
+ };
+ target.Value = source.Value;
+ return target;
+ """
+ );
+ }
+}
diff --git a/test/Riok.Mapperly.Tests/Mapping/ObjectPropertyInitPropertyTest.cs b/test/Riok.Mapperly.Tests/Mapping/ObjectPropertyInitPropertyTest.cs
index 7aa3fe1ff5..5f678290f8 100644
--- a/test/Riok.Mapperly.Tests/Mapping/ObjectPropertyInitPropertyTest.cs
+++ b/test/Riok.Mapperly.Tests/Mapping/ObjectPropertyInitPropertyTest.cs
@@ -312,6 +312,66 @@ public void RequiredProperty()
);
}
+ [Fact]
+ public void IgnoredTargetRequiredPropertyWithConfiguration()
+ {
+ var source = TestSourceBuilder.MapperWithBodyAndTypes(
+ """
+ [MapProperty("StringValue", "StringValue")]
+ [MapperIgnoreTarget("StringValue")]
+ partial B Map(A source);
+ """,
+ "A",
+ "B",
+ "class A { public string StringValue { get; init; } public int IntValue { get; set; } }",
+ "class B { public required string StringValue { get; set; } public int IntValue { get; set; } }"
+ );
+
+ TestHelper
+ .GenerateMapper(source)
+ .Should()
+ .HaveSingleMethodBody(
+ """
+ var target = new global::B()
+ {
+ StringValue = source.StringValue
+ };
+ target.IntValue = source.IntValue;
+ return target;
+ """
+ );
+ }
+
+ [Fact]
+ public void IgnoredTargetInitPropertyWithConfiguration()
+ {
+ var source = TestSourceBuilder.MapperWithBodyAndTypes(
+ """
+ [MapProperty("StringValue", "StringValue")]
+ [MapperIgnoreTarget("StringValue")]
+ partial B Map(A source);
+ """,
+ "A",
+ "B",
+ "class A { public string StringValue { get; init; } public int IntValue { get; set; } }",
+ "class B { public string StringValue { get; init; } public int IntValue { get; set; } }"
+ );
+
+ TestHelper
+ .GenerateMapper(source)
+ .Should()
+ .HaveSingleMethodBody(
+ """
+ var target = new global::B()
+ {
+ StringValue = source.StringValue
+ };
+ target.IntValue = source.IntValue;
+ return target;
+ """
+ );
+ }
+
[Fact]
public Task RequiredPropertySourceNotFoundShouldDiagnostic()
{
diff --git a/test/Riok.Mapperly.Tests/TestSourceBuilder.cs b/test/Riok.Mapperly.Tests/TestSourceBuilder.cs
index 724cc92778..bee56a2910 100644
--- a/test/Riok.Mapperly.Tests/TestSourceBuilder.cs
+++ b/test/Riok.Mapperly.Tests/TestSourceBuilder.cs
@@ -85,6 +85,7 @@ private static string BuildAttribute(TestSourceBuilderOptions options)
Attribute(options.PropertyNameMappingStrategy),
Attribute(options.EnumMappingStrategy),
Attribute(options.EnumMappingIgnoreCase),
+ Attribute(options.IgnoreObsoleteMembersStrategy),
};
return $"[Mapper({string.Join(", ", attrs)})]";
diff --git a/test/Riok.Mapperly.Tests/TestSourceBuilderOptions.cs b/test/Riok.Mapperly.Tests/TestSourceBuilderOptions.cs
index 6fbbf2256a..fcc0c8684c 100644
--- a/test/Riok.Mapperly.Tests/TestSourceBuilderOptions.cs
+++ b/test/Riok.Mapperly.Tests/TestSourceBuilderOptions.cs
@@ -11,13 +11,17 @@ public record TestSourceBuilderOptions(
PropertyNameMappingStrategy PropertyNameMappingStrategy = PropertyNameMappingStrategy.CaseSensitive,
MappingConversionType EnabledConversions = MappingConversionType.All,
EnumMappingStrategy EnumMappingStrategy = EnumMappingStrategy.ByValue,
- bool EnumMappingIgnoreCase = false
+ bool EnumMappingIgnoreCase = false,
+ IgnoreObsoleteMembersStrategy IgnoreObsoleteMembersStrategy = IgnoreObsoleteMembersStrategy.None
)
{
public static readonly TestSourceBuilderOptions Default = new();
public static readonly TestSourceBuilderOptions WithDeepCloning = new(UseDeepCloning: true);
public static readonly TestSourceBuilderOptions WithReferenceHandling = new(UseReferenceHandling: true);
+ public static TestSourceBuilderOptions WithIgnoreObsolete(IgnoreObsoleteMembersStrategy ignoreObsoleteStrategy) =>
+ new(IgnoreObsoleteMembersStrategy: ignoreObsoleteStrategy);
+
public static TestSourceBuilderOptions WithDisabledMappingConversion(params MappingConversionType[] conversionTypes)
{
var enabled = MappingConversionType.All;