-
-
Notifications
You must be signed in to change notification settings - Fork 141
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix!: replace RMG017 and RMG027 with a new RMG074 (#1334)
The refactored member matching works as follows: * A member matching state and context is created * For each target member a matching source is looked up Now this happens unified for all types (constructor parameter, init property, ...) * A mapping is created with a MemberMappingInfo containing the information which members are mapped. * The mapping is added to the container. Based on the MemberMappingInfo the members are marked as mapped. * refactors and improves the readability of the member matching process * Introduces a new IgnoredMembersBuilder to build and validate ignored members * Introduces a new MemberMappingDiagnosticReporter to report all diagnostics after the member mapping build phase * Introduces a new NestedMappingsContext to handle nested mappings (MapPropertyFromSourceAttribute) * Introduces a new MemberMappingBuilder to build member (assignment) mappings for all types (objects, existing objects and tuples) * Introduces a new MembersMappingState holding the state during the member matching process (which members are not yet mapped, which are ignored, ...) * replace RMG017 and RMG027 with a new RMG074 when a target member path is used where it is not possible * Introduces a new MemberMappingInfo representing the mapped source and target members. This simplifies marking members as mapped * Introduces a new ConstructorParameterMember to simplify constructor parameter handling (can be treated as IMappableMember now) * Introduces a new ISourceValue interface which represents the right side of a member assignment mapping. For now only mapped source members and null mapped source members are supported. With #631 constant values and method provided values will also be ISourceValues BREAKING CHANGE: Replaces RMG017 and RMG027 with RMG074
- Loading branch information
Showing
67 changed files
with
1,711 additions
and
1,063 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
16 changes: 8 additions & 8 deletions
16
...iok.Mapperly/Descriptors/MappingBodyBuilders/BuilderContext/INewInstanceBuilderContext.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,23 +1,23 @@ | ||
using System.Diagnostics.CodeAnalysis; | ||
using Microsoft.CodeAnalysis; | ||
using Riok.Mapperly.Descriptors.Mappings; | ||
using Riok.Mapperly.Descriptors.Mappings.MemberMappings; | ||
using Riok.Mapperly.Symbols; | ||
|
||
namespace Riok.Mapperly.Descriptors.MappingBodyBuilders.BuilderContext; | ||
|
||
/// <summary> | ||
/// A <see cref="IMembersBuilderContext{T}"/> for mappings which create the target object via new(). | ||
/// A <see cref="IMembersBuilderContext{T}"/> for mappings which create the target object via new ...(). | ||
/// </summary> | ||
/// <typeparam name="T">The mapping type.</typeparam> | ||
public interface INewInstanceBuilderContext<out T> : IMembersBuilderContext<T> | ||
where T : IMapping | ||
{ | ||
bool TryMatchParameter(IParameterSymbol parameter, [NotNullWhen(true)] out MemberMappingInfo? memberInfo); | ||
|
||
bool TryMatchInitOnlyMember(IMappableMember targetMember, [NotNullWhen(true)] out MemberMappingInfo? memberInfo); | ||
|
||
void AddConstructorParameterMapping(ConstructorParameterMapping mapping); | ||
|
||
void AddInitMemberMapping(MemberAssignmentMapping mapping); | ||
|
||
/// <summary> | ||
/// Maps case insensitive target root member names to their real case sensitive names. | ||
/// For example id => Id. The real name can then be used as key for <see cref="IMembersBuilderContext{T}.MemberConfigsByRootTargetName"/>. | ||
/// This allows resolving case insensitive configuration member names (eg. when mapping to constructor parameters). | ||
/// </summary> | ||
IReadOnlyDictionary<string, string> RootTargetNameCasingMapping { get; } | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
112 changes: 112 additions & 0 deletions
112
src/Riok.Mapperly/Descriptors/MappingBodyBuilders/BuilderContext/IgnoredMembersBuilder.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
using Riok.Mapperly.Abstractions; | ||
using Riok.Mapperly.Configuration; | ||
using Riok.Mapperly.Diagnostics; | ||
using Riok.Mapperly.Symbols; | ||
|
||
namespace Riok.Mapperly.Descriptors.MappingBodyBuilders.BuilderContext; | ||
|
||
/// <summary> | ||
/// Builds a set of ignored source or target members | ||
/// and reports diagnostics for any invalid configured members. | ||
/// If a member is ignored and configured, | ||
/// the configuration takes precedence and the member is not ignored. | ||
/// </summary> | ||
internal static class IgnoredMembersBuilder | ||
{ | ||
internal static HashSet<string> BuildIgnoredMembers( | ||
MappingBuilderContext ctx, | ||
MappingSourceTarget sourceTarget, | ||
IReadOnlyCollection<string> allMembers | ||
) | ||
{ | ||
HashSet<string> ignoredMembers = | ||
[ | ||
.. sourceTarget == MappingSourceTarget.Source | ||
? ctx.Configuration.Members.IgnoredSources | ||
: ctx.Configuration.Members.IgnoredTargets, | ||
.. GetIgnoredAtMemberMembers(ctx, sourceTarget), | ||
.. GetIgnoredObsoleteMembers(ctx, sourceTarget), | ||
]; | ||
|
||
RemoveAndReportConfiguredIgnoredMembers(ctx, sourceTarget, ignoredMembers); | ||
ReportUnmatchedIgnoredMembers(ctx, sourceTarget, ignoredMembers, allMembers); | ||
return ignoredMembers; | ||
} | ||
|
||
private static void RemoveAndReportConfiguredIgnoredMembers( | ||
MappingBuilderContext ctx, | ||
MappingSourceTarget sourceTarget, | ||
HashSet<string> ignoredMembers | ||
) | ||
{ | ||
var isSource = sourceTarget == MappingSourceTarget.Source; | ||
var diagnostic = isSource | ||
? DiagnosticDescriptors.IgnoredSourceMemberExplicitlyMapped | ||
: DiagnosticDescriptors.IgnoredTargetMemberExplicitlyMapped; | ||
var type = isSource ? ctx.Source : ctx.Target; | ||
var configuredMembers = ctx.Configuration.Members.GetMembersWithExplicitConfigurations(sourceTarget); | ||
foreach (var configuredMember in configuredMembers) | ||
{ | ||
if (ignoredMembers.Remove(configuredMember)) | ||
{ | ||
ctx.ReportDiagnostic(diagnostic, configuredMember, type); | ||
} | ||
} | ||
} | ||
|
||
private static void ReportUnmatchedIgnoredMembers( | ||
MappingBuilderContext ctx, | ||
MappingSourceTarget sourceTarget, | ||
IEnumerable<string> ignoredMembers, | ||
IEnumerable<string> allMembers | ||
) | ||
{ | ||
var isSource = sourceTarget == MappingSourceTarget.Source; | ||
var type = isSource ? ctx.Source : ctx.Target; | ||
var nestedDiagnostic = isSource ? DiagnosticDescriptors.NestedIgnoredSourceMember : DiagnosticDescriptors.NestedIgnoredTargetMember; | ||
var notFoundDiagnostic = isSource | ||
? DiagnosticDescriptors.IgnoredSourceMemberNotFound | ||
: DiagnosticDescriptors.IgnoredTargetMemberNotFound; | ||
|
||
var unmatchedMembers = new HashSet<string>(ignoredMembers); | ||
unmatchedMembers.ExceptWith(allMembers); | ||
|
||
foreach (var member in unmatchedMembers) | ||
{ | ||
if (member.Contains(StringMemberPath.MemberAccessSeparator, StringComparison.Ordinal)) | ||
{ | ||
ctx.ReportDiagnostic(nestedDiagnostic, member, type); | ||
continue; | ||
} | ||
|
||
ctx.ReportDiagnostic(notFoundDiagnostic, member, type); | ||
} | ||
} | ||
|
||
private static IEnumerable<string> GetIgnoredAtMemberMembers(MappingBuilderContext ctx, MappingSourceTarget sourceTarget) | ||
{ | ||
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); | ||
} | ||
|
||
private static IEnumerable<string> GetIgnoredObsoleteMembers(MappingBuilderContext ctx, MappingSourceTarget sourceTarget) | ||
{ | ||
var obsoleteStrategy = ctx.Configuration.Members.IgnoreObsoleteMembersStrategy; | ||
var strategy = | ||
sourceTarget == MappingSourceTarget.Source ? IgnoreObsoleteMembersStrategy.Source : IgnoreObsoleteMembersStrategy.Target; | ||
|
||
if (!obsoleteStrategy.HasFlag(strategy)) | ||
return Enumerable.Empty<string>(); | ||
|
||
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); | ||
} | ||
} |
59 changes: 59 additions & 0 deletions
59
...apperly/Descriptors/MappingBodyBuilders/BuilderContext/MemberMappingDiagnosticReporter.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
using Riok.Mapperly.Abstractions; | ||
using Riok.Mapperly.Diagnostics; | ||
|
||
namespace Riok.Mapperly.Descriptors.MappingBodyBuilders.BuilderContext; | ||
|
||
internal static class MemberMappingDiagnosticReporter | ||
{ | ||
public static void ReportDiagnostics(MappingBuilderContext ctx, MembersMappingState state) | ||
{ | ||
AddUnusedTargetMembersDiagnostics(ctx, state); | ||
AddUnmappedSourceMembersDiagnostics(ctx, state); | ||
AddUnmappedTargetMembersDiagnostics(ctx, state); | ||
AddNoMemberMappedDiagnostic(ctx, state); | ||
} | ||
|
||
private static void AddUnusedTargetMembersDiagnostics(MappingBuilderContext ctx, MembersMappingState state) | ||
{ | ||
foreach (var memberConfig in state.UnusedMemberConfigs) | ||
{ | ||
ctx.ReportDiagnostic(DiagnosticDescriptors.ConfiguredMappingTargetMemberNotFound, memberConfig.Target.FullName, ctx.Target); | ||
} | ||
} | ||
|
||
private static void AddUnmappedSourceMembersDiagnostics(MappingBuilderContext ctx, MembersMappingState state) | ||
{ | ||
if (!ctx.Configuration.Members.RequiredMappingStrategy.HasFlag(RequiredMappingStrategy.Source)) | ||
return; | ||
|
||
foreach (var sourceMemberName in state.UnmappedSourceMemberNames) | ||
{ | ||
ctx.ReportDiagnostic(DiagnosticDescriptors.SourceMemberNotMapped, sourceMemberName, ctx.Source, ctx.Target); | ||
} | ||
} | ||
|
||
private static void AddUnmappedTargetMembersDiagnostics(MappingBuilderContext ctx, MembersMappingState state) | ||
{ | ||
foreach (var targetMember in state.UnmappedTargetMembers) | ||
{ | ||
if (targetMember.IsRequired) | ||
{ | ||
ctx.ReportDiagnostic(DiagnosticDescriptors.RequiredMemberNotMapped, targetMember.Name, ctx.Target, ctx.Source); | ||
continue; | ||
} | ||
|
||
if (targetMember.CanSet && ctx.Configuration.Members.RequiredMappingStrategy.HasFlag(RequiredMappingStrategy.Target)) | ||
{ | ||
ctx.ReportDiagnostic(DiagnosticDescriptors.SourceMemberNotFound, targetMember.Name, ctx.Target, ctx.Source); | ||
} | ||
} | ||
} | ||
|
||
private static void AddNoMemberMappedDiagnostic(MappingBuilderContext ctx, MembersMappingState state) | ||
{ | ||
if (!state.HasMemberMapping) | ||
{ | ||
ctx.ReportDiagnostic(DiagnosticDescriptors.NoMemberMappings, ctx.Source.ToDisplayString(), ctx.Target.ToDisplayString()); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.