From c2a338fa91ca8bfc761d20ac8ceb9a553dc45846 Mon Sep 17 00:00:00 2001 From: Lars Date: Thu, 23 Mar 2023 16:18:52 +0100 Subject: [PATCH] feat: Support IQueryable projection mappings (#287) --- .../03-generated-mapper-example.mdx | 2 +- .../11-queryable-projections.mdx | 73 ++++ .../{11-conversions.md => 12-conversions.md} | 16 + ...ostics.mdx => 13-analyzer-diagnostics.mdx} | 0 ...ted-source.mdx => 14-generated-source.mdx} | 0 .../MappingConversionType.cs | 19 + .../PublicAPI.Shipped.txt | 3 + src/Riok.Mapperly/AnalyzerReleases.Shipped.md | 11 + .../Descriptors/Configuration.cs | 52 +++ .../Descriptors/DescriptorBuilder.cs | 84 ++-- .../InlineExpressionMappingBuilderContext.cs | 118 ++++++ .../Descriptors/MapperDescriptor.cs | 17 +- .../INewInstanceBuilderContext.cs | 16 + .../IPropertiesBuilderContext.cs | 25 ++ .../IPropertiesContainerBuilderContext.cs | 16 + .../NewInstanceBuilderContext.cs} | 15 +- .../NewInstanceContainerBuilderContext.cs | 33 ++ .../PropertiesContainerBuilderContext.cs | 86 ++++ .../PropertiesMappingBuilderContext.cs} | 89 +---- .../MappingBodyBuilders/MappingBodyBuilder.cs | 6 + ...nstanceObjectPropertyMappingBodyBuilder.cs | 108 +++-- .../ObjectPropertyMappingBodyBuilder.cs | 22 +- .../UserMethodMappingBodyBuilder.cs | 5 +- .../Descriptors/MappingBuilderContext.cs | 100 +++-- .../DictionaryMappingBuilder.cs | 4 + .../MappingBuilders/EnumMappingBuilder.cs | 19 +- .../EnumerableMappingBuilder.cs | 15 +- .../ExistingTargetMappingBuilder.cs | 31 +- .../MappingBuilders/MappingBuilder.cs | 55 +-- ...NewInstanceObjectPropertyMappingBuilder.cs | 6 +- .../QueryableMappingBuilder.cs | 36 ++ .../StringToEnumMappingBuilder.cs | 11 +- .../Descriptors/MappingCollection.cs | 14 +- .../Mappings/EnumFromStringParseMapping.cs | 52 +++ ...ping.cs => EnumFromStringSwitchMapping.cs} | 17 +- .../INewInstanceObjectPropertyMapping.cs | 13 + ...NewInstanceObjectFactoryPropertyMapping.cs | 2 +- .../NewInstanceObjectPropertyMapping.cs | 47 +-- .../NewInstanceObjectPropertyMethodMapping.cs | 82 ++++ .../Mappings/NullDelegateMapping.cs | 4 +- ...ping.cs => ObjectPropertyMethodMapping.cs} | 4 +- .../PropertyMappings/NullPropertyMapping.cs | 40 +- .../Mappings/PropertyMappings/PropertyPath.cs | 61 ++- .../Mappings/QueryableProjectionMapping.cs | 46 +++ .../SimpleMappingBuilderContext.cs | 51 ++- .../Descriptors/WellKnownTypes.cs | 7 + .../Diagnostics/DiagnosticDescriptors.cs | 34 +- src/Riok.Mapperly/Emit/SyntaxFactoryHelper.cs | 40 +- .../Helpers/PropertySymbolExtensions.cs | 3 + .../BaseMapperTest.cs | 12 +- .../Dto/TestObjectDtoProjection.cs | 65 ++++ .../Mapper/ProjectionMapper.cs | 22 ++ .../Mapper/StaticTestMapper.cs | 6 +- .../Mapper/TestMapper.cs | 3 +- .../Models/TestObjectProjection.cs | 64 +++ .../ProjectionMapperTest.cs | 71 ++++ .../Riok.Mapperly.IntegrationTests.csproj | 9 +- ...erTest.SnapshotGeneratedSource.verified.cs | 26 ++ ...pperTest.RunMappingShouldWork.verified.txt | 66 ++++ ...erTest.SnapshotGeneratedSource.verified.cs | 311 +++++++++++++++ ...erTest.SnapshotGeneratedSource.verified.cs | 99 +++++ ...RunExtensionMappingShouldWork.verified.txt | 61 +++ ...pperTest.RunMappingShouldWork.verified.txt | 66 ++++ ...erTest.SnapshotGeneratedSource.verified.cs | 368 ++++++++++++++++++ ...erTest.SnapshotGeneratedSource.verified.cs | 7 +- ...erTest.SnapshotGeneratedSource.verified.cs | 101 +++++ ...RunExtensionMappingShouldWork.verified.txt | 1 - ...erTest.SnapshotGeneratedSource.verified.cs | 6 +- ...erTest.SnapshotGeneratedSource.verified.cs | 5 +- ...erTest.SnapshotGeneratedSource.verified.cs | 99 +++++ ...RunExtensionMappingShouldWork.verified.txt | 1 - ...erTest.SnapshotGeneratedSource.verified.cs | 6 +- .../Mapping/DictionaryTest.cs | 13 + test/Riok.Mapperly.Tests/Mapping/EnumTest.cs | 4 +- .../Mapping/EnumerableTest.cs | 15 + .../ObjectPropertyConstructorResolverTest.cs | 8 +- .../Mapping/ObjectPropertyIgnoreTest.cs | 4 +- .../Mapping/ObjectPropertyInitPropertyTest.cs | 4 +- .../Mapping/QueryableProjectionEnumTest.cs | 65 ++++ .../QueryableProjectionEnumerableTest.cs | 29 ++ .../QueryableProjectionNullableTest.cs | 119 ++++++ .../Mapping/QueryableProjectionTest.cs | 117 ++++++ ...umTest.EnumFromString#Mapper.g.verified.cs | 11 + ...est.EnumToAnotherEnum#Mapper.g.verified.cs | 11 + ...EnumTest.EnumToString#Mapper.g.verified.cs | 11 + ...ayToArrayExplicitCast#Mapper.g.verified.cs | 11 + ...ableTest.ExplicitCast#Mapper.g.verified.cs | 11 + ...ctionReadOnlyProperty#Mapper.g.verified.cs | 0 ....ToCollectionReadOnlyProperty.verified.txt | 0 ...urceAndTargetProperty#Mapper.g.verified.cs | 11 + ...rgetValueTypeProperty#Mapper.g.verified.cs | 11 + ...SourcePathAutoFlatten#Mapper.g.verified.cs | 11 + ...PathAutoFlattenString#Mapper.g.verified.cs | 11 + ...cePathManuallyFlatten#Mapper.g.verified.cs | 22 ++ ...ullableSourceProperty#Mapper.g.verified.cs | 11 + ...urceValueTypeProperty#Mapper.g.verified.cs | 11 + ...ullableTargetProperty#Mapper.g.verified.cs | 11 + ...rgetValueTypeProperty#Mapper.g.verified.cs | 11 + ...tionTest.ClassToClass#Mapper.g.verified.cs | 11 + ...assMultipleProperties#Mapper.g.verified.cs | 11 + ...urceAndTargetProperty#Mapper.g.verified.cs | 11 + ...ullableSourceProperty#Mapper.g.verified.cs | 11 + ...assToClassWithConfigs#Mapper.g.verified.cs | 34 ++ ...hedOptionalParameters#Mapper.g.verified.cs | 11 + ...est.ReferenceLoopCtor#Mapper.g.verified.cs | 11 + ...jectionTest.ReferenceLoopCtor.verified.txt | 40 ++ ...renceLoopInitProperty#Mapper.g.verified.cs | 11 + ...est.ReferenceLoopInitProperty.verified.txt | 28 ++ ...ethodsShouldBeIgnored#Mapper.g.verified.cs | 19 + 109 files changed, 3385 insertions(+), 441 deletions(-) create mode 100644 docs/docs/02-configuration/11-queryable-projections.mdx rename docs/docs/02-configuration/{11-conversions.md => 12-conversions.md} (86%) rename docs/docs/02-configuration/{12-analyzer-diagnostics.mdx => 13-analyzer-diagnostics.mdx} (100%) rename docs/docs/02-configuration/{13-generated-source.mdx => 14-generated-source.mdx} (100%) create mode 100644 src/Riok.Mapperly/Descriptors/Configuration.cs create mode 100644 src/Riok.Mapperly/Descriptors/InlineExpressionMappingBuilderContext.cs create mode 100644 src/Riok.Mapperly/Descriptors/MappingBodyBuilders/BuilderContext/INewInstanceBuilderContext.cs create mode 100644 src/Riok.Mapperly/Descriptors/MappingBodyBuilders/BuilderContext/IPropertiesBuilderContext.cs create mode 100644 src/Riok.Mapperly/Descriptors/MappingBodyBuilders/BuilderContext/IPropertiesContainerBuilderContext.cs rename src/Riok.Mapperly/Descriptors/{MappingBuilders/NewInstanceMappingBuilderContext.cs => MappingBodyBuilders/BuilderContext/NewInstanceBuilderContext.cs} (53%) create mode 100644 src/Riok.Mapperly/Descriptors/MappingBodyBuilders/BuilderContext/NewInstanceContainerBuilderContext.cs create mode 100644 src/Riok.Mapperly/Descriptors/MappingBodyBuilders/BuilderContext/PropertiesContainerBuilderContext.cs rename src/Riok.Mapperly/Descriptors/{MappingBuilders/ObjectPropertyMappingBuilderContext.cs => MappingBodyBuilders/BuilderContext/PropertiesMappingBuilderContext.cs} (60%) create mode 100644 src/Riok.Mapperly/Descriptors/MappingBuilders/QueryableMappingBuilder.cs create mode 100644 src/Riok.Mapperly/Descriptors/Mappings/EnumFromStringParseMapping.cs rename src/Riok.Mapperly/Descriptors/Mappings/{EnumFromStringMapping.cs => EnumFromStringSwitchMapping.cs} (84%) create mode 100644 src/Riok.Mapperly/Descriptors/Mappings/INewInstanceObjectPropertyMapping.cs create mode 100644 src/Riok.Mapperly/Descriptors/Mappings/NewInstanceObjectPropertyMethodMapping.cs rename src/Riok.Mapperly/Descriptors/Mappings/{ObjectPropertyMapping.cs => ObjectPropertyMethodMapping.cs} (91%) create mode 100644 src/Riok.Mapperly/Descriptors/Mappings/QueryableProjectionMapping.cs create mode 100644 test/Riok.Mapperly.IntegrationTests/Dto/TestObjectDtoProjection.cs create mode 100644 test/Riok.Mapperly.IntegrationTests/Mapper/ProjectionMapper.cs create mode 100644 test/Riok.Mapperly.IntegrationTests/Models/TestObjectProjection.cs create mode 100644 test/Riok.Mapperly.IntegrationTests/ProjectionMapperTest.cs create mode 100644 test/Riok.Mapperly.IntegrationTests/_snapshots/NET_48/CircularReferenceMapperTest.SnapshotGeneratedSource.verified.cs create mode 100644 test/Riok.Mapperly.IntegrationTests/_snapshots/NET_48/MapperTest.RunMappingShouldWork.verified.txt create mode 100644 test/Riok.Mapperly.IntegrationTests/_snapshots/NET_48/MapperTest.SnapshotGeneratedSource.verified.cs create mode 100644 test/Riok.Mapperly.IntegrationTests/_snapshots/NET_48/ProjectionMapperTest.SnapshotGeneratedSource.verified.cs create mode 100644 test/Riok.Mapperly.IntegrationTests/_snapshots/NET_48/StaticMapperTest.RunExtensionMappingShouldWork.verified.txt create mode 100644 test/Riok.Mapperly.IntegrationTests/_snapshots/NET_48/StaticMapperTest.RunMappingShouldWork.verified.txt create mode 100644 test/Riok.Mapperly.IntegrationTests/_snapshots/NET_48/StaticMapperTest.SnapshotGeneratedSource.verified.cs create mode 100644 test/Riok.Mapperly.IntegrationTests/_snapshots/Roslyn_4_4_OR_LOWER/ProjectionMapperTest.SnapshotGeneratedSource.verified.cs create mode 100644 test/Riok.Mapperly.IntegrationTests/_snapshots/Roslyn_4_5/ProjectionMapperTest.SnapshotGeneratedSource.verified.cs create mode 100644 test/Riok.Mapperly.Tests/Mapping/QueryableProjectionEnumTest.cs create mode 100644 test/Riok.Mapperly.Tests/Mapping/QueryableProjectionEnumerableTest.cs create mode 100644 test/Riok.Mapperly.Tests/Mapping/QueryableProjectionNullableTest.cs create mode 100644 test/Riok.Mapperly.Tests/Mapping/QueryableProjectionTest.cs create mode 100644 test/Riok.Mapperly.Tests/_snapshots/QueryableProjectionEnumTest.EnumFromString#Mapper.g.verified.cs create mode 100644 test/Riok.Mapperly.Tests/_snapshots/QueryableProjectionEnumTest.EnumToAnotherEnum#Mapper.g.verified.cs create mode 100644 test/Riok.Mapperly.Tests/_snapshots/QueryableProjectionEnumTest.EnumToString#Mapper.g.verified.cs create mode 100644 test/Riok.Mapperly.Tests/_snapshots/QueryableProjectionEnumerableTest.ArrayToArrayExplicitCast#Mapper.g.verified.cs create mode 100644 test/Riok.Mapperly.Tests/_snapshots/QueryableProjectionEnumerableTest.ExplicitCast#Mapper.g.verified.cs create mode 100644 test/Riok.Mapperly.Tests/_snapshots/QueryableProjectionEnumerableTest.ToCollectionReadOnlyProperty#Mapper.g.verified.cs create mode 100644 test/Riok.Mapperly.Tests/_snapshots/QueryableProjectionEnumerableTest.ToCollectionReadOnlyProperty.verified.txt create mode 100644 test/Riok.Mapperly.Tests/_snapshots/QueryableProjectionNullableTest.ClassToClassNullableSourceAndTargetProperty#Mapper.g.verified.cs create mode 100644 test/Riok.Mapperly.Tests/_snapshots/QueryableProjectionNullableTest.ClassToClassNullableSourceAndTargetValueTypeProperty#Mapper.g.verified.cs create mode 100644 test/Riok.Mapperly.Tests/_snapshots/QueryableProjectionNullableTest.ClassToClassNullableSourcePathAutoFlatten#Mapper.g.verified.cs create mode 100644 test/Riok.Mapperly.Tests/_snapshots/QueryableProjectionNullableTest.ClassToClassNullableSourcePathAutoFlattenString#Mapper.g.verified.cs create mode 100644 test/Riok.Mapperly.Tests/_snapshots/QueryableProjectionNullableTest.ClassToClassNullableSourcePathManuallyFlatten#Mapper.g.verified.cs create mode 100644 test/Riok.Mapperly.Tests/_snapshots/QueryableProjectionNullableTest.ClassToClassNullableSourceProperty#Mapper.g.verified.cs create mode 100644 test/Riok.Mapperly.Tests/_snapshots/QueryableProjectionNullableTest.ClassToClassNullableSourceValueTypeProperty#Mapper.g.verified.cs create mode 100644 test/Riok.Mapperly.Tests/_snapshots/QueryableProjectionNullableTest.ClassToClassNullableTargetProperty#Mapper.g.verified.cs create mode 100644 test/Riok.Mapperly.Tests/_snapshots/QueryableProjectionNullableTest.ClassToClassNullableTargetValueTypeProperty#Mapper.g.verified.cs create mode 100644 test/Riok.Mapperly.Tests/_snapshots/QueryableProjectionTest.ClassToClass#Mapper.g.verified.cs create mode 100644 test/Riok.Mapperly.Tests/_snapshots/QueryableProjectionTest.ClassToClassMultipleProperties#Mapper.g.verified.cs create mode 100644 test/Riok.Mapperly.Tests/_snapshots/QueryableProjectionTest.ClassToClassNullableSourceAndTargetProperty#Mapper.g.verified.cs create mode 100644 test/Riok.Mapperly.Tests/_snapshots/QueryableProjectionTest.ClassToClassNullableSourceProperty#Mapper.g.verified.cs create mode 100644 test/Riok.Mapperly.Tests/_snapshots/QueryableProjectionTest.ClassToClassWithConfigs#Mapper.g.verified.cs create mode 100644 test/Riok.Mapperly.Tests/_snapshots/QueryableProjectionTest.CtorShouldSkipUnmatchedOptionalParameters#Mapper.g.verified.cs create mode 100644 test/Riok.Mapperly.Tests/_snapshots/QueryableProjectionTest.ReferenceLoopCtor#Mapper.g.verified.cs create mode 100644 test/Riok.Mapperly.Tests/_snapshots/QueryableProjectionTest.ReferenceLoopCtor.verified.txt create mode 100644 test/Riok.Mapperly.Tests/_snapshots/QueryableProjectionTest.ReferenceLoopInitProperty#Mapper.g.verified.cs create mode 100644 test/Riok.Mapperly.Tests/_snapshots/QueryableProjectionTest.ReferenceLoopInitProperty.verified.txt create mode 100644 test/Riok.Mapperly.Tests/_snapshots/QueryableProjectionTest.UserImplementedMethodsShouldBeIgnored#Mapper.g.verified.cs diff --git a/docs/docs/01-getting-started/03-generated-mapper-example.mdx b/docs/docs/01-getting-started/03-generated-mapper-example.mdx index 4c1ea53427..920957636b 100644 --- a/docs/docs/01-getting-started/03-generated-mapper-example.mdx +++ b/docs/docs/01-getting-started/03-generated-mapper-example.mdx @@ -8,7 +8,7 @@ import GeneratedCarMapperSource from '!!raw-loader!../../src/data/generated/samp This example will show you what kind of code Mapperly generates. It is based on the [Mapperly sample](https://github.com/riok/mapperly/tree/main/samples/Riok.Mapperly.Sample). -To view the generated code of your own mapper, refer to the [generated source configuration](../02-configuration/13-generated-source.mdx). +To view the generated code of your own mapper, refer to the [generated source configuration](../02-configuration/14-generated-source.mdx). ## The source classes diff --git a/docs/docs/02-configuration/11-queryable-projections.mdx b/docs/docs/02-configuration/11-queryable-projections.mdx new file mode 100644 index 0000000000..7674a66e58 --- /dev/null +++ b/docs/docs/02-configuration/11-queryable-projections.mdx @@ -0,0 +1,73 @@ +import Tabs from '@theme/Tabs'; + +# IQueryable projections + +Mapperly does support `IQueryable` projections: + + + + + + +```csharp +[Mapper] +public static partial class CarMapper +{ + // highlight-start + public static partial IQueryable ProjectToDto(this IQueryable q); + // highlight-end +} +``` + + + + +```csharp +var dtos = await DbContext.Cars + .Where(...) + // highlight-start + .ProjectToDto() + // highlight-end + .ToListAsync(); +``` + + + + +This is useful in combination with Entity Framework and other ORM solutions which expose `IQueryable`. +Only fields present in the target class will be retrieved from the database. + +:::info + +Since queryable projection mappings use `System.Linq.Expressions.Expression` under the hood, +such mappings have several limitations: + +- Object factories are not applied +- Constructors with unmatched optional parameters are ignored +- `ThrowOnPropertyMappingNullMismatch` is ignored +- User implemented mappings are not supported +- Enum mappings do not support the `ByName` strategy +- Reference handling is not supported +- Nullable reference types are disabled + +::: + +## Property configurations + +To configure property mappings add partial mapping method definitions with attributes as needed. +Set these methods to private to hide them from callers. + +```csharp +[Mapper] +public static partial class CarMapper +{ + // highlight-start + public static partial IQueryable ProjectToDto(this IQueryable q); + // highlight-end + + // highlight-start + [MapProperty(nameof(Car.Manufacturer), nameof(CarDto.Producer)] + // highlight-end + private static partial CarDto Map(Car car); +} +``` diff --git a/docs/docs/02-configuration/11-conversions.md b/docs/docs/02-configuration/12-conversions.md similarity index 86% rename from docs/docs/02-configuration/11-conversions.md rename to docs/docs/02-configuration/12-conversions.md index 25746e1058..60bcb78d40 100644 --- a/docs/docs/02-configuration/11-conversions.md +++ b/docs/docs/02-configuration/12-conversions.md @@ -5,6 +5,7 @@ Mapperly implements several types of automatic conversions (in order of priority | Name | Description | Conditions | | -------------------- | ------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- | | Direct assignment | Directly assigns the source object to the target | Source type is assignable to the target type and `UseDeepCloning` is `false` | +| Queryable | Projects the source queryable to the target queryable | Source and target types are `IQueryable<>` | | Dictionary | Maps a source dictionary to an enumerable target | Source type is an `IDictionary<,>` or an `IReadOnlyDictionary<,>` | | Enumerable | Maps an enumerable source to an enumerable target | Source type is an `IEnumerable<>` | | Implicit cast | Implicit cast operator | An implicit cast operator is defined to cast from the source type to the target type | @@ -47,3 +48,18 @@ public partial class CarMapper ... } ``` + +## Enable only specific automatic conversions + +To enable only specific conversion types, set `EnabledConversions` the conversion type to enable: + +```csharp +// This disables conversions using the ToString() method, which is enabled by default: +// highlight-start +[Mapper(EnabledConversions = MappingConversionType.Constructor | MappingConversionType.ExplicitCast)] +// highlight-end +public partial class CarMapper +{ + ... +} +``` diff --git a/docs/docs/02-configuration/12-analyzer-diagnostics.mdx b/docs/docs/02-configuration/13-analyzer-diagnostics.mdx similarity index 100% rename from docs/docs/02-configuration/12-analyzer-diagnostics.mdx rename to docs/docs/02-configuration/13-analyzer-diagnostics.mdx diff --git a/docs/docs/02-configuration/13-generated-source.mdx b/docs/docs/02-configuration/14-generated-source.mdx similarity index 100% rename from docs/docs/02-configuration/13-generated-source.mdx rename to docs/docs/02-configuration/14-generated-source.mdx diff --git a/src/Riok.Mapperly.Abstractions/MappingConversionType.cs b/src/Riok.Mapperly.Abstractions/MappingConversionType.cs index 4c7839e233..1d99673f94 100644 --- a/src/Riok.Mapperly.Abstractions/MappingConversionType.cs +++ b/src/Riok.Mapperly.Abstractions/MappingConversionType.cs @@ -76,6 +76,25 @@ public enum MappingConversionType /// DateTimeToTimeOnly = 1 << 9, + /// + /// If the source and the target is a . + /// Only uses object initializers and inlines the mapping code. + /// + Queryable = 1 << 10, + + /// + /// If the source and the target is an + /// Maps each element individually. + /// + Enumerable = 1 << 11, + + /// + /// If the source and targets are + /// or . + /// Maps each individually. + /// + Dictionary = 1 << 12, + /// /// Enables all supported conversions. /// diff --git a/src/Riok.Mapperly.Abstractions/PublicAPI.Shipped.txt b/src/Riok.Mapperly.Abstractions/PublicAPI.Shipped.txt index a310d7a666..97efdd499f 100644 --- a/src/Riok.Mapperly.Abstractions/PublicAPI.Shipped.txt +++ b/src/Riok.Mapperly.Abstractions/PublicAPI.Shipped.txt @@ -52,6 +52,8 @@ Riok.Mapperly.Abstractions.MappingConversionType.All = -1 -> Riok.Mapperly.Abstr Riok.Mapperly.Abstractions.MappingConversionType.Constructor = 1 -> Riok.Mapperly.Abstractions.MappingConversionType Riok.Mapperly.Abstractions.MappingConversionType.DateTimeToDateOnly = 256 -> Riok.Mapperly.Abstractions.MappingConversionType Riok.Mapperly.Abstractions.MappingConversionType.DateTimeToTimeOnly = 512 -> Riok.Mapperly.Abstractions.MappingConversionType +Riok.Mapperly.Abstractions.MappingConversionType.Dictionary = 4096 -> Riok.Mapperly.Abstractions.MappingConversionType +Riok.Mapperly.Abstractions.MappingConversionType.Enumerable = 2048 -> Riok.Mapperly.Abstractions.MappingConversionType Riok.Mapperly.Abstractions.MappingConversionType.EnumToEnum = 128 -> Riok.Mapperly.Abstractions.MappingConversionType Riok.Mapperly.Abstractions.MappingConversionType.EnumToString = 64 -> Riok.Mapperly.Abstractions.MappingConversionType Riok.Mapperly.Abstractions.MappingConversionType.ExplicitCast = 4 -> Riok.Mapperly.Abstractions.MappingConversionType @@ -60,6 +62,7 @@ Riok.Mapperly.Abstractions.MappingConversionType.None = 0 -> Riok.Mapperly.Abstr Riok.Mapperly.Abstractions.MappingConversionType.ParseMethod = 8 -> Riok.Mapperly.Abstractions.MappingConversionType Riok.Mapperly.Abstractions.MappingConversionType.StringToEnum = 32 -> Riok.Mapperly.Abstractions.MappingConversionType Riok.Mapperly.Abstractions.MappingConversionType.ToStringMethod = 16 -> Riok.Mapperly.Abstractions.MappingConversionType +Riok.Mapperly.Abstractions.MappingConversionType.Queryable = 1024 -> Riok.Mapperly.Abstractions.MappingConversionType Riok.Mapperly.Abstractions.MapperAttribute.UseReferenceHandling.get -> bool Riok.Mapperly.Abstractions.MapperAttribute.UseReferenceHandling.set -> void Riok.Mapperly.Abstractions.ReferenceHandling.Internal.PreserveReferenceHandler diff --git a/src/Riok.Mapperly/AnalyzerReleases.Shipped.md b/src/Riok.Mapperly/AnalyzerReleases.Shipped.md index 1dac6409d0..b5f875ed42 100644 --- a/src/Riok.Mapperly/AnalyzerReleases.Shipped.md +++ b/src/Riok.Mapperly/AnalyzerReleases.Shipped.md @@ -67,3 +67,14 @@ Rule ID | Category | Severity | Notes RMG026 | Mapper | Info | Cannot map from indexed property RMG027 | Mapper | Warning | A constructor parameter can have one configuration at max RMG028 | Mapper | Error | Constructor parameter cannot handle target paths + +## Release 2.8 + +### New Rules + +Rule ID | Category | Severity | Notes +--------|----------|----------|------- +RMG029 | Mapper | Error | Queryable projection mappings do not support reference handling +RMG030 | Mapper | Error | Reference loop detected while mapping to an init only property +RMG031 | Mapper | Warning | Reference loop detected while mapping to a constructor property +RMG032 | Mapper | Warning | The enum mapping strategy ByName cannot be used in projection mappings diff --git a/src/Riok.Mapperly/Descriptors/Configuration.cs b/src/Riok.Mapperly/Descriptors/Configuration.cs new file mode 100644 index 0000000000..035cfb149a --- /dev/null +++ b/src/Riok.Mapperly/Descriptors/Configuration.cs @@ -0,0 +1,52 @@ +using Microsoft.CodeAnalysis; +using Riok.Mapperly.Abstractions; +using Riok.Mapperly.Configuration; + +namespace Riok.Mapperly.Descriptors; + +public class Configuration +{ + /// + /// Default configurations, used if a configuration is required for a mapping + /// but no configuration is provided by the user. + /// These are the default configurations registered for each configuration attribute (eg. the ). + /// Usually these are derived from the or default values. + /// + private readonly Dictionary _defaultConfigurations = new(); + + private readonly Compilation _compilation; + + public Configuration(Compilation compilation, INamedTypeSymbol mapperSymbol) + { + _compilation = compilation; + Mapper = AttributeDataAccessor.AccessFirstOrDefault(compilation, mapperSymbol) ?? new(); + InitDefaultConfigurations(); + } + + public MapperAttribute Mapper { get; } + + public T GetOrDefault(IMethodSymbol? userSymbol) + where T : Attribute + { + return ListConfiguration(userSymbol).FirstOrDefault() + ?? (T)_defaultConfigurations[typeof(T)]; + } + + public IEnumerable ListConfiguration(IMethodSymbol? userSymbol) + where T : Attribute + { + return userSymbol == null + ? Enumerable.Empty() + : AttributeDataAccessor.Access(_compilation, userSymbol); + } + + private void InitDefaultConfigurations() + { + _defaultConfigurations.Add( + typeof(MapEnumAttribute), + new MapEnumAttribute(Mapper.EnumMappingStrategy) + { + IgnoreCase = Mapper.EnumMappingIgnoreCase + }); + } +} diff --git a/src/Riok.Mapperly/Descriptors/DescriptorBuilder.cs b/src/Riok.Mapperly/Descriptors/DescriptorBuilder.cs index e69c78bed7..af63714568 100644 --- a/src/Riok.Mapperly/Descriptors/DescriptorBuilder.cs +++ b/src/Riok.Mapperly/Descriptors/DescriptorBuilder.cs @@ -1,7 +1,5 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; -using Riok.Mapperly.Abstractions; -using Riok.Mapperly.Configuration; using Riok.Mapperly.Descriptors.MappingBodyBuilders; using Riok.Mapperly.Descriptors.MappingBuilders; using Riok.Mapperly.Descriptors.ObjectFactories; @@ -11,18 +9,14 @@ namespace Riok.Mapperly.Descriptors; public class DescriptorBuilder { - private readonly SourceProductionContext _context; - private readonly ITypeSymbol _mapperSymbol; private readonly MapperDescriptor _mapperDescriptor; - // default configurations, used a configuration is needed but no configuration is provided by the user - // these are the default configurations registered for each configuration attribute. - // Usually these are derived from the mapper attribute or default values. - private readonly Dictionary _defaultConfigurations = new(); - private readonly MappingCollection _mappings = new(); private readonly MethodNameBuilder _methodNameBuilder = new(); private readonly MappingBodyBuilder _mappingBodyBuilder; + private readonly SimpleMappingBuilderContext _builderContext; + + private ObjectFactoryCollection _objectFactories = ObjectFactoryCollection.Empty; public DescriptorBuilder( SourceProductionContext sourceContext, @@ -30,43 +24,16 @@ public DescriptorBuilder( ClassDeclarationSyntax mapperSyntax, INamedTypeSymbol mapperSymbol) { - _mapperSymbol = mapperSymbol; - _context = sourceContext; _mapperDescriptor = new MapperDescriptor(mapperSyntax, mapperSymbol, _methodNameBuilder); _mappingBodyBuilder = new MappingBodyBuilder(_mappings); - Compilation = compilation; - WellKnownTypes = new WellKnownTypes(Compilation); - MappingBuilder = new MappingBuilder(this, _mappings); - ExistingTargetMappingBuilder = new ExistingTargetMappingBuilder(this, _mappings); - MapperConfiguration = Configure(); - } - - internal IReadOnlyDictionary DefaultConfigurations => _defaultConfigurations; - - internal Compilation Compilation { get; } - - internal WellKnownTypes WellKnownTypes { get; } - - internal ObjectFactoryCollection ObjectFactories { get; private set; } = ObjectFactoryCollection.Empty; - - public MapperAttribute MapperConfiguration { get; } - - public MappingBuilder MappingBuilder { get; } - - public ExistingTargetMappingBuilder ExistingTargetMappingBuilder { get; } - - private MapperAttribute Configure() - { - var mapperAttribute = AttributeDataAccessor.AccessFirstOrDefault(Compilation, _mapperSymbol) ?? new(); - if (!_mapperSymbol.ContainingNamespace.IsGlobalNamespace) - { - _mapperDescriptor.Namespace = _mapperSymbol.ContainingNamespace.ToDisplayString(); - } - - _defaultConfigurations.Add( - typeof(MapEnumAttribute), - new MapEnumAttribute(mapperAttribute.EnumMappingStrategy) { IgnoreCase = mapperAttribute.EnumMappingIgnoreCase }); - return mapperAttribute; + _builderContext = new SimpleMappingBuilderContext( + compilation, + new Configuration(compilation, mapperSymbol), + new WellKnownTypes(compilation), + _mapperDescriptor, + sourceContext, + new MappingBuilder(_mappings), + new ExistingTargetMappingBuilder(_mappings)); } public MapperDescriptor Build() @@ -83,31 +50,28 @@ public MapperDescriptor Build() private void ExtractObjectFactories() { - var ctx = new SimpleMappingBuilderContext(this); - ObjectFactories = ObjectFactoryBuilder.ExtractObjectFactories(ctx, _mapperSymbol); + _objectFactories = ObjectFactoryBuilder.ExtractObjectFactories(_builderContext, _mapperDescriptor.Symbol); } - internal void ReportDiagnostic(DiagnosticDescriptor descriptor, Location? location, params object[] messageArgs) - => _context.ReportDiagnostic(Diagnostic.Create(descriptor, location ?? _mapperDescriptor.Syntax.GetLocation(), messageArgs)); - private void ExtractUserMappings() { - var defaultContext = new SimpleMappingBuilderContext(this); - foreach (var userMapping in UserMethodMappingExtractor.ExtractUserMappings(defaultContext, _mapperSymbol)) + foreach (var userMapping in UserMethodMappingExtractor.ExtractUserMappings(_builderContext, _mapperDescriptor.Symbol)) { var ctx = new MappingBuilderContext( - this, + _builderContext, + _objectFactories, + userMapping.Method, userMapping.SourceType, - userMapping.TargetType, - userMapping.Method); - _mappings.AddMapping(userMapping); - _mappings.EnqueueMappingToBuildBody(userMapping, ctx); + userMapping.TargetType); + + _mappings.Add(userMapping); + _mappings.EnqueueToBuildBody(userMapping, ctx); } } private void ReserveMethodNames() { - foreach (var methodSymbol in _mapperSymbol.GetAllMembers()) + foreach (var methodSymbol in _mapperDescriptor.Symbol.GetAllMembers()) { _methodNameBuilder.Reserve(methodSymbol.Name); } @@ -123,19 +87,19 @@ private void BuildMappingMethodNames() private void BuildReferenceHandlingParameters() { - if (!MapperConfiguration.UseReferenceHandling) + if (!_builderContext.MapperConfiguration.UseReferenceHandling) return; foreach (var methodMapping in _mappings.MethodMappings) { - methodMapping.EnableReferenceHandling(WellKnownTypes.IReferenceHandler); + methodMapping.EnableReferenceHandling(_builderContext.Types.IReferenceHandler); } } private void AddMappingsToDescriptor() { // add generated mappings to the mapper - foreach (var mapping in _mappings.All) + foreach (var mapping in _mappings.MethodMappings) { _mapperDescriptor.AddTypeMapping(mapping); } diff --git a/src/Riok.Mapperly/Descriptors/InlineExpressionMappingBuilderContext.cs b/src/Riok.Mapperly/Descriptors/InlineExpressionMappingBuilderContext.cs new file mode 100644 index 0000000000..a9888ceb9e --- /dev/null +++ b/src/Riok.Mapperly/Descriptors/InlineExpressionMappingBuilderContext.cs @@ -0,0 +1,118 @@ +using Microsoft.CodeAnalysis; +using Riok.Mapperly.Abstractions; +using Riok.Mapperly.Descriptors.Mappings; +using Riok.Mapperly.Descriptors.Mappings.ExistingTarget; +using Riok.Mapperly.Helpers; + +namespace Riok.Mapperly.Descriptors; + +/// +/// A implementation, +/// which tries to only build mappings which are safe to be used in . +/// +public class InlineExpressionMappingBuilderContext : MappingBuilderContext +{ + private readonly MappingCollection _inlineExpressionMappings; + + public InlineExpressionMappingBuilderContext(MappingBuilderContext ctx, ITypeSymbol sourceType, ITypeSymbol targetType) + : this(ctx, (ctx.FindMapping(sourceType, targetType) as IUserMapping)?.Method, sourceType, targetType) + { + } + + private InlineExpressionMappingBuilderContext( + MappingBuilderContext ctx, + IMethodSymbol? userSymbol, + ITypeSymbol source, + ITypeSymbol target) + : base(ctx, userSymbol, source, target) + { + _inlineExpressionMappings = new MappingCollection(); + } + + private InlineExpressionMappingBuilderContext( + InlineExpressionMappingBuilderContext ctx, + IMethodSymbol? userSymbol, + ITypeSymbol source, + ITypeSymbol target) + : base(ctx, userSymbol, source, target) + { + _inlineExpressionMappings = ctx._inlineExpressionMappings; + } + + public override bool IsExpression => true; + + public override bool IsConversionEnabled(MappingConversionType conversionType) + // cannot convert enum to string via optimized mapping since this would include a switch + // expression, which is not valid in an expression. + // fall back to the ToString implementation. + => conversionType + is not MappingConversionType.EnumToString + and not MappingConversionType.Dictionary + && base.IsConversionEnabled(conversionType); + + /// + /// Tries to find an existing mapping for the provided types. + /// The nullable annotation of reference types is ignored and always set to non-nullable. + /// + /// The source type. + /// The target type. + /// The if a mapping was found or null if none was found. + public override ITypeMapping? FindMapping(ITypeSymbol sourceType, ITypeSymbol targetType) + => _inlineExpressionMappings.Find(sourceType, targetType); + + /// + /// Existing target instance mappings are not supported. + /// + /// The source type. + /// The target type. + /// null + public override IExistingTargetMapping? FindOrBuildExistingTargetMapping(ITypeSymbol sourceType, ITypeSymbol targetType) + => null; + + /// + /// Existing target instance mappings are not supported. + /// + /// The source type. + /// The target type. + /// null + public override IExistingTargetMapping? BuildExistingTargetMappingWithUserSymbol(ITypeSymbol sourceType, ITypeSymbol targetType) + => null; + + /// + /// Always builds a new mapping with the user symbol of the first user defined mapping method for the provided types + /// or no user symbol if no user defined mapping is available unless if this + /// already built a mapping for the specified types, then this mapping is reused. + /// The nullable annotation of reference types is ignored and always set to non-nullable. + /// This ensures, the configuration of the user defined method is reused. + /// + /// + /// The user symbol. + /// The source type. + /// The target type. + /// Whether the built mapping is usable by other mappings, this implementation always sets this to false. + /// + protected override ITypeMapping? FindOrBuildMapping(IMethodSymbol? userSymbol, ITypeSymbol sourceType, ITypeSymbol targetType, bool reusable) + { + sourceType = sourceType.UpgradeNullable(); + targetType = targetType.UpgradeNullable(); + var mapping = _inlineExpressionMappings.Find(sourceType, targetType); + if (mapping != null) + return mapping; + + userSymbol ??= (MappingBuilder.Find(sourceType, targetType) as IUserMapping)?.Method; + + mapping = BuildMapping(userSymbol, sourceType, targetType, false); + if (mapping != null) + { + _inlineExpressionMappings.Add(mapping); + } + + return mapping; + } + + protected override NullFallbackValue GetNullFallbackValue(ITypeSymbol targetType, bool throwOnMappingNullMismatch) + => base.GetNullFallbackValue(targetType, false); // never throw inside expressions (not translatable) + + protected override MappingBuilderContext ContextForMapping(IMethodSymbol? userSymbol, ITypeSymbol sourceType, ITypeSymbol targetType) + => new InlineExpressionMappingBuilderContext(this, userSymbol, sourceType, targetType); +} diff --git a/src/Riok.Mapperly/Descriptors/MapperDescriptor.cs b/src/Riok.Mapperly/Descriptors/MapperDescriptor.cs index 7cc313a569..f1078081b2 100644 --- a/src/Riok.Mapperly/Descriptors/MapperDescriptor.cs +++ b/src/Riok.Mapperly/Descriptors/MapperDescriptor.cs @@ -7,17 +7,21 @@ namespace Riok.Mapperly.Descriptors; public class MapperDescriptor { - - private readonly List _mappings = new(); + private readonly List _methodMappings = new(); public MapperDescriptor(ClassDeclarationSyntax syntax, INamedTypeSymbol symbol, UniqueNameBuilder nameBuilder) { Syntax = syntax; Symbol = symbol; NameBuilder = nameBuilder; + + if (!Symbol.ContainingNamespace.IsGlobalNamespace) + { + Namespace = Symbol.ContainingNamespace.ToDisplayString(); + } } - public string? Namespace { get; set; } + public string? Namespace { get; } public ClassDeclarationSyntax Syntax { get; } @@ -25,9 +29,8 @@ public MapperDescriptor(ClassDeclarationSyntax syntax, INamedTypeSymbol symbol, public UniqueNameBuilder NameBuilder { get; } - public IEnumerable MethodTypeMappings - => _mappings.OfType(); + public IReadOnlyCollection MethodTypeMappings => _methodMappings; - public void AddTypeMapping(ITypeMapping mapping) - => _mappings.Add(mapping); + public void AddTypeMapping(MethodMapping mapping) + => _methodMappings.Add(mapping); } diff --git a/src/Riok.Mapperly/Descriptors/MappingBodyBuilders/BuilderContext/INewInstanceBuilderContext.cs b/src/Riok.Mapperly/Descriptors/MappingBodyBuilders/BuilderContext/INewInstanceBuilderContext.cs new file mode 100644 index 0000000000..53222e33a6 --- /dev/null +++ b/src/Riok.Mapperly/Descriptors/MappingBodyBuilders/BuilderContext/INewInstanceBuilderContext.cs @@ -0,0 +1,16 @@ +using Riok.Mapperly.Descriptors.Mappings; +using Riok.Mapperly.Descriptors.Mappings.PropertyMappings; + +namespace Riok.Mapperly.Descriptors.MappingBodyBuilders.BuilderContext; + +/// +/// A for mappings which create the target object via new(). +/// +/// The mapping type. +public interface INewInstanceBuilderContext : IPropertiesBuilderContext + where T : IMapping +{ + void AddConstructorParameterMapping(ConstructorParameterMapping mapping); + + void AddInitPropertyMapping(PropertyAssignmentMapping mapping); +} diff --git a/src/Riok.Mapperly/Descriptors/MappingBodyBuilders/BuilderContext/IPropertiesBuilderContext.cs b/src/Riok.Mapperly/Descriptors/MappingBodyBuilders/BuilderContext/IPropertiesBuilderContext.cs new file mode 100644 index 0000000000..9848051c82 --- /dev/null +++ b/src/Riok.Mapperly/Descriptors/MappingBodyBuilders/BuilderContext/IPropertiesBuilderContext.cs @@ -0,0 +1,25 @@ +using Microsoft.CodeAnalysis; +using Riok.Mapperly.Abstractions; +using Riok.Mapperly.Descriptors.Mappings; + +namespace Riok.Mapperly.Descriptors.MappingBodyBuilders.BuilderContext; + +/// +/// Context to build property mappings. +/// +/// The type of the mapping. +public interface IPropertiesBuilderContext + where T : IMapping +{ + T Mapping { get; } + + void AddDiagnostics(); + + MappingBuilderContext BuilderContext { get; } + + IReadOnlyCollection IgnoredSourcePropertyNames { get; } + + Dictionary TargetProperties { get; } + + Dictionary> PropertyConfigsByRootTargetName { get; } +} diff --git a/src/Riok.Mapperly/Descriptors/MappingBodyBuilders/BuilderContext/IPropertiesContainerBuilderContext.cs b/src/Riok.Mapperly/Descriptors/MappingBodyBuilders/BuilderContext/IPropertiesContainerBuilderContext.cs new file mode 100644 index 0000000000..1cea201898 --- /dev/null +++ b/src/Riok.Mapperly/Descriptors/MappingBodyBuilders/BuilderContext/IPropertiesContainerBuilderContext.cs @@ -0,0 +1,16 @@ +using Riok.Mapperly.Descriptors.Mappings.PropertyMappings; + +namespace Riok.Mapperly.Descriptors.MappingBodyBuilders.BuilderContext; + +/// +/// An which supports containers. +/// A container groups several property mappings in one not-null checked block. +/// +/// The type of the mapping. +public interface IPropertiesContainerBuilderContext : IPropertiesBuilderContext + where T : IPropertyAssignmentTypeMapping +{ + void AddPropertyAssignmentMapping(IPropertyAssignmentMapping propertyMapping); + + void AddNullDelegatePropertyAssignmentMapping(IPropertyAssignmentMapping propertyMapping); +} diff --git a/src/Riok.Mapperly/Descriptors/MappingBuilders/NewInstanceMappingBuilderContext.cs b/src/Riok.Mapperly/Descriptors/MappingBodyBuilders/BuilderContext/NewInstanceBuilderContext.cs similarity index 53% rename from src/Riok.Mapperly/Descriptors/MappingBuilders/NewInstanceMappingBuilderContext.cs rename to src/Riok.Mapperly/Descriptors/MappingBodyBuilders/BuilderContext/NewInstanceBuilderContext.cs index 50c84219d9..d7aaf114e1 100644 --- a/src/Riok.Mapperly/Descriptors/MappingBuilders/NewInstanceMappingBuilderContext.cs +++ b/src/Riok.Mapperly/Descriptors/MappingBodyBuilders/BuilderContext/NewInstanceBuilderContext.cs @@ -1,11 +1,19 @@ using Riok.Mapperly.Descriptors.Mappings; using Riok.Mapperly.Descriptors.Mappings.PropertyMappings; -namespace Riok.Mapperly.Descriptors.MappingBuilders; +namespace Riok.Mapperly.Descriptors.MappingBodyBuilders.BuilderContext; -public class NewInstanceMappingBuilderContext : ObjectPropertyMappingBuilderContext +/// +/// An implementation of . +/// +/// The type of the mapping. +public class NewInstanceBuilderContext : + PropertiesMappingBuilderContext, + INewInstanceBuilderContext + where T : INewInstanceObjectPropertyMapping { - public NewInstanceMappingBuilderContext(MappingBuilderContext builderContext, NewInstanceObjectPropertyMapping mapping) : base(builderContext, mapping) + public NewInstanceBuilderContext(MappingBuilderContext builderContext, T mapping) + : base(builderContext, mapping) { } @@ -18,7 +26,6 @@ public void AddInitPropertyMapping(PropertyAssignmentMapping mapping) public void AddConstructorParameterMapping(ConstructorParameterMapping mapping) { PropertyConfigsByRootTargetName.Remove(mapping.Parameter.Name); - SetSourcePropertyMapped(mapping.DelegateMapping.SourcePath); Mapping.AddConstructorParameterMapping(mapping); } diff --git a/src/Riok.Mapperly/Descriptors/MappingBodyBuilders/BuilderContext/NewInstanceContainerBuilderContext.cs b/src/Riok.Mapperly/Descriptors/MappingBodyBuilders/BuilderContext/NewInstanceContainerBuilderContext.cs new file mode 100644 index 0000000000..a64c74d6ff --- /dev/null +++ b/src/Riok.Mapperly/Descriptors/MappingBodyBuilders/BuilderContext/NewInstanceContainerBuilderContext.cs @@ -0,0 +1,33 @@ +using Riok.Mapperly.Descriptors.Mappings; +using Riok.Mapperly.Descriptors.Mappings.PropertyMappings; + +namespace Riok.Mapperly.Descriptors.MappingBodyBuilders.BuilderContext; + +/// +/// An implementation of an +/// which supports containers (). +/// +/// +public class NewInstanceContainerBuilderContext : + PropertiesContainerBuilderContext, + INewInstanceBuilderContext + where T : INewInstanceObjectPropertyMapping, IPropertyAssignmentTypeMapping +{ + public NewInstanceContainerBuilderContext(MappingBuilderContext builderContext, T mapping) + : base(builderContext, mapping) + { + } + + public void AddInitPropertyMapping(PropertyAssignmentMapping mapping) + { + SetSourcePropertyMapped(mapping.SourcePath); + Mapping.AddInitPropertyMapping(mapping); + } + + public void AddConstructorParameterMapping(ConstructorParameterMapping mapping) + { + PropertyConfigsByRootTargetName.Remove(mapping.Parameter.Name); + SetSourcePropertyMapped(mapping.DelegateMapping.SourcePath); + Mapping.AddConstructorParameterMapping(mapping); + } +} diff --git a/src/Riok.Mapperly/Descriptors/MappingBodyBuilders/BuilderContext/PropertiesContainerBuilderContext.cs b/src/Riok.Mapperly/Descriptors/MappingBodyBuilders/BuilderContext/PropertiesContainerBuilderContext.cs new file mode 100644 index 0000000000..46a6098262 --- /dev/null +++ b/src/Riok.Mapperly/Descriptors/MappingBodyBuilders/BuilderContext/PropertiesContainerBuilderContext.cs @@ -0,0 +1,86 @@ +using Riok.Mapperly.Descriptors.Mappings.PropertyMappings; +using Riok.Mapperly.Diagnostics; +using Riok.Mapperly.Helpers; + +namespace Riok.Mapperly.Descriptors.MappingBodyBuilders.BuilderContext; + +/// +/// An implementation. +/// +/// The type of mapping. +public class PropertiesContainerBuilderContext : + PropertiesMappingBuilderContext, + IPropertiesContainerBuilderContext + where T : IPropertyAssignmentTypeMapping +{ + private readonly Dictionary _nullDelegateMappings = new(); + + public PropertiesContainerBuilderContext( + MappingBuilderContext builderContext, + T mapping) + : base(builderContext, mapping) + { + } + + public void AddPropertyAssignmentMapping(IPropertyAssignmentMapping propertyMapping) + => AddPropertyAssignmentMapping(Mapping, propertyMapping); + + public void AddNullDelegatePropertyAssignmentMapping(IPropertyAssignmentMapping propertyMapping) + { + var nullConditionSourcePath = new PropertyPath(propertyMapping.SourcePath.PathWithoutTrailingNonNullable().ToList()); + var container = GetOrCreateNullDelegateMappingForPath(nullConditionSourcePath); + AddPropertyAssignmentMapping(container, propertyMapping); + } + + private void AddPropertyAssignmentMapping(IPropertyAssignmentMappingContainer container, IPropertyAssignmentMapping mapping) + { + SetSourcePropertyMapped(mapping.SourcePath); + AddNullPropertyInitializers(container, mapping.TargetPath); + container.AddPropertyMapping(mapping); + } + + + private void AddNullPropertyInitializers(IPropertyAssignmentMappingContainer container, PropertyPath path) + { + foreach (var nullableTrailPath in path.ObjectPathNullableSubPaths()) + { + var nullablePath = new PropertyPath(nullableTrailPath); + var type = nullablePath.Member.Type; + if (!type.HasAccessibleParameterlessConstructor()) + { + BuilderContext.ReportDiagnostic( + DiagnosticDescriptors.NoParameterlessConstructorFound, + type); + continue; + } + + container.AddPropertyMappingContainer(new PropertyNullAssignmentInitializerMapping(nullablePath)); + } + } + + private PropertyNullDelegateAssignmentMapping GetOrCreateNullDelegateMappingForPath(PropertyPath nullConditionSourcePath) + { + // if there is already an exact match return that + if (_nullDelegateMappings.TryGetValue(nullConditionSourcePath, out var mapping)) + return mapping; + + IPropertyAssignmentMappingContainer parentMapping = Mapping; + + // try to reuse parent path mappings and wrap inside them + foreach (var nullablePath in nullConditionSourcePath.ObjectPathNullableSubPaths().Reverse()) + { + if (_nullDelegateMappings.TryGetValue(new PropertyPath(nullablePath), out var parentMappingHolder)) + { + parentMapping = parentMappingHolder; + } + } + + mapping = new PropertyNullDelegateAssignmentMapping( + nullConditionSourcePath, + parentMapping, + BuilderContext.MapperConfiguration.ThrowOnPropertyMappingNullMismatch); + _nullDelegateMappings[nullConditionSourcePath] = mapping; + parentMapping.AddPropertyMappingContainer(mapping); + return mapping; + } +} diff --git a/src/Riok.Mapperly/Descriptors/MappingBuilders/ObjectPropertyMappingBuilderContext.cs b/src/Riok.Mapperly/Descriptors/MappingBodyBuilders/BuilderContext/PropertiesMappingBuilderContext.cs similarity index 60% rename from src/Riok.Mapperly/Descriptors/MappingBuilders/ObjectPropertyMappingBuilderContext.cs rename to src/Riok.Mapperly/Descriptors/MappingBodyBuilders/BuilderContext/PropertiesMappingBuilderContext.cs index fd9f2cfa08..6c200648ac 100644 --- a/src/Riok.Mapperly/Descriptors/MappingBuilders/ObjectPropertyMappingBuilderContext.cs +++ b/src/Riok.Mapperly/Descriptors/MappingBodyBuilders/BuilderContext/PropertiesMappingBuilderContext.cs @@ -1,32 +1,24 @@ using Microsoft.CodeAnalysis; using Riok.Mapperly.Abstractions; +using Riok.Mapperly.Descriptors.Mappings; using Riok.Mapperly.Descriptors.Mappings.PropertyMappings; using Riok.Mapperly.Diagnostics; using Riok.Mapperly.Helpers; -namespace Riok.Mapperly.Descriptors.MappingBuilders; +namespace Riok.Mapperly.Descriptors.MappingBodyBuilders.BuilderContext; -public class ObjectPropertyMappingBuilderContext - : ObjectPropertyMappingBuilderContext - where T : IPropertyAssignmentTypeMapping +/// +/// An abstract base implementation of . +/// +/// The type of the mapping. +public abstract class PropertiesMappingBuilderContext : IPropertiesBuilderContext + where T : IMapping { - public ObjectPropertyMappingBuilderContext(MappingBuilderContext builderContext, T mapping) - : base(builderContext, mapping) - { - Mapping = mapping; - } - - protected new T Mapping { get; } -} - -public class ObjectPropertyMappingBuilderContext -{ - private readonly Dictionary _nullDelegateMappings = new(); private readonly HashSet _unmappedSourcePropertyNames; private readonly IReadOnlyCollection _ignoredUnmatchedTargetPropertyNames; private readonly IReadOnlyCollection _ignoredUnmatchedSourcePropertyNames; - protected ObjectPropertyMappingBuilderContext(MappingBuilderContext builderContext, IPropertyAssignmentTypeMapping mapping) + protected PropertiesMappingBuilderContext(MappingBuilderContext builderContext, T mapping) { BuilderContext = builderContext; Mapping = mapping; @@ -48,7 +40,7 @@ protected ObjectPropertyMappingBuilderContext(MappingBuilderContext builderConte public MappingBuilderContext BuilderContext { get; } - public IPropertyAssignmentTypeMapping Mapping { get; } + public T Mapping { get; } public IReadOnlyCollection IgnoredSourcePropertyNames { get; } @@ -64,70 +56,9 @@ public void AddDiagnostics() AddUnmatchedSourcePropertiesDiagnostics(); } - public void AddPropertyAssignmentMapping(IPropertyAssignmentMapping propertyMapping) - => AddPropertyAssignmentMapping(Mapping, propertyMapping); - - public void AddNullDelegatePropertyAssignmentMapping(IPropertyAssignmentMapping propertyMapping) - { - var nullConditionSourcePath = new PropertyPath(propertyMapping.SourcePath.PathWithoutTrailingNonNullable().ToList()); - var container = GetOrCreateNullDelegateMappingForPath(nullConditionSourcePath); - AddPropertyAssignmentMapping(container, propertyMapping); - } - - private void AddPropertyAssignmentMapping(IPropertyAssignmentMappingContainer container, IPropertyAssignmentMapping mapping) - { - SetSourcePropertyMapped(mapping.SourcePath); - AddNullPropertyInitializers(container, mapping.TargetPath); - container.AddPropertyMapping(mapping); - } - protected void SetSourcePropertyMapped(PropertyPath sourcePath) => _unmappedSourcePropertyNames.Remove(sourcePath.Path.First().Name); - private void AddNullPropertyInitializers(IPropertyAssignmentMappingContainer container, PropertyPath path) - { - foreach (var nullableTrailPath in path.ObjectPathNullableSubPaths()) - { - var nullablePath = new PropertyPath(nullableTrailPath); - var type = nullablePath.Member.Type; - if (!type.HasAccessibleParameterlessConstructor()) - { - BuilderContext.ReportDiagnostic( - DiagnosticDescriptors.NoParameterlessConstructorFound, - type); - continue; - } - - container.AddPropertyMappingContainer(new PropertyNullAssignmentInitializerMapping(nullablePath)); - } - } - - private PropertyNullDelegateAssignmentMapping GetOrCreateNullDelegateMappingForPath(PropertyPath nullConditionSourcePath) - { - // if there is already an exact match return that - if (_nullDelegateMappings.TryGetValue(nullConditionSourcePath, out var mapping)) - return mapping; - - IPropertyAssignmentMappingContainer parentMapping = Mapping; - - // try to reuse parent path mappings and wrap inside them - foreach (var nullablePath in nullConditionSourcePath.ObjectPathNullableSubPaths().Reverse()) - { - if (_nullDelegateMappings.TryGetValue(new PropertyPath(nullablePath), out var parentMappingHolder)) - { - parentMapping = parentMappingHolder; - } - } - - mapping = new PropertyNullDelegateAssignmentMapping( - nullConditionSourcePath, - parentMapping, - BuilderContext.MapperConfiguration.ThrowOnPropertyMappingNullMismatch); - _nullDelegateMappings[nullConditionSourcePath] = mapping; - parentMapping.AddPropertyMappingContainer(mapping); - return mapping; - } - private HashSet InitIgnoredUnmatchedProperties(IEnumerable allProperties, IEnumerable mappedProperties) { var unmatched = new HashSet(allProperties); diff --git a/src/Riok.Mapperly/Descriptors/MappingBodyBuilders/MappingBodyBuilder.cs b/src/Riok.Mapperly/Descriptors/MappingBodyBuilders/MappingBodyBuilder.cs index bee079ab87..6630688f4c 100644 --- a/src/Riok.Mapperly/Descriptors/MappingBodyBuilders/MappingBodyBuilder.cs +++ b/src/Riok.Mapperly/Descriptors/MappingBodyBuilders/MappingBodyBuilder.cs @@ -4,6 +4,9 @@ namespace Riok.Mapperly.Descriptors.MappingBodyBuilders; +/// +/// Builds bodies mappings (the body of the mapping methods). +/// public class MappingBodyBuilder { private readonly MappingCollection _mappings; @@ -19,6 +22,9 @@ public void BuildMappingBodies() { switch (typeMapping) { + case NewInstanceObjectPropertyMethodMapping mapping: + NewInstanceObjectPropertyMappingBodyBuilder.BuildMappingBody(ctx, mapping); + break; case NewInstanceObjectPropertyMapping mapping: NewInstanceObjectPropertyMappingBodyBuilder.BuildMappingBody(ctx, mapping); break; diff --git a/src/Riok.Mapperly/Descriptors/MappingBodyBuilders/NewInstanceObjectPropertyMappingBodyBuilder.cs b/src/Riok.Mapperly/Descriptors/MappingBodyBuilders/NewInstanceObjectPropertyMappingBodyBuilder.cs index 748736f65b..d5618e75c6 100644 --- a/src/Riok.Mapperly/Descriptors/MappingBodyBuilders/NewInstanceObjectPropertyMappingBodyBuilder.cs +++ b/src/Riok.Mapperly/Descriptors/MappingBodyBuilders/NewInstanceObjectPropertyMappingBodyBuilder.cs @@ -1,7 +1,7 @@ using System.Diagnostics.CodeAnalysis; using Microsoft.CodeAnalysis; using Riok.Mapperly.Abstractions; -using Riok.Mapperly.Descriptors.MappingBuilders; +using Riok.Mapperly.Descriptors.MappingBodyBuilders.BuilderContext; using Riok.Mapperly.Descriptors.Mappings; using Riok.Mapperly.Descriptors.Mappings.PropertyMappings; using Riok.Mapperly.Diagnostics; @@ -9,29 +9,32 @@ namespace Riok.Mapperly.Descriptors.MappingBodyBuilders; +/// +/// Body builder for new instance object property mappings (mappings for which the target object gets created via new()). +/// public static class NewInstanceObjectPropertyMappingBodyBuilder { public static void BuildMappingBody(MappingBuilderContext ctx, NewInstanceObjectPropertyMapping mapping) { - var mappingCtx = new NewInstanceMappingBuilderContext(ctx, mapping); - - // map constructor - if (TryBuildConstructorMapping(mappingCtx, out var mappedTargetPropertyNames)) - { - mappingCtx.TargetProperties.RemoveRange(mappedTargetPropertyNames); - } - else - { - ctx.ReportDiagnostic(DiagnosticDescriptors.NoConstructorFound, ctx.Target); - } + var mappingCtx = new NewInstanceBuilderContext(ctx, mapping); + BuildConstructorMapping(mappingCtx); + BuildInitOnlyPropertyMappings(mappingCtx, true); + mappingCtx.AddDiagnostics(); + } + public static void BuildMappingBody(MappingBuilderContext ctx, NewInstanceObjectPropertyMethodMapping mapping) + { + var mappingCtx = new NewInstanceContainerBuilderContext(ctx, mapping); + BuildConstructorMapping(mappingCtx); BuildInitOnlyPropertyMappings(mappingCtx); ObjectPropertyMappingBodyBuilder.BuildMappingBody(mappingCtx); } - private static void BuildInitOnlyPropertyMappings(NewInstanceMappingBuilderContext ctx) + private static void BuildInitOnlyPropertyMappings(INewInstanceBuilderContext ctx, bool includeAllProperties = false) { - var initOnlyTargetProperties = ctx.TargetProperties.Values.Where(x => x.IsInitOnly() || x.IsRequired()).ToArray(); + var initOnlyTargetProperties = includeAllProperties + ? ctx.TargetProperties.Values.ToArray() + : ctx.TargetProperties.Values.Where(x => x.CanOnlySetViaInitializer()).ToArray(); foreach (var targetProperty in initOnlyTargetProperties) { ctx.TargetProperties.Remove(targetProperty.Name); @@ -51,7 +54,7 @@ private static void BuildInitOnlyPropertyMappings(NewInstanceMappingBuilderConte ctx.BuilderContext.ReportDiagnostic( targetProperty.IsRequired() ? DiagnosticDescriptors.RequiredPropertyNotMapped - : DiagnosticDescriptors.MappingSourcePropertyNotFound, + : DiagnosticDescriptors.SourcePropertyNotFound, targetProperty.Name, ctx.Mapping.SourceType); continue; @@ -62,7 +65,7 @@ private static void BuildInitOnlyPropertyMappings(NewInstanceMappingBuilderConte } private static void BuildInitPropertyMapping( - NewInstanceMappingBuilderContext ctx, + INewInstanceBuilderContext ctx, IPropertySymbol targetProperty, IReadOnlyCollection propertyConfigs) { @@ -92,7 +95,7 @@ private static void BuildInitPropertyMapping( out var sourcePropertyPath)) { ctx.BuilderContext.ReportDiagnostic( - DiagnosticDescriptors.MappingSourcePropertyNotFound, + DiagnosticDescriptors.SourcePropertyNotFound, targetProperty.Name, ctx.Mapping.SourceType); return; @@ -102,11 +105,14 @@ private static void BuildInitPropertyMapping( } private static void BuildInitPropertyMapping( - NewInstanceMappingBuilderContext ctx, + INewInstanceBuilderContext ctx, IPropertySymbol targetProperty, PropertyPath sourcePath) { - var targetPath = new PropertyPath(new[] { targetProperty }); + var targetPath = new PropertyPath(new[] + { + targetProperty + }); if (!ObjectPropertyMappingBodyBuilder.ValidateMappingSpecification(ctx, sourcePath, targetPath, true)) return; @@ -126,24 +132,41 @@ private static void BuildInitPropertyMapping( return; } + if (delegateMapping.Equals(ctx.Mapping)) + { + ctx.BuilderContext.ReportDiagnostic( + DiagnosticDescriptors.ReferenceLoopInInitOnlyMapping, + ctx.Mapping.SourceType, + sourcePath.FullName, + ctx.Mapping.TargetType, + targetPath.FullName); + return; + } + + var nullFallback = NullFallbackValue.Default; + if (!delegateMapping.SourceType.IsNullable() && sourcePath.IsAnyNullable()) + { + nullFallback = ctx.BuilderContext.GetNullFallbackValue(targetProperty.Type); + } + var propertyMapping = new NullPropertyMapping( delegateMapping, sourcePath, - ctx.BuilderContext.GetNullFallbackValue(targetProperty.Type)); + targetProperty.Type, + nullFallback, + !ctx.BuilderContext.IsExpression); var propertyAssignmentMapping = new PropertyAssignmentMapping( targetPath, propertyMapping); ctx.AddInitPropertyMapping(propertyAssignmentMapping); } - private static bool TryBuildConstructorMapping( - NewInstanceMappingBuilderContext ctx, - [NotNullWhen(true)] out ISet? mappedTargetPropertyNames) + private static void BuildConstructorMapping(INewInstanceBuilderContext ctx) { if (ctx.Mapping.TargetType is not INamedTypeSymbol namedTargetType) { - mappedTargetPropertyNames = null; - return false; + ctx.BuilderContext.ReportDiagnostic(DiagnosticDescriptors.NoConstructorFound, ctx.BuilderContext.Target); + return; } // attributed ctor is prio 1 @@ -161,7 +184,7 @@ private static bool TryBuildConstructorMapping( if (!TryBuildConstructorMapping( ctx, ctorCandidate, - out mappedTargetPropertyNames, + out var mappedTargetPropertyNames, out var constructorParameterMappings)) { if (ctorCandidate.HasAttribute(ctx.BuilderContext.Types.MapperConstructorAttribute)) @@ -175,20 +198,20 @@ private static bool TryBuildConstructorMapping( continue; } + ctx.TargetProperties.RemoveRange(mappedTargetPropertyNames); foreach (var constructorParameterMapping in constructorParameterMappings) { ctx.AddConstructorParameterMapping(constructorParameterMapping); } - return true; + return; } - mappedTargetPropertyNames = null; - return false; + ctx.BuilderContext.ReportDiagnostic(DiagnosticDescriptors.NoConstructorFound, ctx.BuilderContext.Target); } private static bool TryBuildConstructorMapping( - NewInstanceMappingBuilderContext ctx, + INewInstanceBuilderContext ctx, IMethodSymbol ctor, [NotNullWhen(true)] out ISet? mappedTargetPropertyNames, [NotNullWhen(true)] out ISet? constructorParameterMappings) @@ -200,7 +223,8 @@ private static bool TryBuildConstructorMapping( { if (!TryFindConstructorParameterSourcePath(ctx, parameter, out var sourcePath)) { - if (!parameter.IsOptional) + // expressions do not allow skipping of optional parameters + if (!parameter.IsOptional || ctx.BuilderContext.IsExpression) return false; skippedOptionalParam = true; @@ -221,7 +245,23 @@ private static bool TryBuildConstructorMapping( continue; } - var propertyMapping = new NullPropertyMapping(delegateMapping, sourcePath, ctx.BuilderContext.GetNullFallbackValue(paramType)); + if (delegateMapping.Equals(ctx.Mapping)) + { + ctx.BuilderContext.ReportDiagnostic( + DiagnosticDescriptors.ReferenceLoopInCtorMapping, + ctx.Mapping.SourceType, + sourcePath.FullName, + ctx.Mapping.TargetType, + parameter.Name); + return false; + } + + var propertyMapping = new NullPropertyMapping( + delegateMapping, + sourcePath, + paramType, + ctx.BuilderContext.GetNullFallbackValue(paramType), + !ctx.BuilderContext.IsExpression); var ctorMapping = new ConstructorParameterMapping(parameter, propertyMapping, skippedOptionalParam); constructorParameterMappings.Add(ctorMapping); mappedTargetPropertyNames.Add(parameter.Name); @@ -231,7 +271,7 @@ private static bool TryBuildConstructorMapping( } private static bool TryFindConstructorParameterSourcePath( - NewInstanceMappingBuilderContext ctx, + INewInstanceBuilderContext ctx, IParameterSymbol parameter, [NotNullWhen(true)] out PropertyPath? sourcePath) { @@ -272,7 +312,7 @@ out sourcePath out sourcePath)) { ctx.BuilderContext.ReportDiagnostic( - DiagnosticDescriptors.MappingSourcePropertyNotFound, + DiagnosticDescriptors.SourcePropertyNotFound, propertyConfig.Source, ctx.Mapping.SourceType); return false; diff --git a/src/Riok.Mapperly/Descriptors/MappingBodyBuilders/ObjectPropertyMappingBodyBuilder.cs b/src/Riok.Mapperly/Descriptors/MappingBodyBuilders/ObjectPropertyMappingBodyBuilder.cs index c1b8933a85..8557e93966 100644 --- a/src/Riok.Mapperly/Descriptors/MappingBodyBuilders/ObjectPropertyMappingBodyBuilder.cs +++ b/src/Riok.Mapperly/Descriptors/MappingBodyBuilders/ObjectPropertyMappingBodyBuilder.cs @@ -1,20 +1,24 @@ using Riok.Mapperly.Abstractions; -using Riok.Mapperly.Descriptors.MappingBuilders; +using Riok.Mapperly.Descriptors.MappingBodyBuilders.BuilderContext; +using Riok.Mapperly.Descriptors.Mappings; using Riok.Mapperly.Descriptors.Mappings.PropertyMappings; using Riok.Mapperly.Diagnostics; using Riok.Mapperly.Helpers; namespace Riok.Mapperly.Descriptors.MappingBodyBuilders; +/// +/// Mapping body builder for object property mappings. +/// public static class ObjectPropertyMappingBodyBuilder { public static void BuildMappingBody(MappingBuilderContext ctx, IPropertyAssignmentTypeMapping mapping) { - var mappingCtx = new ObjectPropertyMappingBuilderContext(ctx, mapping); + var mappingCtx = new PropertiesContainerBuilderContext(ctx, mapping); BuildMappingBody(mappingCtx); } - public static void BuildMappingBody(ObjectPropertyMappingBuilderContext ctx) + public static void BuildMappingBody(IPropertiesContainerBuilderContext ctx) { var propertyNameComparer = ctx.BuilderContext.MapperConfiguration.PropertyNameMappingStrategy == PropertyNameMappingStrategy.CaseSensitive @@ -44,7 +48,7 @@ public static void BuildMappingBody(ObjectPropertyMappingBuilderContext ctx) out var sourcePropertyPath)) { ctx.BuilderContext.ReportDiagnostic( - DiagnosticDescriptors.MappingSourcePropertyNotFound, + DiagnosticDescriptors.SourcePropertyNotFound, targetProperty.Name, ctx.Mapping.SourceType); continue; @@ -56,7 +60,9 @@ public static void BuildMappingBody(ObjectPropertyMappingBuilderContext ctx) ctx.AddDiagnostics(); } - private static void BuildPropertyAssignmentMapping(ObjectPropertyMappingBuilderContext ctx, MapPropertyAttribute config) + private static void BuildPropertyAssignmentMapping( + IPropertiesContainerBuilderContext ctx, + MapPropertyAttribute config) { if (!PropertyPath.TryFind(ctx.Mapping.TargetType, config.Target, out var targetPropertyPath)) { @@ -80,7 +86,7 @@ private static void BuildPropertyAssignmentMapping(ObjectPropertyMappingBuilderC } public static bool ValidateMappingSpecification( - ObjectPropertyMappingBuilderContext ctx, + IPropertiesBuilderContext ctx, PropertyPath sourcePropertyPath, PropertyPath targetPropertyPath, bool allowInitOnlyMember = false) @@ -158,7 +164,7 @@ public static bool ValidateMappingSpecification( } private static void BuildPropertyAssignmentMapping( - ObjectPropertyMappingBuilderContext ctx, + IPropertiesContainerBuilderContext ctx, PropertyPath sourcePropertyPath, PropertyPath targetPropertyPath) { @@ -221,7 +227,7 @@ private static void BuildPropertyAssignmentMapping( } private static bool TryAddExistingTargetMapping( - ObjectPropertyMappingBuilderContext ctx, + IPropertiesContainerBuilderContext ctx, PropertyPath sourcePropertyPath, PropertyPath targetPropertyPath) { diff --git a/src/Riok.Mapperly/Descriptors/MappingBodyBuilders/UserMethodMappingBodyBuilder.cs b/src/Riok.Mapperly/Descriptors/MappingBodyBuilders/UserMethodMappingBodyBuilder.cs index 00c5207b73..648145e97d 100644 --- a/src/Riok.Mapperly/Descriptors/MappingBodyBuilders/UserMethodMappingBodyBuilder.cs +++ b/src/Riok.Mapperly/Descriptors/MappingBodyBuilders/UserMethodMappingBodyBuilder.cs @@ -5,12 +5,15 @@ namespace Riok.Mapperly.Descriptors.MappingBodyBuilders; +/// +/// Mapping body builder for user defined methods. +/// public static class UserMethodMappingBodyBuilder { public static void BuildMappingBody(MappingBuilderContext ctx, UserDefinedExistingTargetMethodMapping mapping) { // UserDefinedExistingTargetMethodMapping handles null already - var delegateMapping = ctx.FindOrBuildExistingTargetMapping( + var delegateMapping = ctx.BuildExistingTargetMappingWithUserSymbol( mapping.SourceType.NonNullable(), mapping.TargetType.NonNullable()); diff --git a/src/Riok.Mapperly/Descriptors/MappingBuilderContext.cs b/src/Riok.Mapperly/Descriptors/MappingBuilderContext.cs index a6a625ccf0..abc289cd41 100644 --- a/src/Riok.Mapperly/Descriptors/MappingBuilderContext.cs +++ b/src/Riok.Mapperly/Descriptors/MappingBuilderContext.cs @@ -1,5 +1,5 @@ +using System.Diagnostics; using Microsoft.CodeAnalysis; -using Riok.Mapperly.Configuration; using Riok.Mapperly.Descriptors.Mappings; using Riok.Mapperly.Descriptors.Mappings.ExistingTarget; using Riok.Mapperly.Descriptors.ObjectFactories; @@ -8,27 +8,50 @@ namespace Riok.Mapperly.Descriptors; +[DebuggerDisplay("{GetType()}({Source.Name} => {Target.Name})")] public class MappingBuilderContext : SimpleMappingBuilderContext { - private readonly ISymbol? _userSymbol; + private readonly IMethodSymbol? _userSymbol; public MappingBuilderContext( - DescriptorBuilder builder, + SimpleMappingBuilderContext parentCtx, + ObjectFactoryCollection objectFactories, + IMethodSymbol? userSymbol, ITypeSymbol source, - ITypeSymbol target, - ISymbol? userSymbol) - : base(builder) + ITypeSymbol target) + : base(parentCtx) { + ObjectFactories = objectFactories; Source = source; Target = target; _userSymbol = userSymbol; } + protected MappingBuilderContext( + MappingBuilderContext ctx, + IMethodSymbol? userSymbol, + ITypeSymbol source, + ITypeSymbol target) + : this(ctx, ctx.ObjectFactories, userSymbol, source, target) + { + } + public ITypeSymbol Source { get; } public ITypeSymbol Target { get; } - public ObjectFactoryCollection ObjectFactories => Builder.ObjectFactories; + /// + /// Whether the current mapping code is generated for a . + /// + public virtual bool IsExpression => false; + + public ObjectFactoryCollection ObjectFactories { get; } + + public T GetConfigurationOrDefault() where T : Attribute + => Configuration.GetOrDefault(_userSymbol); + + public IEnumerable ListConfiguration() where T : Attribute + => Configuration.ListConfiguration(_userSymbol); /// /// Tries to find an existing mapping for the provided types. @@ -37,8 +60,8 @@ public MappingBuilderContext( /// The source type. /// The target type. /// The found mapping, or null if none is found. - public ITypeMapping? FindMapping(ITypeSymbol sourceType, ITypeSymbol targetType) - => Builder.MappingBuilder.FindMapping(sourceType.UpgradeNullable(), targetType.UpgradeNullable()); + public virtual ITypeMapping? FindMapping(ITypeSymbol sourceType, ITypeSymbol targetType) + => MappingBuilder.Find(sourceType.UpgradeNullable(), targetType.UpgradeNullable()); /// /// Tries to find an existing mapping for the provided types. @@ -53,7 +76,7 @@ public MappingBuilderContext( /// The target type. /// The found or created mapping, or null if no mapping could be created. public ITypeMapping? FindOrBuildMapping(ITypeSymbol sourceType, ITypeSymbol targetType) - => Builder.MappingBuilder.FindOrBuild(sourceType.UpgradeNullable(), targetType.UpgradeNullable()); + => FindOrBuildMapping(null, sourceType, targetType, true); /// /// Tries to build a new mapping for the given types. @@ -65,7 +88,7 @@ public MappingBuilderContext( /// The target type. /// The created mapping or null if none could be created. public ITypeMapping? BuildDelegateMapping(ITypeSymbol source, ITypeSymbol target) - => Builder.MappingBuilder.BuildDelegate(_userSymbol, source.UpgradeNullable(), target.UpgradeNullable()); + => BuildMapping(_userSymbol, source, target, false); /// /// Tries to build a new mapping for the given types while keeping the current user symbol reference. @@ -75,15 +98,16 @@ public MappingBuilderContext( /// If a new mapping is created, it is added to the mapping descriptor /// and returned in further calls. /// - /// - /// - /// + /// The source type. + /// The target type. + /// The created mapping or null if none could be created. /// public ITypeMapping? BuildMappingWithUserSymbol(ITypeSymbol source, ITypeSymbol target) - => Builder.MappingBuilder.BuildWithUserSymbol( + => BuildMapping( _userSymbol ?? throw new InvalidOperationException(nameof(BuildMappingWithUserSymbol) + " can only be called for contexts with a user symbol"), source.UpgradeNullable(), - target.UpgradeNullable()); + target.UpgradeNullable(), + true); /// /// Tries to find an existing mapping which can work with an existing target object instance for the provided types. @@ -97,32 +121,44 @@ public MappingBuilderContext( /// The source type. /// The target type. /// The found or created mapping, or null if no mapping could be created. - public IExistingTargetMapping? FindOrBuildExistingTargetMapping(ITypeSymbol sourceType, ITypeSymbol targetType) - => Builder.ExistingTargetMappingBuilder.FindOrBuild(_userSymbol, sourceType, targetType); + public virtual IExistingTargetMapping? FindOrBuildExistingTargetMapping(ITypeSymbol sourceType, ITypeSymbol targetType) + => ExistingTargetMappingBuilder.Find(sourceType, targetType) + ?? ExistingTargetMappingBuilder.Build(ContextForMapping(null, sourceType, targetType), true); - public T GetConfigurationOrDefault() where T : Attribute - { - return ListConfiguration().FirstOrDefault() - ?? (T)Builder.DefaultConfigurations[typeof(T)]; - } + /// + /// Tries to build an existing target instance mapping. + /// If no mapping is possible for the provided types, + /// null is returned. + /// If a new mapping is created, it is added to the mapping descriptor + /// and returned in further calls. + /// No configuration / user symbol is passed. + /// + /// The source type. + /// The target type. + /// The created mapping, or null if no mapping could be created. + public virtual IExistingTargetMapping? BuildExistingTargetMappingWithUserSymbol(ITypeSymbol sourceType, ITypeSymbol targetType) + => ExistingTargetMappingBuilder.Build(ContextForMapping(_userSymbol, sourceType, targetType), false); - public IEnumerable ListConfiguration() where T : Attribute + protected virtual ITypeMapping? FindOrBuildMapping(IMethodSymbol? userSymbol, ITypeSymbol sourceType, ITypeSymbol targetType, bool reusable) { - return _userSymbol == null - ? Enumerable.Empty() - : AttributeDataAccessor.Access(Compilation, _userSymbol); + sourceType = sourceType.UpgradeNullable(); + targetType = targetType.UpgradeNullable(); + return MappingBuilder.Find(sourceType, targetType) + ?? BuildMapping(userSymbol, sourceType, targetType, reusable); } public void ReportDiagnostic(DiagnosticDescriptor descriptor, params object[] messageArgs) => base.ReportDiagnostic(descriptor, _userSymbol, messageArgs); public NullFallbackValue GetNullFallbackValue(ITypeSymbol? targetType = null) + => GetNullFallbackValue(targetType ?? Target, MapperConfiguration.ThrowOnMappingNullMismatch); + + protected virtual NullFallbackValue GetNullFallbackValue(ITypeSymbol targetType, bool throwOnMappingNullMismatch) { - targetType ??= Target; if (targetType.IsNullable()) return NullFallbackValue.Default; - if (MapperConfiguration.ThrowOnMappingNullMismatch) + if (throwOnMappingNullMismatch) return NullFallbackValue.ThrowArgumentNullException; if (!targetType.IsReferenceType) @@ -137,4 +173,10 @@ public NullFallbackValue GetNullFallbackValue(ITypeSymbol? targetType = null) ReportDiagnostic(DiagnosticDescriptors.NoParameterlessConstructorFound, targetType); return NullFallbackValue.ThrowArgumentNullException; } + + protected virtual MappingBuilderContext ContextForMapping(IMethodSymbol? userSymbol, ITypeSymbol sourceType, ITypeSymbol targetType) + => new(this, userSymbol, sourceType, targetType); + + protected ITypeMapping? BuildMapping(IMethodSymbol? userSymbol, ITypeSymbol sourceType, ITypeSymbol targetType, bool reusable) + => MappingBuilder.Build(ContextForMapping(userSymbol, sourceType.UpgradeNullable(), targetType.UpgradeNullable()), reusable); } diff --git a/src/Riok.Mapperly/Descriptors/MappingBuilders/DictionaryMappingBuilder.cs b/src/Riok.Mapperly/Descriptors/MappingBuilders/DictionaryMappingBuilder.cs index 1584201ec3..8179ec7a95 100644 --- a/src/Riok.Mapperly/Descriptors/MappingBuilders/DictionaryMappingBuilder.cs +++ b/src/Riok.Mapperly/Descriptors/MappingBuilders/DictionaryMappingBuilder.cs @@ -1,4 +1,5 @@ using Microsoft.CodeAnalysis; +using Riok.Mapperly.Abstractions; using Riok.Mapperly.Descriptors.Mappings; using Riok.Mapperly.Descriptors.Mappings.ExistingTarget; using Riok.Mapperly.Diagnostics; @@ -12,6 +13,9 @@ public static class DictionaryMappingBuilder public static ITypeMapping? TryBuildMapping(MappingBuilderContext ctx) { + if (!ctx.IsConversionEnabled(MappingConversionType.Dictionary)) + return null; + if (BuildKeyValueMapping(ctx) is not var (keyMapping, valueMapping)) return null; diff --git a/src/Riok.Mapperly/Descriptors/MappingBuilders/EnumMappingBuilder.cs b/src/Riok.Mapperly/Descriptors/MappingBuilders/EnumMappingBuilder.cs index 71813648c4..eda215bd02 100644 --- a/src/Riok.Mapperly/Descriptors/MappingBuilders/EnumMappingBuilder.cs +++ b/src/Riok.Mapperly/Descriptors/MappingBuilders/EnumMappingBuilder.cs @@ -37,11 +37,21 @@ public static class EnumMappingBuilder var config = ctx.GetConfigurationOrDefault(); return config.Strategy switch { + EnumMappingStrategy.ByName when ctx.IsExpression => BuildCastMappingAndDiagnostic(ctx), EnumMappingStrategy.ByName => BuildNameMapping(ctx, config.IgnoreCase), _ => new CastMapping(ctx.Source, ctx.Target), }; } + private static TypeMapping BuildCastMappingAndDiagnostic(MappingBuilderContext ctx) + { + ctx.ReportDiagnostic( + DiagnosticDescriptors.EnumMappingStrategyByNameNotSupportedInProjectionMappings, + ctx.Source.ToDisplayString(), + ctx.Target.ToDisplayString()); + return new CastMapping(ctx.Source, ctx.Target); + } + private static TypeMapping BuildNameMapping(MappingBuilderContext ctx, bool ignoreCase) { var targetFieldsByName = ctx.Target.GetMembers().OfType().ToDictionary(x => x.Name); @@ -58,7 +68,9 @@ private static TypeMapping BuildNameMapping(MappingBuilderContext ctx, bool igno getTargetField = source => targetFieldsByName.GetValueOrDefault(source.Name); } - var enumMemberMappings = ctx.Source.GetMembers().OfType() + var enumMemberMappings = ctx.Source + .GetMembers() + .OfType() .Select(x => (Source: x, Target: getTargetField(x))) .Where(x => x.Target != null) .ToDictionary(x => x.Source.Name, x => x.Target!.Name); @@ -71,6 +83,9 @@ private static TypeMapping BuildNameMapping(MappingBuilderContext ctx, bool igno ctx.Target); } - return new EnumNameMapping(ctx.Source, ctx.Target, enumMemberMappings); + return new EnumNameMapping( + ctx.Source, + ctx.Target, + enumMemberMappings); } } diff --git a/src/Riok.Mapperly/Descriptors/MappingBuilders/EnumerableMappingBuilder.cs b/src/Riok.Mapperly/Descriptors/MappingBuilders/EnumerableMappingBuilder.cs index f776fe6b8b..1dd53de37b 100644 --- a/src/Riok.Mapperly/Descriptors/MappingBuilders/EnumerableMappingBuilder.cs +++ b/src/Riok.Mapperly/Descriptors/MappingBuilders/EnumerableMappingBuilder.cs @@ -1,4 +1,5 @@ using Microsoft.CodeAnalysis; +using Riok.Mapperly.Abstractions; using Riok.Mapperly.Descriptors.Mappings; using Riok.Mapperly.Descriptors.Mappings.ExistingTarget; using Riok.Mapperly.Diagnostics; @@ -14,6 +15,9 @@ public static class EnumerableMappingBuilder public static TypeMapping? TryBuildMapping(MappingBuilderContext ctx) { + if (!ctx.IsConversionEnabled(MappingConversionType.Enumerable)) + return null; + if (BuildElementMapping(ctx) is not { } elementMapping) return null; @@ -23,7 +27,8 @@ public static class EnumerableMappingBuilder return new CastMapping(ctx.Source, ctx.Target); // if source is an array and target is an array or IReadOnlyCollection faster mappings can be applied - if (ctx.Source.IsArrayType() + if (!ctx.IsExpression + && ctx.Source.IsArrayType() && (ctx.Target.IsArrayType() || SymbolEqualityComparer.Default.Equals(ctx.Target.OriginalDefinition, ctx.Types.IReadOnlyCollection))) { // if element mapping is synthetic @@ -40,10 +45,12 @@ public static class EnumerableMappingBuilder // try linq mapping: x.Select(Map).ToArray/ToList // if that doesn't work do a foreach with add calls var (canMapWithLinq, collectMethodName) = ResolveCollectMethodName(ctx); - if (!canMapWithLinq) - return BuildCustomTypeMapping(ctx, elementMapping); + if (canMapWithLinq) + return BuildLinqMapping(ctx, elementMapping, collectMethodName); - return BuildLinqMapping(ctx, elementMapping, collectMethodName); + return ctx.IsExpression + ? null + : BuildCustomTypeMapping(ctx, elementMapping); } public static IExistingTargetMapping? TryBuildExistingTargetMapping(MappingBuilderContext ctx) diff --git a/src/Riok.Mapperly/Descriptors/MappingBuilders/ExistingTargetMappingBuilder.cs b/src/Riok.Mapperly/Descriptors/MappingBuilders/ExistingTargetMappingBuilder.cs index c498963d55..c24c28340f 100644 --- a/src/Riok.Mapperly/Descriptors/MappingBuilders/ExistingTargetMappingBuilder.cs +++ b/src/Riok.Mapperly/Descriptors/MappingBuilders/ExistingTargetMappingBuilder.cs @@ -16,38 +16,29 @@ public class ExistingTargetMappingBuilder }; private readonly MappingCollection _mappings; - private readonly DescriptorBuilder _descriptorBuilder; - public ExistingTargetMappingBuilder(DescriptorBuilder descriptorBuilder, MappingCollection mappings) + public ExistingTargetMappingBuilder(MappingCollection mappings) { - _descriptorBuilder = descriptorBuilder; _mappings = mappings; } - /// - public IExistingTargetMapping? FindOrBuild( - ISymbol? userSymbol, - ITypeSymbol sourceType, - ITypeSymbol targetType) - { - return _mappings.FindExistingInstanceMapping(sourceType, targetType) - ?? Build(userSymbol, sourceType, targetType); - } + public IExistingTargetMapping? Find(ITypeSymbol sourceType, ITypeSymbol targetType) + => _mappings.FindExistingInstanceMapping(sourceType, targetType); - private IExistingTargetMapping? Build( - ISymbol? userSymbol, - ITypeSymbol sourceType, - ITypeSymbol targetType) + public IExistingTargetMapping? Build(MappingBuilderContext ctx, bool resultIsReusable) { - var ctx = new MappingBuilderContext(_descriptorBuilder, sourceType, targetType, userSymbol); foreach (var mappingBuilder in _builders) { - if (mappingBuilder(ctx) is { } mapping) + if (mappingBuilder(ctx) is not { } mapping) + continue; + + if (resultIsReusable) { _mappings.AddExistingTargetMapping(mapping); - _mappings.EnqueueMappingToBuildBody(mapping, ctx); - return mapping; } + + _mappings.EnqueueToBuildBody(mapping, ctx); + return mapping; } return null; diff --git a/src/Riok.Mapperly/Descriptors/MappingBuilders/MappingBuilder.cs b/src/Riok.Mapperly/Descriptors/MappingBuilders/MappingBuilder.cs index d7f75345b6..e4437af5ea 100644 --- a/src/Riok.Mapperly/Descriptors/MappingBuilders/MappingBuilder.cs +++ b/src/Riok.Mapperly/Descriptors/MappingBuilders/MappingBuilder.cs @@ -12,6 +12,7 @@ public class MappingBuilder NullableMappingBuilder.TryBuildMapping, SpecialTypeMappingBuilder.TryBuildMapping, DirectAssignmentMappingBuilder.TryBuildMapping, + QueryableMappingBuilder.TryBuildMapping, DictionaryMappingBuilder.TryBuildMapping, EnumerableMappingBuilder.TryBuildMapping, ImplicitCastMappingBuilder.TryBuildMapping, @@ -27,61 +28,31 @@ public class MappingBuilder NewInstanceObjectPropertyMappingBuilder.TryBuildMapping, }; - private readonly DescriptorBuilder _descriptorBuilder; private readonly MappingCollection _mappings; - public MappingBuilder(DescriptorBuilder descriptorBuilder, MappingCollection mappings) + public MappingBuilder(MappingCollection mappings) { - _descriptorBuilder = descriptorBuilder; _mappings = mappings; } /// - public ITypeMapping? FindMapping(ITypeSymbol sourceType, ITypeSymbol targetType) - => _mappings.FindMapping(sourceType, targetType); + public ITypeMapping? Find(ITypeSymbol sourceType, ITypeSymbol targetType) + => _mappings.Find(sourceType, targetType); - /// - public ITypeMapping? FindOrBuild( - ITypeSymbol sourceType, - ITypeSymbol targetType) + public ITypeMapping? Build(MappingBuilderContext ctx, bool resultIsReusable) { - if (_mappings.FindMapping(sourceType, targetType) is { } foundMapping) - return foundMapping; - - if (BuildDelegate(null, sourceType, targetType) is not { } mapping) - return null; - - _mappings.AddMapping(mapping); - return mapping; - } - - /// - public ITypeMapping? BuildWithUserSymbol( - ISymbol userSymbol, - ITypeSymbol sourceType, - ITypeSymbol targetType) - { - if (BuildDelegate(userSymbol, sourceType, targetType) is not { } mapping) - return null; - - _mappings.AddMapping(mapping); - return mapping; - } - - /// - public ITypeMapping? BuildDelegate( - ISymbol? userSymbol, - ITypeSymbol sourceType, - ITypeSymbol targetType) - { - var ctx = new MappingBuilderContext(_descriptorBuilder, sourceType, targetType, userSymbol); foreach (var mappingBuilder in _builders) { - if (mappingBuilder(ctx) is { } mapping) + if (mappingBuilder(ctx) is not { } mapping) + continue; + + if (resultIsReusable) { - _mappings.EnqueueMappingToBuildBody(mapping, ctx); - return mapping; + _mappings.Add(mapping); } + + _mappings.EnqueueToBuildBody(mapping, ctx); + return mapping; } return null; diff --git a/src/Riok.Mapperly/Descriptors/MappingBuilders/NewInstanceObjectPropertyMappingBuilder.cs b/src/Riok.Mapperly/Descriptors/MappingBuilders/NewInstanceObjectPropertyMappingBuilder.cs index 5fca571487..9582cf653d 100644 --- a/src/Riok.Mapperly/Descriptors/MappingBuilders/NewInstanceObjectPropertyMappingBuilder.cs +++ b/src/Riok.Mapperly/Descriptors/MappingBuilders/NewInstanceObjectPropertyMappingBuilder.cs @@ -21,7 +21,11 @@ public static class NewInstanceObjectPropertyMappingBuilder if (ctx.Source.IsEnum() || ctx.Target.IsEnum()) return null; - return new NewInstanceObjectPropertyMapping(ctx.Source, ctx.Target.NonNullable(), ctx.MapperConfiguration.UseReferenceHandling); + // inline expressions don't support method property mappings + // and can only map to properties via object initializers. + return ctx.IsExpression + ? new NewInstanceObjectPropertyMapping(ctx.Source, ctx.Target.NonNullable()) + : new NewInstanceObjectPropertyMethodMapping(ctx.Source, ctx.Target.NonNullable(), ctx.MapperConfiguration.UseReferenceHandling); } public static IExistingTargetMapping? TryBuildExistingTargetMapping(MappingBuilderContext ctx) diff --git a/src/Riok.Mapperly/Descriptors/MappingBuilders/QueryableMappingBuilder.cs b/src/Riok.Mapperly/Descriptors/MappingBuilders/QueryableMappingBuilder.cs new file mode 100644 index 0000000000..974b33c48b --- /dev/null +++ b/src/Riok.Mapperly/Descriptors/MappingBuilders/QueryableMappingBuilder.cs @@ -0,0 +1,36 @@ +using Riok.Mapperly.Abstractions; +using Riok.Mapperly.Descriptors.Mappings; +using Riok.Mapperly.Diagnostics; +using Riok.Mapperly.Helpers; + +namespace Riok.Mapperly.Descriptors.MappingBuilders; + +public static class QueryableMappingBuilder +{ + public static TypeMapping? TryBuildMapping(MappingBuilderContext ctx) + { + if (!ctx.IsConversionEnabled(MappingConversionType.Queryable)) + return null; + + if (!ctx.Source.ImplementsGeneric(ctx.Types.IQueryable, out var sourceQueryable)) + return null; + + if (!ctx.Target.ImplementsGeneric(ctx.Types.IQueryable, out var targetQueryable)) + return null; + + var sourceType = sourceQueryable.TypeArguments[0]; + var targetType = targetQueryable.TypeArguments[0]; + + var inlineCtx = new InlineExpressionMappingBuilderContext(ctx, sourceType, targetType); + var mapping = inlineCtx.BuildDelegateMapping(sourceType, targetType); + if (mapping == null) + return null; + + if (ctx.MapperConfiguration.UseReferenceHandling) + { + ctx.ReportDiagnostic(DiagnosticDescriptors.QueryableProjectionMappingsDoNotSupportReferenceHandling); + } + + return new QueryableProjectionMapping(ctx.Source, ctx.Target, mapping); + } +} diff --git a/src/Riok.Mapperly/Descriptors/MappingBuilders/StringToEnumMappingBuilder.cs b/src/Riok.Mapperly/Descriptors/MappingBuilders/StringToEnumMappingBuilder.cs index 44f997cb9e..fec4327a58 100644 --- a/src/Riok.Mapperly/Descriptors/MappingBuilders/StringToEnumMappingBuilder.cs +++ b/src/Riok.Mapperly/Descriptors/MappingBuilders/StringToEnumMappingBuilder.cs @@ -15,11 +15,18 @@ public static class StringToEnumMappingBuilder if (ctx.Source.SpecialType != SpecialType.System_String || !ctx.Target.IsEnum()) return null; + var genericEnumParseMethodSupported = ctx.Types.Enum.GetMembers(nameof(Enum.Parse)) + .OfType() + .Any(x => x.IsGenericMethod); + + var config = ctx.GetConfigurationOrDefault(); + if (ctx.IsExpression) + return new EnumFromStringParseMapping(ctx.Source, ctx.Target, genericEnumParseMethodSupported, config.IgnoreCase); + // from string => use an optimized method of Enum.Parse which would use slow reflection // however we currently don't support all features of Enum.Parse yet (ex. flags) // therefore we use Enum.Parse as fallback. var members = ctx.Target.GetMembers().OfType(); - var config = ctx.GetConfigurationOrDefault(); - return new EnumFromStringMapping(ctx.Source, ctx.Target, members, config.IgnoreCase); + return new EnumFromStringSwitchMapping(ctx.Source, ctx.Target, members, genericEnumParseMethodSupported, config.IgnoreCase); } } diff --git a/src/Riok.Mapperly/Descriptors/MappingCollection.cs b/src/Riok.Mapperly/Descriptors/MappingCollection.cs index 3da2a52bc3..05762f521a 100644 --- a/src/Riok.Mapperly/Descriptors/MappingCollection.cs +++ b/src/Riok.Mapperly/Descriptors/MappingCollection.cs @@ -13,9 +13,6 @@ public class MappingCollection // a list of all method mappings (extra mappings and mappings) private readonly List _methodMappings = new(); - // a list of all mappings (extra mappings and mappings) - private readonly List _allMappings = new(); - // queue of mappings which don't have the body built yet private readonly Queue<(IMapping, MappingBuilderContext)> _mappingsToBuildBody = new(); @@ -24,9 +21,7 @@ public class MappingCollection public IReadOnlyCollection MethodMappings => _methodMappings; - public IReadOnlyCollection All => _allMappings; - - public ITypeMapping? FindMapping(ITypeSymbol sourceType, ITypeSymbol targetType) + public ITypeMapping? Find(ITypeSymbol sourceType, ITypeSymbol targetType) { _mappings.TryGetValue(new TypeMappingKey(sourceType, targetType), out var mapping); return mapping; @@ -38,18 +33,17 @@ public class MappingCollection return mapping; } - public void EnqueueMappingToBuildBody(IMapping mapping, MappingBuilderContext ctx) + public void EnqueueToBuildBody(IMapping mapping, MappingBuilderContext ctx) => _mappingsToBuildBody.Enqueue((mapping, ctx)); - public void AddMapping(ITypeMapping mapping) + public void Add(ITypeMapping mapping) { - _allMappings.Add(mapping); if (mapping is MethodMapping methodMapping) { _methodMappings.Add(methodMapping); } - if (mapping.CallableByOtherMappings && FindMapping(mapping.SourceType, mapping.TargetType) is null) + if (mapping.CallableByOtherMappings && Find(mapping.SourceType, mapping.TargetType) is null) { _mappings.Add(new TypeMappingKey(mapping), mapping); } diff --git a/src/Riok.Mapperly/Descriptors/Mappings/EnumFromStringParseMapping.cs b/src/Riok.Mapperly/Descriptors/Mappings/EnumFromStringParseMapping.cs new file mode 100644 index 0000000000..9385333c00 --- /dev/null +++ b/src/Riok.Mapperly/Descriptors/Mappings/EnumFromStringParseMapping.cs @@ -0,0 +1,52 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; +using static Riok.Mapperly.Emit.SyntaxFactoryHelper; + +namespace Riok.Mapperly.Descriptors.Mappings; + +/// +/// Represents a mapping from a string to an enum. +/// Uses . +/// Less efficient than +/// but works in . +/// +public class EnumFromStringParseMapping : TypeMapping +{ + private const string EnumClassName = "System.Enum"; + private const string ParseMethodName = "Parse"; + + private readonly bool _genericParseMethodSupported; + private readonly bool _ignoreCase; + + public EnumFromStringParseMapping( + ITypeSymbol sourceType, + ITypeSymbol targetType, + bool genericParseMethodSupported, + bool ignoreCase) + : base(sourceType, targetType) + { + _genericParseMethodSupported = genericParseMethodSupported; + _ignoreCase = ignoreCase; + } + + public override ExpressionSyntax Build(TypeMappingBuildContext ctx) + { + // System.Enum.Parse(source, ignoreCase) + if (_genericParseMethodSupported) + { + return GenericInvocation( + EnumClassName, + ParseMethodName, + new[] { IdentifierName(TargetType.ToDisplayString()) }, + ctx.Source, + BooleanLiteral(_ignoreCase)); + } + + // (TargetType)System.Enum.Parse(typeof(TargetType), source, ignoreCase) + var enumParseInvocation = Invocation( + MemberAccess(EnumClassName, ParseMethodName), + TypeOfExpression(IdentifierName(TargetType.ToDisplayString())), ctx.Source, BooleanLiteral(_ignoreCase)); + return CastExpression(IdentifierName(TargetType.ToDisplayString()), enumParseInvocation); + } +} diff --git a/src/Riok.Mapperly/Descriptors/Mappings/EnumFromStringMapping.cs b/src/Riok.Mapperly/Descriptors/Mappings/EnumFromStringSwitchMapping.cs similarity index 84% rename from src/Riok.Mapperly/Descriptors/Mappings/EnumFromStringMapping.cs rename to src/Riok.Mapperly/Descriptors/Mappings/EnumFromStringSwitchMapping.cs index c90a98163e..826e1a7246 100644 --- a/src/Riok.Mapperly/Descriptors/Mappings/EnumFromStringMapping.cs +++ b/src/Riok.Mapperly/Descriptors/Mappings/EnumFromStringSwitchMapping.cs @@ -9,38 +9,37 @@ namespace Riok.Mapperly.Descriptors.Mappings; /// /// Represents a mapping from a string to an enum. /// Uses a switch expression for performance reasons (in comparison to ). +/// Optimized version of . /// -public class EnumFromStringMapping : MethodMapping +public class EnumFromStringSwitchMapping : MethodMapping { - private const string EnumClassName = "System.Enum"; - private const string ParseMethodName = "Parse"; private const string IgnoreCaseSwitchDesignatedVariableName = "s"; private const string StringEqualsMethodName = nameof(string.Equals); private const string StringComparisonFullName = "System.StringComparison.OrdinalIgnoreCase"; private readonly IEnumerable _enumMembers; private readonly bool _ignoreCase; + private readonly EnumFromStringParseMapping _fallbackMapping; - public EnumFromStringMapping( + public EnumFromStringSwitchMapping( ITypeSymbol sourceType, ITypeSymbol targetType, IEnumerable enumMembers, + bool genericParseMethodSupported, bool ignoreCase) : base(sourceType, targetType) { _enumMembers = enumMembers; _ignoreCase = ignoreCase; + _fallbackMapping = new EnumFromStringParseMapping(sourceType, targetType, genericParseMethodSupported, ignoreCase); } public override IEnumerable BuildBody(TypeMappingBuildContext ctx) { - // fallback switch arm: _ => (TargetType)System.Enum.Parse(typeof(TargetType), source, ignoreCase) - var enumParseInvocation = Invocation( - MemberAccess(EnumClassName, ParseMethodName), - TypeOfExpression(IdentifierName(TargetType.ToDisplayString())), ctx.Source, BooleanLiteral(_ignoreCase)); + // fallback switch arm: _ => System.Enum.Parse(source, ignoreCase) var fallbackArm = SwitchExpressionArm( DiscardPattern(), - CastExpression(IdentifierName(TargetType.ToDisplayString()), enumParseInvocation)); + _fallbackMapping.Build(ctx)); // switch for each name to the enum value var arms = _ignoreCase diff --git a/src/Riok.Mapperly/Descriptors/Mappings/INewInstanceObjectPropertyMapping.cs b/src/Riok.Mapperly/Descriptors/Mappings/INewInstanceObjectPropertyMapping.cs new file mode 100644 index 0000000000..f278f08384 --- /dev/null +++ b/src/Riok.Mapperly/Descriptors/Mappings/INewInstanceObjectPropertyMapping.cs @@ -0,0 +1,13 @@ +using Riok.Mapperly.Descriptors.Mappings.PropertyMappings; + +namespace Riok.Mapperly.Descriptors.Mappings; + +/// +/// An object mapping creating the target instance via a new() call. +/// +public interface INewInstanceObjectPropertyMapping : IMapping +{ + void AddConstructorParameterMapping(ConstructorParameterMapping mapping); + + void AddInitPropertyMapping(PropertyAssignmentMapping mapping); +} diff --git a/src/Riok.Mapperly/Descriptors/Mappings/NewInstanceObjectFactoryPropertyMapping.cs b/src/Riok.Mapperly/Descriptors/Mappings/NewInstanceObjectFactoryPropertyMapping.cs index dcfac24a40..8a61bf7fc9 100644 --- a/src/Riok.Mapperly/Descriptors/Mappings/NewInstanceObjectFactoryPropertyMapping.cs +++ b/src/Riok.Mapperly/Descriptors/Mappings/NewInstanceObjectFactoryPropertyMapping.cs @@ -10,7 +10,7 @@ namespace Riok.Mapperly.Descriptors.Mappings; /// /// An object mapping creating the target instance via an object factory. /// -public class NewInstanceObjectFactoryPropertyMapping : ObjectPropertyMapping +public class NewInstanceObjectFactoryPropertyMapping : ObjectPropertyMethodMapping { private const string TargetVariableName = "target"; private readonly ObjectFactory _objectFactory; diff --git a/src/Riok.Mapperly/Descriptors/Mappings/NewInstanceObjectPropertyMapping.cs b/src/Riok.Mapperly/Descriptors/Mappings/NewInstanceObjectPropertyMapping.cs index 56ff17bdd3..c6b6f96672 100644 --- a/src/Riok.Mapperly/Descriptors/Mappings/NewInstanceObjectPropertyMapping.cs +++ b/src/Riok.Mapperly/Descriptors/Mappings/NewInstanceObjectPropertyMapping.cs @@ -1,29 +1,25 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; using Riok.Mapperly.Descriptors.Mappings.PropertyMappings; -using Riok.Mapperly.Emit; -using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; using static Riok.Mapperly.Emit.SyntaxFactoryHelper; namespace Riok.Mapperly.Descriptors.Mappings; /// -/// An object mapping creating the target instance via a new() call. +/// An object mapping creating the target instance via a new() call, +/// mapping properties via ctor, object initializer but not by assigning. +/// /// -public class NewInstanceObjectPropertyMapping : ObjectPropertyMapping +public class NewInstanceObjectPropertyMapping : TypeMapping, INewInstanceObjectPropertyMapping { - private const string TargetVariableName = "target"; private readonly HashSet _constructorPropertyMappings = new(); private readonly HashSet _initPropertyMappings = new(); - private readonly bool _enableReferenceHandling; public NewInstanceObjectPropertyMapping( ITypeSymbol sourceType, - ITypeSymbol targetType, - bool enableReferenceHandling) + ITypeSymbol targetType) : base(sourceType, targetType) { - _enableReferenceHandling = enableReferenceHandling; } public void AddConstructorParameterMapping(ConstructorParameterMapping mapping) @@ -32,16 +28,8 @@ public void AddConstructorParameterMapping(ConstructorParameterMapping mapping) public void AddInitPropertyMapping(PropertyAssignmentMapping mapping) => _initPropertyMappings.Add(mapping); - public override IEnumerable BuildBody(TypeMappingBuildContext ctx) + public override ExpressionSyntax Build(TypeMappingBuildContext ctx) { - var targetVariableName = ctx.NameBuilder.New(TargetVariableName); - - if (_enableReferenceHandling) - { - // TryGetReference - yield return ReferenceHandlingSyntaxFactoryHelper.TryGetReference(this, ctx); - } - // new T(ctorArgs) { ... }; var ctorArgs = _constructorPropertyMappings.Select(x => x.BuildArgument(ctx)).ToArray(); var objectCreationExpression = CreateInstance(TargetType, ctorArgs); @@ -55,27 +43,6 @@ public override IEnumerable BuildBody(TypeMappingBuildContext c objectCreationExpression = objectCreationExpression.WithInitializer(ObjectInitializer(initMappings)); } - // var target = new T() { ... }; - yield return DeclareLocalVariable(targetVariableName, objectCreationExpression); - - // set the reference as soon as it is created, - // as property mappings could refer to the same instance. - if (_enableReferenceHandling) - { - // SetReference - yield return ExpressionStatement(ReferenceHandlingSyntaxFactoryHelper.SetReference( - this, - ctx, - IdentifierName(targetVariableName))); - } - - // map properties - foreach (var expression in BuildBody(ctx, IdentifierName(targetVariableName))) - { - yield return expression; - } - - // return target; - yield return ReturnVariable(targetVariableName); + return objectCreationExpression; } } diff --git a/src/Riok.Mapperly/Descriptors/Mappings/NewInstanceObjectPropertyMethodMapping.cs b/src/Riok.Mapperly/Descriptors/Mappings/NewInstanceObjectPropertyMethodMapping.cs new file mode 100644 index 0000000000..351a58dd9a --- /dev/null +++ b/src/Riok.Mapperly/Descriptors/Mappings/NewInstanceObjectPropertyMethodMapping.cs @@ -0,0 +1,82 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Riok.Mapperly.Descriptors.Mappings.PropertyMappings; +using Riok.Mapperly.Emit; +using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; +using static Riok.Mapperly.Emit.SyntaxFactoryHelper; + +namespace Riok.Mapperly.Descriptors.Mappings; + +/// +/// An object mapping creating the target instance via a new() call, +/// mapping properties via ctor, object initializer and by assigning. +/// +public class NewInstanceObjectPropertyMethodMapping : ObjectPropertyMethodMapping, INewInstanceObjectPropertyMapping +{ + private const string TargetVariableName = "target"; + private readonly HashSet _constructorPropertyMappings = new(); + private readonly HashSet _initPropertyMappings = new(); + private readonly bool _enableReferenceHandling; + + public NewInstanceObjectPropertyMethodMapping( + ITypeSymbol sourceType, + ITypeSymbol targetType, + bool enableReferenceHandling) + : base(sourceType, targetType) + { + _enableReferenceHandling = enableReferenceHandling; + } + + public void AddConstructorParameterMapping(ConstructorParameterMapping mapping) + => _constructorPropertyMappings.Add(mapping); + + public void AddInitPropertyMapping(PropertyAssignmentMapping mapping) + => _initPropertyMappings.Add(mapping); + + public override IEnumerable BuildBody(TypeMappingBuildContext ctx) + { + var targetVariableName = ctx.NameBuilder.New(TargetVariableName); + + if (_enableReferenceHandling) + { + // TryGetReference + yield return ReferenceHandlingSyntaxFactoryHelper.TryGetReference(this, ctx); + } + + // new T(ctorArgs) { ... }; + var ctorArgs = _constructorPropertyMappings.Select(x => x.BuildArgument(ctx)).ToArray(); + var objectCreationExpression = CreateInstance(TargetType, ctorArgs); + + // add initializer + if (_initPropertyMappings.Count > 0) + { + var initMappings = _initPropertyMappings + .Select(x => x.BuildExpression(ctx, null)) + .ToArray(); + objectCreationExpression = objectCreationExpression.WithInitializer(ObjectInitializer(initMappings)); + } + + // var target = new T() { ... }; + yield return DeclareLocalVariable(targetVariableName, objectCreationExpression); + + // set the reference as soon as it is created, + // as property mappings could refer to the same instance. + if (_enableReferenceHandling) + { + // SetReference + yield return ExpressionStatement(ReferenceHandlingSyntaxFactoryHelper.SetReference( + this, + ctx, + IdentifierName(targetVariableName))); + } + + // map properties + foreach (var expression in BuildBody(ctx, IdentifierName(targetVariableName))) + { + yield return expression; + } + + // return target; + yield return ReturnVariable(targetVariableName); + } +} diff --git a/src/Riok.Mapperly/Descriptors/Mappings/NullDelegateMapping.cs b/src/Riok.Mapperly/Descriptors/Mappings/NullDelegateMapping.cs index 29aa8ad3e4..175f0272c2 100644 --- a/src/Riok.Mapperly/Descriptors/Mappings/NullDelegateMapping.cs +++ b/src/Riok.Mapperly/Descriptors/Mappings/NullDelegateMapping.cs @@ -44,8 +44,8 @@ public override ExpressionSyntax Build(TypeMappingBuildContext ctx) if (!SourceType.IsNullable()) { - // if the target type is a nullable value type, there needs to be an additional cast - // eg. int => int? needs to be casted. + // if the target type is a nullable value type, there needs to be an additional cast in some cases + // (eg. in a linq expression, int => int?) return TargetType.IsNullableValueType() ? CastExpression(IdentifierName(TargetType.ToDisplayString()), _delegateMapping.Build(ctx)) : _delegateMapping.Build(ctx); diff --git a/src/Riok.Mapperly/Descriptors/Mappings/ObjectPropertyMapping.cs b/src/Riok.Mapperly/Descriptors/Mappings/ObjectPropertyMethodMapping.cs similarity index 91% rename from src/Riok.Mapperly/Descriptors/Mappings/ObjectPropertyMapping.cs rename to src/Riok.Mapperly/Descriptors/Mappings/ObjectPropertyMethodMapping.cs index 5c2c983614..15d4f84bd0 100644 --- a/src/Riok.Mapperly/Descriptors/Mappings/ObjectPropertyMapping.cs +++ b/src/Riok.Mapperly/Descriptors/Mappings/ObjectPropertyMethodMapping.cs @@ -9,13 +9,13 @@ namespace Riok.Mapperly.Descriptors.Mappings; /// A mapping from type to another by mapping each property. /// A implementation of . /// -public abstract class ObjectPropertyMapping : +public abstract class ObjectPropertyMethodMapping : MethodMapping, IPropertyAssignmentTypeMapping { private readonly ObjectPropertyExistingTargetMapping _mapping; - protected ObjectPropertyMapping(ITypeSymbol sourceType, ITypeSymbol targetType) + protected ObjectPropertyMethodMapping(ITypeSymbol sourceType, ITypeSymbol targetType) : base(sourceType, targetType) { _mapping = new ObjectPropertyExistingTargetMapping(sourceType, targetType); diff --git a/src/Riok.Mapperly/Descriptors/Mappings/PropertyMappings/NullPropertyMapping.cs b/src/Riok.Mapperly/Descriptors/Mappings/PropertyMappings/NullPropertyMapping.cs index 98752d3db5..d441e9bafa 100644 --- a/src/Riok.Mapperly/Descriptors/Mappings/PropertyMappings/NullPropertyMapping.cs +++ b/src/Riok.Mapperly/Descriptors/Mappings/PropertyMappings/NullPropertyMapping.cs @@ -1,4 +1,5 @@ using System.Diagnostics; +using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; using Riok.Mapperly.Helpers; using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; @@ -8,25 +9,36 @@ namespace Riok.Mapperly.Descriptors.Mappings.PropertyMappings; /// /// Represents a null safe . -/// (eg. source?.A?.B ?? null-substitute or source?.A?.B == null ? null-substitute : MapToD(source.A.B)) +/// (eg. source?.A?.B ?? null-substitute or source?.A?.B != null ? MapToD(source.A.B) : null-substitute) /// [DebuggerDisplay("NullPropertyMapping({SourcePath}: {_delegateMapping})")] public class NullPropertyMapping : IPropertyMapping { private readonly ITypeMapping _delegateMapping; + private readonly ITypeSymbol _targetType; private readonly NullFallbackValue _nullFallback; + private readonly bool _useNullConditionalAccess; - public NullPropertyMapping(ITypeMapping delegateMapping, PropertyPath sourcePath, NullFallbackValue nullFallback) + public NullPropertyMapping( + ITypeMapping delegateMapping, + PropertyPath sourcePath, + ITypeSymbol targetType, + NullFallbackValue nullFallback, + bool useNullConditionalAccess) { SourcePath = sourcePath; _delegateMapping = delegateMapping; _nullFallback = nullFallback; + _useNullConditionalAccess = useNullConditionalAccess; + _targetType = targetType; } public PropertyPath SourcePath { get; } public ExpressionSyntax Build(TypeMappingBuildContext ctx) { + // the source type of the delegate mapping is nullable or the source path is not nullable + // build mapping with null conditional access if (_delegateMapping.SourceType.IsNullable() || !SourcePath.IsAnyNullable()) { ctx = ctx.WithSource(SourcePath.BuildAccess(ctx.Source, nullConditional: true)); @@ -38,22 +50,28 @@ public ExpressionSyntax Build(TypeMappingBuildContext ctx) // source.A?.B == null ? : Map(source.A.B) // or for nullable value types: // source.A?.B == null ? : Map(source.A.B.Value) - // use simplified coalesce expression for direct assignments: + // use simplified coalesce expression for synthetic mappings: // source.A?.B ?? - if (_delegateMapping.IsSynthetic) + if (_delegateMapping.IsSynthetic && (_useNullConditionalAccess || !SourcePath.IsAnyObjectPathNullable())) { var nullConditionalSourceAccess = SourcePath.BuildAccess(ctx.Source, nullConditional: true); - return Coalesce( - _delegateMapping.Build(ctx.WithSource(nullConditionalSourceAccess)), - NullSubstitute(_delegateMapping.TargetType, nullConditionalSourceAccess, _nullFallback)); + var mapping = _delegateMapping.Build(ctx.WithSource(nullConditionalSourceAccess)); + return _nullFallback == NullFallbackValue.Default && _targetType.IsNullable() + ? mapping + : Coalesce( + mapping, + NullSubstitute(_delegateMapping.TargetType, nullConditionalSourceAccess, _nullFallback)); } - var nullCheckPath = SourcePath.BuildAccess(ctx.Source, nullConditional: true, skipTrailingNonNullable: true); + var notNullCondition = _useNullConditionalAccess + ? IsNotNull(SourcePath.BuildAccess(ctx.Source, nullConditional: true, skipTrailingNonNullable: true)) + : SourcePath.BuildNonNullConditionWithoutConditionalAccess(ctx.Source)!; var sourcePropertyAccess = SourcePath.BuildAccess(ctx.Source, true); + ctx = ctx.WithSource(sourcePropertyAccess); return ConditionalExpression( - IsNull(nullCheckPath), - NullSubstitute(_delegateMapping.TargetType, sourcePropertyAccess, _nullFallback), - _delegateMapping.Build(ctx.WithSource(sourcePropertyAccess))); + notNullCondition, + _delegateMapping.Build(ctx), + NullSubstitute(_delegateMapping.TargetType, sourcePropertyAccess, _nullFallback)); } protected bool Equals(NullPropertyMapping other) diff --git a/src/Riok.Mapperly/Descriptors/Mappings/PropertyMappings/PropertyPath.cs b/src/Riok.Mapperly/Descriptors/Mappings/PropertyMappings/PropertyPath.cs index f169f82be4..8e9bd4656d 100644 --- a/src/Riok.Mapperly/Descriptors/Mappings/PropertyMappings/PropertyPath.cs +++ b/src/Riok.Mapperly/Descriptors/Mappings/PropertyMappings/PropertyPath.cs @@ -82,6 +82,9 @@ public IEnumerable> ObjectPathNullableSubPa public bool IsAnyNullable() => Path.Any(p => p.IsNullable()); + public bool IsAnyObjectPathNullable() + => ObjectPath.Any(p => p.IsNullable()); + public ExpressionSyntax BuildAccess( ExpressionSyntax? baseAccess, bool addValuePropertyOnNullable = false, @@ -98,23 +101,53 @@ public ExpressionSyntax BuildAccess( path = path.Skip(1); } - if (!nullConditional) + if (nullConditional) + { + return path.AggregateWithPrevious( + baseAccess, + (expr, prevProp, prop) => prevProp?.IsNullable() == true + ? ConditionalAccess(expr, prop.Name) + : MemberAccess(expr, prop.Name)); + } + + if (addValuePropertyOnNullable) + { + return path.Aggregate(baseAccess, (a, b) => b.Type.IsNullableValueType() + ? MemberAccess(MemberAccess(a, b.Name), NullableValueProperty) + : MemberAccess(a, b.Name)); + } + + return path.Aggregate(baseAccess, (a, b) => MemberAccess(a, b.Name)); + } + + /// + /// Builds a condition (the resulting expression evaluates to a boolean) + /// whether the path is non-null. + /// + /// The base access to access the property or null. + /// null if no part of the path is nullable or the condition which needs to be true, that the path cannot be null. + public ExpressionSyntax? BuildNonNullConditionWithoutConditionalAccess(ExpressionSyntax? baseAccess) + { + var path = PathWithoutTrailingNonNullable(); + ExpressionSyntax? condition = null; + var access = baseAccess; + if (access == null) + { + access = IdentifierName(path.First().Name); + path = path.Skip(1); + } + + foreach (var pathPart in path) { - if (addValuePropertyOnNullable) - { - return path.Aggregate(baseAccess, (a, b) => b.Type.IsNullableValueType() - ? MemberAccess(MemberAccess(a, b.Name), NullableValueProperty) - : MemberAccess(a, b.Name)); - } - - return path.Aggregate(baseAccess, (a, b) => MemberAccess(a, b.Name)); + access = MemberAccess(access, pathPart.Name); + + if (!pathPart.IsNullable()) + continue; + + condition = And(condition, IsNotNull(access)); } - return path.AggregateWithPrevious( - baseAccess, - (expr, prevProp, prop) => prevProp?.IsNullable() == true - ? ConditionalAccess(expr, prop.Name) - : MemberAccess(expr, prop.Name)); + return condition; } public override bool Equals(object? obj) diff --git a/src/Riok.Mapperly/Descriptors/Mappings/QueryableProjectionMapping.cs b/src/Riok.Mapperly/Descriptors/Mappings/QueryableProjectionMapping.cs new file mode 100644 index 0000000000..afffcd99e0 --- /dev/null +++ b/src/Riok.Mapperly/Descriptors/Mappings/QueryableProjectionMapping.cs @@ -0,0 +1,46 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; +using static Riok.Mapperly.Emit.SyntaxFactoryHelper; + +namespace Riok.Mapperly.Descriptors.Mappings; + +/// +/// A projections queryable mapping +/// to map from one generic to another. +/// +public class QueryableProjectionMapping : MethodMapping +{ + private const string SelectLambdaParameterName = "x"; + + private const string QueryableReceiverName = "System.Linq.Queryable"; + private const string SelectMethodName = nameof(Queryable.Select); + + private readonly ITypeMapping _delegateMapping; + + public QueryableProjectionMapping( + ITypeSymbol sourceType, + ITypeSymbol targetType, + ITypeMapping delegateMapping) : base(sourceType, targetType) + { + _delegateMapping = delegateMapping; + } + + public override IEnumerable BuildBody(TypeMappingBuildContext ctx) + { + // disable nullable reference types for expressions, as for ORMs nullables usually don't apply + // #nullable disable + // return System.Linq.Enumerable.Select(source, x => ...); + // #nullable enable + var lambdaParamName = ctx.NameBuilder.New(SelectLambdaParameterName); + var delegateMapping = _delegateMapping.Build(ctx.WithSource(IdentifierName(lambdaParamName))); + var projectionLambda = SimpleLambdaExpression(Parameter(Identifier(lambdaParamName))).WithExpressionBody(delegateMapping); + var select = StaticInvocation(QueryableReceiverName, SelectMethodName, ctx.Source, projectionLambda); + return new[] + { + ReturnStatement(select) + .WithLeadingTrivia(TriviaList(Nullable(false))) + .WithTrailingTrivia(TriviaList(Nullable(true))) + }; + } +} diff --git a/src/Riok.Mapperly/Descriptors/SimpleMappingBuilderContext.cs b/src/Riok.Mapperly/Descriptors/SimpleMappingBuilderContext.cs index ff6072fe46..baef6d1747 100644 --- a/src/Riok.Mapperly/Descriptors/SimpleMappingBuilderContext.cs +++ b/src/Riok.Mapperly/Descriptors/SimpleMappingBuilderContext.cs @@ -1,5 +1,6 @@ using Microsoft.CodeAnalysis; using Riok.Mapperly.Abstractions; +using Riok.Mapperly.Descriptors.MappingBuilders; namespace Riok.Mapperly.Descriptors; @@ -8,21 +9,53 @@ namespace Riok.Mapperly.Descriptors; /// public class SimpleMappingBuilderContext { - public SimpleMappingBuilderContext(DescriptorBuilder builder) + private readonly MapperDescriptor _descriptor; + private readonly SourceProductionContext _context; + + public SimpleMappingBuilderContext( + Compilation compilation, + Configuration configuration, + WellKnownTypes types, + MapperDescriptor descriptor, + SourceProductionContext context, + MappingBuilder mappingBuilder, + ExistingTargetMappingBuilder existingTargetMappingBuilder) { - Builder = builder; + Compilation = compilation; + Types = types; + Configuration = configuration; + _descriptor = descriptor; + _context = context; + MappingBuilder = mappingBuilder; + ExistingTargetMappingBuilder = existingTargetMappingBuilder; } - protected DescriptorBuilder Builder { get; } - public Compilation Compilation => Builder.Compilation; + protected SimpleMappingBuilderContext(SimpleMappingBuilderContext ctx) + : this( + ctx.Compilation, + ctx.Configuration, + ctx.Types, + ctx._descriptor, + ctx._context, + ctx.MappingBuilder, + ctx.ExistingTargetMappingBuilder) + { + } + public Compilation Compilation { get; } - public MapperAttribute MapperConfiguration => Builder.MapperConfiguration; + public MapperAttribute MapperConfiguration => Configuration.Mapper; - public bool IsConversionEnabled(MappingConversionType conversionType) - => MapperConfiguration.EnabledConversions.HasFlag(conversionType); + public WellKnownTypes Types { get; } - public WellKnownTypes Types => Builder.WellKnownTypes; + protected Configuration Configuration { get; } + + protected MappingBuilder MappingBuilder { get; } + + protected ExistingTargetMappingBuilder ExistingTargetMappingBuilder { get; } + + public virtual bool IsConversionEnabled(MappingConversionType conversionType) + => MapperConfiguration.EnabledConversions.HasFlag(conversionType); public void ReportDiagnostic(DiagnosticDescriptor descriptor, ISymbol? location, params object[] messageArgs) => ReportDiagnostic(descriptor, location?.DeclaringSyntaxReferences.FirstOrDefault()?.GetSyntax(), messageArgs); @@ -31,5 +64,5 @@ public void ReportDiagnostic(DiagnosticDescriptor descriptor, SyntaxNode? locati => ReportDiagnostic(descriptor, location?.GetLocation(), messageArgs); private void ReportDiagnostic(DiagnosticDescriptor descriptor, Location? location, params object[] messageArgs) - => Builder.ReportDiagnostic(descriptor, location, messageArgs); + => _context.ReportDiagnostic(Diagnostic.Create(descriptor, location ?? _descriptor.Syntax.GetLocation(), messageArgs)); } diff --git a/src/Riok.Mapperly/Descriptors/WellKnownTypes.cs b/src/Riok.Mapperly/Descriptors/WellKnownTypes.cs index 3c3ab48166..4a1afaf13f 100644 --- a/src/Riok.Mapperly/Descriptors/WellKnownTypes.cs +++ b/src/Riok.Mapperly/Descriptors/WellKnownTypes.cs @@ -29,6 +29,9 @@ public class WellKnownTypes private INamedTypeSymbol? _iReadOnlyList; private INamedTypeSymbol? _keyValuePair; private INamedTypeSymbol? _dictionary; + private INamedTypeSymbol? _enum; + + private INamedTypeSymbol? _iQueryable; private INamedTypeSymbol? _dateOnly; private INamedTypeSymbol? _timeOnly; @@ -55,6 +58,10 @@ internal WellKnownTypes(Compilation compilation) public INamedTypeSymbol IReadOnlyList => _iReadOnlyList ??= GetTypeSymbol(typeof(IReadOnlyList<>)); public INamedTypeSymbol KeyValuePair => _keyValuePair ??= GetTypeSymbol(typeof(KeyValuePair<,>)); public INamedTypeSymbol Dictionary => _dictionary ??= GetTypeSymbol(typeof(Dictionary<,>)); + public INamedTypeSymbol Enum => _enum ??= GetTypeSymbol(typeof(Enum)); + public INamedTypeSymbol IQueryable => _iQueryable ??= GetTypeSymbol(typeof(IQueryable<>)); + + // use string type name as they are not available in netstandard2.0 public INamedTypeSymbol? DateOnly => _dateOnly ??= GetTypeSymbol("System.DateOnly"); public INamedTypeSymbol? TimeOnly => _timeOnly ??= GetTypeSymbol("System.TimeOnly"); diff --git a/src/Riok.Mapperly/Diagnostics/DiagnosticDescriptors.cs b/src/Riok.Mapperly/Diagnostics/DiagnosticDescriptors.cs index 91b5bfb017..222b1c476a 100644 --- a/src/Riok.Mapperly/Diagnostics/DiagnosticDescriptors.cs +++ b/src/Riok.Mapperly/Diagnostics/DiagnosticDescriptors.cs @@ -94,7 +94,7 @@ internal static class DiagnosticDescriptors DiagnosticSeverity.Info, true); - public static readonly DiagnosticDescriptor MappingSourcePropertyNotFound = new DiagnosticDescriptor( + public static readonly DiagnosticDescriptor SourcePropertyNotFound = new DiagnosticDescriptor( "RMG012", "Mapping source property not found", "Property {0} on source type {1} was not found", @@ -229,4 +229,36 @@ internal static class DiagnosticDescriptors DiagnosticCategories.Mapper, DiagnosticSeverity.Error, true); + + public static readonly DiagnosticDescriptor QueryableProjectionMappingsDoNotSupportReferenceHandling = new DiagnosticDescriptor( + "RMG029", + "Queryable projection mappings do not support reference handling", + "Queryable projection mappings do not support reference handling", + DiagnosticCategories.Mapper, + DiagnosticSeverity.Error, + true); + + public static readonly DiagnosticDescriptor ReferenceLoopInInitOnlyMapping = new DiagnosticDescriptor( + "RMG030", + "Reference loop detected while mapping to an init only property", + "Reference loop detected while mapping from {0}.{1} to the init only property {2}.{3}, consider ignoring this property", + DiagnosticCategories.Mapper, + DiagnosticSeverity.Error, + true); + + public static readonly DiagnosticDescriptor ReferenceLoopInCtorMapping = new DiagnosticDescriptor( + "RMG031", + "Reference loop detected while mapping to a constructor parameter", + "Reference loop detected while mapping from {0}.{1} to the constructor parameter {3} of {2}, consider ignoring this property or mark another constructor as mapping constructor", + DiagnosticCategories.Mapper, + DiagnosticSeverity.Warning, + true); + + public static readonly DiagnosticDescriptor EnumMappingStrategyByNameNotSupportedInProjectionMappings = new DiagnosticDescriptor( + "RMG032", + "The enum mapping strategy ByName cannot be used in projection mappings", + "The enum mapping strategy ByName cannot be used in projection mappings to map from {0} to {1}", + DiagnosticCategories.Mapper, + DiagnosticSeverity.Warning, + true); } diff --git a/src/Riok.Mapperly/Emit/SyntaxFactoryHelper.cs b/src/Riok.Mapperly/Emit/SyntaxFactoryHelper.cs index b7db69a489..af672ee1da 100644 --- a/src/Riok.Mapperly/Emit/SyntaxFactoryHelper.cs +++ b/src/Riok.Mapperly/Emit/SyntaxFactoryHelper.cs @@ -42,11 +42,14 @@ public static BinaryExpressionSyntax Coalesce( coalesceExpr); } - public static ExpressionSyntax Or(IEnumerable values) - => values.Aggregate((a, b) => BinaryExpression(SyntaxKind.LogicalOrExpression, a, b)); + public static ExpressionSyntax Or(IEnumerable values) + => values.WhereNotNull().Aggregate((a, b) => BinaryExpression(SyntaxKind.LogicalOrExpression, a, b)); - public static ExpressionSyntax And(IEnumerable values) - => values.Aggregate((a, b) => BinaryExpression(SyntaxKind.LogicalAndExpression, a, b)); + public static ExpressionSyntax And(params ExpressionSyntax?[] values) + => And((IEnumerable)values); + + public static ExpressionSyntax And(IEnumerable values) + => values.WhereNotNull().Aggregate((a, b) => BinaryExpression(SyntaxKind.LogicalAndExpression, a, b)); public static ExpressionSyntax IfNoneNull(params (ITypeSymbol Type, ExpressionSyntax Access)[] values) { @@ -116,7 +119,10 @@ public static MemberAccessExpressionSyntax MemberAccess(string identifierName, s => MemberAccess(IdentifierName(identifierName), propertyIdentifierName); public static MemberAccessExpressionSyntax MemberAccess(ExpressionSyntax idExpression, string propertyIdentifierName) - => MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, idExpression, IdentifierName(propertyIdentifierName)); + => MemberAccess(idExpression, IdentifierName(propertyIdentifierName)); + + public static MemberAccessExpressionSyntax MemberAccess(ExpressionSyntax idExpression, SimpleNameSyntax property) + => MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, idExpression, property); public static AssignmentExpressionSyntax Assignment( ExpressionSyntax target, @@ -157,6 +163,14 @@ public static ThrowExpressionSyntax ThrowNotImplementedException() .WithArgumentList(SyntaxFactory.ArgumentList())); } + public static InvocationExpressionSyntax GenericInvocation(string receiver, string methodName, IEnumerable typeParams, params ExpressionSyntax[] arguments) + { + var method = GenericName(methodName) + .WithTypeArgumentList(TypeArgumentList(typeParams.ToArray())); + return InvocationExpression(MemberAccess(IdentifierName(receiver), method)) + .WithArgumentList(ArgumentList(arguments)); + } + public static InvocationExpressionSyntax GenericInvocation(string methodName, IEnumerable typeParams, params ExpressionSyntax[] arguments) { var method = GenericName(methodName) @@ -211,15 +225,19 @@ public static ParameterSyntax Parameter(bool addThisKeyword, MethodParameter par return param; } - public static InvocationExpressionSyntax StaticInvocation(IMethodSymbol method, params ExpressionSyntax[] arguments) + public static InvocationExpressionSyntax StaticInvocation(string receiverType, string methodName, params ExpressionSyntax[] arguments) { - var receiverType = method.ReceiverType ?? throw new ArgumentNullException(nameof(method.ReceiverType)); - var receiverTypeIdentifier = NonNullableIdentifier(receiverType); - var methodAccess = MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, receiverTypeIdentifier, IdentifierName(method.Name)); - return InvocationExpression(methodAccess) - .WithArgumentList(ArgumentList(arguments)); + var receiverTypeIdentifier = IdentifierName(receiverType); + var methodAccess = MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, receiverTypeIdentifier, IdentifierName(methodName)); + return InvocationExpression(methodAccess).WithArgumentList(ArgumentList(arguments)); } + public static InvocationExpressionSyntax StaticInvocation(IMethodSymbol method, params ExpressionSyntax[] arguments) + => StaticInvocation( + method.ReceiverType?.NonNullable().ToDisplayString() ?? throw new ArgumentNullException(nameof(method.ReceiverType)), + method.Name, + arguments); + public static ForStatementSyntax IncrementalForLoop(string counterName, StatementSyntax body, ExpressionSyntax maxValueExclusive) { var counterDeclaration = DeclareVariable(counterName, IntLiteral(0)); diff --git a/src/Riok.Mapperly/Helpers/PropertySymbolExtensions.cs b/src/Riok.Mapperly/Helpers/PropertySymbolExtensions.cs index c7912e2670..cbe4e495e8 100644 --- a/src/Riok.Mapperly/Helpers/PropertySymbolExtensions.cs +++ b/src/Riok.Mapperly/Helpers/PropertySymbolExtensions.cs @@ -10,6 +10,9 @@ public static bool CanSet(this IPropertySymbol prop) public static bool CanGet(this IPropertySymbol prop) => !prop.IsWriteOnly && prop.GetMethod?.IsAccessible() != false; + public static bool CanOnlySetViaInitializer(this IPropertySymbol prop) + => prop.IsInitOnly() || prop.IsRequired(); + public static bool IsInitOnly(this IPropertySymbol prop) => prop.SetMethod?.IsInitOnly == true; diff --git a/test/Riok.Mapperly.IntegrationTests/BaseMapperTest.cs b/test/Riok.Mapperly.IntegrationTests/BaseMapperTest.cs index 532f0b0447..0d28a4db4f 100644 --- a/test/Riok.Mapperly.IntegrationTests/BaseMapperTest.cs +++ b/test/Riok.Mapperly.IntegrationTests/BaseMapperTest.cs @@ -26,7 +26,7 @@ static BaseMapperTest() VerifierSettings.DontScrubDateTimes(); Verifier.DerivePathInfo((file, _, type, method) - => new PathInfo(Path.Combine(Path.GetDirectoryName(file)!, "_snapshots", "Roslyn_" + GetRoslynVersion()), type.Name, method.Name)); + => new PathInfo(Path.Combine(Path.GetDirectoryName(file)!, "_snapshots", GetPlatformVersion()), type.Name, method.Name)); } protected string GetGeneratedMapperFilePath(string name, [CallerFilePath] string filePath = "") @@ -79,12 +79,14 @@ protected TestObject NewTestObj() }; } - private static string GetRoslynVersion() + private static string GetPlatformVersion() { -#if NET7_0_OR_GREATER || NET48_OR_GREATER - return "4_5"; +#if NET48_OR_GREATER + return "NET_48"; +#elif NET7_0_OR_GREATER + return "Roslyn_4_5"; #else - return "4_4_OR_LOWER"; + return "Roslyn_4_4_OR_LOWER"; #endif } } diff --git a/test/Riok.Mapperly.IntegrationTests/Dto/TestObjectDtoProjection.cs b/test/Riok.Mapperly.IntegrationTests/Dto/TestObjectDtoProjection.cs new file mode 100644 index 0000000000..b731b62805 --- /dev/null +++ b/test/Riok.Mapperly.IntegrationTests/Dto/TestObjectDtoProjection.cs @@ -0,0 +1,65 @@ +using System; +using System.Collections.Generic; +using Riok.Mapperly.IntegrationTests.Models; + +namespace Riok.Mapperly.IntegrationTests.Dto +{ + public class TestObjectDtoProjection + { + public TestObjectDtoProjection(int ctorValue) + { + CtorValue = ctorValue; + } + + public int CtorValue { get; set; } + + public int IntValue { get; set; } + + public int IntInitOnlyValue { get; init; } + +#if NET7_0_OR_GREATER + public required int RequiredValue { get; init; } +#else + public int RequiredValue { get; init; } +#endif + + public string StringValue { get; set; } = string.Empty; + + public string RenamedStringValue2 { get; set; } = string.Empty; + + public int FlatteningIdValue { get; set; } + + public int? NullableFlatteningIdValue { get; set; } + + public int NestedNullableIntValue { get; set; } + + public TestObjectNestedDto? NestedNullable { get; set; } + + public TestObjectNestedDto NestedNullableTargetNotNullable { get; set; } = new(); + + public string StringNullableTargetNotNullable { get; set; } = string.Empty; + + public TestObjectProjection? SourceTargetSameObjectType { get; set; } + + public IReadOnlyCollection? NullableReadOnlyObjectCollection { get; set; } + + public TestEnumDtoByValue EnumValue { get; set; } + + public TestEnumDtoByName EnumName { get; set; } + + public byte EnumRawValue { get; set; } + + public string EnumStringValue { get; set; } = string.Empty; + + public TestEnumDtoByValue EnumReverseStringValue { get; set; } + + public InheritanceSubObjectDto? SubObject { get; set; } + + public string? IgnoredStringValue { get; set; } + public int IgnoredIntValue { get; set; } + + public DateOnly DateTimeValueTargetDateOnly { get; set; } + + public TimeOnly DateTimeValueTargetTimeOnly { get; set; } + } +} diff --git a/test/Riok.Mapperly.IntegrationTests/Mapper/ProjectionMapper.cs b/test/Riok.Mapperly.IntegrationTests/Mapper/ProjectionMapper.cs new file mode 100644 index 0000000000..683e791f2f --- /dev/null +++ b/test/Riok.Mapperly.IntegrationTests/Mapper/ProjectionMapper.cs @@ -0,0 +1,22 @@ +using System.Linq; +using Riok.Mapperly.Abstractions; +using Riok.Mapperly.IntegrationTests.Dto; +using Riok.Mapperly.IntegrationTests.Models; + +namespace Riok.Mapperly.IntegrationTests.Mapper +{ + [Mapper] + public static partial class ProjectionMapper + { + public static partial IQueryable ProjectToDto(this IQueryable q); + + // disable obsolete warning, as the obsolete attribute should still be tested. +#pragma warning disable CS0618 + [MapperIgnore(nameof(TestObjectDtoProjection.IgnoredStringValue))] +#pragma warning restore CS0618 + [MapperIgnoreTarget(nameof(TestObjectDtoProjection.IgnoredIntValue))] + [MapperIgnoreSource(nameof(TestObjectProjection.IgnoredStringValue))] + [MapProperty(nameof(TestObjectProjection.RenamedStringValue), nameof(TestObjectDtoProjection.RenamedStringValue2))] + private static partial TestObjectDtoProjection ProjectToDto(this TestObjectProjection testObject); + } +} diff --git a/test/Riok.Mapperly.IntegrationTests/Mapper/StaticTestMapper.cs b/test/Riok.Mapperly.IntegrationTests/Mapper/StaticTestMapper.cs index 891cd2d54a..6aff552029 100644 --- a/test/Riok.Mapperly.IntegrationTests/Mapper/StaticTestMapper.cs +++ b/test/Riok.Mapperly.IntegrationTests/Mapper/StaticTestMapper.cs @@ -26,6 +26,7 @@ public static partial class StaticTestMapper public static partial IEnumerable MapAllDtos(IEnumerable objects); [MapperIgnoreSource(nameof(TestObject.IgnoredIntValue))] + [MapperIgnoreTarget(nameof(TestObjectDto.IgnoredStringValue))] public static partial TestObjectDto MapToDtoExt(this TestObject src); public static TestObjectDto MapToDto(TestObject src) @@ -46,8 +47,8 @@ public static TestObjectDto MapToDto(TestObject src) [MapProperty( nameof(TestObject.NullableUnflatteningIdValue), nameof(TestObjectDto.NullableUnflattening) + "." + nameof(TestObjectDto.NullableUnflattening.IdValue))] - [MapperIgnoreTarget(nameof(TestObject.IgnoredIntValue))] - [MapperIgnoreSource(nameof(TestObjectDto.IgnoredIntValue))] + [MapperIgnoreSource(nameof(TestObject.IgnoredIntValue))] + [MapperIgnoreTarget(nameof(TestObjectDto.IgnoredIntValue))] private static partial TestObjectDto MapToDtoInternal(TestObject testObject); // disable obsolete warning, as the obsolete attribute should still be tested. @@ -64,6 +65,7 @@ public static TestObjectDto MapToDto(TestObject src) public static partial TestEnumDtoByName MapToEnumDtoByName(TestEnum v); [MapperIgnoreTarget(nameof(TestObjectDto.IgnoredIntValue))] + [MapperIgnoreSource(nameof(TestObject.IgnoredStringValue))] public static partial void UpdateDto(TestObject source, TestObjectDto target); private static partial int PrivateDirectInt(int value); diff --git a/test/Riok.Mapperly.IntegrationTests/Mapper/TestMapper.cs b/test/Riok.Mapperly.IntegrationTests/Mapper/TestMapper.cs index d2c9d6c114..1b066337de 100644 --- a/test/Riok.Mapperly.IntegrationTests/Mapper/TestMapper.cs +++ b/test/Riok.Mapperly.IntegrationTests/Mapper/TestMapper.cs @@ -60,7 +60,8 @@ public TestObjectDto MapToDto(TestObject src) [MapEnum(EnumMappingStrategy.ByName)] public partial TestEnumDtoByName MapToEnumDtoByName(TestEnum v); - [MapperIgnoreSource(nameof(TestObject.IgnoredIntValue))] + [MapperIgnoreTarget(nameof(TestObjectDto.IgnoredIntValue))] + [MapperIgnoreSource(nameof(TestObject.IgnoredStringValue))] public partial void UpdateDto(TestObject source, TestObjectDto target); private partial int PrivateDirectInt(int value); diff --git a/test/Riok.Mapperly.IntegrationTests/Models/TestObjectProjection.cs b/test/Riok.Mapperly.IntegrationTests/Models/TestObjectProjection.cs new file mode 100644 index 0000000000..097f6ec402 --- /dev/null +++ b/test/Riok.Mapperly.IntegrationTests/Models/TestObjectProjection.cs @@ -0,0 +1,64 @@ +using System; +using System.Collections.Generic; + +namespace Riok.Mapperly.IntegrationTests.Models +{ + public class TestObjectProjection + { + public int CtorValue { get; set; } + + public int IntValue { get; set; } + + public int IntInitOnlyValue { get; init; } + +#if NET7_0_OR_GREATER + public required int RequiredValue { get; init; } +#else + public int RequiredValue { get; init; } +#endif + + public string StringValue { get; set; } = string.Empty; + + public string RenamedStringValue { get; set; } = string.Empty; + + public IdObject Flattening { get; set; } = new(); + + public IdObject? NullableFlattening { get; set; } + + public int UnflatteningIdValue { get; set; } + + public int? NullableUnflatteningIdValue { get; set; } + + public TestObjectNested? NestedNullable { get; set; } + + public TestObjectNested? NestedNullableTargetNotNullable { get; set; } + + public string? StringNullableTargetNotNullable { get; set; } + + public TestObjectProjection? RecursiveObject { get; set; } + + public TestObjectProjection? SourceTargetSameObjectType { get; set; } + + public List? NullableReadOnlyObjectCollection { get; set; } + + public TestEnum EnumValue { get; set; } + + public TestEnum EnumName { get; set; } + + public TestEnum EnumRawValue { get; set; } + + public TestEnum EnumStringValue { get; set; } + + public string EnumReverseStringValue { get; set; } = string.Empty; + + public InheritanceSubObject? SubObject { get; set; } + + public string? IgnoredStringValue { get; set; } + + public int IgnoredIntValue { get; set; } + + public DateTime DateTimeValueTargetDateOnly { get; set; } + + public DateTime DateTimeValueTargetTimeOnly { get; set; } + } +} diff --git a/test/Riok.Mapperly.IntegrationTests/ProjectionMapperTest.cs b/test/Riok.Mapperly.IntegrationTests/ProjectionMapperTest.cs new file mode 100644 index 0000000000..bbfc03e482 --- /dev/null +++ b/test/Riok.Mapperly.IntegrationTests/ProjectionMapperTest.cs @@ -0,0 +1,71 @@ +using System.Threading.Tasks; +using FluentAssertions; +using Riok.Mapperly.IntegrationTests.Mapper; +using Riok.Mapperly.IntegrationTests.Models; +using VerifyXunit; +using Xunit; +#if NET7_0_OR_GREATER +using Microsoft.EntityFrameworkCore; +#endif + +namespace Riok.Mapperly.IntegrationTests +{ + [UsesVerify] + public class ProjectionMapperTest : BaseMapperTest + { + [Fact] + public Task SnapshotGeneratedSource() + { + var path = GetGeneratedMapperFilePath(nameof(ProjectionMapper)); + return Verifier.VerifyFile(path); + } + +#if NET7_0_OR_GREATER + [Fact] + public void ProjectionShouldTranslateToQuery() + { + using var ctx = new ProjectionDbContext(); + var query = ctx.Objects.ProjectToDto().ToQueryString(); + query.Should().Be( + """ + SELECT "o"."CtorValue", "o"."IntValue", "o"."IntInitOnlyValue", "o"."RequiredValue", "o"."StringValue", "o"."RenamedStringValue", "i"."IdValue", CASE + WHEN "i0"."IdValue" IS NOT NULL THEN "i0"."IdValue" + ELSE 0 + END, CASE + WHEN "t"."IntValue" IS NOT NULL THEN "t"."IntValue" + ELSE 0 + END, "t"."IntValue" IS NOT NULL, "t"."IntValue", "t0"."IntValue" IS NOT NULL, "t0"."IntValue", COALESCE("o"."StringNullableTargetNotNullable", ''), "o0"."IntValue", "o0"."CtorValue", "o0"."DateTimeValueTargetDateOnly", "o0"."DateTimeValueTargetTimeOnly", "o0"."EnumName", "o0"."EnumRawValue", "o0"."EnumReverseStringValue", "o0"."EnumStringValue", "o0"."EnumValue", "o0"."FlatteningIdValue", "o0"."IgnoredIntValue", "o0"."IgnoredStringValue", "o0"."IntInitOnlyValue", "o0"."NestedNullableIntValue", "o0"."NestedNullableTargetNotNullableIntValue", "o0"."NullableFlatteningIdValue", "o0"."NullableUnflatteningIdValue", "o0"."RecursiveObjectIntValue", "o0"."RenamedStringValue", "o0"."RequiredValue", "o0"."StringNullableTargetNotNullable", "o0"."StringValue", "o0"."SubObjectSubIntValue", "o0"."UnflatteningIdValue", "i0"."IdValue", "i1"."SubIntValue", "t1"."IntValue", "t1"."TestObjectProjectionIntValue", "t2"."IntValue", CAST("o"."EnumValue" AS INTEGER), CAST("o"."EnumName" AS INTEGER), CAST("o"."EnumRawValue" AS INTEGER), "o"."EnumStringValue", "o"."EnumReverseStringValue", "i1"."SubIntValue" IS NOT NULL, "i1"."BaseIntValue", "o"."DateTimeValueTargetDateOnly", "o"."DateTimeValueTargetTimeOnly" + FROM "Objects" AS "o" + INNER JOIN "IdObject" AS "i" ON "o"."FlatteningIdValue" = "i"."IdValue" + LEFT JOIN "IdObject" AS "i0" ON "o"."NullableFlatteningIdValue" = "i0"."IdValue" + LEFT JOIN "TestObjectNested" AS "t" ON "o"."NestedNullableIntValue" = "t"."IntValue" + LEFT JOIN "TestObjectNested" AS "t0" ON "o"."NestedNullableTargetNotNullableIntValue" = "t0"."IntValue" + LEFT JOIN "Objects" AS "o0" ON "o"."IntValue" = "o0"."RecursiveObjectIntValue" + LEFT JOIN "InheritanceSubObject" AS "i1" ON "o"."SubObjectSubIntValue" = "i1"."SubIntValue" + LEFT JOIN "TestObjectNested" AS "t1" ON "o"."IntValue" = "t1"."TestObjectProjectionIntValue" + LEFT JOIN "TestObjectNested" AS "t2" ON "o"."IntValue" = "t2"."TestObjectProjectionIntValue" + ORDER BY "o"."IntValue", "i"."IdValue", "i0"."IdValue", "t"."IntValue", "t0"."IntValue", "o0"."IntValue", "i1"."SubIntValue", "t1"."IntValue" + """); + } + + class ProjectionDbContext : DbContext + { + public DbSet Objects { get; set; } = null!; + + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + => optionsBuilder.UseSqlite("Data Source=:memory:"); + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity().HasKey(p => p.IntValue); + modelBuilder.Entity().HasOne(p => p.RecursiveObject); + modelBuilder.Entity().HasOne(p => p.SubObject); + + modelBuilder.Entity().HasKey(p => p.IdValue); + modelBuilder.Entity().HasKey(p => p.SubIntValue); + modelBuilder.Entity().HasKey(p => p.IntValue); + } + } +#endif + } +} diff --git a/test/Riok.Mapperly.IntegrationTests/Riok.Mapperly.IntegrationTests.csproj b/test/Riok.Mapperly.IntegrationTests/Riok.Mapperly.IntegrationTests.csproj index 5497b5126f..2e64c4d6ee 100644 --- a/test/Riok.Mapperly.IntegrationTests/Riok.Mapperly.IntegrationTests.csproj +++ b/test/Riok.Mapperly.IntegrationTests/Riok.Mapperly.IntegrationTests.csproj @@ -12,7 +12,7 @@ disable - + 9.0 @@ -29,7 +29,12 @@ - + + + + + + diff --git a/test/Riok.Mapperly.IntegrationTests/_snapshots/NET_48/CircularReferenceMapperTest.SnapshotGeneratedSource.verified.cs b/test/Riok.Mapperly.IntegrationTests/_snapshots/NET_48/CircularReferenceMapperTest.SnapshotGeneratedSource.verified.cs new file mode 100644 index 0000000000..e643d503f6 --- /dev/null +++ b/test/Riok.Mapperly.IntegrationTests/_snapshots/NET_48/CircularReferenceMapperTest.SnapshotGeneratedSource.verified.cs @@ -0,0 +1,26 @@ +#nullable enable +namespace Riok.Mapperly.IntegrationTests.Mapper +{ + public static partial class CircularReferenceMapper + { + public static partial Riok.Mapperly.IntegrationTests.Dto.CircularReferenceDto ToDto(Riok.Mapperly.IntegrationTests.Models.CircularReferenceObject obj) + { + return MapToCircularReferenceDto(obj, new Riok.Mapperly.Abstractions.ReferenceHandling.Internal.PreserveReferenceHandler()); + } + + private static Riok.Mapperly.IntegrationTests.Dto.CircularReferenceDto MapToCircularReferenceDto(Riok.Mapperly.IntegrationTests.Models.CircularReferenceObject source, Riok.Mapperly.Abstractions.ReferenceHandling.IReferenceHandler refHandler) + { + if (refHandler.TryGetReference(source, out var existingTargetReference)) + return existingTargetReference; + var target = new Riok.Mapperly.IntegrationTests.Dto.CircularReferenceDto(); + refHandler.SetReference(source, target); + if (source.Parent != null) + { + target.Parent = MapToCircularReferenceDto(source.Parent, refHandler); + } + + target.Value = source.Value; + return target; + } + } +} \ No newline at end of file diff --git a/test/Riok.Mapperly.IntegrationTests/_snapshots/NET_48/MapperTest.RunMappingShouldWork.verified.txt b/test/Riok.Mapperly.IntegrationTests/_snapshots/NET_48/MapperTest.RunMappingShouldWork.verified.txt new file mode 100644 index 0000000000..9618e0d2cc --- /dev/null +++ b/test/Riok.Mapperly.IntegrationTests/_snapshots/NET_48/MapperTest.RunMappingShouldWork.verified.txt @@ -0,0 +1,66 @@ +{ + CtorValue: 7, + CtorValue2: 100, + IntValue: 10, + IntInitOnlyValue: 3, + RequiredValue: 4, + StringValue: fooBar+after-map, + RenamedStringValue2: fooBar2, + FlatteningIdValue: 10, + NullableFlatteningIdValue: 100, + Unflattening: { + IdValue: 20 + }, + NullableUnflattening: { + IdValue: 200 + }, + NestedNullableIntValue: 100, + NestedNullable: { + IntValue: 100 + }, + NestedNullableTargetNotNullable: {}, + StringNullableTargetNotNullable: fooBar3, + RecursiveObject: { + CtorValue: 5, + CtorValue2: 100, + RequiredValue: 4, + StringValue: +after-map, + RenamedStringValue2: , + Unflattening: {}, + NestedNullableTargetNotNullable: {}, + StringNullableTargetNotNullable: , + EnumValue: DtoValue1, + EnumName: Value30, + EnumStringValue: 0, + EnumReverseStringValue: DtoValue3 + }, + SourceTargetSameObjectType: { + CtorValue: 8, + CtorValue2: 100, + IntValue: 99, + RequiredValue: 98, + StringValue: , + RenamedStringValue: , + Flattening: {}, + EnumReverseStringValue: + }, + NullableReadOnlyObjectCollection: [ + { + IntValue: 10 + }, + { + IntValue: 20 + } + ], + EnumValue: DtoValue1, + EnumName: Value10, + EnumRawValue: 20, + EnumStringValue: Value30, + EnumReverseStringValue: DtoValue3, + SubObject: { + SubIntValue: 2, + BaseIntValue: 1 + }, + DateTimeValueTargetDateOnly: 2020-01-03, + DateTimeValueTargetTimeOnly: 3:10 PM +} \ No newline at end of file diff --git a/test/Riok.Mapperly.IntegrationTests/_snapshots/NET_48/MapperTest.SnapshotGeneratedSource.verified.cs b/test/Riok.Mapperly.IntegrationTests/_snapshots/NET_48/MapperTest.SnapshotGeneratedSource.verified.cs new file mode 100644 index 0000000000..0287265625 --- /dev/null +++ b/test/Riok.Mapperly.IntegrationTests/_snapshots/NET_48/MapperTest.SnapshotGeneratedSource.verified.cs @@ -0,0 +1,311 @@ +#nullable enable +namespace Riok.Mapperly.IntegrationTests.Mapper +{ + public partial class TestMapper + { + public partial int DirectInt(int value) + { + return value; + } + + public partial long ImplicitCastInt(int value) + { + return (long)value; + } + + public partial int ExplicitCastInt(uint value) + { + return (int)value; + } + + public partial int? CastIntNullable(int value) + { + return (int?)value; + } + + public partial System.Guid ParseableGuid(string id) + { + return System.Guid.Parse(id); + } + + public partial int ParseableInt(string value) + { + return int.Parse(value); + } + + public partial System.DateTime DirectDateTime(System.DateTime dateTime) + { + return dateTime; + } + + public partial System.Collections.Generic.IEnumerable MapAllDtos(System.Collections.Generic.IEnumerable objects) + { + return System.Linq.Enumerable.Select(objects, x => MapToDto(x)); + } + + private partial Riok.Mapperly.IntegrationTests.Dto.TestObjectDto MapToDtoInternal(Riok.Mapperly.IntegrationTests.Models.TestObject testObject) + { + var target = new Riok.Mapperly.IntegrationTests.Dto.TestObjectDto(DirectInt(testObject.CtorValue), ctorValue2: DirectInt(testObject.CtorValue2)) + { + IntInitOnlyValue = DirectInt(testObject.IntInitOnlyValue), + RequiredValue = DirectInt(testObject.RequiredValue) + }; + if (testObject.NullableFlattening != null) + { + target.NullableFlatteningIdValue = CastIntNullable(testObject.NullableFlattening.IdValue); + } + + if (testObject.NullableUnflatteningIdValue != null) + { + target.NullableUnflattening ??= new(); + target.NullableUnflattening.IdValue = DirectInt(testObject.NullableUnflatteningIdValue.Value); + } + + if (testObject.NestedNullable != null) + { + target.NestedNullableIntValue = DirectInt(testObject.NestedNullable.IntValue); + target.NestedNullable = MapToTestObjectNestedDto(testObject.NestedNullable); + } + + if (testObject.NestedNullableTargetNotNullable != null) + { + target.NestedNullableTargetNotNullable = MapToTestObjectNestedDto(testObject.NestedNullableTargetNotNullable); + } + + if (testObject.StringNullableTargetNotNullable != null) + { + target.StringNullableTargetNotNullable = testObject.StringNullableTargetNotNullable; + } + + if (testObject.RecursiveObject != null) + { + target.RecursiveObject = MapToDto(testObject.RecursiveObject); + } + + if (testObject.NullableReadOnlyObjectCollection != null) + { + target.NullableReadOnlyObjectCollection = System.Linq.Enumerable.ToArray(System.Linq.Enumerable.Select(testObject.NullableReadOnlyObjectCollection, x => MapToTestObjectNestedDto(x))); + } + + if (testObject.SubObject != null) + { + target.SubObject = MapToInheritanceSubObjectDto(testObject.SubObject); + } + + target.IntValue = DirectInt(testObject.IntValue); + target.StringValue = testObject.StringValue; + target.RenamedStringValue2 = testObject.RenamedStringValue; + target.FlatteningIdValue = DirectInt(testObject.Flattening.IdValue); + target.Unflattening.IdValue = DirectInt(testObject.UnflatteningIdValue); + target.SourceTargetSameObjectType = testObject.SourceTargetSameObjectType; + target.EnumValue = (Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByValue)testObject.EnumValue; + target.EnumName = MapToEnumDtoByName(testObject.EnumName); + target.EnumRawValue = (byte)testObject.EnumRawValue; + target.EnumStringValue = MapToString(testObject.EnumStringValue); + target.EnumReverseStringValue = MapToTestEnumDtoByValue(testObject.EnumReverseStringValue); + target.DateTimeValueTargetDateOnly = System.DateOnly.FromDateTime(testObject.DateTimeValueTargetDateOnly); + target.DateTimeValueTargetTimeOnly = System.TimeOnly.FromDateTime(testObject.DateTimeValueTargetTimeOnly); + return target; + } + + public partial Riok.Mapperly.IntegrationTests.Models.TestObject MapFromDto(Riok.Mapperly.IntegrationTests.Dto.TestObjectDto dto) + { + var target = new Riok.Mapperly.IntegrationTests.Models.TestObject(DirectInt(dto.CtorValue), ctorValue2: DirectInt(dto.CtorValue2)) + { + IntInitOnlyValue = DirectInt(dto.IntInitOnlyValue), + RequiredValue = DirectInt(dto.RequiredValue) + }; + if (dto.NullableUnflattening != null) + { + target.NullableUnflatteningIdValue = CastIntNullable(dto.NullableUnflattening.IdValue); + } + + if (dto.NestedNullable != null) + { + target.NestedNullable = MapToTestObjectNested(dto.NestedNullable); + } + + if (dto.RecursiveObject != null) + { + target.RecursiveObject = MapFromDto(dto.RecursiveObject); + } + + if (dto.NullableReadOnlyObjectCollection != null) + { + target.NullableReadOnlyObjectCollection = MapToIReadOnlyCollection(dto.NullableReadOnlyObjectCollection); + } + + if (dto.SubObject != null) + { + target.SubObject = MapToInheritanceSubObject(dto.SubObject); + } + + target.IntValue = DirectInt(dto.IntValue); + target.StringValue = dto.StringValue; + target.UnflatteningIdValue = DirectInt(dto.Unflattening.IdValue); + target.NestedNullableTargetNotNullable = MapToTestObjectNested(dto.NestedNullableTargetNotNullable); + target.StringNullableTargetNotNullable = dto.StringNullableTargetNotNullable; + target.SourceTargetSameObjectType = dto.SourceTargetSameObjectType; + target.EnumValue = (Riok.Mapperly.IntegrationTests.Models.TestEnum)dto.EnumValue; + target.EnumName = (Riok.Mapperly.IntegrationTests.Models.TestEnum)dto.EnumName; + target.EnumRawValue = (Riok.Mapperly.IntegrationTests.Models.TestEnum)dto.EnumRawValue; + target.EnumStringValue = MapToTestEnum(dto.EnumStringValue); + target.EnumReverseStringValue = MapToString1(dto.EnumReverseStringValue); + return target; + } + + public partial Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByName MapToEnumDtoByName(Riok.Mapperly.IntegrationTests.Models.TestEnum v) + { + return v switch + { + Riok.Mapperly.IntegrationTests.Models.TestEnum.Value10 => Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByName.Value10, + Riok.Mapperly.IntegrationTests.Models.TestEnum.Value20 => Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByName.Value20, + Riok.Mapperly.IntegrationTests.Models.TestEnum.Value30 => Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByName.Value30, + _ => throw new System.ArgumentOutOfRangeException(nameof(v), v, "The value of enum TestEnum is not supported"), + }; + } + + public partial void UpdateDto(Riok.Mapperly.IntegrationTests.Models.TestObject source, Riok.Mapperly.IntegrationTests.Dto.TestObjectDto target) + { + if (source.NullableFlattening != null) + { + target.NullableFlatteningIdValue = CastIntNullable(source.NullableFlattening.IdValue); + } + + if (source.NestedNullable != null) + { + target.NestedNullableIntValue = DirectInt(source.NestedNullable.IntValue); + target.NestedNullable = MapToTestObjectNestedDto(source.NestedNullable); + } + + if (source.NestedNullableTargetNotNullable != null) + { + target.NestedNullableTargetNotNullable = MapToTestObjectNestedDto(source.NestedNullableTargetNotNullable); + } + + if (source.StringNullableTargetNotNullable != null) + { + target.StringNullableTargetNotNullable = source.StringNullableTargetNotNullable; + } + + if (source.RecursiveObject != null) + { + target.RecursiveObject = MapToDto(source.RecursiveObject); + } + + if (source.NullableReadOnlyObjectCollection != null) + { + target.NullableReadOnlyObjectCollection = System.Linq.Enumerable.ToArray(System.Linq.Enumerable.Select(source.NullableReadOnlyObjectCollection, x => MapToTestObjectNestedDto(x))); + } + + if (source.SubObject != null) + { + target.SubObject = MapToInheritanceSubObjectDto(source.SubObject); + } + + target.CtorValue = DirectInt(source.CtorValue); + target.CtorValue2 = DirectInt(source.CtorValue2); + target.IntValue = DirectInt(source.IntValue); + target.StringValue = source.StringValue; + target.FlatteningIdValue = DirectInt(source.Flattening.IdValue); + target.SourceTargetSameObjectType = source.SourceTargetSameObjectType; + target.EnumValue = (Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByValue)source.EnumValue; + target.EnumName = MapToEnumDtoByName(source.EnumName); + target.EnumRawValue = (byte)source.EnumRawValue; + target.EnumStringValue = MapToString(source.EnumStringValue); + target.EnumReverseStringValue = MapToTestEnumDtoByValue(source.EnumReverseStringValue); + target.DateTimeValueTargetDateOnly = System.DateOnly.FromDateTime(source.DateTimeValueTargetDateOnly); + target.DateTimeValueTargetTimeOnly = System.TimeOnly.FromDateTime(source.DateTimeValueTargetTimeOnly); + } + + private partial int PrivateDirectInt(int value) + { + return value; + } + + private Riok.Mapperly.IntegrationTests.Dto.TestObjectNestedDto MapToTestObjectNestedDto(Riok.Mapperly.IntegrationTests.Models.TestObjectNested source) + { + var target = new Riok.Mapperly.IntegrationTests.Dto.TestObjectNestedDto(); + target.IntValue = DirectInt(source.IntValue); + return target; + } + + private string MapToString(Riok.Mapperly.IntegrationTests.Models.TestEnum source) + { + return source switch + { + Riok.Mapperly.IntegrationTests.Models.TestEnum.Value10 => nameof(Riok.Mapperly.IntegrationTests.Models.TestEnum.Value10), + Riok.Mapperly.IntegrationTests.Models.TestEnum.Value20 => nameof(Riok.Mapperly.IntegrationTests.Models.TestEnum.Value20), + Riok.Mapperly.IntegrationTests.Models.TestEnum.Value30 => nameof(Riok.Mapperly.IntegrationTests.Models.TestEnum.Value30), + _ => source.ToString(), + }; + } + + private Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByValue MapToTestEnumDtoByValue(string source) + { + return source switch + { + nameof(Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByValue.DtoValue1) => Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByValue.DtoValue1, + nameof(Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByValue.DtoValue2) => Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByValue.DtoValue2, + nameof(Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByValue.DtoValue3) => Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByValue.DtoValue3, + _ => (Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByValue)System.Enum.Parse(typeof(Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByValue), source, false), + }; + } + + private Riok.Mapperly.IntegrationTests.Dto.InheritanceSubObjectDto MapToInheritanceSubObjectDto(Riok.Mapperly.IntegrationTests.Models.InheritanceSubObject source) + { + var target = new Riok.Mapperly.IntegrationTests.Dto.InheritanceSubObjectDto(); + target.SubIntValue = DirectInt(source.SubIntValue); + target.BaseIntValue = DirectInt(source.BaseIntValue); + return target; + } + + private Riok.Mapperly.IntegrationTests.Models.TestObjectNested MapToTestObjectNested(Riok.Mapperly.IntegrationTests.Dto.TestObjectNestedDto source) + { + var target = new Riok.Mapperly.IntegrationTests.Models.TestObjectNested(); + target.IntValue = DirectInt(source.IntValue); + return target; + } + + private System.Collections.Generic.IReadOnlyCollection MapToIReadOnlyCollection(Riok.Mapperly.IntegrationTests.Dto.TestObjectNestedDto[] source) + { + var target = new Riok.Mapperly.IntegrationTests.Models.TestObjectNested[source.Length]; + for (var i = 0; i < source.Length; i++) + { + target[i] = MapToTestObjectNested(source[i]); + } + + return target; + } + + private Riok.Mapperly.IntegrationTests.Models.TestEnum MapToTestEnum(string source) + { + return source switch + { + nameof(Riok.Mapperly.IntegrationTests.Models.TestEnum.Value10) => Riok.Mapperly.IntegrationTests.Models.TestEnum.Value10, + nameof(Riok.Mapperly.IntegrationTests.Models.TestEnum.Value20) => Riok.Mapperly.IntegrationTests.Models.TestEnum.Value20, + nameof(Riok.Mapperly.IntegrationTests.Models.TestEnum.Value30) => Riok.Mapperly.IntegrationTests.Models.TestEnum.Value30, + _ => (Riok.Mapperly.IntegrationTests.Models.TestEnum)System.Enum.Parse(typeof(Riok.Mapperly.IntegrationTests.Models.TestEnum), source, false), + }; + } + + private string MapToString1(Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByValue source) + { + return source switch + { + Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByValue.DtoValue1 => nameof(Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByValue.DtoValue1), + Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByValue.DtoValue2 => nameof(Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByValue.DtoValue2), + Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByValue.DtoValue3 => nameof(Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByValue.DtoValue3), + _ => source.ToString(), + }; + } + + private Riok.Mapperly.IntegrationTests.Models.InheritanceSubObject MapToInheritanceSubObject(Riok.Mapperly.IntegrationTests.Dto.InheritanceSubObjectDto source) + { + var target = new Riok.Mapperly.IntegrationTests.Models.InheritanceSubObject(); + target.SubIntValue = DirectInt(source.SubIntValue); + target.BaseIntValue = DirectInt(source.BaseIntValue); + return target; + } + } +} diff --git a/test/Riok.Mapperly.IntegrationTests/_snapshots/NET_48/ProjectionMapperTest.SnapshotGeneratedSource.verified.cs b/test/Riok.Mapperly.IntegrationTests/_snapshots/NET_48/ProjectionMapperTest.SnapshotGeneratedSource.verified.cs new file mode 100644 index 0000000000..d3164d56b2 --- /dev/null +++ b/test/Riok.Mapperly.IntegrationTests/_snapshots/NET_48/ProjectionMapperTest.SnapshotGeneratedSource.verified.cs @@ -0,0 +1,99 @@ +#nullable enable +namespace Riok.Mapperly.IntegrationTests.Mapper +{ + public static partial class ProjectionMapper + { + public static partial System.Linq.IQueryable ProjectToDto(this System.Linq.IQueryable q) + { +#nullable disable + return System.Linq.Queryable.Select(q, x => new Riok.Mapperly.IntegrationTests.Dto.TestObjectDtoProjection(x.CtorValue) { IntValue = x.IntValue, IntInitOnlyValue = x.IntInitOnlyValue, RequiredValue = x.RequiredValue, StringValue = x.StringValue, RenamedStringValue2 = x.RenamedStringValue, FlatteningIdValue = x.Flattening.IdValue, NullableFlatteningIdValue = x.NullableFlattening != null ? x.NullableFlattening.IdValue : default, NestedNullableIntValue = x.NestedNullable != null ? x.NestedNullable.IntValue : default, NestedNullable = x.NestedNullable != null ? new Riok.Mapperly.IntegrationTests.Dto.TestObjectNestedDto() { IntValue = x.NestedNullable.IntValue } : default, NestedNullableTargetNotNullable = x.NestedNullableTargetNotNullable != null ? new Riok.Mapperly.IntegrationTests.Dto.TestObjectNestedDto() { IntValue = x.NestedNullableTargetNotNullable.IntValue } : new Riok.Mapperly.IntegrationTests.Dto.TestObjectNestedDto(), StringNullableTargetNotNullable = x.StringNullableTargetNotNullable ?? "", SourceTargetSameObjectType = x.SourceTargetSameObjectType, NullableReadOnlyObjectCollection = x.NullableReadOnlyObjectCollection != null ? System.Linq.Enumerable.ToArray(System.Linq.Enumerable.Select(x.NullableReadOnlyObjectCollection, x1 => new Riok.Mapperly.IntegrationTests.Dto.TestObjectNestedDto() { IntValue = x1.IntValue })) : default, EnumValue = (Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByValue)x.EnumValue, EnumName = (Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByName)x.EnumName, EnumRawValue = (byte)x.EnumRawValue, EnumStringValue = (string)x.EnumStringValue.ToString(), EnumReverseStringValue = (Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByValue)System.Enum.Parse(typeof(Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByValue), x.EnumReverseStringValue, false), SubObject = x.SubObject != null ? new Riok.Mapperly.IntegrationTests.Dto.InheritanceSubObjectDto() { SubIntValue = x.SubObject.SubIntValue, BaseIntValue = x.SubObject.BaseIntValue } : default, DateTimeValueTargetDateOnly = System.DateOnly.FromDateTime(x.DateTimeValueTargetDateOnly), DateTimeValueTargetTimeOnly = System.TimeOnly.FromDateTime(x.DateTimeValueTargetTimeOnly) }); +#nullable enable + } + + private static partial Riok.Mapperly.IntegrationTests.Dto.TestObjectDtoProjection ProjectToDto(this Riok.Mapperly.IntegrationTests.Models.TestObjectProjection testObject) + { + var target = new Riok.Mapperly.IntegrationTests.Dto.TestObjectDtoProjection(testObject.CtorValue) + { + IntInitOnlyValue = testObject.IntInitOnlyValue, + RequiredValue = testObject.RequiredValue + }; + if (testObject.NestedNullable != null) + { + target.NestedNullableIntValue = testObject.NestedNullable.IntValue; + target.NestedNullable = MapToTestObjectNestedDto(testObject.NestedNullable); + } + + if (testObject.NestedNullableTargetNotNullable != null) + { + target.NestedNullableTargetNotNullable = MapToTestObjectNestedDto(testObject.NestedNullableTargetNotNullable); + } + + if (testObject.StringNullableTargetNotNullable != null) + { + target.StringNullableTargetNotNullable = testObject.StringNullableTargetNotNullable; + } + + if (testObject.NullableReadOnlyObjectCollection != null) + { + target.NullableReadOnlyObjectCollection = System.Linq.Enumerable.ToArray(System.Linq.Enumerable.Select(testObject.NullableReadOnlyObjectCollection, x => MapToTestObjectNestedDto(x))); + } + + if (testObject.SubObject != null) + { + target.SubObject = MapToInheritanceSubObjectDto(testObject.SubObject); + } + + target.IntValue = testObject.IntValue; + target.StringValue = testObject.StringValue; + target.RenamedStringValue2 = testObject.RenamedStringValue; + target.FlatteningIdValue = testObject.Flattening.IdValue; + target.NullableFlatteningIdValue = testObject.NullableFlattening?.IdValue; + target.SourceTargetSameObjectType = testObject.SourceTargetSameObjectType; + target.EnumValue = (Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByValue)testObject.EnumValue; + target.EnumName = (Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByName)testObject.EnumName; + target.EnumRawValue = (byte)testObject.EnumRawValue; + target.EnumStringValue = MapToString(testObject.EnumStringValue); + target.EnumReverseStringValue = MapToTestEnumDtoByValue(testObject.EnumReverseStringValue); + target.DateTimeValueTargetDateOnly = System.DateOnly.FromDateTime(testObject.DateTimeValueTargetDateOnly); + target.DateTimeValueTargetTimeOnly = System.TimeOnly.FromDateTime(testObject.DateTimeValueTargetTimeOnly); + return target; + } + + private static Riok.Mapperly.IntegrationTests.Dto.TestObjectNestedDto MapToTestObjectNestedDto(Riok.Mapperly.IntegrationTests.Models.TestObjectNested source) + { + var target = new Riok.Mapperly.IntegrationTests.Dto.TestObjectNestedDto(); + target.IntValue = source.IntValue; + return target; + } + + private static string MapToString(Riok.Mapperly.IntegrationTests.Models.TestEnum source) + { + return source switch + { + Riok.Mapperly.IntegrationTests.Models.TestEnum.Value10 => nameof(Riok.Mapperly.IntegrationTests.Models.TestEnum.Value10), + Riok.Mapperly.IntegrationTests.Models.TestEnum.Value20 => nameof(Riok.Mapperly.IntegrationTests.Models.TestEnum.Value20), + Riok.Mapperly.IntegrationTests.Models.TestEnum.Value30 => nameof(Riok.Mapperly.IntegrationTests.Models.TestEnum.Value30), + _ => source.ToString(), + }; + } + + private static Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByValue MapToTestEnumDtoByValue(string source) + { + return source switch + { + nameof(Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByValue.DtoValue1) => Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByValue.DtoValue1, + nameof(Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByValue.DtoValue2) => Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByValue.DtoValue2, + nameof(Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByValue.DtoValue3) => Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByValue.DtoValue3, + _ => (Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByValue)System.Enum.Parse(typeof(Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByValue), source, false), + }; + } + + private static Riok.Mapperly.IntegrationTests.Dto.InheritanceSubObjectDto MapToInheritanceSubObjectDto(Riok.Mapperly.IntegrationTests.Models.InheritanceSubObject source) + { + var target = new Riok.Mapperly.IntegrationTests.Dto.InheritanceSubObjectDto(); + target.SubIntValue = source.SubIntValue; + target.BaseIntValue = source.BaseIntValue; + return target; + } + } +} diff --git a/test/Riok.Mapperly.IntegrationTests/_snapshots/NET_48/StaticMapperTest.RunExtensionMappingShouldWork.verified.txt b/test/Riok.Mapperly.IntegrationTests/_snapshots/NET_48/StaticMapperTest.RunExtensionMappingShouldWork.verified.txt new file mode 100644 index 0000000000..9586df48d2 --- /dev/null +++ b/test/Riok.Mapperly.IntegrationTests/_snapshots/NET_48/StaticMapperTest.RunExtensionMappingShouldWork.verified.txt @@ -0,0 +1,61 @@ +{ + CtorValue: 7, + CtorValue2: 100, + IntValue: 10, + IntInitOnlyValue: 3, + RequiredValue: 4, + StringValue: fooBar, + RenamedStringValue2: , + FlatteningIdValue: 10, + NullableFlatteningIdValue: 100, + Unflattening: {}, + NestedNullableIntValue: 100, + NestedNullable: { + IntValue: 100 + }, + NestedNullableTargetNotNullable: {}, + StringNullableTargetNotNullable: fooBar3, + RecursiveObject: { + CtorValue: 5, + CtorValue2: 100, + RequiredValue: 4, + StringValue: , + RenamedStringValue2: , + Unflattening: {}, + NestedNullableTargetNotNullable: {}, + StringNullableTargetNotNullable: , + EnumValue: DtoValue1, + EnumName: Value30, + EnumStringValue: 0, + EnumReverseStringValue: DtoValue3 + }, + SourceTargetSameObjectType: { + CtorValue: 8, + CtorValue2: 100, + IntValue: 99, + RequiredValue: 98, + StringValue: , + RenamedStringValue: , + Flattening: {}, + EnumReverseStringValue: + }, + NullableReadOnlyObjectCollection: [ + { + IntValue: 10 + }, + { + IntValue: 20 + } + ], + EnumValue: DtoValue1, + EnumName: Value10, + EnumRawValue: 20, + EnumStringValue: Value30, + EnumReverseStringValue: DtoValue3, + SubObject: { + SubIntValue: 2, + BaseIntValue: 1 + }, + DateTimeValueTargetDateOnly: 2020-01-03, + DateTimeValueTargetTimeOnly: 3:10 PM +} \ No newline at end of file diff --git a/test/Riok.Mapperly.IntegrationTests/_snapshots/NET_48/StaticMapperTest.RunMappingShouldWork.verified.txt b/test/Riok.Mapperly.IntegrationTests/_snapshots/NET_48/StaticMapperTest.RunMappingShouldWork.verified.txt new file mode 100644 index 0000000000..e31744e1f7 --- /dev/null +++ b/test/Riok.Mapperly.IntegrationTests/_snapshots/NET_48/StaticMapperTest.RunMappingShouldWork.verified.txt @@ -0,0 +1,66 @@ +{ + CtorValue: 7, + CtorValue2: 100, + IntValue: 10, + IntInitOnlyValue: 3, + RequiredValue: 4, + StringValue: fooBar+after-map, + RenamedStringValue2: fooBar2, + FlatteningIdValue: 10, + NullableFlatteningIdValue: 100, + Unflattening: { + IdValue: 20 + }, + NullableUnflattening: { + IdValue: 200 + }, + NestedNullableIntValue: 100, + NestedNullable: { + IntValue: 100 + }, + NestedNullableTargetNotNullable: {}, + StringNullableTargetNotNullable: fooBar3, + RecursiveObject: { + CtorValue: 5, + CtorValue2: 100, + RequiredValue: 4, + StringValue: , + RenamedStringValue2: , + Unflattening: {}, + NestedNullableTargetNotNullable: {}, + StringNullableTargetNotNullable: , + EnumValue: DtoValue1, + EnumName: Value30, + EnumStringValue: 0, + EnumReverseStringValue: DtoValue3 + }, + SourceTargetSameObjectType: { + CtorValue: 8, + CtorValue2: 100, + IntValue: 99, + RequiredValue: 98, + StringValue: , + RenamedStringValue: , + Flattening: {}, + EnumReverseStringValue: + }, + NullableReadOnlyObjectCollection: [ + { + IntValue: 10 + }, + { + IntValue: 20 + } + ], + EnumValue: DtoValue1, + EnumName: Value10, + EnumRawValue: 20, + EnumStringValue: Value30, + EnumReverseStringValue: DtoValue3, + SubObject: { + SubIntValue: 2, + BaseIntValue: 1 + }, + DateTimeValueTargetDateOnly: 2020-01-03, + DateTimeValueTargetTimeOnly: 3:10 PM +} \ No newline at end of file diff --git a/test/Riok.Mapperly.IntegrationTests/_snapshots/NET_48/StaticMapperTest.SnapshotGeneratedSource.verified.cs b/test/Riok.Mapperly.IntegrationTests/_snapshots/NET_48/StaticMapperTest.SnapshotGeneratedSource.verified.cs new file mode 100644 index 0000000000..7342ea7a10 --- /dev/null +++ b/test/Riok.Mapperly.IntegrationTests/_snapshots/NET_48/StaticMapperTest.SnapshotGeneratedSource.verified.cs @@ -0,0 +1,368 @@ +#nullable enable +namespace Riok.Mapperly.IntegrationTests.Mapper +{ + public static partial class StaticTestMapper + { + public static partial int DirectInt(int value) + { + return value; + } + + public static partial long ImplicitCastInt(int value) + { + return (long)value; + } + + public static partial int ExplicitCastInt(uint value) + { + return (int)value; + } + + public static partial int? CastIntNullable(int value) + { + return (int?)value; + } + + public static partial System.Guid ParseableGuid(string id) + { + return System.Guid.Parse(id); + } + + public static partial int ParseableInt(string value) + { + return int.Parse(value); + } + + public static partial System.DateTime DirectDateTime(System.DateTime dateTime) + { + return dateTime; + } + + public static partial System.Collections.Generic.IEnumerable MapAllDtos(System.Collections.Generic.IEnumerable objects) + { + return System.Linq.Enumerable.Select(objects, x => MapToDtoExt(x)); + } + + public static partial Riok.Mapperly.IntegrationTests.Dto.TestObjectDto MapToDtoExt(this Riok.Mapperly.IntegrationTests.Models.TestObject src) + { + var target = new Riok.Mapperly.IntegrationTests.Dto.TestObjectDto(DirectInt(src.CtorValue), ctorValue2: DirectInt(src.CtorValue2)) + { + IntInitOnlyValue = DirectInt(src.IntInitOnlyValue), + RequiredValue = DirectInt(src.RequiredValue) + }; + if (src.NullableFlattening != null) + { + target.NullableFlatteningIdValue = CastIntNullable(src.NullableFlattening.IdValue); + } + + if (src.NestedNullable != null) + { + target.NestedNullableIntValue = DirectInt(src.NestedNullable.IntValue); + target.NestedNullable = MapToTestObjectNestedDto(src.NestedNullable); + } + + if (src.NestedNullableTargetNotNullable != null) + { + target.NestedNullableTargetNotNullable = MapToTestObjectNestedDto(src.NestedNullableTargetNotNullable); + } + + if (src.StringNullableTargetNotNullable != null) + { + target.StringNullableTargetNotNullable = src.StringNullableTargetNotNullable; + } + + if (src.RecursiveObject != null) + { + target.RecursiveObject = MapToDtoExt(src.RecursiveObject); + } + + if (src.NullableReadOnlyObjectCollection != null) + { + target.NullableReadOnlyObjectCollection = System.Linq.Enumerable.ToArray(System.Linq.Enumerable.Select(src.NullableReadOnlyObjectCollection, x => MapToTestObjectNestedDto(x))); + } + + if (src.SubObject != null) + { + target.SubObject = MapToInheritanceSubObjectDto(src.SubObject); + } + + target.IntValue = DirectInt(src.IntValue); + target.StringValue = src.StringValue; + target.FlatteningIdValue = DirectInt(src.Flattening.IdValue); + target.SourceTargetSameObjectType = src.SourceTargetSameObjectType; + target.EnumValue = (Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByValue)src.EnumValue; + target.EnumName = MapToEnumDtoByName(src.EnumName); + target.EnumRawValue = (byte)src.EnumRawValue; + target.EnumStringValue = MapToString(src.EnumStringValue); + target.EnumReverseStringValue = MapToTestEnumDtoByValue(src.EnumReverseStringValue); + target.DateTimeValueTargetDateOnly = System.DateOnly.FromDateTime(src.DateTimeValueTargetDateOnly); + target.DateTimeValueTargetTimeOnly = System.TimeOnly.FromDateTime(src.DateTimeValueTargetTimeOnly); + return target; + } + + private static partial Riok.Mapperly.IntegrationTests.Dto.TestObjectDto MapToDtoInternal(Riok.Mapperly.IntegrationTests.Models.TestObject testObject) + { + var target = new Riok.Mapperly.IntegrationTests.Dto.TestObjectDto(DirectInt(testObject.CtorValue), ctorValue2: DirectInt(testObject.CtorValue2)) + { + IntInitOnlyValue = DirectInt(testObject.IntInitOnlyValue), + RequiredValue = DirectInt(testObject.RequiredValue) + }; + if (testObject.NullableFlattening != null) + { + target.NullableFlatteningIdValue = CastIntNullable(testObject.NullableFlattening.IdValue); + } + + if (testObject.NullableUnflatteningIdValue != null) + { + target.NullableUnflattening ??= new(); + target.NullableUnflattening.IdValue = DirectInt(testObject.NullableUnflatteningIdValue.Value); + } + + if (testObject.NestedNullable != null) + { + target.NestedNullableIntValue = DirectInt(testObject.NestedNullable.IntValue); + target.NestedNullable = MapToTestObjectNestedDto(testObject.NestedNullable); + } + + if (testObject.NestedNullableTargetNotNullable != null) + { + target.NestedNullableTargetNotNullable = MapToTestObjectNestedDto(testObject.NestedNullableTargetNotNullable); + } + + if (testObject.StringNullableTargetNotNullable != null) + { + target.StringNullableTargetNotNullable = testObject.StringNullableTargetNotNullable; + } + + if (testObject.RecursiveObject != null) + { + target.RecursiveObject = MapToDtoExt(testObject.RecursiveObject); + } + + if (testObject.NullableReadOnlyObjectCollection != null) + { + target.NullableReadOnlyObjectCollection = System.Linq.Enumerable.ToArray(System.Linq.Enumerable.Select(testObject.NullableReadOnlyObjectCollection, x => MapToTestObjectNestedDto(x))); + } + + if (testObject.SubObject != null) + { + target.SubObject = MapToInheritanceSubObjectDto(testObject.SubObject); + } + + target.IntValue = DirectInt(testObject.IntValue); + target.StringValue = testObject.StringValue; + target.RenamedStringValue2 = testObject.RenamedStringValue; + target.FlatteningIdValue = DirectInt(testObject.Flattening.IdValue); + target.Unflattening.IdValue = DirectInt(testObject.UnflatteningIdValue); + target.SourceTargetSameObjectType = testObject.SourceTargetSameObjectType; + target.EnumValue = (Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByValue)testObject.EnumValue; + target.EnumName = MapToEnumDtoByName(testObject.EnumName); + target.EnumRawValue = (byte)testObject.EnumRawValue; + target.EnumStringValue = MapToString(testObject.EnumStringValue); + target.EnumReverseStringValue = MapToTestEnumDtoByValue(testObject.EnumReverseStringValue); + target.DateTimeValueTargetDateOnly = System.DateOnly.FromDateTime(testObject.DateTimeValueTargetDateOnly); + target.DateTimeValueTargetTimeOnly = System.TimeOnly.FromDateTime(testObject.DateTimeValueTargetTimeOnly); + return target; + } + + public static partial Riok.Mapperly.IntegrationTests.Models.TestObject MapFromDto(Riok.Mapperly.IntegrationTests.Dto.TestObjectDto dto) + { + var target = new Riok.Mapperly.IntegrationTests.Models.TestObject(DirectInt(dto.CtorValue), ctorValue2: DirectInt(dto.CtorValue2)) + { + IntInitOnlyValue = DirectInt(dto.IntInitOnlyValue), + RequiredValue = DirectInt(dto.RequiredValue) + }; + if (dto.NullableUnflattening != null) + { + target.NullableUnflatteningIdValue = CastIntNullable(dto.NullableUnflattening.IdValue); + } + + if (dto.NestedNullable != null) + { + target.NestedNullable = MapToTestObjectNested(dto.NestedNullable); + } + + if (dto.RecursiveObject != null) + { + target.RecursiveObject = MapFromDto(dto.RecursiveObject); + } + + if (dto.NullableReadOnlyObjectCollection != null) + { + target.NullableReadOnlyObjectCollection = MapToIReadOnlyCollection(dto.NullableReadOnlyObjectCollection); + } + + if (dto.SubObject != null) + { + target.SubObject = MapToInheritanceSubObject(dto.SubObject); + } + + target.IntValue = DirectInt(dto.IntValue); + target.StringValue = dto.StringValue; + target.UnflatteningIdValue = DirectInt(dto.Unflattening.IdValue); + target.NestedNullableTargetNotNullable = MapToTestObjectNested(dto.NestedNullableTargetNotNullable); + target.StringNullableTargetNotNullable = dto.StringNullableTargetNotNullable; + target.SourceTargetSameObjectType = dto.SourceTargetSameObjectType; + target.EnumValue = (Riok.Mapperly.IntegrationTests.Models.TestEnum)dto.EnumValue; + target.EnumName = (Riok.Mapperly.IntegrationTests.Models.TestEnum)dto.EnumName; + target.EnumRawValue = (Riok.Mapperly.IntegrationTests.Models.TestEnum)dto.EnumRawValue; + target.EnumStringValue = MapToTestEnum(dto.EnumStringValue); + target.EnumReverseStringValue = MapToString1(dto.EnumReverseStringValue); + return target; + } + + public static partial Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByName MapToEnumDtoByName(Riok.Mapperly.IntegrationTests.Models.TestEnum v) + { + return v switch + { + Riok.Mapperly.IntegrationTests.Models.TestEnum.Value10 => Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByName.Value10, + Riok.Mapperly.IntegrationTests.Models.TestEnum.Value20 => Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByName.Value20, + Riok.Mapperly.IntegrationTests.Models.TestEnum.Value30 => Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByName.Value30, + _ => throw new System.ArgumentOutOfRangeException(nameof(v), v, "The value of enum TestEnum is not supported"), + }; + } + + public static partial void UpdateDto(Riok.Mapperly.IntegrationTests.Models.TestObject source, Riok.Mapperly.IntegrationTests.Dto.TestObjectDto target) + { + if (source.NullableFlattening != null) + { + target.NullableFlatteningIdValue = CastIntNullable(source.NullableFlattening.IdValue); + } + + if (source.NestedNullable != null) + { + target.NestedNullableIntValue = DirectInt(source.NestedNullable.IntValue); + target.NestedNullable = MapToTestObjectNestedDto(source.NestedNullable); + } + + if (source.NestedNullableTargetNotNullable != null) + { + target.NestedNullableTargetNotNullable = MapToTestObjectNestedDto(source.NestedNullableTargetNotNullable); + } + + if (source.StringNullableTargetNotNullable != null) + { + target.StringNullableTargetNotNullable = source.StringNullableTargetNotNullable; + } + + if (source.RecursiveObject != null) + { + target.RecursiveObject = MapToDtoExt(source.RecursiveObject); + } + + if (source.NullableReadOnlyObjectCollection != null) + { + target.NullableReadOnlyObjectCollection = System.Linq.Enumerable.ToArray(System.Linq.Enumerable.Select(source.NullableReadOnlyObjectCollection, x => MapToTestObjectNestedDto(x))); + } + + if (source.SubObject != null) + { + target.SubObject = MapToInheritanceSubObjectDto(source.SubObject); + } + + target.CtorValue = DirectInt(source.CtorValue); + target.CtorValue2 = DirectInt(source.CtorValue2); + target.IntValue = DirectInt(source.IntValue); + target.StringValue = source.StringValue; + target.FlatteningIdValue = DirectInt(source.Flattening.IdValue); + target.SourceTargetSameObjectType = source.SourceTargetSameObjectType; + target.EnumValue = (Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByValue)source.EnumValue; + target.EnumName = MapToEnumDtoByName(source.EnumName); + target.EnumRawValue = (byte)source.EnumRawValue; + target.EnumStringValue = MapToString(source.EnumStringValue); + target.EnumReverseStringValue = MapToTestEnumDtoByValue(source.EnumReverseStringValue); + target.DateTimeValueTargetDateOnly = System.DateOnly.FromDateTime(source.DateTimeValueTargetDateOnly); + target.DateTimeValueTargetTimeOnly = System.TimeOnly.FromDateTime(source.DateTimeValueTargetTimeOnly); + } + + private static partial int PrivateDirectInt(int value) + { + return value; + } + + private static Riok.Mapperly.IntegrationTests.Dto.TestObjectNestedDto MapToTestObjectNestedDto(Riok.Mapperly.IntegrationTests.Models.TestObjectNested source) + { + var target = new Riok.Mapperly.IntegrationTests.Dto.TestObjectNestedDto(); + target.IntValue = DirectInt(source.IntValue); + return target; + } + + private static string MapToString(Riok.Mapperly.IntegrationTests.Models.TestEnum source) + { + return source switch + { + Riok.Mapperly.IntegrationTests.Models.TestEnum.Value10 => nameof(Riok.Mapperly.IntegrationTests.Models.TestEnum.Value10), + Riok.Mapperly.IntegrationTests.Models.TestEnum.Value20 => nameof(Riok.Mapperly.IntegrationTests.Models.TestEnum.Value20), + Riok.Mapperly.IntegrationTests.Models.TestEnum.Value30 => nameof(Riok.Mapperly.IntegrationTests.Models.TestEnum.Value30), + _ => source.ToString(), + }; + } + + private static Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByValue MapToTestEnumDtoByValue(string source) + { + return source switch + { + nameof(Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByValue.DtoValue1) => Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByValue.DtoValue1, + nameof(Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByValue.DtoValue2) => Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByValue.DtoValue2, + nameof(Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByValue.DtoValue3) => Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByValue.DtoValue3, + _ => (Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByValue)System.Enum.Parse(typeof(Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByValue), source, false), + }; + } + + private static Riok.Mapperly.IntegrationTests.Dto.InheritanceSubObjectDto MapToInheritanceSubObjectDto(Riok.Mapperly.IntegrationTests.Models.InheritanceSubObject source) + { + var target = new Riok.Mapperly.IntegrationTests.Dto.InheritanceSubObjectDto(); + target.SubIntValue = DirectInt(source.SubIntValue); + target.BaseIntValue = DirectInt(source.BaseIntValue); + return target; + } + + private static Riok.Mapperly.IntegrationTests.Models.TestObjectNested MapToTestObjectNested(Riok.Mapperly.IntegrationTests.Dto.TestObjectNestedDto source) + { + var target = new Riok.Mapperly.IntegrationTests.Models.TestObjectNested(); + target.IntValue = DirectInt(source.IntValue); + return target; + } + + private static System.Collections.Generic.IReadOnlyCollection MapToIReadOnlyCollection(Riok.Mapperly.IntegrationTests.Dto.TestObjectNestedDto[] source) + { + var target = new Riok.Mapperly.IntegrationTests.Models.TestObjectNested[source.Length]; + for (var i = 0; i < source.Length; i++) + { + target[i] = MapToTestObjectNested(source[i]); + } + + return target; + } + + private static Riok.Mapperly.IntegrationTests.Models.TestEnum MapToTestEnum(string source) + { + return source switch + { + nameof(Riok.Mapperly.IntegrationTests.Models.TestEnum.Value10) => Riok.Mapperly.IntegrationTests.Models.TestEnum.Value10, + nameof(Riok.Mapperly.IntegrationTests.Models.TestEnum.Value20) => Riok.Mapperly.IntegrationTests.Models.TestEnum.Value20, + nameof(Riok.Mapperly.IntegrationTests.Models.TestEnum.Value30) => Riok.Mapperly.IntegrationTests.Models.TestEnum.Value30, + _ => (Riok.Mapperly.IntegrationTests.Models.TestEnum)System.Enum.Parse(typeof(Riok.Mapperly.IntegrationTests.Models.TestEnum), source, false), + }; + } + + private static string MapToString1(Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByValue source) + { + return source switch + { + Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByValue.DtoValue1 => nameof(Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByValue.DtoValue1), + Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByValue.DtoValue2 => nameof(Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByValue.DtoValue2), + Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByValue.DtoValue3 => nameof(Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByValue.DtoValue3), + _ => source.ToString(), + }; + } + + private static Riok.Mapperly.IntegrationTests.Models.InheritanceSubObject MapToInheritanceSubObject(Riok.Mapperly.IntegrationTests.Dto.InheritanceSubObjectDto source) + { + var target = new Riok.Mapperly.IntegrationTests.Models.InheritanceSubObject(); + target.SubIntValue = DirectInt(source.SubIntValue); + target.BaseIntValue = DirectInt(source.BaseIntValue); + return target; + } + } +} diff --git a/test/Riok.Mapperly.IntegrationTests/_snapshots/Roslyn_4_4_OR_LOWER/MapperTest.SnapshotGeneratedSource.verified.cs b/test/Riok.Mapperly.IntegrationTests/_snapshots/Roslyn_4_4_OR_LOWER/MapperTest.SnapshotGeneratedSource.verified.cs index 3384a2ceed..c597dc4731 100644 --- a/test/Riok.Mapperly.IntegrationTests/_snapshots/Roslyn_4_4_OR_LOWER/MapperTest.SnapshotGeneratedSource.verified.cs +++ b/test/Riok.Mapperly.IntegrationTests/_snapshots/Roslyn_4_4_OR_LOWER/MapperTest.SnapshotGeneratedSource.verified.cs @@ -208,7 +208,6 @@ public partial void UpdateDto(Riok.Mapperly.IntegrationTests.Models.TestObject s target.EnumRawValue = (byte)source.EnumRawValue; target.EnumStringValue = MapToString(source.EnumStringValue); target.EnumReverseStringValue = MapToTestEnumDtoByValue(source.EnumReverseStringValue); - target.IgnoredStringValue = source.IgnoredStringValue; target.DateTimeValueTargetDateOnly = System.DateOnly.FromDateTime(source.DateTimeValueTargetDateOnly); target.DateTimeValueTargetTimeOnly = System.TimeOnly.FromDateTime(source.DateTimeValueTargetTimeOnly); } @@ -243,7 +242,7 @@ private Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByValue MapToTestEnumDtoBy nameof(Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByValue.DtoValue1) => Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByValue.DtoValue1, nameof(Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByValue.DtoValue2) => Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByValue.DtoValue2, nameof(Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByValue.DtoValue3) => Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByValue.DtoValue3, - _ => (Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByValue)System.Enum.Parse(typeof(Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByValue), source, false), + _ => System.Enum.Parse(source, false), }; } @@ -280,7 +279,7 @@ private Riok.Mapperly.IntegrationTests.Models.TestEnum MapToTestEnum(string sour nameof(Riok.Mapperly.IntegrationTests.Models.TestEnum.Value10) => Riok.Mapperly.IntegrationTests.Models.TestEnum.Value10, nameof(Riok.Mapperly.IntegrationTests.Models.TestEnum.Value20) => Riok.Mapperly.IntegrationTests.Models.TestEnum.Value20, nameof(Riok.Mapperly.IntegrationTests.Models.TestEnum.Value30) => Riok.Mapperly.IntegrationTests.Models.TestEnum.Value30, - _ => (Riok.Mapperly.IntegrationTests.Models.TestEnum)System.Enum.Parse(typeof(Riok.Mapperly.IntegrationTests.Models.TestEnum), source, false), + _ => System.Enum.Parse(source, false), }; } @@ -303,4 +302,4 @@ private Riok.Mapperly.IntegrationTests.Models.InheritanceSubObject MapToInherita return target; } } -} +} \ No newline at end of file diff --git a/test/Riok.Mapperly.IntegrationTests/_snapshots/Roslyn_4_4_OR_LOWER/ProjectionMapperTest.SnapshotGeneratedSource.verified.cs b/test/Riok.Mapperly.IntegrationTests/_snapshots/Roslyn_4_4_OR_LOWER/ProjectionMapperTest.SnapshotGeneratedSource.verified.cs new file mode 100644 index 0000000000..65770dd923 --- /dev/null +++ b/test/Riok.Mapperly.IntegrationTests/_snapshots/Roslyn_4_4_OR_LOWER/ProjectionMapperTest.SnapshotGeneratedSource.verified.cs @@ -0,0 +1,101 @@ +#nullable enable +namespace Riok.Mapperly.IntegrationTests.Mapper +{ + public static partial class ProjectionMapper + { + public static partial System.Linq.IQueryable ProjectToDto(this System.Linq.IQueryable q) + { +#nullable disable + return System.Linq.Queryable.Select(q, x => new Riok.Mapperly.IntegrationTests.Dto.TestObjectDtoProjection(x.CtorValue) + {IntValue = x.IntValue, IntInitOnlyValue = x.IntInitOnlyValue, RequiredValue = x.RequiredValue, StringValue = x.StringValue, RenamedStringValue2 = x.RenamedStringValue, FlatteningIdValue = x.Flattening.IdValue, NullableFlatteningIdValue = x.NullableFlattening != null ? x.NullableFlattening.IdValue : default, NestedNullableIntValue = x.NestedNullable != null ? x.NestedNullable.IntValue : default, NestedNullable = x.NestedNullable != null ? new Riok.Mapperly.IntegrationTests.Dto.TestObjectNestedDto() + {IntValue = x.NestedNullable.IntValue} : default, NestedNullableTargetNotNullable = x.NestedNullableTargetNotNullable != null ? new Riok.Mapperly.IntegrationTests.Dto.TestObjectNestedDto() + {IntValue = x.NestedNullableTargetNotNullable.IntValue} : new Riok.Mapperly.IntegrationTests.Dto.TestObjectNestedDto(), StringNullableTargetNotNullable = x.StringNullableTargetNotNullable ?? "", SourceTargetSameObjectType = x.SourceTargetSameObjectType, NullableReadOnlyObjectCollection = x.NullableReadOnlyObjectCollection != null ? System.Linq.Enumerable.ToArray(System.Linq.Enumerable.Select(x.NullableReadOnlyObjectCollection, x1 => new Riok.Mapperly.IntegrationTests.Dto.TestObjectNestedDto() + {IntValue = x1.IntValue})) : default, EnumValue = (Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByValue)x.EnumValue, EnumName = (Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByName)x.EnumName, EnumRawValue = (byte)x.EnumRawValue, EnumStringValue = (string)x.EnumStringValue.ToString(), EnumReverseStringValue = System.Enum.Parse(x.EnumReverseStringValue, false), SubObject = x.SubObject != null ? new Riok.Mapperly.IntegrationTests.Dto.InheritanceSubObjectDto() + {SubIntValue = x.SubObject.SubIntValue, BaseIntValue = x.SubObject.BaseIntValue} : default, DateTimeValueTargetDateOnly = System.DateOnly.FromDateTime(x.DateTimeValueTargetDateOnly), DateTimeValueTargetTimeOnly = System.TimeOnly.FromDateTime(x.DateTimeValueTargetTimeOnly)}); +#nullable enable + } + + private static partial Riok.Mapperly.IntegrationTests.Dto.TestObjectDtoProjection ProjectToDto(this Riok.Mapperly.IntegrationTests.Models.TestObjectProjection testObject) + { + var target = new Riok.Mapperly.IntegrationTests.Dto.TestObjectDtoProjection(testObject.CtorValue) + {IntInitOnlyValue = testObject.IntInitOnlyValue, RequiredValue = testObject.RequiredValue}; + if (testObject.NestedNullable != null) + { + target.NestedNullableIntValue = testObject.NestedNullable.IntValue; + target.NestedNullable = MapToTestObjectNestedDto(testObject.NestedNullable); + } + + if (testObject.NestedNullableTargetNotNullable != null) + { + target.NestedNullableTargetNotNullable = MapToTestObjectNestedDto(testObject.NestedNullableTargetNotNullable); + } + + if (testObject.StringNullableTargetNotNullable != null) + { + target.StringNullableTargetNotNullable = testObject.StringNullableTargetNotNullable; + } + + if (testObject.NullableReadOnlyObjectCollection != null) + { + target.NullableReadOnlyObjectCollection = System.Linq.Enumerable.ToArray(System.Linq.Enumerable.Select(testObject.NullableReadOnlyObjectCollection, x => MapToTestObjectNestedDto(x))); + } + + if (testObject.SubObject != null) + { + target.SubObject = MapToInheritanceSubObjectDto(testObject.SubObject); + } + + target.IntValue = testObject.IntValue; + target.StringValue = testObject.StringValue; + target.RenamedStringValue2 = testObject.RenamedStringValue; + target.FlatteningIdValue = testObject.Flattening.IdValue; + target.NullableFlatteningIdValue = testObject.NullableFlattening?.IdValue; + target.SourceTargetSameObjectType = testObject.SourceTargetSameObjectType; + target.EnumValue = (Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByValue)testObject.EnumValue; + target.EnumName = (Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByName)testObject.EnumName; + target.EnumRawValue = (byte)testObject.EnumRawValue; + target.EnumStringValue = MapToString(testObject.EnumStringValue); + target.EnumReverseStringValue = MapToTestEnumDtoByValue(testObject.EnumReverseStringValue); + target.DateTimeValueTargetDateOnly = System.DateOnly.FromDateTime(testObject.DateTimeValueTargetDateOnly); + target.DateTimeValueTargetTimeOnly = System.TimeOnly.FromDateTime(testObject.DateTimeValueTargetTimeOnly); + return target; + } + + private static Riok.Mapperly.IntegrationTests.Dto.TestObjectNestedDto MapToTestObjectNestedDto(Riok.Mapperly.IntegrationTests.Models.TestObjectNested source) + { + var target = new Riok.Mapperly.IntegrationTests.Dto.TestObjectNestedDto(); + target.IntValue = source.IntValue; + return target; + } + + private static string MapToString(Riok.Mapperly.IntegrationTests.Models.TestEnum source) + { + return source switch + { + Riok.Mapperly.IntegrationTests.Models.TestEnum.Value10 => nameof(Riok.Mapperly.IntegrationTests.Models.TestEnum.Value10), + Riok.Mapperly.IntegrationTests.Models.TestEnum.Value20 => nameof(Riok.Mapperly.IntegrationTests.Models.TestEnum.Value20), + Riok.Mapperly.IntegrationTests.Models.TestEnum.Value30 => nameof(Riok.Mapperly.IntegrationTests.Models.TestEnum.Value30), + _ => source.ToString(), + }; + } + + private static Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByValue MapToTestEnumDtoByValue(string source) + { + return source switch + { + nameof(Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByValue.DtoValue1) => Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByValue.DtoValue1, + nameof(Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByValue.DtoValue2) => Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByValue.DtoValue2, + nameof(Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByValue.DtoValue3) => Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByValue.DtoValue3, + _ => System.Enum.Parse(source, false), + }; + } + + private static Riok.Mapperly.IntegrationTests.Dto.InheritanceSubObjectDto MapToInheritanceSubObjectDto(Riok.Mapperly.IntegrationTests.Models.InheritanceSubObject source) + { + var target = new Riok.Mapperly.IntegrationTests.Dto.InheritanceSubObjectDto(); + target.SubIntValue = source.SubIntValue; + target.BaseIntValue = source.BaseIntValue; + return target; + } + } +} diff --git a/test/Riok.Mapperly.IntegrationTests/_snapshots/Roslyn_4_4_OR_LOWER/StaticMapperTest.RunExtensionMappingShouldWork.verified.txt b/test/Riok.Mapperly.IntegrationTests/_snapshots/Roslyn_4_4_OR_LOWER/StaticMapperTest.RunExtensionMappingShouldWork.verified.txt index 3d4586103b..9586df48d2 100644 --- a/test/Riok.Mapperly.IntegrationTests/_snapshots/Roslyn_4_4_OR_LOWER/StaticMapperTest.RunExtensionMappingShouldWork.verified.txt +++ b/test/Riok.Mapperly.IntegrationTests/_snapshots/Roslyn_4_4_OR_LOWER/StaticMapperTest.RunExtensionMappingShouldWork.verified.txt @@ -56,7 +56,6 @@ SubIntValue: 2, BaseIntValue: 1 }, - IgnoredStringValue: ignored, DateTimeValueTargetDateOnly: 2020-01-03, DateTimeValueTargetTimeOnly: 3:10 PM } \ No newline at end of file diff --git a/test/Riok.Mapperly.IntegrationTests/_snapshots/Roslyn_4_4_OR_LOWER/StaticMapperTest.SnapshotGeneratedSource.verified.cs b/test/Riok.Mapperly.IntegrationTests/_snapshots/Roslyn_4_4_OR_LOWER/StaticMapperTest.SnapshotGeneratedSource.verified.cs index 648cce5197..435c70d541 100644 --- a/test/Riok.Mapperly.IntegrationTests/_snapshots/Roslyn_4_4_OR_LOWER/StaticMapperTest.SnapshotGeneratedSource.verified.cs +++ b/test/Riok.Mapperly.IntegrationTests/_snapshots/Roslyn_4_4_OR_LOWER/StaticMapperTest.SnapshotGeneratedSource.verified.cs @@ -92,7 +92,6 @@ public static partial Riok.Mapperly.IntegrationTests.Dto.TestObjectDto MapToDtoE target.EnumRawValue = (byte)src.EnumRawValue; target.EnumStringValue = MapToString(src.EnumStringValue); target.EnumReverseStringValue = MapToTestEnumDtoByValue(src.EnumReverseStringValue); - target.IgnoredStringValue = src.IgnoredStringValue; target.DateTimeValueTargetDateOnly = System.DateOnly.FromDateTime(src.DateTimeValueTargetDateOnly); target.DateTimeValueTargetTimeOnly = System.TimeOnly.FromDateTime(src.DateTimeValueTargetTimeOnly); return target; @@ -263,7 +262,6 @@ public static partial void UpdateDto(Riok.Mapperly.IntegrationTests.Models.TestO target.EnumRawValue = (byte)source.EnumRawValue; target.EnumStringValue = MapToString(source.EnumStringValue); target.EnumReverseStringValue = MapToTestEnumDtoByValue(source.EnumReverseStringValue); - target.IgnoredStringValue = source.IgnoredStringValue; target.DateTimeValueTargetDateOnly = System.DateOnly.FromDateTime(source.DateTimeValueTargetDateOnly); target.DateTimeValueTargetTimeOnly = System.TimeOnly.FromDateTime(source.DateTimeValueTargetTimeOnly); } @@ -298,7 +296,7 @@ private static Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByValue MapToTestEn nameof(Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByValue.DtoValue1) => Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByValue.DtoValue1, nameof(Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByValue.DtoValue2) => Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByValue.DtoValue2, nameof(Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByValue.DtoValue3) => Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByValue.DtoValue3, - _ => (Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByValue)System.Enum.Parse(typeof(Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByValue), source, false), + _ => System.Enum.Parse(source, false), }; } @@ -335,7 +333,7 @@ private static Riok.Mapperly.IntegrationTests.Models.TestEnum MapToTestEnum(stri nameof(Riok.Mapperly.IntegrationTests.Models.TestEnum.Value10) => Riok.Mapperly.IntegrationTests.Models.TestEnum.Value10, nameof(Riok.Mapperly.IntegrationTests.Models.TestEnum.Value20) => Riok.Mapperly.IntegrationTests.Models.TestEnum.Value20, nameof(Riok.Mapperly.IntegrationTests.Models.TestEnum.Value30) => Riok.Mapperly.IntegrationTests.Models.TestEnum.Value30, - _ => (Riok.Mapperly.IntegrationTests.Models.TestEnum)System.Enum.Parse(typeof(Riok.Mapperly.IntegrationTests.Models.TestEnum), source, false), + _ => System.Enum.Parse(source, false), }; } diff --git a/test/Riok.Mapperly.IntegrationTests/_snapshots/Roslyn_4_5/MapperTest.SnapshotGeneratedSource.verified.cs b/test/Riok.Mapperly.IntegrationTests/_snapshots/Roslyn_4_5/MapperTest.SnapshotGeneratedSource.verified.cs index 94490f400e..307b8f8b73 100644 --- a/test/Riok.Mapperly.IntegrationTests/_snapshots/Roslyn_4_5/MapperTest.SnapshotGeneratedSource.verified.cs +++ b/test/Riok.Mapperly.IntegrationTests/_snapshots/Roslyn_4_5/MapperTest.SnapshotGeneratedSource.verified.cs @@ -214,7 +214,6 @@ public partial void UpdateDto(Riok.Mapperly.IntegrationTests.Models.TestObject s target.EnumRawValue = (byte)source.EnumRawValue; target.EnumStringValue = MapToString(source.EnumStringValue); target.EnumReverseStringValue = MapToTestEnumDtoByValue(source.EnumReverseStringValue); - target.IgnoredStringValue = source.IgnoredStringValue; target.DateTimeValueTargetDateOnly = System.DateOnly.FromDateTime(source.DateTimeValueTargetDateOnly); target.DateTimeValueTargetTimeOnly = System.TimeOnly.FromDateTime(source.DateTimeValueTargetTimeOnly); } @@ -249,7 +248,7 @@ private Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByValue MapToTestEnumDtoBy nameof(Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByValue.DtoValue1) => Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByValue.DtoValue1, nameof(Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByValue.DtoValue2) => Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByValue.DtoValue2, nameof(Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByValue.DtoValue3) => Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByValue.DtoValue3, - _ => (Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByValue)System.Enum.Parse(typeof(Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByValue), source, false), + _ => System.Enum.Parse(source, false), }; } @@ -286,7 +285,7 @@ private Riok.Mapperly.IntegrationTests.Models.TestEnum MapToTestEnum(string sour nameof(Riok.Mapperly.IntegrationTests.Models.TestEnum.Value10) => Riok.Mapperly.IntegrationTests.Models.TestEnum.Value10, nameof(Riok.Mapperly.IntegrationTests.Models.TestEnum.Value20) => Riok.Mapperly.IntegrationTests.Models.TestEnum.Value20, nameof(Riok.Mapperly.IntegrationTests.Models.TestEnum.Value30) => Riok.Mapperly.IntegrationTests.Models.TestEnum.Value30, - _ => (Riok.Mapperly.IntegrationTests.Models.TestEnum)System.Enum.Parse(typeof(Riok.Mapperly.IntegrationTests.Models.TestEnum), source, false), + _ => System.Enum.Parse(source, false), }; } diff --git a/test/Riok.Mapperly.IntegrationTests/_snapshots/Roslyn_4_5/ProjectionMapperTest.SnapshotGeneratedSource.verified.cs b/test/Riok.Mapperly.IntegrationTests/_snapshots/Roslyn_4_5/ProjectionMapperTest.SnapshotGeneratedSource.verified.cs new file mode 100644 index 0000000000..8427505b31 --- /dev/null +++ b/test/Riok.Mapperly.IntegrationTests/_snapshots/Roslyn_4_5/ProjectionMapperTest.SnapshotGeneratedSource.verified.cs @@ -0,0 +1,99 @@ +#nullable enable +namespace Riok.Mapperly.IntegrationTests.Mapper +{ + public static partial class ProjectionMapper + { + public static partial System.Linq.IQueryable ProjectToDto(this System.Linq.IQueryable q) + { +#nullable disable + return System.Linq.Queryable.Select(q, x => new Riok.Mapperly.IntegrationTests.Dto.TestObjectDtoProjection(x.CtorValue) { IntValue = x.IntValue, IntInitOnlyValue = x.IntInitOnlyValue, RequiredValue = x.RequiredValue, StringValue = x.StringValue, RenamedStringValue2 = x.RenamedStringValue, FlatteningIdValue = x.Flattening.IdValue, NullableFlatteningIdValue = x.NullableFlattening != null ? x.NullableFlattening.IdValue : default, NestedNullableIntValue = x.NestedNullable != null ? x.NestedNullable.IntValue : default, NestedNullable = x.NestedNullable != null ? new Riok.Mapperly.IntegrationTests.Dto.TestObjectNestedDto() { IntValue = x.NestedNullable.IntValue } : default, NestedNullableTargetNotNullable = x.NestedNullableTargetNotNullable != null ? new Riok.Mapperly.IntegrationTests.Dto.TestObjectNestedDto() { IntValue = x.NestedNullableTargetNotNullable.IntValue } : new Riok.Mapperly.IntegrationTests.Dto.TestObjectNestedDto(), StringNullableTargetNotNullable = x.StringNullableTargetNotNullable ?? "", SourceTargetSameObjectType = x.SourceTargetSameObjectType, NullableReadOnlyObjectCollection = x.NullableReadOnlyObjectCollection != null ? System.Linq.Enumerable.ToArray(System.Linq.Enumerable.Select(x.NullableReadOnlyObjectCollection, x1 => new Riok.Mapperly.IntegrationTests.Dto.TestObjectNestedDto() { IntValue = x1.IntValue })) : default, EnumValue = (Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByValue)x.EnumValue, EnumName = (Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByName)x.EnumName, EnumRawValue = (byte)x.EnumRawValue, EnumStringValue = (string)x.EnumStringValue.ToString(), EnumReverseStringValue = System.Enum.Parse(x.EnumReverseStringValue, false), SubObject = x.SubObject != null ? new Riok.Mapperly.IntegrationTests.Dto.InheritanceSubObjectDto() { SubIntValue = x.SubObject.SubIntValue, BaseIntValue = x.SubObject.BaseIntValue } : default, DateTimeValueTargetDateOnly = System.DateOnly.FromDateTime(x.DateTimeValueTargetDateOnly), DateTimeValueTargetTimeOnly = System.TimeOnly.FromDateTime(x.DateTimeValueTargetTimeOnly) }); +#nullable enable + } + + private static partial Riok.Mapperly.IntegrationTests.Dto.TestObjectDtoProjection ProjectToDto(this Riok.Mapperly.IntegrationTests.Models.TestObjectProjection testObject) + { + var target = new Riok.Mapperly.IntegrationTests.Dto.TestObjectDtoProjection(testObject.CtorValue) + { + IntInitOnlyValue = testObject.IntInitOnlyValue, + RequiredValue = testObject.RequiredValue + }; + if (testObject.NestedNullable != null) + { + target.NestedNullableIntValue = testObject.NestedNullable.IntValue; + target.NestedNullable = MapToTestObjectNestedDto(testObject.NestedNullable); + } + + if (testObject.NestedNullableTargetNotNullable != null) + { + target.NestedNullableTargetNotNullable = MapToTestObjectNestedDto(testObject.NestedNullableTargetNotNullable); + } + + if (testObject.StringNullableTargetNotNullable != null) + { + target.StringNullableTargetNotNullable = testObject.StringNullableTargetNotNullable; + } + + if (testObject.NullableReadOnlyObjectCollection != null) + { + target.NullableReadOnlyObjectCollection = System.Linq.Enumerable.ToArray(System.Linq.Enumerable.Select(testObject.NullableReadOnlyObjectCollection, x => MapToTestObjectNestedDto(x))); + } + + if (testObject.SubObject != null) + { + target.SubObject = MapToInheritanceSubObjectDto(testObject.SubObject); + } + + target.IntValue = testObject.IntValue; + target.StringValue = testObject.StringValue; + target.RenamedStringValue2 = testObject.RenamedStringValue; + target.FlatteningIdValue = testObject.Flattening.IdValue; + target.NullableFlatteningIdValue = testObject.NullableFlattening?.IdValue; + target.SourceTargetSameObjectType = testObject.SourceTargetSameObjectType; + target.EnumValue = (Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByValue)testObject.EnumValue; + target.EnumName = (Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByName)testObject.EnumName; + target.EnumRawValue = (byte)testObject.EnumRawValue; + target.EnumStringValue = MapToString(testObject.EnumStringValue); + target.EnumReverseStringValue = MapToTestEnumDtoByValue(testObject.EnumReverseStringValue); + target.DateTimeValueTargetDateOnly = System.DateOnly.FromDateTime(testObject.DateTimeValueTargetDateOnly); + target.DateTimeValueTargetTimeOnly = System.TimeOnly.FromDateTime(testObject.DateTimeValueTargetTimeOnly); + return target; + } + + private static Riok.Mapperly.IntegrationTests.Dto.TestObjectNestedDto MapToTestObjectNestedDto(Riok.Mapperly.IntegrationTests.Models.TestObjectNested source) + { + var target = new Riok.Mapperly.IntegrationTests.Dto.TestObjectNestedDto(); + target.IntValue = source.IntValue; + return target; + } + + private static string MapToString(Riok.Mapperly.IntegrationTests.Models.TestEnum source) + { + return source switch + { + Riok.Mapperly.IntegrationTests.Models.TestEnum.Value10 => nameof(Riok.Mapperly.IntegrationTests.Models.TestEnum.Value10), + Riok.Mapperly.IntegrationTests.Models.TestEnum.Value20 => nameof(Riok.Mapperly.IntegrationTests.Models.TestEnum.Value20), + Riok.Mapperly.IntegrationTests.Models.TestEnum.Value30 => nameof(Riok.Mapperly.IntegrationTests.Models.TestEnum.Value30), + _ => source.ToString(), + }; + } + + private static Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByValue MapToTestEnumDtoByValue(string source) + { + return source switch + { + nameof(Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByValue.DtoValue1) => Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByValue.DtoValue1, + nameof(Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByValue.DtoValue2) => Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByValue.DtoValue2, + nameof(Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByValue.DtoValue3) => Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByValue.DtoValue3, + _ => System.Enum.Parse(source, false), + }; + } + + private static Riok.Mapperly.IntegrationTests.Dto.InheritanceSubObjectDto MapToInheritanceSubObjectDto(Riok.Mapperly.IntegrationTests.Models.InheritanceSubObject source) + { + var target = new Riok.Mapperly.IntegrationTests.Dto.InheritanceSubObjectDto(); + target.SubIntValue = source.SubIntValue; + target.BaseIntValue = source.BaseIntValue; + return target; + } + } +} \ No newline at end of file diff --git a/test/Riok.Mapperly.IntegrationTests/_snapshots/Roslyn_4_5/StaticMapperTest.RunExtensionMappingShouldWork.verified.txt b/test/Riok.Mapperly.IntegrationTests/_snapshots/Roslyn_4_5/StaticMapperTest.RunExtensionMappingShouldWork.verified.txt index 3d4586103b..9586df48d2 100644 --- a/test/Riok.Mapperly.IntegrationTests/_snapshots/Roslyn_4_5/StaticMapperTest.RunExtensionMappingShouldWork.verified.txt +++ b/test/Riok.Mapperly.IntegrationTests/_snapshots/Roslyn_4_5/StaticMapperTest.RunExtensionMappingShouldWork.verified.txt @@ -56,7 +56,6 @@ SubIntValue: 2, BaseIntValue: 1 }, - IgnoredStringValue: ignored, DateTimeValueTargetDateOnly: 2020-01-03, DateTimeValueTargetTimeOnly: 3:10 PM } \ No newline at end of file diff --git a/test/Riok.Mapperly.IntegrationTests/_snapshots/Roslyn_4_5/StaticMapperTest.SnapshotGeneratedSource.verified.cs b/test/Riok.Mapperly.IntegrationTests/_snapshots/Roslyn_4_5/StaticMapperTest.SnapshotGeneratedSource.verified.cs index ed18af272b..868b553483 100644 --- a/test/Riok.Mapperly.IntegrationTests/_snapshots/Roslyn_4_5/StaticMapperTest.SnapshotGeneratedSource.verified.cs +++ b/test/Riok.Mapperly.IntegrationTests/_snapshots/Roslyn_4_5/StaticMapperTest.SnapshotGeneratedSource.verified.cs @@ -95,7 +95,6 @@ public static partial Riok.Mapperly.IntegrationTests.Dto.TestObjectDto MapToDtoE target.EnumRawValue = (byte)src.EnumRawValue; target.EnumStringValue = MapToString(src.EnumStringValue); target.EnumReverseStringValue = MapToTestEnumDtoByValue(src.EnumReverseStringValue); - target.IgnoredStringValue = src.IgnoredStringValue; target.DateTimeValueTargetDateOnly = System.DateOnly.FromDateTime(src.DateTimeValueTargetDateOnly); target.DateTimeValueTargetTimeOnly = System.TimeOnly.FromDateTime(src.DateTimeValueTargetTimeOnly); return target; @@ -272,7 +271,6 @@ public static partial void UpdateDto(Riok.Mapperly.IntegrationTests.Models.TestO target.EnumRawValue = (byte)source.EnumRawValue; target.EnumStringValue = MapToString(source.EnumStringValue); target.EnumReverseStringValue = MapToTestEnumDtoByValue(source.EnumReverseStringValue); - target.IgnoredStringValue = source.IgnoredStringValue; target.DateTimeValueTargetDateOnly = System.DateOnly.FromDateTime(source.DateTimeValueTargetDateOnly); target.DateTimeValueTargetTimeOnly = System.TimeOnly.FromDateTime(source.DateTimeValueTargetTimeOnly); } @@ -307,7 +305,7 @@ private static Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByValue MapToTestEn nameof(Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByValue.DtoValue1) => Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByValue.DtoValue1, nameof(Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByValue.DtoValue2) => Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByValue.DtoValue2, nameof(Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByValue.DtoValue3) => Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByValue.DtoValue3, - _ => (Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByValue)System.Enum.Parse(typeof(Riok.Mapperly.IntegrationTests.Dto.TestEnumDtoByValue), source, false), + _ => System.Enum.Parse(source, false), }; } @@ -344,7 +342,7 @@ private static Riok.Mapperly.IntegrationTests.Models.TestEnum MapToTestEnum(stri nameof(Riok.Mapperly.IntegrationTests.Models.TestEnum.Value10) => Riok.Mapperly.IntegrationTests.Models.TestEnum.Value10, nameof(Riok.Mapperly.IntegrationTests.Models.TestEnum.Value20) => Riok.Mapperly.IntegrationTests.Models.TestEnum.Value20, nameof(Riok.Mapperly.IntegrationTests.Models.TestEnum.Value30) => Riok.Mapperly.IntegrationTests.Models.TestEnum.Value30, - _ => (Riok.Mapperly.IntegrationTests.Models.TestEnum)System.Enum.Parse(typeof(Riok.Mapperly.IntegrationTests.Models.TestEnum), source, false), + _ => System.Enum.Parse(source, false), }; } diff --git a/test/Riok.Mapperly.Tests/Mapping/DictionaryTest.cs b/test/Riok.Mapperly.Tests/Mapping/DictionaryTest.cs index ec66d8b042..beb61dc1b1 100644 --- a/test/Riok.Mapperly.Tests/Mapping/DictionaryTest.cs +++ b/test/Riok.Mapperly.Tests/Mapping/DictionaryTest.cs @@ -1,3 +1,4 @@ +using Riok.Mapperly.Abstractions; using Riok.Mapperly.Diagnostics; namespace Riok.Mapperly.Tests.Mapping; @@ -266,4 +267,16 @@ public void ReadOnlyDictionaryToReadOnlyDictionaryExistingInstanceShouldIgnore() return target; """); } + + [Fact] + public void DictionaryMappingDisabledShouldDiagnostic() + { + var source = TestSourceBuilder.Mapping( + "Dictionary", + "Dictionary", + TestSourceBuilderOptions.WithDisabledMappingConversion(MappingConversionType.Dictionary)); + TestHelper.GenerateMapper(source, TestHelperOptions.AllowDiagnostics) + .Should() + .HaveDiagnostics(); + } } diff --git a/test/Riok.Mapperly.Tests/Mapping/EnumTest.cs b/test/Riok.Mapperly.Tests/Mapping/EnumTest.cs index 17699a74c4..848916f8f0 100644 --- a/test/Riok.Mapperly.Tests/Mapping/EnumTest.cs +++ b/test/Riok.Mapperly.Tests/Mapping/EnumTest.cs @@ -241,7 +241,7 @@ public void StringToEnumShouldSwitchIgnoreCase() { } s when s.Equals(nameof(E1.A), System.StringComparison.OrdinalIgnoreCase) => E1.A, { } s when s.Equals(nameof(E1.B), System.StringComparison.OrdinalIgnoreCase) => E1.B, { } s when s.Equals(nameof(E1.C), System.StringComparison.OrdinalIgnoreCase) => E1.C, - _ => (E1)System.Enum.Parse(typeof(E1), source, true), + _ => System.Enum.Parse(source, true), }; """); } @@ -261,7 +261,7 @@ public void StringToEnumShouldSwitch() nameof(E1.A) => E1.A, nameof(E1.B) => E1.B, nameof(E1.C) => E1.C, - _ => (E1)System.Enum.Parse(typeof(E1), source, false), + _ => System.Enum.Parse(source, false), }; """); } diff --git a/test/Riok.Mapperly.Tests/Mapping/EnumerableTest.cs b/test/Riok.Mapperly.Tests/Mapping/EnumerableTest.cs index 5139251ca0..fb804586a6 100644 --- a/test/Riok.Mapperly.Tests/Mapping/EnumerableTest.cs +++ b/test/Riok.Mapperly.Tests/Mapping/EnumerableTest.cs @@ -1,3 +1,6 @@ +using Riok.Mapperly.Abstractions; +using Riok.Mapperly.Diagnostics; + namespace Riok.Mapperly.Tests.Mapping; [UsesVerify] @@ -567,4 +570,16 @@ public Task MapToReadOnlyCollectionProperty() return TestHelper.VerifyGenerator(source); } + + [Fact] + public void EnumerableMappingDisabledShouldDiagnostic() + { + var source = TestSourceBuilder.Mapping( + "IEnumerable", + "IEnumerable", + TestSourceBuilderOptions.WithDisabledMappingConversion(MappingConversionType.Enumerable)); + TestHelper.GenerateMapper(source, TestHelperOptions.AllowDiagnostics) + .Should() + .HaveDiagnostic(new(DiagnosticDescriptors.CouldNotCreateMapping)); + } } diff --git a/test/Riok.Mapperly.Tests/Mapping/ObjectPropertyConstructorResolverTest.cs b/test/Riok.Mapperly.Tests/Mapping/ObjectPropertyConstructorResolverTest.cs index 0bcd1992ed..924a021054 100644 --- a/test/Riok.Mapperly.Tests/Mapping/ObjectPropertyConstructorResolverTest.cs +++ b/test/Riok.Mapperly.Tests/Mapping/ObjectPropertyConstructorResolverTest.cs @@ -384,7 +384,7 @@ public void RecordToRecordNonDirectAssignmentNullHandling() .Should() .HaveSingleMethodBody( """ - var target = new B(source.Value == null ? throw new System.ArgumentNullException(nameof(source.Value.Value)) : (double)source.Value.Value); + var target = new B(source.Value != null ? (double)source.Value.Value : throw new System.ArgumentNullException(nameof(source.Value.Value))); return target; """); } @@ -403,7 +403,7 @@ public void RecordToRecordFlattenedNonDirectAssignmentNullHandling() .Should() .HaveSingleMethodBody( """ - var target = new B(source.Nested == null ? throw new System.ArgumentNullException(nameof(source.Nested.Value)) : (double)source.Nested.Value); + var target = new B(source.Nested != null ? (double)source.Nested.Value : throw new System.ArgumentNullException(nameof(source.Nested.Value))); return target; """); } @@ -422,7 +422,7 @@ public void CanResolveToRecordConstructorWithMapPropertyAttribute() .Should() .HaveSingleMethodBody( """ - var target = new B(a.Id ?? default, a.F); + var target = new B(a.Id, a.F); return target; """); } @@ -441,7 +441,7 @@ public void CanResolveToClassConstructorWithMapPropertyAttribute() .Should() .HaveSingleMethodBody( """ - var target = new B(a.Id ?? default, a.F); + var target = new B(a.Id, a.F); return target; """); } diff --git a/test/Riok.Mapperly.Tests/Mapping/ObjectPropertyIgnoreTest.cs b/test/Riok.Mapperly.Tests/Mapping/ObjectPropertyIgnoreTest.cs index bd0184dd80..0a67509bbe 100644 --- a/test/Riok.Mapperly.Tests/Mapping/ObjectPropertyIgnoreTest.cs +++ b/test/Riok.Mapperly.Tests/Mapping/ObjectPropertyIgnoreTest.cs @@ -34,7 +34,7 @@ public void ExistingTargetWithIgnoredSourceAndTargetPropertyShouldIgnore() TestHelper.GenerateMapper(source, TestHelperOptions.AllowDiagnostics) .Should() .HaveDiagnostic(new(DiagnosticDescriptors.SourcePropertyNotMapped, "The property StringValue2 on the mapping source type A is not mapped to any property on the mapping target type B")) - .HaveDiagnostic(new(DiagnosticDescriptors.MappingSourcePropertyNotFound, "Property StringValue on source type A was not found")) + .HaveDiagnostic(new(DiagnosticDescriptors.SourcePropertyNotFound, "Property StringValue on source type A was not found")) .HaveSingleMethodBody("target.IntValue = source.IntValue;"); } @@ -48,7 +48,7 @@ public void WithIgnoredSourcePropertyShouldIgnoreAndGenerateDiagnostics() TestHelper.GenerateMapper(source, TestHelperOptions.AllowDiagnostics) .Should() - .HaveDiagnostic(new(DiagnosticDescriptors.MappingSourcePropertyNotFound, "Property IntValue on source type A was not found")) + .HaveDiagnostic(new(DiagnosticDescriptors.SourcePropertyNotFound, "Property IntValue on source type A was not found")) .HaveSingleMethodBody( """ var target = new B(); diff --git a/test/Riok.Mapperly.Tests/Mapping/ObjectPropertyInitPropertyTest.cs b/test/Riok.Mapperly.Tests/Mapping/ObjectPropertyInitPropertyTest.cs index fab526bd79..8a67f4b4a5 100644 --- a/test/Riok.Mapperly.Tests/Mapping/ObjectPropertyInitPropertyTest.cs +++ b/test/Riok.Mapperly.Tests/Mapping/ObjectPropertyInitPropertyTest.cs @@ -127,7 +127,7 @@ public void InitOnlyReferenceLoop() """ var target = new B() { - Parent = source.Parent == null ? default : Map(source.Parent) + Parent = source.Parent != null ? Map(source.Parent) : default }; return target; """); @@ -201,7 +201,7 @@ public void InitOnlyPropertySourceNotFoundShouldDiagnostic() TestHelper.GenerateMapper(source, TestHelperOptions.AllowDiagnostics) .Should() - .HaveDiagnostic(new(DiagnosticDescriptors.MappingSourcePropertyNotFound, "Property StringValue on source type A was not found")) + .HaveDiagnostic(new(DiagnosticDescriptors.SourcePropertyNotFound, "Property StringValue on source type A was not found")) .HaveDiagnostic(new(DiagnosticDescriptors.SourcePropertyNotMapped, "The property StringValue2 on the mapping source type A is not mapped to any property on the mapping target type B")); } diff --git a/test/Riok.Mapperly.Tests/Mapping/QueryableProjectionEnumTest.cs b/test/Riok.Mapperly.Tests/Mapping/QueryableProjectionEnumTest.cs new file mode 100644 index 0000000000..e5a3255378 --- /dev/null +++ b/test/Riok.Mapperly.Tests/Mapping/QueryableProjectionEnumTest.cs @@ -0,0 +1,65 @@ +using Riok.Mapperly.Abstractions; +using Riok.Mapperly.Diagnostics; + +namespace Riok.Mapperly.Tests.Mapping; + +[UsesVerify] +public class QueryableProjectionEnumTest +{ + [Fact] + public Task EnumToAnotherEnum() + { + var source = TestSourceBuilder.Mapping( + "System.Linq.IQueryable", + "System.Linq.IQueryable", + "class A { public C Value { get; set; } }", + "class B { public D Value { get; set; } }", + "enum C { Value1, Value2 }", + "enum D { Value1, Value2 }"); + + return TestHelper.VerifyGenerator(source); + } + + [Fact] + public void EnumToAnotherEnumByNameShouldDiagnostic() + { + var source = TestSourceBuilder.Mapping( + "System.Linq.IQueryable", + "System.Linq.IQueryable", + TestSourceBuilderOptions.Default with { EnumMappingStrategy = EnumMappingStrategy.ByName }, + "class A { public C Value { get; set; } }", + "class B { public D Value { get; set; } }", + "enum C { Value1, Value2 }", + "enum D { Value1, Value2 }"); + + TestHelper.GenerateMapper(source, TestHelperOptions.AllowDiagnostics) + .Should() + .HaveDiagnostic(new(DiagnosticDescriptors.EnumMappingStrategyByNameNotSupportedInProjectionMappings, "The enum mapping strategy ByName cannot be used in projection mappings to map from C to D")); + } + + [Fact] + public Task EnumToString() + { + var source = TestSourceBuilder.Mapping( + "System.Linq.IQueryable", + "System.Linq.IQueryable", + "class A { public C Value { get; set; } }", + "class B { public string Value { get; set; } }", + "enum C { Value1, Value2 }"); + + return TestHelper.VerifyGenerator(source); + } + + [Fact] + public Task EnumFromString() + { + var source = TestSourceBuilder.Mapping( + "System.Linq.IQueryable", + "System.Linq.IQueryable", + "class A { public string Value { get; set; } }", + "class B { public C Value { get; set; } }", + "enum C { Value1, Value2 }"); + + return TestHelper.VerifyGenerator(source); + } +} diff --git a/test/Riok.Mapperly.Tests/Mapping/QueryableProjectionEnumerableTest.cs b/test/Riok.Mapperly.Tests/Mapping/QueryableProjectionEnumerableTest.cs new file mode 100644 index 0000000000..d028e4868b --- /dev/null +++ b/test/Riok.Mapperly.Tests/Mapping/QueryableProjectionEnumerableTest.cs @@ -0,0 +1,29 @@ +namespace Riok.Mapperly.Tests.Mapping; + +[UsesVerify] +public class QueryableProjectionEnumerableTest +{ + [Fact] + public Task ExplicitCast() + { + var source = TestSourceBuilder.Mapping( + "System.Linq.IQueryable", + "System.Linq.IQueryable", + "class A { public IEnumerable Values { get; set; } }", + "class B { public IEnumerable Values { get; set; } }"); + + return TestHelper.VerifyGenerator(source); + } + + [Fact] + public Task ArrayToArrayExplicitCast() + { + var source = TestSourceBuilder.Mapping( + "System.Linq.IQueryable", + "System.Linq.IQueryable", + "class A { public long[] Values { get; set; } }", + "class B { public int[] Values { get; set; } }"); + + return TestHelper.VerifyGenerator(source); + } +} diff --git a/test/Riok.Mapperly.Tests/Mapping/QueryableProjectionNullableTest.cs b/test/Riok.Mapperly.Tests/Mapping/QueryableProjectionNullableTest.cs new file mode 100644 index 0000000000..e0da6557ca --- /dev/null +++ b/test/Riok.Mapperly.Tests/Mapping/QueryableProjectionNullableTest.cs @@ -0,0 +1,119 @@ +namespace Riok.Mapperly.Tests.Mapping; + +[UsesVerify] +public class QueryableProjectionNullableTest +{ + [Fact] + public Task ClassToClassNullableSourceProperty() + { + var source = TestSourceBuilder.Mapping( + "System.Linq.IQueryable", + "System.Linq.IQueryable", + "class A { public string? StringValue { get; set; } }", + "class B { public string StringValue { get; set; } }"); + + return TestHelper.VerifyGenerator(source); + } + + [Fact] + public Task ClassToClassNullableSourceValueTypeProperty() + { + var source = TestSourceBuilder.Mapping( + "System.Linq.IQueryable", + "System.Linq.IQueryable", + "class A { public int? IntValue { get; set; } }", + "class B { public int IntValue { get; set; } }"); + + return TestHelper.VerifyGenerator(source); + } + + [Fact] + public Task ClassToClassNullableTargetProperty() + { + var source = TestSourceBuilder.Mapping( + "System.Linq.IQueryable", + "System.Linq.IQueryable", + "class A { public string StringValue { get; set; } }", + "class B { public string? StringValue { get; set; } }"); + + return TestHelper.VerifyGenerator(source); + } + + [Fact] + public Task ClassToClassNullableTargetValueTypeProperty() + { + var source = TestSourceBuilder.Mapping( + "System.Linq.IQueryable", + "System.Linq.IQueryable", + "class A { public int IntValue { get; set; } }", + "class B { public int? IntValue { get; set; } }"); + + return TestHelper.VerifyGenerator(source); + } + + [Fact] + public Task ClassToClassNullableSourceAndTargetProperty() + { + var source = TestSourceBuilder.Mapping( + "System.Linq.IQueryable", + "System.Linq.IQueryable", + "class A { public string? StringValue { get; set; } }", + "class B { public string? StringValue { get; set; } }"); + + return TestHelper.VerifyGenerator(source); + } + + [Fact] + public Task ClassToClassNullableSourceAndTargetValueTypeProperty() + { + var source = TestSourceBuilder.Mapping( + "System.Linq.IQueryable", + "System.Linq.IQueryable", + "class A { public int? IntValue { get; set; } }", + "class B { public int? IntValue { get; set; } }"); + + return TestHelper.VerifyGenerator(source); + } + + [Fact] + public Task ClassToClassNullableSourcePathAutoFlatten() + { + var source = TestSourceBuilder.Mapping( + "System.Linq.IQueryable", + "System.Linq.IQueryable", + "class A { public C? Nested { get; set; } }", + "class B { public int NestedValue { get; set; } }", + "class C { public int Value { get; set; }}"); + + return TestHelper.VerifyGenerator(source); + } + + [Fact] + public Task ClassToClassNullableSourcePathAutoFlattenString() + { + var source = TestSourceBuilder.Mapping( + "System.Linq.IQueryable", + "System.Linq.IQueryable", + "class A { public C? Nested { get; set; } }", + "class B { public string NestedValue { get; set; } }", + "class C { public string Value { get; set; }}"); + + return TestHelper.VerifyGenerator(source); + } + + [Fact] + public Task ClassToClassNullableSourcePathManuallyFlatten() + { + var source = TestSourceBuilder.MapperWithBodyAndTypes( + """ + public partial System.Linq.IQueryable Map(System.Linq.IQueryable q); + [MapProperty("Nested.Nested2.Value3", "NestedValue4")] private partial B Map(A source); + """, + "class A { public C? Nested { get; set; } }", + "class B { public int NestedValue4 { get; set; } }", + "class C { public D? Nested2 { get; set; } }", + "class D { public int Value3 { get; set; }}"); + + return TestHelper.VerifyGenerator(source); + } +} diff --git a/test/Riok.Mapperly.Tests/Mapping/QueryableProjectionTest.cs b/test/Riok.Mapperly.Tests/Mapping/QueryableProjectionTest.cs new file mode 100644 index 0000000000..a22c297900 --- /dev/null +++ b/test/Riok.Mapperly.Tests/Mapping/QueryableProjectionTest.cs @@ -0,0 +1,117 @@ +using Riok.Mapperly.Diagnostics; + +namespace Riok.Mapperly.Tests.Mapping; + +[UsesVerify] +public class QueryableProjectionTest +{ + [Fact] + public Task ClassToClass() + { + var source = TestSourceBuilder.Mapping( + "System.Linq.IQueryable", + "System.Linq.IQueryable", + "class A { public string StringValue { get; set; } }", + "class B { public string StringValue { get; set; } }"); + + return TestHelper.VerifyGenerator(source); + } + + [Fact] + public Task ClassToClassMultipleProperties() + { + var source = TestSourceBuilder.Mapping( + "System.Linq.IQueryable", + "System.Linq.IQueryable", + "class A { public string StringValue { get; set; } public int IntValue { get; set; } public char CharValue { get; set; } }", + "class B { public string StringValue { get; set; } public int IntValue { get; set; } public char CharValue { get; set; } }"); + + return TestHelper.VerifyGenerator(source); + } + + [Fact] + public Task ClassToClassWithConfigs() + { + var source = TestSourceBuilder.MapperWithBodyAndTypes( + """ + partial System.Linq.IQueryable Map(System.Linq.IQueryable source); + [MapProperty("StringValue", "StringValue2")] partial B MapToB(A source); + [MapProperty("LongValue", "IntValue")] partial D MapToD(C source); + """, + "class A { public string StringValue { get; set; } public C NestedValue { get; set; } }", + "class B { public string StringValue2 { get; set; } public D NestedValue { get; set; } }", + "class C { public long LongValue { get; set; } public E NestedValue { get; set; } }", + "class D { public int IntValue { get; set; } public F NestedValue { get; set; } }", + "class E { public short ShortValue { get; set; } }", + "class F { public short ShortValue { get; set; } }"); + + return TestHelper.VerifyGenerator(source); + } + + [Fact] + public Task UserImplementedMethodsShouldBeIgnored() + { + var source = TestSourceBuilder.MapperWithBodyAndTypes( + """ + partial System.Linq.IQueryable Map(System.Linq.IQueryable source); + partial B MapToB(A source); + private D MapToD(C source) + => new D(); + """, + "class A { public string StringValue { get; set; } public C NestedValue { get; set; } }", + "class B { public string StringValue { get; set; } public D NestedValue { get; set; } }", + "class C { public long Value { get; set; } }", + "class D { public int Value { get; set; } }"); + + return TestHelper.VerifyGenerator(source); + } + + [Fact] + public Task ReferenceLoopInitProperty() + { + var source = TestSourceBuilder.Mapping( + "System.Linq.IQueryable", + "System.Linq.IQueryable", + "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", + "System.Linq.IQueryable", + "class A { public A? Parent { get; set; } }", + "class B { public B(B? parent) {} }"); + + return TestHelper.VerifyGenerator(source); + } + + [Fact] + public Task CtorShouldSkipUnmatchedOptionalParameters() + { + var source = TestSourceBuilder.Mapping( + "System.Linq.IQueryable", + "System.Linq.IQueryable", + "class A { public string StringValue { get; } public int IntValue { get; } }", + "class B { public B(string stringValue, int optionalArgument = 10, int intValue = 20) {} public B(string stringValue) {} public int IntValue { set; } }"); + + return TestHelper.VerifyGenerator(source); + } + + [Fact] + public void WithReferenceHandlingShouldDiagnostic() + { + var source = TestSourceBuilder.Mapping( + "System.Linq.IQueryable", + "System.Linq.IQueryable", + TestSourceBuilderOptions.WithReferenceHandling); + + TestHelper.GenerateMapper(source, TestHelperOptions.AllowDiagnostics) + .Should() + .HaveDiagnostic(new(DiagnosticDescriptors.QueryableProjectionMappingsDoNotSupportReferenceHandling)); + } +} diff --git a/test/Riok.Mapperly.Tests/_snapshots/QueryableProjectionEnumTest.EnumFromString#Mapper.g.verified.cs b/test/Riok.Mapperly.Tests/_snapshots/QueryableProjectionEnumTest.EnumFromString#Mapper.g.verified.cs new file mode 100644 index 0000000000..3c36a3ad67 --- /dev/null +++ b/test/Riok.Mapperly.Tests/_snapshots/QueryableProjectionEnumTest.EnumFromString#Mapper.g.verified.cs @@ -0,0 +1,11 @@ +//HintName: Mapper.g.cs +#nullable enable +public partial class Mapper +{ + private partial System.Linq.IQueryable Map(System.Linq.IQueryable source) + { +#nullable disable + return System.Linq.Queryable.Select(source, x => new B() { Value = System.Enum.Parse(x.Value, false) }); +#nullable enable + } +} diff --git a/test/Riok.Mapperly.Tests/_snapshots/QueryableProjectionEnumTest.EnumToAnotherEnum#Mapper.g.verified.cs b/test/Riok.Mapperly.Tests/_snapshots/QueryableProjectionEnumTest.EnumToAnotherEnum#Mapper.g.verified.cs new file mode 100644 index 0000000000..23eb92e024 --- /dev/null +++ b/test/Riok.Mapperly.Tests/_snapshots/QueryableProjectionEnumTest.EnumToAnotherEnum#Mapper.g.verified.cs @@ -0,0 +1,11 @@ +//HintName: Mapper.g.cs +#nullable enable +public partial class Mapper +{ + private partial System.Linq.IQueryable Map(System.Linq.IQueryable source) + { +#nullable disable + return System.Linq.Queryable.Select(source, x => new B() { Value = (D)x.Value }); +#nullable enable + } +} \ No newline at end of file diff --git a/test/Riok.Mapperly.Tests/_snapshots/QueryableProjectionEnumTest.EnumToString#Mapper.g.verified.cs b/test/Riok.Mapperly.Tests/_snapshots/QueryableProjectionEnumTest.EnumToString#Mapper.g.verified.cs new file mode 100644 index 0000000000..1083609b99 --- /dev/null +++ b/test/Riok.Mapperly.Tests/_snapshots/QueryableProjectionEnumTest.EnumToString#Mapper.g.verified.cs @@ -0,0 +1,11 @@ +//HintName: Mapper.g.cs +#nullable enable +public partial class Mapper +{ + private partial System.Linq.IQueryable Map(System.Linq.IQueryable source) + { +#nullable disable + return System.Linq.Queryable.Select(source, x => new B() { Value = (string)x.Value.ToString() }); +#nullable enable + } +} \ No newline at end of file diff --git a/test/Riok.Mapperly.Tests/_snapshots/QueryableProjectionEnumerableTest.ArrayToArrayExplicitCast#Mapper.g.verified.cs b/test/Riok.Mapperly.Tests/_snapshots/QueryableProjectionEnumerableTest.ArrayToArrayExplicitCast#Mapper.g.verified.cs new file mode 100644 index 0000000000..115fe8694c --- /dev/null +++ b/test/Riok.Mapperly.Tests/_snapshots/QueryableProjectionEnumerableTest.ArrayToArrayExplicitCast#Mapper.g.verified.cs @@ -0,0 +1,11 @@ +//HintName: Mapper.g.cs +#nullable enable +public partial class Mapper +{ + private partial System.Linq.IQueryable Map(System.Linq.IQueryable source) + { +#nullable disable + return System.Linq.Queryable.Select(source, x => new B() { Values = System.Linq.Enumerable.ToArray(System.Linq.Enumerable.Select(x.Values, x1 => (int)x1)) }); +#nullable enable + } +} \ No newline at end of file diff --git a/test/Riok.Mapperly.Tests/_snapshots/QueryableProjectionEnumerableTest.ExplicitCast#Mapper.g.verified.cs b/test/Riok.Mapperly.Tests/_snapshots/QueryableProjectionEnumerableTest.ExplicitCast#Mapper.g.verified.cs new file mode 100644 index 0000000000..a6d2e27315 --- /dev/null +++ b/test/Riok.Mapperly.Tests/_snapshots/QueryableProjectionEnumerableTest.ExplicitCast#Mapper.g.verified.cs @@ -0,0 +1,11 @@ +//HintName: Mapper.g.cs +#nullable enable +public partial class Mapper +{ + private partial System.Linq.IQueryable Map(System.Linq.IQueryable source) + { +#nullable disable + return System.Linq.Queryable.Select(source, x => new B() { Values = System.Linq.Enumerable.Select(x.Values, x1 => (int)x1) }); +#nullable enable + } +} \ No newline at end of file diff --git a/test/Riok.Mapperly.Tests/_snapshots/QueryableProjectionEnumerableTest.ToCollectionReadOnlyProperty#Mapper.g.verified.cs b/test/Riok.Mapperly.Tests/_snapshots/QueryableProjectionEnumerableTest.ToCollectionReadOnlyProperty#Mapper.g.verified.cs new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/Riok.Mapperly.Tests/_snapshots/QueryableProjectionEnumerableTest.ToCollectionReadOnlyProperty.verified.txt b/test/Riok.Mapperly.Tests/_snapshots/QueryableProjectionEnumerableTest.ToCollectionReadOnlyProperty.verified.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/Riok.Mapperly.Tests/_snapshots/QueryableProjectionNullableTest.ClassToClassNullableSourceAndTargetProperty#Mapper.g.verified.cs b/test/Riok.Mapperly.Tests/_snapshots/QueryableProjectionNullableTest.ClassToClassNullableSourceAndTargetProperty#Mapper.g.verified.cs new file mode 100644 index 0000000000..6b5cb679e9 --- /dev/null +++ b/test/Riok.Mapperly.Tests/_snapshots/QueryableProjectionNullableTest.ClassToClassNullableSourceAndTargetProperty#Mapper.g.verified.cs @@ -0,0 +1,11 @@ +//HintName: Mapper.g.cs +#nullable enable +public partial class Mapper +{ + private partial System.Linq.IQueryable Map(System.Linq.IQueryable source) + { +#nullable disable + return System.Linq.Queryable.Select(source, x => new B() { StringValue = x.StringValue }); +#nullable enable + } +} \ No newline at end of file diff --git a/test/Riok.Mapperly.Tests/_snapshots/QueryableProjectionNullableTest.ClassToClassNullableSourceAndTargetValueTypeProperty#Mapper.g.verified.cs b/test/Riok.Mapperly.Tests/_snapshots/QueryableProjectionNullableTest.ClassToClassNullableSourceAndTargetValueTypeProperty#Mapper.g.verified.cs new file mode 100644 index 0000000000..a24235b6bb --- /dev/null +++ b/test/Riok.Mapperly.Tests/_snapshots/QueryableProjectionNullableTest.ClassToClassNullableSourceAndTargetValueTypeProperty#Mapper.g.verified.cs @@ -0,0 +1,11 @@ +//HintName: Mapper.g.cs +#nullable enable +public partial class Mapper +{ + private partial System.Linq.IQueryable Map(System.Linq.IQueryable source) + { +#nullable disable + return System.Linq.Queryable.Select(source, x => new B() { IntValue = x.IntValue }); +#nullable enable + } +} \ No newline at end of file diff --git a/test/Riok.Mapperly.Tests/_snapshots/QueryableProjectionNullableTest.ClassToClassNullableSourcePathAutoFlatten#Mapper.g.verified.cs b/test/Riok.Mapperly.Tests/_snapshots/QueryableProjectionNullableTest.ClassToClassNullableSourcePathAutoFlatten#Mapper.g.verified.cs new file mode 100644 index 0000000000..1b4582b178 --- /dev/null +++ b/test/Riok.Mapperly.Tests/_snapshots/QueryableProjectionNullableTest.ClassToClassNullableSourcePathAutoFlatten#Mapper.g.verified.cs @@ -0,0 +1,11 @@ +//HintName: Mapper.g.cs +#nullable enable +public partial class Mapper +{ + private partial System.Linq.IQueryable Map(System.Linq.IQueryable source) + { +#nullable disable + return System.Linq.Queryable.Select(source, x => new B() { NestedValue = x.Nested != null ? x.Nested.Value : default }); +#nullable enable + } +} \ No newline at end of file diff --git a/test/Riok.Mapperly.Tests/_snapshots/QueryableProjectionNullableTest.ClassToClassNullableSourcePathAutoFlattenString#Mapper.g.verified.cs b/test/Riok.Mapperly.Tests/_snapshots/QueryableProjectionNullableTest.ClassToClassNullableSourcePathAutoFlattenString#Mapper.g.verified.cs new file mode 100644 index 0000000000..6621a6ee61 --- /dev/null +++ b/test/Riok.Mapperly.Tests/_snapshots/QueryableProjectionNullableTest.ClassToClassNullableSourcePathAutoFlattenString#Mapper.g.verified.cs @@ -0,0 +1,11 @@ +//HintName: Mapper.g.cs +#nullable enable +public partial class Mapper +{ + private partial System.Linq.IQueryable Map(System.Linq.IQueryable source) + { +#nullable disable + return System.Linq.Queryable.Select(source, x => new B() { NestedValue = x.Nested != null ? x.Nested.Value : "" }); +#nullable enable + } +} \ No newline at end of file diff --git a/test/Riok.Mapperly.Tests/_snapshots/QueryableProjectionNullableTest.ClassToClassNullableSourcePathManuallyFlatten#Mapper.g.verified.cs b/test/Riok.Mapperly.Tests/_snapshots/QueryableProjectionNullableTest.ClassToClassNullableSourcePathManuallyFlatten#Mapper.g.verified.cs new file mode 100644 index 0000000000..d2e89a0696 --- /dev/null +++ b/test/Riok.Mapperly.Tests/_snapshots/QueryableProjectionNullableTest.ClassToClassNullableSourcePathManuallyFlatten#Mapper.g.verified.cs @@ -0,0 +1,22 @@ +//HintName: Mapper.g.cs +#nullable enable +public partial class Mapper +{ + public partial System.Linq.IQueryable Map(System.Linq.IQueryable q) + { +#nullable disable + return System.Linq.Queryable.Select(q, x => new B() { NestedValue4 = x.Nested != null && x.Nested.Nested2 != null ? x.Nested.Nested2.Value3 : default }); +#nullable enable + } + + private partial B Map(A source) + { + var target = new B(); + if (source.Nested?.Nested2 != null) + { + target.NestedValue4 = source.Nested.Nested2.Value3; + } + + return target; + } +} \ No newline at end of file diff --git a/test/Riok.Mapperly.Tests/_snapshots/QueryableProjectionNullableTest.ClassToClassNullableSourceProperty#Mapper.g.verified.cs b/test/Riok.Mapperly.Tests/_snapshots/QueryableProjectionNullableTest.ClassToClassNullableSourceProperty#Mapper.g.verified.cs new file mode 100644 index 0000000000..616551a11e --- /dev/null +++ b/test/Riok.Mapperly.Tests/_snapshots/QueryableProjectionNullableTest.ClassToClassNullableSourceProperty#Mapper.g.verified.cs @@ -0,0 +1,11 @@ +//HintName: Mapper.g.cs +#nullable enable +public partial class Mapper +{ + private partial System.Linq.IQueryable Map(System.Linq.IQueryable source) + { +#nullable disable + return System.Linq.Queryable.Select(source, x => new B() { StringValue = x.StringValue ?? "" }); +#nullable enable + } +} \ No newline at end of file diff --git a/test/Riok.Mapperly.Tests/_snapshots/QueryableProjectionNullableTest.ClassToClassNullableSourceValueTypeProperty#Mapper.g.verified.cs b/test/Riok.Mapperly.Tests/_snapshots/QueryableProjectionNullableTest.ClassToClassNullableSourceValueTypeProperty#Mapper.g.verified.cs new file mode 100644 index 0000000000..b87e16258a --- /dev/null +++ b/test/Riok.Mapperly.Tests/_snapshots/QueryableProjectionNullableTest.ClassToClassNullableSourceValueTypeProperty#Mapper.g.verified.cs @@ -0,0 +1,11 @@ +//HintName: Mapper.g.cs +#nullable enable +public partial class Mapper +{ + private partial System.Linq.IQueryable Map(System.Linq.IQueryable source) + { +#nullable disable + return System.Linq.Queryable.Select(source, x => new B() { IntValue = x.IntValue ?? default }); +#nullable enable + } +} \ No newline at end of file diff --git a/test/Riok.Mapperly.Tests/_snapshots/QueryableProjectionNullableTest.ClassToClassNullableTargetProperty#Mapper.g.verified.cs b/test/Riok.Mapperly.Tests/_snapshots/QueryableProjectionNullableTest.ClassToClassNullableTargetProperty#Mapper.g.verified.cs new file mode 100644 index 0000000000..6b5cb679e9 --- /dev/null +++ b/test/Riok.Mapperly.Tests/_snapshots/QueryableProjectionNullableTest.ClassToClassNullableTargetProperty#Mapper.g.verified.cs @@ -0,0 +1,11 @@ +//HintName: Mapper.g.cs +#nullable enable +public partial class Mapper +{ + private partial System.Linq.IQueryable Map(System.Linq.IQueryable source) + { +#nullable disable + return System.Linq.Queryable.Select(source, x => new B() { StringValue = x.StringValue }); +#nullable enable + } +} \ No newline at end of file diff --git a/test/Riok.Mapperly.Tests/_snapshots/QueryableProjectionNullableTest.ClassToClassNullableTargetValueTypeProperty#Mapper.g.verified.cs b/test/Riok.Mapperly.Tests/_snapshots/QueryableProjectionNullableTest.ClassToClassNullableTargetValueTypeProperty#Mapper.g.verified.cs new file mode 100644 index 0000000000..a24235b6bb --- /dev/null +++ b/test/Riok.Mapperly.Tests/_snapshots/QueryableProjectionNullableTest.ClassToClassNullableTargetValueTypeProperty#Mapper.g.verified.cs @@ -0,0 +1,11 @@ +//HintName: Mapper.g.cs +#nullable enable +public partial class Mapper +{ + private partial System.Linq.IQueryable Map(System.Linq.IQueryable source) + { +#nullable disable + return System.Linq.Queryable.Select(source, x => new B() { IntValue = x.IntValue }); +#nullable enable + } +} \ No newline at end of file diff --git a/test/Riok.Mapperly.Tests/_snapshots/QueryableProjectionTest.ClassToClass#Mapper.g.verified.cs b/test/Riok.Mapperly.Tests/_snapshots/QueryableProjectionTest.ClassToClass#Mapper.g.verified.cs new file mode 100644 index 0000000000..6b5cb679e9 --- /dev/null +++ b/test/Riok.Mapperly.Tests/_snapshots/QueryableProjectionTest.ClassToClass#Mapper.g.verified.cs @@ -0,0 +1,11 @@ +//HintName: Mapper.g.cs +#nullable enable +public partial class Mapper +{ + private partial System.Linq.IQueryable Map(System.Linq.IQueryable source) + { +#nullable disable + return System.Linq.Queryable.Select(source, x => new B() { StringValue = x.StringValue }); +#nullable enable + } +} \ No newline at end of file diff --git a/test/Riok.Mapperly.Tests/_snapshots/QueryableProjectionTest.ClassToClassMultipleProperties#Mapper.g.verified.cs b/test/Riok.Mapperly.Tests/_snapshots/QueryableProjectionTest.ClassToClassMultipleProperties#Mapper.g.verified.cs new file mode 100644 index 0000000000..2ab8c285dd --- /dev/null +++ b/test/Riok.Mapperly.Tests/_snapshots/QueryableProjectionTest.ClassToClassMultipleProperties#Mapper.g.verified.cs @@ -0,0 +1,11 @@ +//HintName: Mapper.g.cs +#nullable enable +public partial class Mapper +{ + private partial System.Linq.IQueryable Map(System.Linq.IQueryable source) + { +#nullable disable + return System.Linq.Queryable.Select(source, x => new B() { StringValue = x.StringValue, IntValue = x.IntValue, CharValue = x.CharValue }); +#nullable enable + } +} diff --git a/test/Riok.Mapperly.Tests/_snapshots/QueryableProjectionTest.ClassToClassNullableSourceAndTargetProperty#Mapper.g.verified.cs b/test/Riok.Mapperly.Tests/_snapshots/QueryableProjectionTest.ClassToClassNullableSourceAndTargetProperty#Mapper.g.verified.cs new file mode 100644 index 0000000000..6b5cb679e9 --- /dev/null +++ b/test/Riok.Mapperly.Tests/_snapshots/QueryableProjectionTest.ClassToClassNullableSourceAndTargetProperty#Mapper.g.verified.cs @@ -0,0 +1,11 @@ +//HintName: Mapper.g.cs +#nullable enable +public partial class Mapper +{ + private partial System.Linq.IQueryable Map(System.Linq.IQueryable source) + { +#nullable disable + return System.Linq.Queryable.Select(source, x => new B() { StringValue = x.StringValue }); +#nullable enable + } +} \ No newline at end of file diff --git a/test/Riok.Mapperly.Tests/_snapshots/QueryableProjectionTest.ClassToClassNullableSourceProperty#Mapper.g.verified.cs b/test/Riok.Mapperly.Tests/_snapshots/QueryableProjectionTest.ClassToClassNullableSourceProperty#Mapper.g.verified.cs new file mode 100644 index 0000000000..8b74c3e1b2 --- /dev/null +++ b/test/Riok.Mapperly.Tests/_snapshots/QueryableProjectionTest.ClassToClassNullableSourceProperty#Mapper.g.verified.cs @@ -0,0 +1,11 @@ +//HintName: Mapper.g.cs +#nullable enable +public partial class Mapper +{ + private partial System.Linq.IQueryable Map(System.Linq.IQueryable source) + { +#nullable disable + return System.Linq.Queryable.Select(source, x => new B() { StringValue = x.StringValue }); +#nullable enable + } +} \ No newline at end of file diff --git a/test/Riok.Mapperly.Tests/_snapshots/QueryableProjectionTest.ClassToClassWithConfigs#Mapper.g.verified.cs b/test/Riok.Mapperly.Tests/_snapshots/QueryableProjectionTest.ClassToClassWithConfigs#Mapper.g.verified.cs new file mode 100644 index 0000000000..efee074949 --- /dev/null +++ b/test/Riok.Mapperly.Tests/_snapshots/QueryableProjectionTest.ClassToClassWithConfigs#Mapper.g.verified.cs @@ -0,0 +1,34 @@ +//HintName: Mapper.g.cs +#nullable enable +public partial class Mapper +{ + private partial System.Linq.IQueryable Map(System.Linq.IQueryable source) + { +#nullable disable + return System.Linq.Queryable.Select(source, x => new B() { StringValue2 = x.StringValue, NestedValue = new D() { IntValue = (int)x.NestedValue.LongValue, NestedValue = new F() { ShortValue = x.NestedValue.NestedValue.ShortValue } } }); +#nullable enable + } + + private partial B MapToB(A source) + { + var target = new B(); + target.StringValue2 = source.StringValue; + target.NestedValue = MapToD(source.NestedValue); + return target; + } + + private partial D MapToD(C source) + { + var target = new D(); + target.IntValue = (int)source.LongValue; + target.NestedValue = MapToF(source.NestedValue); + return target; + } + + private F MapToF(E source) + { + var target = new F(); + target.ShortValue = source.ShortValue; + return target; + } +} diff --git a/test/Riok.Mapperly.Tests/_snapshots/QueryableProjectionTest.CtorShouldSkipUnmatchedOptionalParameters#Mapper.g.verified.cs b/test/Riok.Mapperly.Tests/_snapshots/QueryableProjectionTest.CtorShouldSkipUnmatchedOptionalParameters#Mapper.g.verified.cs new file mode 100644 index 0000000000..a4b8848ad7 --- /dev/null +++ b/test/Riok.Mapperly.Tests/_snapshots/QueryableProjectionTest.CtorShouldSkipUnmatchedOptionalParameters#Mapper.g.verified.cs @@ -0,0 +1,11 @@ +//HintName: Mapper.g.cs +#nullable enable +public partial class Mapper +{ + private partial System.Linq.IQueryable Map(System.Linq.IQueryable source) + { +#nullable disable + return System.Linq.Queryable.Select(source, x => new B(x.StringValue) { IntValue = x.IntValue }); +#nullable enable + } +} \ No newline at end of file diff --git a/test/Riok.Mapperly.Tests/_snapshots/QueryableProjectionTest.ReferenceLoopCtor#Mapper.g.verified.cs b/test/Riok.Mapperly.Tests/_snapshots/QueryableProjectionTest.ReferenceLoopCtor#Mapper.g.verified.cs new file mode 100644 index 0000000000..3f2d4d9ce8 --- /dev/null +++ b/test/Riok.Mapperly.Tests/_snapshots/QueryableProjectionTest.ReferenceLoopCtor#Mapper.g.verified.cs @@ -0,0 +1,11 @@ +//HintName: Mapper.g.cs +#nullable enable +public partial class Mapper +{ + private partial System.Linq.IQueryable Map(System.Linq.IQueryable source) + { +#nullable disable + return System.Linq.Queryable.Select(source, x => new B(x.Parent != null ? new B() : default)); +#nullable enable + } +} \ No newline at end of file diff --git a/test/Riok.Mapperly.Tests/_snapshots/QueryableProjectionTest.ReferenceLoopCtor.verified.txt b/test/Riok.Mapperly.Tests/_snapshots/QueryableProjectionTest.ReferenceLoopCtor.verified.txt new file mode 100644 index 0000000000..715638313e --- /dev/null +++ b/test/Riok.Mapperly.Tests/_snapshots/QueryableProjectionTest.ReferenceLoopCtor.verified.txt @@ -0,0 +1,40 @@ +{ + Diagnostics: [ + { + Id: RMG031, + Title: Reference loop detected while mapping to a constructor parameter, + Severity: Warning, + WarningLevel: 1, + Location: : (8,0)-(12,1), + Description: , + HelpLink: , + MessageFormat: Reference loop detected while mapping from {0}.{1} to the constructor parameter {3} of {2}, consider ignoring this property or mark another constructor as mapping constructor, + Message: Reference loop detected while mapping from A.Parent to the constructor parameter parent of B, consider ignoring this property or mark another constructor as mapping constructor, + Category: Mapper + }, + { + Id: RMG013, + Title: No accessible constructor with mappable arguments found, + Severity: Error, + WarningLevel: 0, + Location: : (8,0)-(12,1), + Description: , + HelpLink: , + MessageFormat: {0} has no accessible constructor with mappable arguments, + Message: B has no accessible constructor with mappable arguments, + Category: Mapper + }, + { + Id: RMG020, + Title: Source property is not mapped to any target property, + Severity: Info, + WarningLevel: 1, + Location: : (8,0)-(12,1), + Description: , + HelpLink: , + MessageFormat: The property {0} on the mapping source type {1} is not mapped to any property on the mapping target type {2}, + Message: The property Parent on the mapping source type A is not mapped to any property on the mapping target type B, + Category: Mapper + } + ] +} diff --git a/test/Riok.Mapperly.Tests/_snapshots/QueryableProjectionTest.ReferenceLoopInitProperty#Mapper.g.verified.cs b/test/Riok.Mapperly.Tests/_snapshots/QueryableProjectionTest.ReferenceLoopInitProperty#Mapper.g.verified.cs new file mode 100644 index 0000000000..bfeb80130e --- /dev/null +++ b/test/Riok.Mapperly.Tests/_snapshots/QueryableProjectionTest.ReferenceLoopInitProperty#Mapper.g.verified.cs @@ -0,0 +1,11 @@ +//HintName: Mapper.g.cs +#nullable enable +public partial class Mapper +{ + private partial System.Linq.IQueryable Map(System.Linq.IQueryable source) + { +#nullable disable + return System.Linq.Queryable.Select(source, x => new B() { Parent = x.Parent != null ? new B() : default }); +#nullable enable + } +} \ No newline at end of file diff --git a/test/Riok.Mapperly.Tests/_snapshots/QueryableProjectionTest.ReferenceLoopInitProperty.verified.txt b/test/Riok.Mapperly.Tests/_snapshots/QueryableProjectionTest.ReferenceLoopInitProperty.verified.txt new file mode 100644 index 0000000000..a19876a568 --- /dev/null +++ b/test/Riok.Mapperly.Tests/_snapshots/QueryableProjectionTest.ReferenceLoopInitProperty.verified.txt @@ -0,0 +1,28 @@ +{ + Diagnostics: [ + { + Id: RMG030, + Title: Reference loop detected while mapping to an init only property, + Severity: Error, + WarningLevel: 0, + Location: : (8,0)-(12,1), + Description: , + HelpLink: , + MessageFormat: Reference loop detected while mapping from {0}.{1} to the init only property {2}.{3}, consider ignoring this property, + Message: Reference loop detected while mapping from A.Parent to the init only property B.Parent, consider ignoring this property, + Category: Mapper + }, + { + Id: RMG020, + Title: Source property is not mapped to any target property, + Severity: Info, + WarningLevel: 1, + Location: : (8,0)-(12,1), + Description: , + HelpLink: , + MessageFormat: The property {0} on the mapping source type {1} is not mapped to any property on the mapping target type {2}, + Message: The property Parent on the mapping source type A is not mapped to any property on the mapping target type B, + Category: Mapper + } + ] +} \ No newline at end of file diff --git a/test/Riok.Mapperly.Tests/_snapshots/QueryableProjectionTest.UserImplementedMethodsShouldBeIgnored#Mapper.g.verified.cs b/test/Riok.Mapperly.Tests/_snapshots/QueryableProjectionTest.UserImplementedMethodsShouldBeIgnored#Mapper.g.verified.cs new file mode 100644 index 0000000000..62f0775d7f --- /dev/null +++ b/test/Riok.Mapperly.Tests/_snapshots/QueryableProjectionTest.UserImplementedMethodsShouldBeIgnored#Mapper.g.verified.cs @@ -0,0 +1,19 @@ +//HintName: Mapper.g.cs +#nullable enable +public partial class Mapper +{ + private partial System.Linq.IQueryable Map(System.Linq.IQueryable source) + { +#nullable disable + return System.Linq.Queryable.Select(source, x => new B() { StringValue = x.StringValue, NestedValue = new D() { Value = (int)x.NestedValue.Value } }); +#nullable enable + } + + private partial B MapToB(A source) + { + var target = new B(); + target.StringValue = source.StringValue; + target.NestedValue = MapToD(source.NestedValue); + return target; + } +} \ No newline at end of file