Skip to content

Commit

Permalink
support additional mapping method parameters
Browse files Browse the repository at this point in the history
  • Loading branch information
latonz committed Jul 17, 2024
1 parent 214dd32 commit 08fc87d
Show file tree
Hide file tree
Showing 116 changed files with 1,898 additions and 1,065 deletions.
58 changes: 58 additions & 0 deletions docs/docs/configuration/additional-mapping-parameters.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
---
sidebar_position: 6
description: Additional mapping parameters
---

# Additional mapping parameters

import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';

A mapping method declaration can have additional parameters.
Each additional parameter is considered the same as a source member and matched by its case-insensitive name.
An additional mapping parameter has lower priority than a `MapProperty` mapping,
but higher than a by-name matched regular member mapping.

<Tabs>
<TabItem default label="Declaration" value="declaration">
```csharp
[Mapper]
public partial class CarMapper
{
// highlight-start
public partial CarDto Map(Car source, string name);
// highlight-end
}
```
</TabItem>
<TabItem default label="Generated code" value="generated">
```csharp
[Mapper]
public partial class CarMapper
{
// highlight-start
public partial CarDto Map(Car source, string name)
// highlight-end
{
var target = new CarDto();
target.Brand = source.Brand;
target.Model = source.Model;
// highlight-start
target.Name = name;
// highlight-end
return target;
}
}
```
</TabItem>
</Tabs>

:::info
Mappings with additional parameters do have some limitions:

- The additional parameters are not passed to nested mappings.
- A mapping with additional mapping parameters cannot be the default mapping
(it is not used by Mapperly when encountering a nested mapping for the given types),
see also [default mapping mehtods](./user-implemented-methods.mdx##default-mapping-methods).
- Generic and runtime target type mappings do not support additional type parameters.
:::
2 changes: 1 addition & 1 deletion docs/docs/configuration/analyzer-diagnostics/index.mdx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
sidebar_position: 15
sidebar_position: 18
description: A list of all analyzer diagnostics used by Mapperly and how to configure them.
---

Expand Down
2 changes: 1 addition & 1 deletion docs/docs/configuration/conversions.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
sidebar_position: 15
sidebar_position: 17
description: A list of conversions supported by Mapperly
---

Expand Down
2 changes: 1 addition & 1 deletion docs/docs/configuration/ctor-mappings.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
sidebar_position: 7
sidebar_position: 8
description: Constructor mappings
---

Expand Down
2 changes: 1 addition & 1 deletion docs/docs/configuration/derived-type-mapping.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
sidebar_position: 10
sidebar_position: 11
description: Map derived types and interfaces
---

Expand Down
2 changes: 1 addition & 1 deletion docs/docs/configuration/existing-target.mdx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
sidebar_position: 9
sidebar_position: 10
description: Map to an existing target object
---

Expand Down
2 changes: 1 addition & 1 deletion docs/docs/configuration/generic-mapping.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
sidebar_position: 11
sidebar_position: 12
description: Create a generic mapping method
---

Expand Down
2 changes: 1 addition & 1 deletion docs/docs/configuration/object-factories.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
sidebar_position: 8
sidebar_position: 9
description: Construct and resolve objects using object factories
---

Expand Down
2 changes: 1 addition & 1 deletion docs/docs/configuration/private-member-mapping.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
sidebar_position: 13
sidebar_position: 14
description: Private member mapping
---

Expand Down
2 changes: 1 addition & 1 deletion docs/docs/configuration/queryable-projections.mdx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
sidebar_position: 14
sidebar_position: 16
description: Use queryable projections to map queryable objects and optimize ORM performance
---

Expand Down
2 changes: 1 addition & 1 deletion docs/docs/configuration/reference-handling.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
sidebar_position: 12
sidebar_position: 13
description: Use reference handling to handle reference loops
---

Expand Down
10 changes: 5 additions & 5 deletions docs/docs/configuration/user-implemented-methods.mdx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
sidebar_position: 6
sidebar_position: 7
description: Manually implement mappings
---

Expand Down Expand Up @@ -27,7 +27,7 @@ The types of the user-implemented mapping method need to match the types to map

If there are multiple user-implemented mapping methods suiting the given type-pair, by default, the first one is used.
This can be customized by using [automatic user-implemented mapping method discovery](#automatic-user-implemented-mapping-method-discovery)
and [default user-implemented mapping method](#default-user-implemented-mapping-method).
and [default mapping method](#default-mapping-methods).

## Automatic user-implemented mapping method discovery

Expand Down Expand Up @@ -82,11 +82,11 @@ public partial class CarMapper
}
```

## Default user-implemented mapping method
## Default mapping methods

Whenever Mapperly will need a mapping for a given type-pair,
it will use the default user-implemented mapping.
A user-implemented mapping is considered the default mapping for a type-pair
it will use the default mapping.
A user-implemented or user-defined mapping is considered the default mapping for a type-pair
if `Default = true` is set on the `UserMapping` attribute.
If no user-implemented mapping with `Default = true` exists and `AutoUserMappings` is enabled,
the first user-implemented mapping which has an unspecified `Default` value is used.
Expand Down
3 changes: 3 additions & 0 deletions src/Riok.Mapperly/AnalyzerReleases.Shipped.md
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,8 @@ RMG012 | Mapper | Warning | Source member was not found for target member
RMG020 | Mapper | Warning | Source member is not mapped to any target member
RMG037 | Mapper | Warning | An enum member could not be found on the source enum
RMG038 | Mapper | Warning | An enum member could not be found on the target enum
RMG081 | Mapper | Error | A mapping method with additional parameters cannot be a default mapping
RMG082 | Mapper | Warning | An additional mapping method parameter is not mapped

### Removed Rules

Expand All @@ -192,3 +194,4 @@ RMG017 | Mapper | Warning | An init only member can have one configuration a
RMG026 | Mapper | Info | Cannot map from indexed member
RMG027 | Mapper | Warning | A constructor parameter can have one configuration at max
RMG028 | Mapper | Warning | Constructor parameter cannot handle target paths

1 change: 1 addition & 0 deletions src/Riok.Mapperly/Descriptors/DescriptorBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using Riok.Mapperly.Descriptors.MappingBuilders;
using Riok.Mapperly.Descriptors.Mappings.UserMappings;
using Riok.Mapperly.Descriptors.ObjectFactories;
using Riok.Mapperly.Descriptors.UnsafeAccess;
using Riok.Mapperly.Diagnostics;
using Riok.Mapperly.Helpers;
using Riok.Mapperly.Symbols;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
using Riok.Mapperly.Abstractions;
using Riok.Mapperly.Diagnostics;
using Riok.Mapperly.Helpers;
using Riok.Mapperly.Symbols;
using Riok.Mapperly.Symbols.Members;

namespace Riok.Mapperly.Descriptors.FormatProviders;

Expand Down Expand Up @@ -33,7 +33,7 @@ public static FormatProviderCollection ExtractFormatProviders(SimpleMappingBuild
if (memberSymbol == null)
return null;

if (!memberSymbol.CanGet || symbol.IsStatic != isStatic || !memberSymbol.Type.Implements(ctx.Types.Get<IFormatProvider>()))
if (!memberSymbol.CanGetDirectly || symbol.IsStatic != isStatic || !memberSymbol.Type.Implements(ctx.Types.Get<IFormatProvider>()))
{
ctx.ReportDiagnostic(DiagnosticDescriptors.InvalidFormatProviderSignature, symbol, symbol.Name);
return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ conversionType is not MappingConversionType.EnumToString and not MappingConversi
// for inline expression mappings.
// This is not needed for regular mappings as these user defined method mappings
// are directly built (with KeepUserSymbol) and called by the other mappings.
userMapping ??= (MappingBuilder.Find(mappingKey) as IUserMapping);
userMapping ??= MappingBuilder.Find(mappingKey) as IUserMapping;
options &= ~MappingBuildingOptions.KeepUserSymbol;
return BuildMapping(userMapping, mappingKey, options, diagnosticLocation);
}
Expand Down
2 changes: 1 addition & 1 deletion src/Riok.Mapperly/Descriptors/MapperDescriptor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Riok.Mapperly.Descriptors.Mappings;
using Riok.Mapperly.Descriptors.Mappings.MemberMappings.UnsafeAccess;
using Riok.Mapperly.Descriptors.UnsafeAccess;
using Riok.Mapperly.Helpers;
using Riok.Mapperly.Symbols;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using Riok.Mapperly.Descriptors.Mappings;
using Riok.Mapperly.Descriptors.Mappings.MemberMappings;
using Riok.Mapperly.Symbols;
using Riok.Mapperly.Symbols.Members;

namespace Riok.Mapperly.Descriptors.MappingBodyBuilders.BuilderContext;

Expand All @@ -17,7 +17,7 @@ public interface IMembersBuilderContext<out T>

void IgnoreMembers(string memberName);

void SetMembersMapped(string memberName);
void SetMembersMapped(MemberMappingInfo members);

void SetTargetMemberMapped(IMappableMember targetMember);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
using Microsoft.CodeAnalysis;
using Riok.Mapperly.Descriptors.Mappings;
using Riok.Mapperly.Descriptors.Mappings.MemberMappings;
using Riok.Mapperly.Symbols;
using Riok.Mapperly.Symbols.Members;

namespace Riok.Mapperly.Descriptors.MappingBodyBuilders.BuilderContext;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,10 +87,7 @@ private static IEnumerable<string> GetIgnoredAtMemberMembers(MappingBuilderConte
{
var type = sourceTarget == MappingSourceTarget.Source ? ctx.Source : ctx.Target;

return ctx
.SymbolAccessor.GetAllAccessibleMappableMembers(type)
.Where(x => ctx.SymbolAccessor.HasAttribute<MapperIgnoreAttribute>(x.MemberSymbol))
.Select(x => x.Name);
return ctx.SymbolAccessor.GetAllAccessibleMappableMembers(type).Where(x => x.IsIgnored).Select(x => x.Name);
}

private static IEnumerable<string> GetIgnoredObsoleteMembers(MappingBuilderContext ctx, MappingSourceTarget sourceTarget)
Expand All @@ -100,13 +97,10 @@ private static IEnumerable<string> GetIgnoredObsoleteMembers(MappingBuilderConte
sourceTarget == MappingSourceTarget.Source ? IgnoreObsoleteMembersStrategy.Source : IgnoreObsoleteMembersStrategy.Target;

if (!obsoleteStrategy.HasFlag(strategy))
return Enumerable.Empty<string>();
return [];

var type = sourceTarget == MappingSourceTarget.Source ? ctx.Source : ctx.Target;

return ctx
.SymbolAccessor.GetAllAccessibleMappableMembers(type)
.Where(x => ctx.SymbolAccessor.HasAttribute<ObsoleteAttribute>(x.MemberSymbol))
.Select(x => x.Name);
return ctx.SymbolAccessor.GetAllAccessibleMappableMembers(type).Where(x => x.IsObsolete).Select(x => x.Name);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ public static void ReportDiagnostics(MappingBuilderContext ctx, MembersMappingSt
AddUnusedTargetMembersDiagnostics(ctx, state);
AddUnmappedSourceMembersDiagnostics(ctx, state);
AddUnmappedTargetMembersDiagnostics(ctx, state);
AddUnmappedAdditionalSourceMembersDiagnostics(ctx, state);
AddNoMemberMappedDiagnostic(ctx, state);
}

Expand Down Expand Up @@ -49,6 +50,14 @@ private static void AddUnmappedTargetMembersDiagnostics(MappingBuilderContext ct
}
}

private static void AddUnmappedAdditionalSourceMembersDiagnostics(MappingBuilderContext ctx, MembersMappingState state)
{
foreach (var name in state.UnmappedAdditionalSourceMemberNames)
{
ctx.ReportDiagnostic(DiagnosticDescriptors.AdditionalParameterNotMapped, name, ctx.UserMapping!.Method.Name);
}
}

private static void AddNoMemberMappedDiagnostic(MappingBuilderContext ctx, MembersMappingState state)
{
if (!state.HasMemberMapping)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using Riok.Mapperly.Descriptors.Mappings.MemberMappings;
using Riok.Mapperly.Diagnostics;
using Riok.Mapperly.Helpers;
using Riok.Mapperly.Symbols;
using Riok.Mapperly.Symbols.Members;

namespace Riok.Mapperly.Descriptors.MappingBodyBuilders.BuilderContext;

Expand Down Expand Up @@ -45,7 +45,8 @@ public void AddNullDelegateMemberAssignmentMapping(IMemberAssignmentMapping memb
&& memberMapping.MemberInfo.TargetMember.Member.Type.IsNullable()
)
{
container.AddNullMemberAssignment(SetterMemberPath.Build(BuilderContext, memberMapping.MemberInfo.TargetMember));
var targetMemberSetter = memberMapping.MemberInfo.TargetMember.BuildSetter(BuilderContext);
container.AddNullMemberAssignment(targetMemberSetter);
}
else if (BuilderContext.Configuration.Mapper.ThrowOnPropertyMappingNullMismatch)
{
Expand Down Expand Up @@ -76,18 +77,17 @@ private void AddNullMemberInitializers(IMemberAssignmentMappingContainer contain
continue;
}

var setterNullablePath = SetterMemberPath.Build(BuilderContext, nullablePath);

if (setterNullablePath.IsMethod)
var nullablePathSetter = nullablePath.BuildSetter(BuilderContext);
if (!nullablePathSetter.SupportsCoalesceAssignment)
{
var getterNullablePath = GetterMemberPath.Build(BuilderContext, nullablePath);
var nullablePathGetter = nullablePath.BuildGetter(BuilderContext);
container.AddMemberMappingContainer(
new MethodMemberNullAssignmentInitializerMapping(setterNullablePath, getterNullablePath)
new MethodMemberNullAssignmentInitializerMapping(nullablePathSetter, nullablePathGetter)
);
continue;
}

container.AddMemberMappingContainer(new MemberNullAssignmentInitializerMapping(setterNullablePath));
container.AddMemberMappingContainer(new MemberNullAssignmentInitializerMapping(nullablePathSetter));
}
}

Expand Down Expand Up @@ -118,11 +118,8 @@ out var parentMappingHolder
needsNullSafeAccess = true;
}

mapping = new MemberNullDelegateAssignmentMapping(
GetterMemberPath.Build(BuilderContext, nullConditionSourcePath),
parentMapping,
needsNullSafeAccess
);
var nullConditionSourcePathGetter = nullConditionSourcePath.BuildGetter(BuilderContext);
mapping = new MemberNullDelegateAssignmentMapping(nullConditionSourcePathGetter, parentMapping, needsNullSafeAccess);
_nullDelegateMappings[nullConditionSourcePath] = mapping;
parentMapping.AddMemberMappingContainer(mapping);
return mapping;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
using Riok.Mapperly.Descriptors.Mappings.MemberMappings;
using Riok.Mapperly.Diagnostics;
using Riok.Mapperly.Helpers;
using Riok.Mapperly.Symbols;
using Riok.Mapperly.Symbols.Members;

namespace Riok.Mapperly.Descriptors.MappingBodyBuilders.BuilderContext;

Expand Down Expand Up @@ -39,7 +39,7 @@ public void AddDiagnostics()
protected void SetTargetMemberMapped(string targetMemberName, bool ignoreCase = false) =>
_state.SetTargetMemberMapped(targetMemberName, ignoreCase);

public void SetMembersMapped(string memberName) => _state.SetMembersMapped(memberName);
public void SetMembersMapped(MemberMappingInfo members) => _state.SetMembersMapped(members, false);

public void IgnoreMembers(string memberName) => _state.IgnoreMembers(memberName);

Expand Down Expand Up @@ -130,6 +130,20 @@ protected virtual bool TryFindSourcePath(
[NotNullWhen(true)] out MemberPath? sourceMemberPath
)
{
// try to match in additional source members
if (
BuilderContext.SymbolAccessor.TryFindMemberPath(
BuilderContext.AdditionalSourceMembers,
pathCandidates,
ignoreCase,
out sourceMemberPath
)
)
{
return true;
}

// match against source type members
return BuilderContext.SymbolAccessor.TryFindMemberPath(
Mapping.SourceType,
pathCandidates,
Expand Down
Loading

0 comments on commit 08fc87d

Please sign in to comment.