diff --git a/docs/docs/configuration/enum.mdx b/docs/docs/configuration/enum.mdx index 9d7c579780..58d1e1412c 100644 --- a/docs/docs/configuration/enum.mdx +++ b/docs/docs/configuration/enum.mdx @@ -24,7 +24,7 @@ The `IgnoreCase` property allows to opt in for case insensitive mappings (defaul - + Applied to all enums mapped inside this mapper. diff --git a/docs/docs/configuration/mapper.mdx b/docs/docs/configuration/mapper.mdx index 2e0febe4df..2d91519c77 100644 --- a/docs/docs/configuration/mapper.mdx +++ b/docs/docs/configuration/mapper.mdx @@ -59,21 +59,25 @@ public partial class CarMapper } ``` -### Ignore obsolete members stratgey +### Ignore obsolete members -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`. +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 | +| Name | Description | +| ------ | --------------------------------------------------------------- | +| None | Will map members marked with the `Obsolete` attribute (default) | +| Both | Ignores source and target members with the `Obsolete` attribute | +| Source | Ignores source members with the `Obsolete` attribute | +| Target | Ignores target members 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`. +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 @@ -88,7 +92,8 @@ public partial class CarMapper -Method will use the provided ignore obsolete mapping strategy, otherwise the `MapperAttribute` property `IgnoreObsoleteMembersStrategy` will be used. +Method will use the provided ignore obsolete mapping strategy, +otherwise the `MapperAttribute` property `IgnoreObsoleteMembersStrategy` will be used. ```csharp [Mapper] diff --git a/src/Riok.Mapperly/Descriptors/DescriptorBuilder.cs b/src/Riok.Mapperly/Descriptors/DescriptorBuilder.cs index ca31ca71e4..1dfe294ed1 100644 --- a/src/Riok.Mapperly/Descriptors/DescriptorBuilder.cs +++ b/src/Riok.Mapperly/Descriptors/DescriptorBuilder.cs @@ -45,7 +45,7 @@ SymbolAccessor symbolAccessor ); } - public (MapperDescriptor descriptor, List diagnostics) Build() + public (MapperDescriptor descriptor, IReadOnlyCollection diagnostics) Build() { ReserveMethodNames(); ExtractObjectFactories(); diff --git a/src/Riok.Mapperly/Descriptors/MappingBodyBuilders/NewInstanceObjectMemberMappingBodyBuilder.cs b/src/Riok.Mapperly/Descriptors/MappingBodyBuilders/NewInstanceObjectMemberMappingBodyBuilder.cs index a799113b37..ed4bf0e7d8 100644 --- a/src/Riok.Mapperly/Descriptors/MappingBodyBuilders/NewInstanceObjectMemberMappingBodyBuilder.cs +++ b/src/Riok.Mapperly/Descriptors/MappingBodyBuilders/NewInstanceObjectMemberMappingBodyBuilder.cs @@ -53,12 +53,11 @@ private static void BuildInitOnlyMemberMappings(INewInstanceBuilderContext memberConfigs } if ( - !MemberPath.TryFind( - ctx.Mapping.SourceType, - memberConfig.Source.Path, - ctx.BuilderContext.SymbolAccessor, - out var sourceMemberPath - ) + !ctx.BuilderContext.SymbolAccessor.TryFindMemberPath(ctx.Mapping.SourceType, memberConfig.Source.Path, out var sourceMemberPath) ) { ctx.BuilderContext.ReportDiagnostic( @@ -302,12 +296,11 @@ private static bool TryFindConstructorParameterSourcePath( if (!ctx.MemberConfigsByRootTargetName.TryGetValue(parameter.Name, out var memberConfigs)) { - return MemberPath.TryFind( + return ctx.BuilderContext.SymbolAccessor.TryFindMemberPath( ctx.Mapping.SourceType, MemberPathCandidateBuilder.BuildMemberPathCandidates(parameter.Name), ctx.IgnoredSourceMemberNames, StringComparer.OrdinalIgnoreCase, - ctx.BuilderContext.SymbolAccessor, out sourcePath ); } @@ -332,7 +325,7 @@ out sourcePath return false; } - if (!MemberPath.TryFind(ctx.Mapping.SourceType, memberConfig.Source.Path, ctx.BuilderContext.SymbolAccessor, out sourcePath)) + if (!ctx.BuilderContext.SymbolAccessor.TryFindMemberPath(ctx.Mapping.SourceType, memberConfig.Source.Path, out sourcePath)) { ctx.BuilderContext.ReportDiagnostic( DiagnosticDescriptors.SourceMemberNotFound, diff --git a/src/Riok.Mapperly/Descriptors/MappingBodyBuilders/ObjectMemberMappingBodyBuilder.cs b/src/Riok.Mapperly/Descriptors/MappingBodyBuilders/ObjectMemberMappingBodyBuilder.cs index e21b921a7d..90df540cc0 100644 --- a/src/Riok.Mapperly/Descriptors/MappingBodyBuilders/ObjectMemberMappingBodyBuilder.cs +++ b/src/Riok.Mapperly/Descriptors/MappingBodyBuilders/ObjectMemberMappingBodyBuilder.cs @@ -43,12 +43,11 @@ public static void BuildMappingBody(IMembersContainerBuilderContext GetAllAccessibleMappableMembers(IT return members; } - internal IEnumerable GetMappableMembers(ITypeSymbol symbol, string name, IEqualityComparer comparer) + internal bool TryFindMemberPath( + ITypeSymbol type, + IEnumerable> pathCandidates, + IReadOnlyCollection ignoredNames, + IEqualityComparer comparer, + [NotNullWhen(true)] out MemberPath? memberPath + ) + { + foreach (var pathCandidate in FindMemberPathCandidates(type, pathCandidates, comparer)) + { + if (ignoredNames.Contains(pathCandidate.Path.First().Name)) + continue; + + memberPath = pathCandidate; + return true; + } + + memberPath = null; + return false; + } + + internal bool TryFindMemberPath(ITypeSymbol type, IReadOnlyCollection path, [NotNullWhen(true)] out MemberPath? memberPath) => + TryFindMemberPath(type, path, StringComparer.Ordinal, out memberPath); + + private IEnumerable FindMemberPathCandidates( + ITypeSymbol type, + IEnumerable> pathCandidates, + IEqualityComparer comparer + ) + { + foreach (var pathCandidate in pathCandidates) + { + if (TryFindMemberPath(type, pathCandidate.ToList(), comparer, out var memberPath)) + yield return memberPath; + } + } + + private bool TryFindMemberPath( + ITypeSymbol type, + IReadOnlyCollection path, + IEqualityComparer comparer, + [NotNullWhen(true)] out MemberPath? memberPath + ) + { + var foundPath = FindMemberPath(type, path, comparer).ToList(); + if (foundPath.Count != path.Count) + { + memberPath = null; + return false; + } + + memberPath = new(foundPath); + return true; + } + + private IEnumerable FindMemberPath(ITypeSymbol type, IEnumerable path, IEqualityComparer comparer) + { + foreach (var name in path) + { + if (GetMappableMembers(type, name, comparer).FirstOrDefault() is not { } member) + break; + + type = member.Type; + yield return member; + } + } + + private IEnumerable GetMappableMembers(ITypeSymbol symbol, string name, IEqualityComparer comparer) { foreach (var member in GetAllAccessibleMappableMembers(symbol)) { - if (comparer.Equals(name, member.Name)) - { + if (comparer.Equals(member.Name, name)) yield return member; - } } } diff --git a/src/Riok.Mapperly/Descriptors/WellKnownTypes.cs b/src/Riok.Mapperly/Descriptors/WellKnownTypes.cs index d7ca04dae2..4dac8e7644 100644 --- a/src/Riok.Mapperly/Descriptors/WellKnownTypes.cs +++ b/src/Riok.Mapperly/Descriptors/WellKnownTypes.cs @@ -29,12 +29,10 @@ public INamedTypeSymbol Get(Type type) { type = type.GetGenericTypeDefinition(); } + return Get(type.FullName ?? throw new InvalidOperationException("Could not get name of type " + type)); } - public INamedTypeSymbol Get(string typeFullName) => - TryGet(typeFullName) ?? throw new InvalidOperationException("Could not get type " + typeFullName); - public INamedTypeSymbol? TryGet(string typeFullName) { if (_cachedTypes.TryGetValue(typeFullName, out var typeSymbol)) @@ -47,4 +45,7 @@ public INamedTypeSymbol Get(string typeFullName) => return typeSymbol; } + + private INamedTypeSymbol Get(string typeFullName) => + TryGet(typeFullName) ?? throw new InvalidOperationException("Could not get type " + typeFullName); } diff --git a/src/Riok.Mapperly/ImmutableEquatableArray.cs b/src/Riok.Mapperly/ImmutableEquatableArray.cs index 5e0a0c61e0..95c1ee95b1 100644 --- a/src/Riok.Mapperly/ImmutableEquatableArray.cs +++ b/src/Riok.Mapperly/ImmutableEquatableArray.cs @@ -27,7 +27,7 @@ public sealed class ImmutableEquatableArray : IEquatable= (uint)_values.Length) + return false; - return false; + _index = newIndex; + return true; } public readonly T Current => _values[_index]; diff --git a/src/Riok.Mapperly/Symbols/MemberPath.cs b/src/Riok.Mapperly/Symbols/MemberPath.cs index 34529a2ffa..2bf154a9a1 100644 --- a/src/Riok.Mapperly/Symbols/MemberPath.cs +++ b/src/Riok.Mapperly/Symbols/MemberPath.cs @@ -1,8 +1,6 @@ using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; -using Riok.Mapperly.Descriptors; using Riok.Mapperly.Helpers; using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; using static Riok.Mapperly.Emit.SyntaxFactoryHelper; @@ -16,18 +14,16 @@ namespace Riok.Mapperly.Symbols; [DebuggerDisplay("{FullName}")] public class MemberPath { - internal const string MemberAccessSeparator = "."; + private const string MemberAccessSeparator = "."; private const string NullableValueProperty = "Value"; - private IMappableMember? _member; - - public MemberPath(IReadOnlyCollection path) + public MemberPath(IReadOnlyList path) { Path = path; FullName = string.Join(MemberAccessSeparator, Path.Select(x => x.Name)); } - public IReadOnlyCollection Path { get; } + public IReadOnlyList Path { get; } /// /// Gets the path without the very last element (the path of the object containing the ). @@ -37,10 +33,7 @@ public MemberPath(IReadOnlyCollection path) /// /// Gets the last part of the path or throws if there is none. /// - public IMappableMember Member - { - get => _member ??= Path.Last(); - } + public IMappableMember Member => Path.Last(); /// /// Gets the type of the . If any part of the path is nullable, this type will be nullable too. @@ -63,7 +56,7 @@ public IMappableMember Member /// If the is nullable, the entire is not returned. /// /// All nullable sub-paths of the . - public IEnumerable> ObjectPathNullableSubPaths() + public IEnumerable> ObjectPathNullableSubPaths() { var pathParts = new List(Path.Count); foreach (var pathPart in ObjectPath) @@ -175,93 +168,4 @@ public override int GetHashCode() public static bool operator !=(MemberPath? left, MemberPath? right) => !Equals(left, right); private bool Equals(MemberPath other) => Path.SequenceEqual(other.Path); - - public static bool TryFind( - ITypeSymbol type, - IEnumerable> pathCandidates, - IReadOnlyCollection ignoredNames, - IEqualityComparer comparer, - SymbolAccessor symbolAccessor, - [NotNullWhen(true)] out MemberPath? memberPath - ) - { - foreach (var pathCandidate in FindCandidates(type, pathCandidates, comparer, symbolAccessor)) - { - if (ignoredNames.Contains(pathCandidate.Path.First().Name)) - continue; - - memberPath = pathCandidate; - return true; - } - - memberPath = null; - return false; - } - - public static bool TryFind( - ITypeSymbol type, - IReadOnlyCollection path, - SymbolAccessor symbolAccessor, - [NotNullWhen(true)] out MemberPath? memberPath - ) => TryFind(type, path, StringComparer.Ordinal, symbolAccessor, out memberPath); - - private static IEnumerable FindCandidates( - ITypeSymbol type, - IEnumerable> pathCandidates, - IEqualityComparer comparer, - SymbolAccessor symbolAccessor - ) - { - foreach (var pathCandidate in pathCandidates) - { - if (TryFind(type, pathCandidate.ToList(), comparer, symbolAccessor, out var memberPath)) - yield return memberPath; - } - } - - private static bool TryFind( - ITypeSymbol type, - IReadOnlyCollection path, - IEqualityComparer comparer, - SymbolAccessor symbolAccessor, - [NotNullWhen(true)] out MemberPath? memberPath - ) - { - var foundPath = Find(type, path, comparer, symbolAccessor).ToList(); - if (foundPath.Count != path.Count) - { - memberPath = null; - return false; - } - - memberPath = new(foundPath); - return true; - } - - private static IEnumerable Find( - ITypeSymbol type, - IEnumerable path, - IEqualityComparer comparer, - SymbolAccessor symbolAccessor - ) - { - foreach (var name in path) - { - if (FindMember(type, name, comparer, symbolAccessor) is not { } member) - break; - - type = member.Type; - yield return member; - } - } - - private static IMappableMember? FindMember( - ITypeSymbol type, - string name, - IEqualityComparer comparer, - SymbolAccessor symbolAccessor - ) - { - return symbolAccessor.GetMappableMembers(type, name, comparer).FirstOrDefault(); - } } diff --git a/test/Riok.Mapperly.Tests/Mapping/IgnoreObsoleteTest.cs b/test/Riok.Mapperly.Tests/Mapping/IgnoreObsoleteTest.cs index 1dd5768588..86ea41f597 100644 --- a/test/Riok.Mapperly.Tests/Mapping/IgnoreObsoleteTest.cs +++ b/test/Riok.Mapperly.Tests/Mapping/IgnoreObsoleteTest.cs @@ -6,7 +6,8 @@ namespace Riok.Mapperly.Tests.Mapping; [UsesVerify] public class IgnoreObsoleteTest { - private const string _classA = """ + private readonly string _classA = TestSourceBuilder.CSharp( + """ class A { public int Value { get; set; } @@ -14,9 +15,11 @@ class A [Obsolete] public int Ignored { get; set; } } - """; + """ + ); - private const string _classB = """ + private readonly string _classB = TestSourceBuilder.CSharp( + """ class B { public int Value { get; set; } @@ -24,7 +27,8 @@ class B [Obsolete] public int Ignored { get; set; } } - """; + """ + ); [Fact] public void ClassAttributeIgnoreObsoleteNone()