Skip to content

Commit

Permalink
feat: add MaxDepth for recursive queries
Browse files Browse the repository at this point in the history
  • Loading branch information
TimothyMakkison committed Nov 21, 2023
1 parent 8ee92f4 commit 3b903b9
Show file tree
Hide file tree
Showing 17 changed files with 269 additions and 133 deletions.
5 changes: 5 additions & 0 deletions src/Riok.Mapperly.Abstractions/MapperAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -97,4 +97,9 @@ public class MapperAttribute : Attribute
/// Determines the access level of members that Mapperly will map.
/// </summary>
public MemberVisibility IncludedMembers { get; set; } = MemberVisibility.AllAccessible;

/// <summary>
/// Defines the maximum recursive depth that an IQueryable mapping will use.
/// </summary>
public int MaxRecursiveDepth { get; set; } = 8;

Check failure on line 104 in src/Riok.Mapperly.Abstractions/MapperAttribute.cs

View workflow job for this annotation

GitHub Actions / test

Symbol 'MaxRecursiveDepth.get' is not part of the declared public API (https://github.com/dotnet/roslyn-analyzers/blob/main/src/PublicApiAnalyzers/PublicApiAnalyzers.Help.md)

Check failure on line 104 in src/Riok.Mapperly.Abstractions/MapperAttribute.cs

View workflow job for this annotation

GitHub Actions / test

Symbol 'MaxRecursiveDepth.set' is not part of the declared public API (https://github.com/dotnet/roslyn-analyzers/blob/main/src/PublicApiAnalyzers/PublicApiAnalyzers.Help.md)

Check failure on line 104 in src/Riok.Mapperly.Abstractions/MapperAttribute.cs

View workflow job for this annotation

GitHub Actions / test

Symbol 'MaxRecursiveDepth.get' is not part of the declared public API (https://github.com/dotnet/roslyn-analyzers/blob/main/src/PublicApiAnalyzers/PublicApiAnalyzers.Help.md)

Check failure on line 104 in src/Riok.Mapperly.Abstractions/MapperAttribute.cs

View workflow job for this annotation

GitHub Actions / test

Symbol 'MaxRecursiveDepth.set' is not part of the declared public API (https://github.com/dotnet/roslyn-analyzers/blob/main/src/PublicApiAnalyzers/PublicApiAnalyzers.Help.md)

Check warning on line 104 in src/Riok.Mapperly.Abstractions/MapperAttribute.cs

View workflow job for this annotation

GitHub Actions / sample

Symbol 'MaxRecursiveDepth.get' is not part of the declared public API (https://github.com/dotnet/roslyn-analyzers/blob/main/src/PublicApiAnalyzers/PublicApiAnalyzers.Help.md)

Check warning on line 104 in src/Riok.Mapperly.Abstractions/MapperAttribute.cs

View workflow job for this annotation

GitHub Actions / sample

Symbol 'MaxRecursiveDepth.set' is not part of the declared public API (https://github.com/dotnet/roslyn-analyzers/blob/main/src/PublicApiAnalyzers/PublicApiAnalyzers.Help.md)

Check warning on line 104 in src/Riok.Mapperly.Abstractions/MapperAttribute.cs

View workflow job for this annotation

GitHub Actions / package

Symbol 'MaxRecursiveDepth.get' is not part of the declared public API (https://github.com/dotnet/roslyn-analyzers/blob/main/src/PublicApiAnalyzers/PublicApiAnalyzers.Help.md)

Check warning on line 104 in src/Riok.Mapperly.Abstractions/MapperAttribute.cs

View workflow job for this annotation

GitHub Actions / package

Symbol 'MaxRecursiveDepth.set' is not part of the declared public API (https://github.com/dotnet/roslyn-analyzers/blob/main/src/PublicApiAnalyzers/PublicApiAnalyzers.Help.md)

Check warning on line 104 in src/Riok.Mapperly.Abstractions/MapperAttribute.cs

View workflow job for this annotation

GitHub Actions / lint-dotnet

Symbol 'MaxRecursiveDepth.get' is not part of the declared public API

Check warning on line 104 in src/Riok.Mapperly.Abstractions/MapperAttribute.cs

View workflow job for this annotation

GitHub Actions / lint-dotnet

Symbol 'MaxRecursiveDepth.set' is not part of the declared public API

Check warning on line 104 in src/Riok.Mapperly.Abstractions/MapperAttribute.cs

View workflow job for this annotation

GitHub Actions / SourceGeneratorBenchmarks

Symbol 'MaxRecursiveDepth.get' is not part of the declared public API (https://github.com/dotnet/roslyn-analyzers/blob/main/src/PublicApiAnalyzers/PublicApiAnalyzers.Help.md)

Check warning on line 104 in src/Riok.Mapperly.Abstractions/MapperAttribute.cs

View workflow job for this annotation

GitHub Actions / SourceGeneratorBenchmarks

Symbol 'MaxRecursiveDepth.set' is not part of the declared public API (https://github.com/dotnet/roslyn-analyzers/blob/main/src/PublicApiAnalyzers/PublicApiAnalyzers.Help.md)

Check warning on line 104 in src/Riok.Mapperly.Abstractions/MapperAttribute.cs

View workflow job for this annotation

GitHub Actions / build

Symbol 'MaxRecursiveDepth.get' is not part of the declared public API (https://github.com/dotnet/roslyn-analyzers/blob/main/src/PublicApiAnalyzers/PublicApiAnalyzers.Help.md)

Check warning on line 104 in src/Riok.Mapperly.Abstractions/MapperAttribute.cs

View workflow job for this annotation

GitHub Actions / build

Symbol 'MaxRecursiveDepth.set' is not part of the declared public API (https://github.com/dotnet/roslyn-analyzers/blob/main/src/PublicApiAnalyzers/PublicApiAnalyzers.Help.md)

Check warning on line 104 in src/Riok.Mapperly.Abstractions/MapperAttribute.cs

View workflow job for this annotation

GitHub Actions / build

Symbol 'MaxRecursiveDepth.get' is not part of the declared public API (https://github.com/dotnet/roslyn-analyzers/blob/main/src/PublicApiAnalyzers/PublicApiAnalyzers.Help.md)

Check warning on line 104 in src/Riok.Mapperly.Abstractions/MapperAttribute.cs

View workflow job for this annotation

GitHub Actions / MappingBenchmarks

Symbol 'MaxRecursiveDepth.set' is not part of the declared public API (https://github.com/dotnet/roslyn-analyzers/blob/main/src/PublicApiAnalyzers/PublicApiAnalyzers.Help.md)

Check warning on line 104 in src/Riok.Mapperly.Abstractions/MapperAttribute.cs

View workflow job for this annotation

GitHub Actions / MappingBenchmarks

Symbol 'MaxRecursiveDepth.get' is not part of the declared public API (https://github.com/dotnet/roslyn-analyzers/blob/main/src/PublicApiAnalyzers/PublicApiAnalyzers.Help.md)

Check warning on line 104 in src/Riok.Mapperly.Abstractions/MapperAttribute.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

Symbol 'MaxRecursiveDepth.get' is not part of the declared public API (https://github.com/dotnet/roslyn-analyzers/blob/main/src/PublicApiAnalyzers/PublicApiAnalyzers.Help.md)

Check warning on line 104 in src/Riok.Mapperly.Abstractions/MapperAttribute.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

Symbol 'MaxRecursiveDepth.set' is not part of the declared public API (https://github.com/dotnet/roslyn-analyzers/blob/main/src/PublicApiAnalyzers/PublicApiAnalyzers.Help.md)

Check warning on line 104 in src/Riok.Mapperly.Abstractions/MapperAttribute.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

Symbol 'MaxRecursiveDepth.get' is not part of the declared public API (https://github.com/dotnet/roslyn-analyzers/blob/main/src/PublicApiAnalyzers/PublicApiAnalyzers.Help.md)
}
22 changes: 22 additions & 0 deletions src/Riok.Mapperly.Abstractions/MapperMaxRecursiveDepthAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
namespace Riok.Mapperly.Abstractions;

/// <summary>
/// Defines the maximum recursive depth that an IQueryable mapping will use.
/// </summary>
[AttributeUsage(AttributeTargets.Method)]
public sealed class MapperMaxRecursiveDepthAttribute : Attribute

Check failure on line 7 in src/Riok.Mapperly.Abstractions/MapperMaxRecursiveDepthAttribute.cs

View workflow job for this annotation

GitHub Actions / test

Symbol 'MapperMaxRecursiveDepthAttribute' is not part of the declared public API (https://github.com/dotnet/roslyn-analyzers/blob/main/src/PublicApiAnalyzers/PublicApiAnalyzers.Help.md)

Check failure on line 7 in src/Riok.Mapperly.Abstractions/MapperMaxRecursiveDepthAttribute.cs

View workflow job for this annotation

GitHub Actions / test

Symbol 'MapperMaxRecursiveDepthAttribute' is not part of the declared public API (https://github.com/dotnet/roslyn-analyzers/blob/main/src/PublicApiAnalyzers/PublicApiAnalyzers.Help.md)

Check warning on line 7 in src/Riok.Mapperly.Abstractions/MapperMaxRecursiveDepthAttribute.cs

View workflow job for this annotation

GitHub Actions / sample

Symbol 'MapperMaxRecursiveDepthAttribute' is not part of the declared public API (https://github.com/dotnet/roslyn-analyzers/blob/main/src/PublicApiAnalyzers/PublicApiAnalyzers.Help.md)

Check warning on line 7 in src/Riok.Mapperly.Abstractions/MapperMaxRecursiveDepthAttribute.cs

View workflow job for this annotation

GitHub Actions / package

Symbol 'MapperMaxRecursiveDepthAttribute' is not part of the declared public API (https://github.com/dotnet/roslyn-analyzers/blob/main/src/PublicApiAnalyzers/PublicApiAnalyzers.Help.md)

Check warning on line 7 in src/Riok.Mapperly.Abstractions/MapperMaxRecursiveDepthAttribute.cs

View workflow job for this annotation

GitHub Actions / lint-dotnet

Symbol 'MapperMaxRecursiveDepthAttribute' is not part of the declared public API

Check warning on line 7 in src/Riok.Mapperly.Abstractions/MapperMaxRecursiveDepthAttribute.cs

View workflow job for this annotation

GitHub Actions / SourceGeneratorBenchmarks

Symbol 'MapperMaxRecursiveDepthAttribute' is not part of the declared public API (https://github.com/dotnet/roslyn-analyzers/blob/main/src/PublicApiAnalyzers/PublicApiAnalyzers.Help.md)

Check warning on line 7 in src/Riok.Mapperly.Abstractions/MapperMaxRecursiveDepthAttribute.cs

View workflow job for this annotation

GitHub Actions / build

Symbol 'MapperMaxRecursiveDepthAttribute' is not part of the declared public API (https://github.com/dotnet/roslyn-analyzers/blob/main/src/PublicApiAnalyzers/PublicApiAnalyzers.Help.md)

Check warning on line 7 in src/Riok.Mapperly.Abstractions/MapperMaxRecursiveDepthAttribute.cs

View workflow job for this annotation

GitHub Actions / build

Symbol 'MapperMaxRecursiveDepthAttribute' is not part of the declared public API (https://github.com/dotnet/roslyn-analyzers/blob/main/src/PublicApiAnalyzers/PublicApiAnalyzers.Help.md)

Check warning on line 7 in src/Riok.Mapperly.Abstractions/MapperMaxRecursiveDepthAttribute.cs

View workflow job for this annotation

GitHub Actions / MappingBenchmarks

Symbol 'MapperMaxRecursiveDepthAttribute' is not part of the declared public API (https://github.com/dotnet/roslyn-analyzers/blob/main/src/PublicApiAnalyzers/PublicApiAnalyzers.Help.md)

Check warning on line 7 in src/Riok.Mapperly.Abstractions/MapperMaxRecursiveDepthAttribute.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

Symbol 'MapperMaxRecursiveDepthAttribute' is not part of the declared public API (https://github.com/dotnet/roslyn-analyzers/blob/main/src/PublicApiAnalyzers/PublicApiAnalyzers.Help.md)

Check warning on line 7 in src/Riok.Mapperly.Abstractions/MapperMaxRecursiveDepthAttribute.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

Symbol 'MapperMaxRecursiveDepthAttribute' is not part of the declared public API (https://github.com/dotnet/roslyn-analyzers/blob/main/src/PublicApiAnalyzers/PublicApiAnalyzers.Help.md)
{
/// <summary>
/// Defines the maximum recursive depth that an IQueryable mapping will use.
/// </summary>
/// <param name="maxRecursiveDepth">The maximum recursive depth used when mapping IQueryable members.</param>
public MapperMaxRecursiveDepthAttribute(int maxRecursiveDepth)

Check failure on line 13 in src/Riok.Mapperly.Abstractions/MapperMaxRecursiveDepthAttribute.cs

View workflow job for this annotation

GitHub Actions / test

Symbol 'MapperMaxRecursiveDepthAttribute' is not part of the declared public API (https://github.com/dotnet/roslyn-analyzers/blob/main/src/PublicApiAnalyzers/PublicApiAnalyzers.Help.md)

Check failure on line 13 in src/Riok.Mapperly.Abstractions/MapperMaxRecursiveDepthAttribute.cs

View workflow job for this annotation

GitHub Actions / test

Symbol 'MapperMaxRecursiveDepthAttribute' is not part of the declared public API (https://github.com/dotnet/roslyn-analyzers/blob/main/src/PublicApiAnalyzers/PublicApiAnalyzers.Help.md)

Check warning on line 13 in src/Riok.Mapperly.Abstractions/MapperMaxRecursiveDepthAttribute.cs

View workflow job for this annotation

GitHub Actions / sample

Symbol 'MapperMaxRecursiveDepthAttribute' is not part of the declared public API (https://github.com/dotnet/roslyn-analyzers/blob/main/src/PublicApiAnalyzers/PublicApiAnalyzers.Help.md)

Check warning on line 13 in src/Riok.Mapperly.Abstractions/MapperMaxRecursiveDepthAttribute.cs

View workflow job for this annotation

GitHub Actions / package

Symbol 'MapperMaxRecursiveDepthAttribute' is not part of the declared public API (https://github.com/dotnet/roslyn-analyzers/blob/main/src/PublicApiAnalyzers/PublicApiAnalyzers.Help.md)

Check warning on line 13 in src/Riok.Mapperly.Abstractions/MapperMaxRecursiveDepthAttribute.cs

View workflow job for this annotation

GitHub Actions / lint-dotnet

Symbol 'MapperMaxRecursiveDepthAttribute' is not part of the declared public API

Check warning on line 13 in src/Riok.Mapperly.Abstractions/MapperMaxRecursiveDepthAttribute.cs

View workflow job for this annotation

GitHub Actions / SourceGeneratorBenchmarks

Symbol 'MapperMaxRecursiveDepthAttribute' is not part of the declared public API (https://github.com/dotnet/roslyn-analyzers/blob/main/src/PublicApiAnalyzers/PublicApiAnalyzers.Help.md)

Check warning on line 13 in src/Riok.Mapperly.Abstractions/MapperMaxRecursiveDepthAttribute.cs

View workflow job for this annotation

GitHub Actions / build

Symbol 'MapperMaxRecursiveDepthAttribute' is not part of the declared public API (https://github.com/dotnet/roslyn-analyzers/blob/main/src/PublicApiAnalyzers/PublicApiAnalyzers.Help.md)

Check warning on line 13 in src/Riok.Mapperly.Abstractions/MapperMaxRecursiveDepthAttribute.cs

View workflow job for this annotation

GitHub Actions / MappingBenchmarks

Symbol 'MapperMaxRecursiveDepthAttribute' is not part of the declared public API (https://github.com/dotnet/roslyn-analyzers/blob/main/src/PublicApiAnalyzers/PublicApiAnalyzers.Help.md)

Check warning on line 13 in src/Riok.Mapperly.Abstractions/MapperMaxRecursiveDepthAttribute.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

Symbol 'MapperMaxRecursiveDepthAttribute' is not part of the declared public API (https://github.com/dotnet/roslyn-analyzers/blob/main/src/PublicApiAnalyzers/PublicApiAnalyzers.Help.md)
{
MaxRecursiveDepth = maxRecursiveDepth;
}

/// <summary>
/// The maximum recursive depth used when mapping IQueryable members.
/// </summary>
public int MaxRecursiveDepth { get; }

Check failure on line 21 in src/Riok.Mapperly.Abstractions/MapperMaxRecursiveDepthAttribute.cs

View workflow job for this annotation

GitHub Actions / test

Symbol 'MaxRecursiveDepth.get' is not part of the declared public API (https://github.com/dotnet/roslyn-analyzers/blob/main/src/PublicApiAnalyzers/PublicApiAnalyzers.Help.md)

Check failure on line 21 in src/Riok.Mapperly.Abstractions/MapperMaxRecursiveDepthAttribute.cs

View workflow job for this annotation

GitHub Actions / test

Symbol 'MaxRecursiveDepth.get' is not part of the declared public API (https://github.com/dotnet/roslyn-analyzers/blob/main/src/PublicApiAnalyzers/PublicApiAnalyzers.Help.md)

Check warning on line 21 in src/Riok.Mapperly.Abstractions/MapperMaxRecursiveDepthAttribute.cs

View workflow job for this annotation

GitHub Actions / sample

Symbol 'MaxRecursiveDepth.get' is not part of the declared public API (https://github.com/dotnet/roslyn-analyzers/blob/main/src/PublicApiAnalyzers/PublicApiAnalyzers.Help.md)

Check warning on line 21 in src/Riok.Mapperly.Abstractions/MapperMaxRecursiveDepthAttribute.cs

View workflow job for this annotation

GitHub Actions / package

Symbol 'MaxRecursiveDepth.get' is not part of the declared public API (https://github.com/dotnet/roslyn-analyzers/blob/main/src/PublicApiAnalyzers/PublicApiAnalyzers.Help.md)

Check warning on line 21 in src/Riok.Mapperly.Abstractions/MapperMaxRecursiveDepthAttribute.cs

View workflow job for this annotation

GitHub Actions / lint-dotnet

Symbol 'MaxRecursiveDepth.get' is not part of the declared public API

Check warning on line 21 in src/Riok.Mapperly.Abstractions/MapperMaxRecursiveDepthAttribute.cs

View workflow job for this annotation

GitHub Actions / SourceGeneratorBenchmarks

Symbol 'MaxRecursiveDepth.get' is not part of the declared public API (https://github.com/dotnet/roslyn-analyzers/blob/main/src/PublicApiAnalyzers/PublicApiAnalyzers.Help.md)

Check warning on line 21 in src/Riok.Mapperly.Abstractions/MapperMaxRecursiveDepthAttribute.cs

View workflow job for this annotation

GitHub Actions / build

Symbol 'MaxRecursiveDepth.get' is not part of the declared public API (https://github.com/dotnet/roslyn-analyzers/blob/main/src/PublicApiAnalyzers/PublicApiAnalyzers.Help.md)

Check warning on line 21 in src/Riok.Mapperly.Abstractions/MapperMaxRecursiveDepthAttribute.cs

View workflow job for this annotation

GitHub Actions / MappingBenchmarks

Symbol 'MaxRecursiveDepth.get' is not part of the declared public API (https://github.com/dotnet/roslyn-analyzers/blob/main/src/PublicApiAnalyzers/PublicApiAnalyzers.Help.md)

Check warning on line 21 in src/Riok.Mapperly.Abstractions/MapperMaxRecursiveDepthAttribute.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

Symbol 'MaxRecursiveDepth.get' is not part of the declared public API (https://github.com/dotnet/roslyn-analyzers/blob/main/src/PublicApiAnalyzers/PublicApiAnalyzers.Help.md)
}
5 changes: 5 additions & 0 deletions src/Riok.Mapperly/Configuration/MapperConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -103,4 +103,9 @@ public record MapperConfiguration
/// Determines the access level of members that Mapperly will map.
/// </summary>
public MemberVisibility? IncludedMembers { get; init; }

/// <summary>
/// Defines the maximum recursive depth that an IQueryable mapping will use.
/// </summary>
public int? MaxRecursiveDepth { get; init; }
}
3 changes: 3 additions & 0 deletions src/Riok.Mapperly/Configuration/MapperConfigurationMerger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ public static MapperAttribute Merge(MapperConfiguration mapperConfiguration, Map
mapper.IncludedMembers =
mapperConfiguration.IncludedMembers ?? defaultMapperConfiguration.IncludedMembers ?? mapper.IncludedMembers;

mapper.MaxRecursiveDepth =
mapperConfiguration.MaxRecursiveDepth ?? defaultMapperConfiguration.MaxRecursiveDepth ?? mapper.MaxRecursiveDepth;

return mapper;
}
}
10 changes: 8 additions & 2 deletions src/Riok.Mapperly/Configuration/MapperConfigurationReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ MapperConfiguration defaultMapperConfiguration
Array.Empty<string>(),
Array.Empty<PropertyMappingConfiguration>(),
Mapper.IgnoreObsoleteMembersStrategy,
Mapper.RequiredMappingStrategy
Mapper.RequiredMappingStrategy,
Mapper.MaxRecursiveDepth
),
Array.Empty<DerivedTypeMappingConfiguration>()
);
Expand Down Expand Up @@ -79,13 +80,18 @@ private PropertiesMappingConfiguration BuildPropertiesConfig(IMethodSymbol metho
var requiredMapping = _dataAccessor.Access<MapperRequiredMappingAttribute>(method).FirstOrDefault() is not { } methodWarnUnmapped
? _defaultConfiguration.Properties.RequiredMappingStrategy
: methodWarnUnmapped.RequiredMappingStrategy;
var maxRecursiveDepth = _dataAccessor.Access<MapperMaxRecursiveDepthAttribute>(method).FirstOrDefault()
is not { } methodMaxRecursiveDepth
? _defaultConfiguration.Properties.MaxRecursiveDepth
: methodMaxRecursiveDepth.MaxRecursiveDepth;

return new PropertiesMappingConfiguration(
ignoredSourceProperties,
ignoredTargetProperties,
propertyConfigurations,
ignoreObsolete,
requiredMapping
requiredMapping,
maxRecursiveDepth
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@ public record PropertiesMappingConfiguration(
IReadOnlyCollection<string> IgnoredTargets,
IReadOnlyCollection<PropertyMappingConfiguration> ExplicitMappings,
IgnoreObsoleteMembersStrategy IgnoreObsoleteMembersStrategy,
RequiredMappingStrategy RequiredMappingStrategy
RequiredMappingStrategy RequiredMappingStrategy,
int MaxRecursiveDepth
);
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Collections.Immutable;
using Microsoft.CodeAnalysis;
using Riok.Mapperly.Abstractions;
using Riok.Mapperly.Descriptors.Mappings;
Expand All @@ -14,6 +15,7 @@ public class InlineExpressionMappingBuilderContext : MappingBuilderContext
{
private readonly MappingCollection _inlineExpressionMappings;
private readonly MappingBuilderContext _parentContext;
private readonly ImmutableDictionary<TypeMappingKey, int> _parentTypes;

public InlineExpressionMappingBuilderContext(MappingBuilderContext ctx, TypeMappingKey mappingKey)
: this(ctx, (ctx.FindMapping(mappingKey) as IUserMapping)?.Method, mappingKey) { }
Expand All @@ -23,18 +25,24 @@ private InlineExpressionMappingBuilderContext(MappingBuilderContext ctx, IMethod
{
_parentContext = ctx;
_inlineExpressionMappings = new MappingCollection();

_parentTypes = ctx is InlineExpressionMappingBuilderContext inlineCtx
? inlineCtx._parentTypes
: ImmutableDictionary.Create<TypeMappingKey, int>();
}

private InlineExpressionMappingBuilderContext(
InlineExpressionMappingBuilderContext ctx,
IMethodSymbol? userSymbol,
ImmutableDictionary<TypeMappingKey, int> parentTypes,
TypeMappingKey mappingKey,
bool clearDerivedTypes
)
: base(ctx, userSymbol, mappingKey, clearDerivedTypes)
{
_parentContext = ctx;
_inlineExpressionMappings = ctx._inlineExpressionMappings;
_parentTypes = parentTypes;
}

public override bool IsExpression => true;
Expand All @@ -56,6 +64,14 @@ conversionType is not MappingConversionType.EnumToString and not MappingConversi
/// <returns>The <see cref="INewInstanceMapping"/> if a mapping was found or <c>null</c> if none was found.</returns>
public override INewInstanceMapping? FindMapping(TypeMappingKey mappingKey)
{
// check for recursive loop returning null to prevent a loop or default when recursive limit is reached.
if (_parentTypes.TryGetValue(mappingKey, out var count))
{
return count >= Configuration.Properties.MaxRecursiveDepth
? new DefaultMemberMapping(mappingKey.Source, mappingKey.Target)
: null;
}

if (_inlineExpressionMappings.Find(mappingKey) is { } mapping)
return mapping;

Expand Down Expand Up @@ -139,9 +155,12 @@ protected override MappingBuilderContext ContextForMapping(
MappingBuildingOptions options
)
{
var mappingRecursionCount = _parentTypes.GetValueOrDefault(mappingKey);
var newParentTypes = _parentTypes.SetItem(mappingKey, mappingRecursionCount + 1);
return new InlineExpressionMappingBuilderContext(
this,
userSymbol,
newParentTypes,
mappingKey,
options.HasFlag(MappingBuildingOptions.ClearDerivedTypes)
);
Expand Down
16 changes: 16 additions & 0 deletions src/Riok.Mapperly/Descriptors/Mappings/DefaultMemberMapping.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using static Riok.Mapperly.Emit.Syntax.SyntaxFactoryHelper;

namespace Riok.Mapperly.Descriptors.Mappings;

/// <summary>
/// Represents a mapping that returns default.
/// <code>
/// target = default;
/// </code>
/// </summary>
public class DefaultMemberMapping(ITypeSymbol sourceType, ITypeSymbol targetType) : NewInstanceMapping(sourceType, targetType)
{
public override ExpressionSyntax Build(TypeMappingBuildContext ctx) => DefaultLiteral();
}
9 changes: 9 additions & 0 deletions src/Riok.Mapperly/Diagnostics/DiagnosticDescriptors.cs
Original file line number Diff line number Diff line change
Expand Up @@ -482,4 +482,13 @@ public static class DiagnosticDescriptors
DiagnosticSeverity.Error,
true
);

public static readonly DiagnosticDescriptor MaxRecursiveDepthMustBeZeroOrMore = new DiagnosticDescriptor(
"RMG056",

Check warning on line 487 in src/Riok.Mapperly/Diagnostics/DiagnosticDescriptors.cs

View workflow job for this annotation

GitHub Actions / sample

Check warning on line 487 in src/Riok.Mapperly/Diagnostics/DiagnosticDescriptors.cs

View workflow job for this annotation

GitHub Actions / package

Check warning on line 487 in src/Riok.Mapperly/Diagnostics/DiagnosticDescriptors.cs

View workflow job for this annotation

GitHub Actions / lint-dotnet

Rule 'RMG056' is not part of any analyzer release

Check warning on line 487 in src/Riok.Mapperly/Diagnostics/DiagnosticDescriptors.cs

View workflow job for this annotation

GitHub Actions / SourceGeneratorBenchmarks

Check warning on line 487 in src/Riok.Mapperly/Diagnostics/DiagnosticDescriptors.cs

View workflow job for this annotation

GitHub Actions / build

Check warning on line 487 in src/Riok.Mapperly/Diagnostics/DiagnosticDescriptors.cs

View workflow job for this annotation

GitHub Actions / MappingBenchmarks

Check warning on line 487 in src/Riok.Mapperly/Diagnostics/DiagnosticDescriptors.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

$"The value {nameof(MapperAttribute.MaxRecursiveDepth)} cannot be less than zero.",

Check warning on line 488 in src/Riok.Mapperly/Diagnostics/DiagnosticDescriptors.cs

View workflow job for this annotation

GitHub Actions / sample

The diagnostic title should not contain a period, nor any line return character, nor any leading or trailing whitespaces

Check warning on line 488 in src/Riok.Mapperly/Diagnostics/DiagnosticDescriptors.cs

View workflow job for this annotation

GitHub Actions / package

The diagnostic title should not contain a period, nor any line return character, nor any leading or trailing whitespaces

Check warning on line 488 in src/Riok.Mapperly/Diagnostics/DiagnosticDescriptors.cs

View workflow job for this annotation

GitHub Actions / package

The diagnostic title should not contain a period, nor any line return character, nor any leading or trailing whitespaces

Check warning on line 488 in src/Riok.Mapperly/Diagnostics/DiagnosticDescriptors.cs

View workflow job for this annotation

GitHub Actions / lint-dotnet

The diagnostic title should not contain a period, nor any line return character, nor any leading or trailing whitespaces

Check warning on line 488 in src/Riok.Mapperly/Diagnostics/DiagnosticDescriptors.cs

View workflow job for this annotation

GitHub Actions / SourceGeneratorBenchmarks

The diagnostic title should not contain a period, nor any line return character, nor any leading or trailing whitespaces

Check warning on line 488 in src/Riok.Mapperly/Diagnostics/DiagnosticDescriptors.cs

View workflow job for this annotation

GitHub Actions / build

The diagnostic title should not contain a period, nor any line return character, nor any leading or trailing whitespaces

Check warning on line 488 in src/Riok.Mapperly/Diagnostics/DiagnosticDescriptors.cs

View workflow job for this annotation

GitHub Actions / MappingBenchmarks

The diagnostic title should not contain a period, nor any line return character, nor any leading or trailing whitespaces

Check warning on line 488 in src/Riok.Mapperly/Diagnostics/DiagnosticDescriptors.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

The diagnostic title should not contain a period, nor any line return character, nor any leading or trailing whitespaces
$"The value {nameof(MapperAttribute.MaxRecursiveDepth)} cannot be less than zero.",

Check warning on line 489 in src/Riok.Mapperly/Diagnostics/DiagnosticDescriptors.cs

View workflow job for this annotation

GitHub Actions / sample

The diagnostic message should not contain any line return character nor any leading or trailing whitespaces and should either be a single sentence without a trailing period or a multi-sentences with a trailing period

Check warning on line 489 in src/Riok.Mapperly/Diagnostics/DiagnosticDescriptors.cs

View workflow job for this annotation

GitHub Actions / package

The diagnostic message should not contain any line return character nor any leading or trailing whitespaces and should either be a single sentence without a trailing period or a multi-sentences with a trailing period

Check warning on line 489 in src/Riok.Mapperly/Diagnostics/DiagnosticDescriptors.cs

View workflow job for this annotation

GitHub Actions / package

The diagnostic message should not contain any line return character nor any leading or trailing whitespaces and should either be a single sentence without a trailing period or a multi-sentences with a trailing period

Check warning on line 489 in src/Riok.Mapperly/Diagnostics/DiagnosticDescriptors.cs

View workflow job for this annotation

GitHub Actions / lint-dotnet

The diagnostic message should not contain any line return character nor any leading or trailing whitespaces and should either be a single sentence without a trailing period or a multi-sentences with a trailing period

Check warning on line 489 in src/Riok.Mapperly/Diagnostics/DiagnosticDescriptors.cs

View workflow job for this annotation

GitHub Actions / SourceGeneratorBenchmarks

The diagnostic message should not contain any line return character nor any leading or trailing whitespaces and should either be a single sentence without a trailing period or a multi-sentences with a trailing period

Check warning on line 489 in src/Riok.Mapperly/Diagnostics/DiagnosticDescriptors.cs

View workflow job for this annotation

GitHub Actions / build

The diagnostic message should not contain any line return character nor any leading or trailing whitespaces and should either be a single sentence without a trailing period or a multi-sentences with a trailing period

Check warning on line 489 in src/Riok.Mapperly/Diagnostics/DiagnosticDescriptors.cs

View workflow job for this annotation

GitHub Actions / MappingBenchmarks

The diagnostic message should not contain any line return character nor any leading or trailing whitespaces and should either be a single sentence without a trailing period or a multi-sentences with a trailing period

Check warning on line 489 in src/Riok.Mapperly/Diagnostics/DiagnosticDescriptors.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

The diagnostic message should not contain any line return character nor any leading or trailing whitespaces and should either be a single sentence without a trailing period or a multi-sentences with a trailing period
DiagnosticCategories.Mapper,
DiagnosticSeverity.Error,
true
);
}
159 changes: 159 additions & 0 deletions test/Riok.Mapperly.Tests/Mapping/QueryableProjectionLoopTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
using Riok.Mapperly.Diagnostics;

namespace Riok.Mapperly.Tests.Mapping;

[UsesVerify]
public class QueryableProjectionLoopTest
{
[Fact]
public Task ReferenceLoopInitProperty()
{
var source = TestSourceBuilder.Mapping(
"System.Linq.IQueryable<A>",
"System.Linq.IQueryable<B>",
"class A { public A? Parent { get; set; } }",
"class B { public B? Parent { get; set; } }"
);

return TestHelper.VerifyGenerator(source);
}

[Fact]
public Task SetRecursiveDepthForLoop()
{
var source = TestSourceBuilder.Mapping(
"System.Linq.IQueryable<A>",
"System.Linq.IQueryable<B>",
TestSourceBuilderOptions.Default with
{
MaxRecursiveDepth = 2
},
"class A { public A? Parent { get; set; } }",
"class B { public B? Parent { get; set; } }"
);

return TestHelper.VerifyGenerator(source);
}

[Fact]
public Task MethodAttributeSetRecursiveDepthForLoop()
{
var source = TestSourceBuilder.MapperWithBodyAndTypes(
"[MapperMaxRecursiveDepth(2)] "
+ "partial System.Linq.IQueryable<B> Map(System.Linq.IQueryable<A> src);"
+ "[MapperMaxRecursiveDepth(2)] partial B Map(A src);",
"class A { public A? Parent { get; set; } }",
"class B { public B? Parent { get; set; } }"
);

return TestHelper.VerifyGenerator(source);
}

[Fact]
public Task MethodAttributeOverridesClassSetRecursiveDepthForLoop()
{
var source = TestSourceBuilder.MapperWithBodyAndTypes(
"[MapperMaxRecursiveDepth(2)] "
+ "partial System.Linq.IQueryable<B> Map(System.Linq.IQueryable<A> src);"
+ "[MapperMaxRecursiveDepth(2)] partial B Map(A src);",
TestSourceBuilderOptions.Default with
{
MaxRecursiveDepth = 4
},
"class A { public A? Parent { get; set; } }",
"class B { public B? Parent { get; set; } }"
);

return TestHelper.VerifyGenerator(source);
}

[Fact]
public Task AssemblyDefaultShouldWork()
{
var source = TestSourceBuilder.CSharp(
"""
using Riok.Mapperly.Abstractions;
[assembly: MapperDefaultsAttribute(MaxRecursiveDepth = 2)]
[Mapper()]
public partial class MyMapper
{
private partial B Map(A source);
}
class A { public A? Parent { get; set; } }
class B { public B? Parent { get; set; } }
"""
);

return TestHelper.VerifyGenerator(source);
}

[Fact]
public Task AttributeShouldOverrideAssemblyDefault()
{
var source = TestSourceBuilder.CSharp(
"""
using Riok.Mapperly.Abstractions;
[assembly: MapperDefaultsAttribute(MaxRecursiveDepth = 2)]
[Mapper(MaxRecursiveDepth = 4)]
public partial class MyMapper
{
private partial B Map(A source);
}
class A { public A? Parent { get; set; } }
class B { public B? Parent { get; set; } }
"""
);

return TestHelper.VerifyGenerator(source);
}

[Fact]
public Task ReferenceLoopCtor()
{
var source = TestSourceBuilder.Mapping(
"System.Linq.IQueryable<A>",
"System.Linq.IQueryable<B>",
"class A { public A? Parent { get; set; } }",
"class B { public B(B? parent) {} }"
);

return TestHelper.VerifyGenerator(source);
}

[Fact]
public Task IndirectReferenceLoop()
{
var source = TestSourceBuilder.Mapping(
"System.Linq.IQueryable<A>",
"System.Linq.IQueryable<B>",
"class A { public string StringValue { get; set; } public C Parent { get; set; } }",
"class B { public string StringValue { get; set; } public D Parent { get; set; } }",
"class C { public A Parent { get; set; } }",
"class D { public B Parent { get; set; } }"
);

return TestHelper.VerifyGenerator(source);
}

[Fact]
public void WithReferenceHandlingShouldDiagnostic()
{
var source = TestSourceBuilder.Mapping(
"System.Linq.IQueryable<long>",
"System.Linq.IQueryable<int>",
TestSourceBuilderOptions.WithReferenceHandling
);

TestHelper
.GenerateMapper(source, TestHelperOptions.AllowDiagnostics)
.Should()
.HaveDiagnostic(DiagnosticDescriptors.QueryableProjectionMappingsDoNotSupportReferenceHandling)
.HaveAssertedAllDiagnostics();
}
}
Loading

0 comments on commit 3b903b9

Please sign in to comment.