diff --git a/docs/docs/configuration/mapper.mdx b/docs/docs/configuration/mapper.mdx
index 558a7bb485..9bd84445aa 100644
--- a/docs/docs/configuration/mapper.mdx
+++ b/docs/docs/configuration/mapper.mdx
@@ -68,6 +68,32 @@ public partial class CarMapper
}
```
+#### Ignore a memeber at definition
+
+To ignore a property or field at definition, the `MapperIgnoreAttribute` can be used.
+
+```csharp
+public partial class CarMapper
+{
+ public partial CarDto ToDto(Car car);
+}
+
+public class Car
+{
+ // highlight-start
+ [MapperIgnore]
+ // highlight-end
+ public int Id { get; set; }
+
+ public string ModelName { get; set; }
+}
+
+public class CarDto
+{
+ public string ModelName { get; set; }
+}
+```
+
#### Ignore obsolete members
By default, Mapperly will map source/target members marked with `ObsoleteAttribute`.
diff --git a/src/Riok.Mapperly.Abstractions/MapperIgnoreAttribute.cs b/src/Riok.Mapperly.Abstractions/MapperIgnoreAttribute.cs
new file mode 100644
index 0000000000..2834cedbb7
--- /dev/null
+++ b/src/Riok.Mapperly.Abstractions/MapperIgnoreAttribute.cs
@@ -0,0 +1,10 @@
+using System.Diagnostics;
+
+namespace Riok.Mapperly.Abstractions;
+
+///
+/// Ignores a member from the mapping.
+///
+[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
+[Conditional("MAPPERLY_ABSTRACTIONS_SCOPE_RUNTIME")]
+public sealed class MapperIgnoreAttribute : Attribute;
diff --git a/src/Riok.Mapperly.Abstractions/PublicAPI.Shipped.txt b/src/Riok.Mapperly.Abstractions/PublicAPI.Shipped.txt
index bca69e392d..f1caebc197 100644
--- a/src/Riok.Mapperly.Abstractions/PublicAPI.Shipped.txt
+++ b/src/Riok.Mapperly.Abstractions/PublicAPI.Shipped.txt
@@ -42,6 +42,8 @@ Riok.Mapperly.Abstractions.MapperConstructorAttribute.MapperConstructorAttribute
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.MapperIgnoreAttribute
+Riok.Mapperly.Abstractions.MapperIgnoreAttribute.MapperIgnoreAttribute() -> 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/Descriptors/MappingBodyBuilders/BuilderContext/MembersMappingBuilderContext.cs b/src/Riok.Mapperly/Descriptors/MappingBodyBuilders/BuilderContext/MembersMappingBuilderContext.cs
index 643e6c4dbf..b05e50da51 100644
--- a/src/Riok.Mapperly/Descriptors/MappingBodyBuilders/BuilderContext/MembersMappingBuilderContext.cs
+++ b/src/Riok.Mapperly/Descriptors/MappingBodyBuilders/BuilderContext/MembersMappingBuilderContext.cs
@@ -34,10 +34,12 @@ protected MembersMappingBuilderContext(MappingBuilderContext builderContext, T m
TargetMembers = GetTargetMembers();
IgnoredSourceMemberNames = builderContext
- .Configuration.Members.IgnoredSources.Concat(GetIgnoredObsoleteSourceMembers())
+ .Configuration.Members.IgnoredSources.Concat(GetIgnoredSourceMembers())
+ .Concat(GetIgnoredObsoleteSourceMembers())
.ToHashSet();
var ignoredTargetMemberNames = builderContext
- .Configuration.Members.IgnoredTargets.Concat(GetIgnoredObsoleteTargetMembers())
+ .Configuration.Members.IgnoredTargets.Concat(GetIgnoredTargetMembers())
+ .Concat(GetIgnoredObsoleteTargetMembers())
.ToHashSet();
_ignoredUnmatchedSourceMemberNames = InitIgnoredUnmatchedProperties(IgnoredSourceMemberNames, _unmappedSourceMemberNames);
@@ -143,6 +145,22 @@ private IEnumerable GetIgnoredObsoleteSourceMembers()
.Select(x => x.Name);
}
+ private IEnumerable GetIgnoredTargetMembers()
+ {
+ return BuilderContext
+ .SymbolAccessor.GetAllAccessibleMappableMembers(Mapping.TargetType)
+ .Where(x => BuilderContext.SymbolAccessor.HasAttribute(x.MemberSymbol))
+ .Select(x => x.Name);
+ }
+
+ private IEnumerable GetIgnoredSourceMembers()
+ {
+ 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/test/Riok.Mapperly.IntegrationTests/Dto/TestObjectDto.cs b/test/Riok.Mapperly.IntegrationTests/Dto/TestObjectDto.cs
index 7a391aa5fc..a92c8d2a07 100644
--- a/test/Riok.Mapperly.IntegrationTests/Dto/TestObjectDto.cs
+++ b/test/Riok.Mapperly.IntegrationTests/Dto/TestObjectDto.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
+using Riok.Mapperly.Abstractions;
using Riok.Mapperly.IntegrationTests.Models;
namespace Riok.Mapperly.IntegrationTests.Dto
@@ -119,6 +120,9 @@ public TestObjectDto(int ctorValue, int unknownValue = 10, int ctorValue2 = 100)
[Obsolete]
public int IgnoredObsoleteValue { get; set; }
+ [MapperIgnore]
+ public int IgnoredMemberValue { get; set; }
+
public DateOnly DateTimeValueTargetDateOnly { get; set; }
public TimeOnly DateTimeValueTargetTimeOnly { get; set; }
diff --git a/test/Riok.Mapperly.Tests/Mapping/IgnoreAttributeTest.cs b/test/Riok.Mapperly.Tests/Mapping/IgnoreAttributeTest.cs
new file mode 100644
index 0000000000..a47e61d79f
--- /dev/null
+++ b/test/Riok.Mapperly.Tests/Mapping/IgnoreAttributeTest.cs
@@ -0,0 +1,82 @@
+using Riok.Mapperly.Diagnostics;
+
+namespace Riok.Mapperly.Tests.Mapping;
+
+[UsesVerify]
+public class IgnoreAttributeTest
+{
+ private readonly string _classA = TestSourceBuilder.CSharp(
+ """
+ class A
+ {
+ public int Value { get; set; }
+
+ [MapperIgnore]
+ public int Ignored { get; set; }
+ }
+ """
+ );
+
+ private readonly string _classB = TestSourceBuilder.CSharp(
+ """
+ class B
+ {
+ public int Value { get; set; }
+
+ [MapperIgnore]
+ public int Ignored { get; set; }
+ }
+ """
+ );
+
+ [Fact]
+ public void ClassAttributeIgnoreMember()
+ {
+ var source = TestSourceBuilder.Mapping("A", "B", TestSourceBuilderOptions.Default, _classA, _classB);
+
+ TestHelper
+ .GenerateMapper(source)
+ .Should()
+ .HaveSingleMethodBody(
+ """
+ var target = new global::B();
+ target.Value = source.Value;
+ return target;
+ """
+ );
+ }
+
+ [Fact]
+ public void MapPropertyOverridesIgnoreMember()
+ {
+ var source = TestSourceBuilder.MapperWithBodyAndTypes(
+ """
+ [MapProperty("Ignored", "Ignored")]
+ partial B Map(A source);
+ """,
+ _classA,
+ _classB
+ );
+
+ TestHelper
+ .GenerateMapper(source, TestHelperOptions.AllowDiagnostics)
+ .Should()
+ .HaveSingleMethodBody(
+ """
+ var target = new global::B();
+ target.Value = source.Value;
+ 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();
+ }
+}