Skip to content

Commit

Permalink
feat: add IgnoreObsoleteMembers
Browse files Browse the repository at this point in the history
  • Loading branch information
TimothyMakkison committed Jul 14, 2023
1 parent 18dec45 commit 65100f3
Show file tree
Hide file tree
Showing 16 changed files with 648 additions and 6 deletions.
44 changes: 44 additions & 0 deletions docs/docs/configuration/mapper.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,50 @@ 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 |

<Tabs>
<TabItem value="global" label="Global (Mapper Level)" default>

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
{
...
}
```

</TabItem>
<TabItem value="method" label="Local (mapping method level)">

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);
}
```

</TabItem>
</Tabs>

### Property name mapping strategy

By default, property and field names are matched using a case sensitive strategy.
Expand Down
30 changes: 30 additions & 0 deletions src/Riok.Mapperly.Abstractions/IgnoreObsoleteMembersStrategy.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
namespace Riok.Mapperly.Abstractions;

/// <summary>
/// Defines the strategy to use when mapping members marked with <see cref="ObsoleteAttribute"/>.
/// Note that <see cref="MapPropertyAttribute"/> will always map <see cref="ObsoleteAttribute"/> marked members,
/// even if they are ignored.
/// </summary>
[Flags]
public enum IgnoreObsoleteMembersStrategy
{
/// <summary>
/// Maps <see cref="ObsoleteAttribute"/> marked members.
/// </summary>
None = 0,

/// <summary>
/// Will not map <see cref="ObsoleteAttribute"/> marked source or target members.
/// </summary>
Both = ~None,

/// <summary>
/// Ignores source <see cref="ObsoleteAttribute"/> marked members.
/// </summary>
Source = 1 << 0,

/// <summary>
/// Ignores target <see cref="ObsoleteAttribute"/> marked members.
/// </summary>
Target = 1 << 1,
}
6 changes: 6 additions & 0 deletions src/Riok.Mapperly.Abstractions/MapperAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,4 +70,10 @@ public sealed class MapperAttribute : Attribute
/// to keep track of and reuse existing target object instances.
/// </summary>
public bool UseReferenceHandling { get; set; }

/// <summary>
/// The ignore obsolete attribute strategy. Determines how <see cref="ObsoleteAttribute"/> marked members are mapped.
/// Defaults to <see cref="IgnoreObsoleteMembersStrategy.None"/>.
/// </summary>
public IgnoreObsoleteMembersStrategy IgnoreObsoleteMembersStrategy { get; set; } = IgnoreObsoleteMembersStrategy.None;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
namespace Riok.Mapperly.Abstractions;

/// <summary>
/// Specifies options for obsolete ignoring strategy.
/// </summary>
[AttributeUsage(AttributeTargets.Method)]
public sealed class MapperIgnoreObsoleteMembersAttribute : Attribute
{
/// <summary>
/// Specifies options for obsolete ignoring strategy.
/// </summary>
/// <param name="ignoreObsoleteStrategy">The strategy to be used to map <see cref="ObsoleteAttribute"/> marked members. Defaults to <see cref="IgnoreObsoleteMembersStrategy.Both"/>.</param>
public MapperIgnoreObsoleteMembersAttribute(IgnoreObsoleteMembersStrategy ignoreObsoleteStrategy = IgnoreObsoleteMembersStrategy.Both)
{
IgnoreObsoleteStrategy = ignoreObsoleteStrategy;
}

/// <summary>
/// The strategy used to map <see cref="ObsoleteAttribute"/> marked members.
/// </summary>
public IgnoreObsoleteMembersStrategy IgnoreObsoleteStrategy { get; }
}
10 changes: 10 additions & 0 deletions src/Riok.Mapperly.Abstractions/PublicAPI.Shipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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!
Expand Down
13 changes: 11 additions & 2 deletions src/Riok.Mapperly/Configuration/MapperConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,12 @@ public MapperConfiguration(SymbolAccessor symbolAccessor, ISymbol mapperSymbol)
Array.Empty<IFieldSymbol>(),
Array.Empty<EnumValueMappingConfiguration>()
),
new PropertiesMappingConfiguration(Array.Empty<string>(), Array.Empty<string>(), Array.Empty<PropertyMappingConfiguration>()),
new PropertiesMappingConfiguration(
Array.Empty<string>(),
Array.Empty<string>(),
Array.Empty<PropertyMappingConfiguration>(),
Mapper.IgnoreObsoleteMembersStrategy
),
Array.Empty<DerivedTypeMappingConfiguration>()
);
}
Expand Down Expand Up @@ -66,7 +71,11 @@ private PropertiesMappingConfiguration BuildPropertiesConfig(IMethodSymbol metho
.WhereNotNull()
.ToList();
var explicitMappings = _dataAccessor.Access<MapPropertyAttribute, PropertyMappingConfiguration>(method).ToList();
return new PropertiesMappingConfiguration(ignoredSourceProperties, ignoredTargetProperties, explicitMappings);
var ignoreObsolete = _dataAccessor.Access<MapperIgnoreObsoleteMembersAttribute>(method).FirstOrDefault() is not { } methodIgnore
? _defaultConfiguration.Properties.IgnoreObsoleteMembersStrategy
: methodIgnore.IgnoreObsoleteStrategy;

return new PropertiesMappingConfiguration(ignoredSourceProperties, ignoredTargetProperties, explicitMappings, ignoreObsolete);
}

