Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: move member path resolution to symbol accessor #570

Merged
merged 1 commit into from
Jul 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/docs/configuration/enum.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ The `IgnoreCase` property allows to opt in for case insensitive mappings (defaul
<!-- do not indent this, it won't work, https://stackoverflow.com/a/67579641/3302887 -->

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

Applied to all enums mapped inside this mapper.

Expand Down
27 changes: 16 additions & 11 deletions docs/docs/configuration/mapper.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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 |

<Tabs>
<TabItem value="global" label="Global (Mapper Level)" default>
<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`.
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
Expand All @@ -88,7 +92,8 @@ 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.
Method will use the provided ignore obsolete mapping strategy,
otherwise the `MapperAttribute` property `IgnoreObsoleteMembersStrategy` will be used.

```csharp
[Mapper]
Expand Down
2 changes: 1 addition & 1 deletion src/Riok.Mapperly/Descriptors/DescriptorBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ SymbolAccessor symbolAccessor
);
}

public (MapperDescriptor descriptor, List<Diagnostic> diagnostics) Build()
public (MapperDescriptor descriptor, IReadOnlyCollection<Diagnostic> diagnostics) Build()
{
ReserveMethodNames();
ExtractObjectFactories();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,11 @@ private static void BuildInitOnlyMemberMappings(INewInstanceBuilderContext<IMapp
}

if (
!MemberPath.TryFind(
!ctx.BuilderContext.SymbolAccessor.TryFindMemberPath(
ctx.Mapping.SourceType,
MemberPathCandidateBuilder.BuildMemberPathCandidates(targetMember.Name),
ctx.IgnoredSourceMemberNames,
memberNameComparer,
ctx.BuilderContext.SymbolAccessor,
out var sourceMemberPath
)
)
Expand Down Expand Up @@ -105,12 +104,7 @@ IReadOnlyCollection<PropertyMappingConfiguration> 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(
Expand Down Expand Up @@ -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
);
}
Expand All @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,11 @@ public static void BuildMappingBody(IMembersContainerBuilderContext<IMemberAssig
}

if (
MemberPath.TryFind(
ctx.BuilderContext.SymbolAccessor.TryFindMemberPath(
ctx.Mapping.SourceType,
MemberPathCandidateBuilder.BuildMemberPathCandidates(targetMember.Name),
ctx.IgnoredSourceMemberNames,
memberNameComparer,
ctx.BuilderContext.SymbolAccessor,
out var sourceMemberPath
)
)
Expand Down Expand Up @@ -76,7 +75,7 @@ private static void BuildMemberAssignmentMapping(
PropertyMappingConfiguration config
)
{
if (!MemberPath.TryFind(ctx.Mapping.TargetType, config.Target.Path, ctx.BuilderContext.SymbolAccessor, out var targetMemberPath))
if (!ctx.BuilderContext.SymbolAccessor.TryFindMemberPath(ctx.Mapping.TargetType, config.Target.Path, out var targetMemberPath))
{
ctx.BuilderContext.ReportDiagnostic(
DiagnosticDescriptors.ConfiguredMappingTargetMemberNotFound,
Expand All @@ -86,7 +85,7 @@ PropertyMappingConfiguration config
return;
}

if (!MemberPath.TryFind(ctx.Mapping.SourceType, config.Source.Path, ctx.BuilderContext.SymbolAccessor, out var sourceMemberPath))
if (!ctx.BuilderContext.SymbolAccessor.TryFindMemberPath(ctx.Mapping.SourceType, config.Source.Path, out var sourceMemberPath))
{
ctx.BuilderContext.ReportDiagnostic(
DiagnosticDescriptors.ConfiguredMappingSourceMemberNotFound,
Expand Down
74 changes: 70 additions & 4 deletions src/Riok.Mapperly/Descriptors/SymbolAccessor.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using Microsoft.CodeAnalysis;
using Riok.Mapperly.Helpers;
using Riok.Mapperly.Symbols;
Expand Down Expand Up @@ -76,14 +77,79 @@ internal IReadOnlyCollection<IMappableMember> GetAllAccessibleMappableMembers(IT
return members;
}

internal IEnumerable<IMappableMember> GetMappableMembers(ITypeSymbol symbol, string name, IEqualityComparer<string> comparer)
internal bool TryFindMemberPath(
ITypeSymbol type,
IEnumerable<IEnumerable<string>> pathCandidates,
IReadOnlyCollection<string> ignoredNames,
IEqualityComparer<string> 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<string> path, [NotNullWhen(true)] out MemberPath? memberPath) =>
TryFindMemberPath(type, path, StringComparer.Ordinal, out memberPath);

private IEnumerable<MemberPath> FindMemberPathCandidates(
ITypeSymbol type,
IEnumerable<IEnumerable<string>> pathCandidates,
IEqualityComparer<string> comparer
)
{
foreach (var pathCandidate in pathCandidates)
{
if (TryFindMemberPath(type, pathCandidate.ToList(), comparer, out var memberPath))
yield return memberPath;
}
}

private bool TryFindMemberPath(
ITypeSymbol type,
IReadOnlyCollection<string> path,
IEqualityComparer<string> 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<IMappableMember> FindMemberPath(ITypeSymbol type, IEnumerable<string> path, IEqualityComparer<string> comparer)
{
foreach (var name in path)
{
if (GetMappableMembers(type, name, comparer).FirstOrDefault() is not { } member)
break;

type = member.Type;
yield return member;
}
}

private IEnumerable<IMappableMember> GetMappableMembers(ITypeSymbol symbol, string name, IEqualityComparer<string> comparer)
{
foreach (var member in GetAllAccessibleMappableMembers(symbol))
{
if (comparer.Equals(name, member.Name))
{
if (comparer.Equals(member.Name, name))
yield return member;
}
}
}

Expand Down
7 changes: 4 additions & 3 deletions src/Riok.Mapperly/Descriptors/WellKnownTypes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand All @@ -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);
}
12 changes: 5 additions & 7 deletions src/Riok.Mapperly/ImmutableEquatableArray.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public sealed class ImmutableEquatableArray<T> : IEquatable<ImmutableEquatableAr
public override int GetHashCode()
{
var hash = 0;
foreach (T value in _values)
foreach (var value in _values)
{
hash = HashHelper.Combine(hash, value.GetHashCode());
}
Expand Down Expand Up @@ -56,13 +56,11 @@ public bool MoveNext()
{
var newIndex = _index + 1;

if ((uint)newIndex < (uint)_values.Length)
{
_index = newIndex;
return true;
}
if ((uint)newIndex >= (uint)_values.Length)
return false;

return false;
_index = newIndex;
return true;
}

public readonly T Current => _values[_index];
Expand Down
Loading