private EnumMappingConfiguration BuildEnumConfig(MappingConfigurationReference configRef)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
using Riok.Mapperly.Abstractions;

namespace Riok.Mapperly.Configuration;

public record PropertiesMappingConfiguration(
IReadOnlyCollection<string> IgnoredSources,
IReadOnlyCollection<string> IgnoredTargets,
IReadOnlyCollection<PropertyMappingConfiguration> ExplicitMappings
IReadOnlyCollection<PropertyMappingConfiguration> ExplicitMappings,
IgnoreObsoleteMembersStrategy IgnoreObsoleteMembersStrategy
);
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using Riok.Mapperly.Abstractions;
using Riok.Mapperly.Configuration;
using Riok.Mapperly.Descriptors.Mappings;
using Riok.Mapperly.Diagnostics;
Expand All @@ -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(
Expand All @@ -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; }
Expand Down Expand Up @@ -66,6 +77,32 @@ private HashSet<string> InitIgnoredUnmatchedProperties(IEnumerable<string> allPr
return unmatched;
}

private IEnumerable<string> GetIgnoredObsoleteTargetMembers()
{
var obsoleteStrategy = BuilderContext.Configuration.Properties.IgnoreObsoleteMembersStrategy;

if (!obsoleteStrategy.HasFlag(IgnoreObsoleteMembersStrategy.Target))
return Enumerable.Empty<string>();

return BuilderContext.SymbolAccessor
.GetAllAccessibleMappableMembers(Mapping.TargetType)
.Where(x => BuilderContext.SymbolAccessor.HasAttribute<ObsoleteAttribute>(x.MemberSymbol))
.Select(x => x.Name);
}

private IEnumerable<string> GetIgnoredObsoleteSourceMembers()
{
var obsoleteStrategy = BuilderContext.Configuration.Properties.IgnoreObsoleteMembersStrategy;

if (!obsoleteStrategy.HasFlag(IgnoreObsoleteMembersStrategy.Source))
return Enumerable.Empty<string>();

return BuilderContext.SymbolAccessor
.GetAllAccessibleMappableMembers(Mapping.SourceType)
.Where(x => BuilderContext.SymbolAccessor.HasAttribute<ObsoleteAttribute>(x.MemberSymbol))
.Select(x => x.Name);
}

private HashSet<string> GetSourceMemberNames()
{
return BuilderContext.SymbolAccessor.GetAllAccessibleMappableMembers(Mapping.SourceType).Select(x => x.Name).ToHashSet();
Expand Down
1 change: 1 addition & 0 deletions src/Riok.Mapperly/Symbols/FieldMember.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
2 changes: 2 additions & 0 deletions src/Riok.Mapperly/Symbols/IMappableMember.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ public interface IMappableMember

ITypeSymbol Type { get; }

ISymbol MemberSymbol { get; }

bool IsNullable { get; }

bool IsIndexer { get; }
Expand Down
1 change: 1 addition & 0 deletions src/Riok.Mapperly/Symbols/PropertyMember.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
Loading

0 comments on commit 65100f3

Please sign in to comment.