diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index fab4718dc9..8998b359fa 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -44,8 +44,9 @@ jobs: - if: failure() uses: actions/upload-artifact@v4 with: - name: verify-test-results + name: verify-test-results-package path: '**/*.received.*' + retention-days: 3 validate-package: needs: package runs-on: ubuntu-latest @@ -102,8 +103,9 @@ jobs: - if: failure() uses: actions/upload-artifact@v4 with: - name: verify-test-results + name: verify-test-results-net${{ matrix.dotnet }} path: '**/*.received.*' + retention-days: 3 integration-test-net-framework: needs: package runs-on: windows-latest @@ -129,8 +131,9 @@ jobs: - if: failure() uses: actions/upload-artifact@v4 with: - name: verify-test-results + name: verify-test-results-net-framework path: '**/*.received.*' + retention-days: 3 sample: runs-on: ubuntu-latest needs: package diff --git a/docs/docs/configuration/analyzer-diagnostics/RMG060.mdx b/docs/docs/configuration/analyzer-diagnostics/RMG060.mdx new file mode 100644 index 0000000000..1c700883dc --- /dev/null +++ b/docs/docs/configuration/analyzer-diagnostics/RMG060.mdx @@ -0,0 +1,61 @@ +--- +sidebar_label: RMG060 +description: 'Mapperly analyzer diagnostic RMG060 — Multiple user mappings discovered without specifying an explicit default' +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +# RMG060 — Multiple user-mappings discovered without specifying an explicit default + +Multiple user-mappings for the same type pair are discovered +without specifying an explicit default. +Mapperly needs to now which mapping it should use. + +To specify a default apply `[UserMapping(Default = true)]`. + +See also [user-implemented mappings](../user-implemented-methods.mdx#default-user-implemented-mapping-method). + +## Example + +Two mappings from `Car` to `CarDto` are defined. +When Mapperly needs to map from `Car` to `CarDto` in `CarsToCarDtos` +it needs to know whether to use `CarToCarDto` or `CarToCarDtoIgnoreId`. +Apply `[UserMapping(Default = true)]` to the mapping which Mapperly should use +in such cases. + + + + ```csharp + [Mapper] + public partial class CarMapper + { + public paratial List CarsToCarDtos(List cars); + + public partial CarDto CarToCarDto(Car car); + + [MapperIgnoreSource(nameof(Car.Id))] + public partial CarDto CarToCarDtoIgnoreId(Car car); + } + ``` + + + + ```csharp + [Mapper] + public partial class CarMapper + { + public paratial List CarsToCarDtos(List cars); + + // highlight-start + [UserMapping(Default = true)] + // highlight-end + public partial CarDto CarToCarDto(Car car); + + [MapperIgnoreSource(nameof(Car.Id))] + public partial CarDto CarToCarDtoIgnoreId(Car car); + } + ``` + + + diff --git a/docs/docs/configuration/user-implemented-methods.mdx b/docs/docs/configuration/user-implemented-methods.mdx index dc40a83749..ec4b5f9889 100644 --- a/docs/docs/configuration/user-implemented-methods.mdx +++ b/docs/docs/configuration/user-implemented-methods.mdx @@ -89,7 +89,7 @@ the first user-implemented mapping which has an unspecified `Default` value is u For each type-pair only one default mapping can exist. If multiple mappings exist for the same type-pair without a default mapping, `RMG060` is reported. -By default `RMG060` has a severity of info, +By default `RMG060` has a severity of warning, which can be changed using the [`.editorconfig`](./analyzer-diagnostics/index.mdx#editorconfig). ## Map properties using user-implemented mappings diff --git a/src/Riok.Mapperly/AnalyzerReleases.Shipped.md b/src/Riok.Mapperly/AnalyzerReleases.Shipped.md index d567dda277..d9b0e0820d 100644 --- a/src/Riok.Mapperly/AnalyzerReleases.Shipped.md +++ b/src/Riok.Mapperly/AnalyzerReleases.Shipped.md @@ -144,11 +144,11 @@ RMG018 | Mapper | Disabled | Partial static mapping method in an instance map Rule ID | Category | Severity | Notes --------|----------|----------|------- RMG059 | Mapper | Error | Multiple default user mappings found, only one is allowed -RMG060 | Mapper | Info | Multiple user mappings discovered without specifying an explicit default +RMG060 | Mapper | Warning | Multiple user mappings discovered without specifying an explicit default RMG061 | Mapper | Error | The referenced mapping was not found RMG062 | Mapper | Error | The referenced mapping name is ambiguous RMG063 | Mapper | Error | Cannot configure an enum mapping on a non-enum mapping RMG064 | Mapper | Error | Cannot configure an object mapping on a non-object mapping RMG065 | Mapper | Warning | Cannot configure an object mapping on a queryable projection mapping, apply the configurations to an object mapping method instead -RMG066 | Mapper | Warning | No members are mapped in an object mapping +RMG066 | Mapper | Warning | No members are mapped in an object mapping RMG067 | Mapper | Error | Invalid usage of the MapPropertyAttribute diff --git a/src/Riok.Mapperly/Diagnostics/DiagnosticDescriptors.cs b/src/Riok.Mapperly/Diagnostics/DiagnosticDescriptors.cs index 46fe1157b5..a05ccced7b 100644 --- a/src/Riok.Mapperly/Diagnostics/DiagnosticDescriptors.cs +++ b/src/Riok.Mapperly/Diagnostics/DiagnosticDescriptors.cs @@ -581,8 +581,9 @@ public static class DiagnosticDescriptors "Multiple user mappings discovered without specifying an explicit default", "Multiple user mappings discovered for the mapping from {0} to {1} without specifying an explicit default", DiagnosticCategories.Mapper, - DiagnosticSeverity.Info, - true + DiagnosticSeverity.Warning, + true, + helpLinkUri: BuildHelpUri("RMG060") ); public static readonly DiagnosticDescriptor ReferencedMappingNotFound = diff --git a/test/Riok.Mapperly.IntegrationTests/Mapper/StaticTestMapper.cs b/test/Riok.Mapperly.IntegrationTests/Mapper/StaticTestMapper.cs index 3010a0c28f..be95d5fec1 100644 --- a/test/Riok.Mapperly.IntegrationTests/Mapper/StaticTestMapper.cs +++ b/test/Riok.Mapperly.IntegrationTests/Mapper/StaticTestMapper.cs @@ -9,6 +9,7 @@ namespace Riok.Mapperly.IntegrationTests.Mapper [Mapper(EnumMappingStrategy = EnumMappingStrategy.ByValue)] public static partial class StaticTestMapper { + [UserMapping(Default = true)] public static partial int DirectInt(int value); public static partial int? DirectIntNullable(int? value); @@ -33,6 +34,7 @@ public static partial class StaticTestMapper [MapperRequiredMapping(RequiredMappingStrategy.Target)] public static partial TestObjectDto MapToDtoExt(this TestObject src); + [UserMapping(Default = true)] public static TestObjectDto MapToDto(TestObject src) { var target = MapToDtoInternal(src); @@ -120,6 +122,7 @@ public static void MapExistingList(List src, List dst) [MapperIgnoreTargetValue(TestEnum.Value30)] public static partial TestEnum MapToEnumByNameWithIgnored(TestEnumDtoAdditionalValue v); + [UserMapping(Default = true)] [MapEnum(EnumMappingStrategy.ByValueCheckDefined)] public static partial TestEnum MapToEnumByValueCheckDefined(TestEnumDtoByValue v); diff --git a/test/Riok.Mapperly.IntegrationTests/Mapper/TestMapper.cs b/test/Riok.Mapperly.IntegrationTests/Mapper/TestMapper.cs index ce61c87400..63fabfe8a8 100644 --- a/test/Riok.Mapperly.IntegrationTests/Mapper/TestMapper.cs +++ b/test/Riok.Mapperly.IntegrationTests/Mapper/TestMapper.cs @@ -32,6 +32,7 @@ public TestMapper() _formatEnUs.NumberFormat.CurrencyPositivePattern = 2; } + [UserMapping(Default = true)] public partial int DirectInt(int value); public partial long ImplicitCastInt(int value); @@ -48,6 +49,7 @@ public TestMapper() public partial IEnumerable MapAllDtos(IEnumerable objects); + [UserMapping(Default = true)] public TestObjectDto MapToDto(TestObject src) { var target = MapToDtoInternal(src); diff --git a/test/Riok.Mapperly.IntegrationTests/_snapshots/StaticMapperTest.RunExtensionMappingShouldWork.verified.txt b/test/Riok.Mapperly.IntegrationTests/_snapshots/StaticMapperTest.RunExtensionMappingShouldWork.verified.txt index 3b351ccf72..af33555c26 100644 --- a/test/Riok.Mapperly.IntegrationTests/_snapshots/StaticMapperTest.RunExtensionMappingShouldWork.verified.txt +++ b/test/Riok.Mapperly.IntegrationTests/_snapshots/StaticMapperTest.RunExtensionMappingShouldWork.verified.txt @@ -23,7 +23,7 @@ CtorValue: 5, CtorValue2: 100, RequiredValue: 4, - StringValue: , + StringValue: +after-map, RenamedStringValue2: , Unflattening: {}, NestedNullableTargetNotNullable: {}, diff --git a/test/Riok.Mapperly.IntegrationTests/_snapshots/StaticMapperTest.RunExtensionMappingShouldWork_NET6_0.verified.txt b/test/Riok.Mapperly.IntegrationTests/_snapshots/StaticMapperTest.RunExtensionMappingShouldWork_NET6_0.verified.txt index 17153c34e0..805c4557bb 100644 --- a/test/Riok.Mapperly.IntegrationTests/_snapshots/StaticMapperTest.RunExtensionMappingShouldWork_NET6_0.verified.txt +++ b/test/Riok.Mapperly.IntegrationTests/_snapshots/StaticMapperTest.RunExtensionMappingShouldWork_NET6_0.verified.txt @@ -23,7 +23,7 @@ CtorValue: 5, CtorValue2: 100, RequiredValue: 4, - StringValue: , + StringValue: +after-map, RenamedStringValue2: , Unflattening: {}, NestedNullableTargetNotNullable: {}, diff --git a/test/Riok.Mapperly.IntegrationTests/_snapshots/StaticMapperTest.RunMappingShouldWork.verified.txt b/test/Riok.Mapperly.IntegrationTests/_snapshots/StaticMapperTest.RunMappingShouldWork.verified.txt index a773be224e..ec9744f29b 100644 --- a/test/Riok.Mapperly.IntegrationTests/_snapshots/StaticMapperTest.RunMappingShouldWork.verified.txt +++ b/test/Riok.Mapperly.IntegrationTests/_snapshots/StaticMapperTest.RunMappingShouldWork.verified.txt @@ -28,7 +28,7 @@ CtorValue: 5, CtorValue2: 100, RequiredValue: 4, - StringValue: , + StringValue: +after-map, RenamedStringValue2: , Unflattening: {}, NestedNullableTargetNotNullable: {}, diff --git a/test/Riok.Mapperly.IntegrationTests/_snapshots/StaticMapperTest.RunMappingShouldWork_NET6_0.verified.txt b/test/Riok.Mapperly.IntegrationTests/_snapshots/StaticMapperTest.RunMappingShouldWork_NET6_0.verified.txt index 4b1ab3647a..0c6249cf99 100644 --- a/test/Riok.Mapperly.IntegrationTests/_snapshots/StaticMapperTest.RunMappingShouldWork_NET6_0.verified.txt +++ b/test/Riok.Mapperly.IntegrationTests/_snapshots/StaticMapperTest.RunMappingShouldWork_NET6_0.verified.txt @@ -28,7 +28,7 @@ CtorValue: 5, CtorValue2: 100, RequiredValue: 4, - StringValue: , + StringValue: +after-map, RenamedStringValue2: , Unflattening: {}, NestedNullableTargetNotNullable: {}, diff --git a/test/Riok.Mapperly.IntegrationTests/_snapshots/StaticMapperTest.SnapshotGeneratedSource.verified.cs b/test/Riok.Mapperly.IntegrationTests/_snapshots/StaticMapperTest.SnapshotGeneratedSource.verified.cs index ebd0c3f0d6..e6597cc9c1 100644 --- a/test/Riok.Mapperly.IntegrationTests/_snapshots/StaticMapperTest.SnapshotGeneratedSource.verified.cs +++ b/test/Riok.Mapperly.IntegrationTests/_snapshots/StaticMapperTest.SnapshotGeneratedSource.verified.cs @@ -1,4 +1,4 @@ -// +// #nullable enable namespace Riok.Mapperly.IntegrationTests.Mapper { @@ -55,7 +55,7 @@ public static partial int ParseableInt(string value) [global::System.CodeDom.Compiler.GeneratedCode("Riok.Mapperly", "0.0.1.0")] public static partial global::System.Collections.Generic.IEnumerable MapAllDtos(global::System.Collections.Generic.IEnumerable objects) { - return global::System.Linq.Enumerable.Select(objects, x => MapToDtoExt(x)); + return global::System.Linq.Enumerable.Select(objects, x => MapToDto(x)); } [global::System.CodeDom.Compiler.GeneratedCode("Riok.Mapperly", "0.0.1.0")] @@ -101,7 +101,7 @@ public static partial int ParseableInt(string value) } if (src.RecursiveObject != null) { - target.RecursiveObject = MapToDtoExt(src.RecursiveObject); + target.RecursiveObject = MapToDto(src.RecursiveObject); } else { @@ -214,7 +214,7 @@ public static partial int ParseableInt(string value) } if (testObject.RecursiveObject != null) { - target.RecursiveObject = MapToDtoExt(testObject.RecursiveObject); + target.RecursiveObject = MapToDto(testObject.RecursiveObject); } else { @@ -420,7 +420,7 @@ public static partial void UpdateDto(global::Riok.Mapperly.IntegrationTests.Mode } if (source.RecursiveObject != null) { - target.RecursiveObject = MapToDtoExt(source.RecursiveObject); + target.RecursiveObject = MapToDto(source.RecursiveObject); } else { diff --git a/test/Riok.Mapperly.IntegrationTests/_snapshots/StaticMapperTest.SnapshotGeneratedSource_NET6_0.verified.cs b/test/Riok.Mapperly.IntegrationTests/_snapshots/StaticMapperTest.SnapshotGeneratedSource_NET6_0.verified.cs index 34084e5f6b..5655aca0fc 100644 --- a/test/Riok.Mapperly.IntegrationTests/_snapshots/StaticMapperTest.SnapshotGeneratedSource_NET6_0.verified.cs +++ b/test/Riok.Mapperly.IntegrationTests/_snapshots/StaticMapperTest.SnapshotGeneratedSource_NET6_0.verified.cs @@ -55,7 +55,7 @@ public static partial int ParseableInt(string value) [global::System.CodeDom.Compiler.GeneratedCode("Riok.Mapperly", "0.0.1.0")] public static partial global::System.Collections.Generic.IEnumerable MapAllDtos(global::System.Collections.Generic.IEnumerable objects) { - return global::System.Linq.Enumerable.Select(objects, x => MapToDtoExt(x)); + return global::System.Linq.Enumerable.Select(objects, x => MapToDto(x)); } [global::System.CodeDom.Compiler.GeneratedCode("Riok.Mapperly", "0.0.1.0")] @@ -101,7 +101,7 @@ public static partial int ParseableInt(string value) } if (src.RecursiveObject != null) { - target.RecursiveObject = MapToDtoExt(src.RecursiveObject); + target.RecursiveObject = MapToDto(src.RecursiveObject); } else { @@ -216,7 +216,7 @@ public static partial int ParseableInt(string value) } if (testObject.RecursiveObject != null) { - target.RecursiveObject = MapToDtoExt(testObject.RecursiveObject); + target.RecursiveObject = MapToDto(testObject.RecursiveObject); } else { @@ -427,7 +427,7 @@ public static partial void UpdateDto(global::Riok.Mapperly.IntegrationTests.Mode } if (source.RecursiveObject != null) { - target.RecursiveObject = MapToDtoExt(source.RecursiveObject); + target.RecursiveObject = MapToDto(source.RecursiveObject); } else { diff --git a/test/Riok.Mapperly.Tests/Mapping/UserMethodTest.cs b/test/Riok.Mapperly.Tests/Mapping/UserMethodTest.cs index cd20e11e39..1dcd546764 100644 --- a/test/Riok.Mapperly.Tests/Mapping/UserMethodTest.cs +++ b/test/Riok.Mapperly.Tests/Mapping/UserMethodTest.cs @@ -654,7 +654,7 @@ public void UserImplementedWithDefaultFalseUserMappingsShouldUseFirstNonFalseDef ); TestHelper - .GenerateMapper(source, TestHelperOptions.AllowInfoDiagnostics) + .GenerateMapper(source, TestHelperOptions.AllowDiagnostics) .Should() .HaveDiagnostic( DiagnosticDescriptors.MultipleUserMappingsWithoutDefault, @@ -694,7 +694,7 @@ public void DisabledAutoUserMappingDiscoveryShouldUseFirstNonFalseDefault() ); TestHelper - .GenerateMapper(source, TestHelperOptions.AllowInfoDiagnostics) + .GenerateMapper(source, TestHelperOptions.AllowDiagnostics) .Should() .HaveDiagnostic( DiagnosticDescriptors.MultipleUserMappingsWithoutDefault, diff --git a/test/Riok.Mapperly.Tests/_snapshots/ReferenceHandlingTest.ManuallyMappedPropertiesShouldWork.verified.txt b/test/Riok.Mapperly.Tests/_snapshots/ReferenceHandlingTest.ManuallyMappedPropertiesShouldWork.verified.txt index 53e9d021c2..df4a69060a 100644 --- a/test/Riok.Mapperly.Tests/_snapshots/ReferenceHandlingTest.ManuallyMappedPropertiesShouldWork.verified.txt +++ b/test/Riok.Mapperly.Tests/_snapshots/ReferenceHandlingTest.ManuallyMappedPropertiesShouldWork.verified.txt @@ -23,9 +23,10 @@ { Id: RMG060, Title: Multiple user mappings discovered without specifying an explicit default, - Severity: Info, + Severity: Warning, WarningLevel: 1, Location: : (12,0)-(12,71), + HelpLink: https://localhost:3000/docs/configuration/analyzer-diagnostics/RMG060, MessageFormat: Multiple user mappings discovered for the mapping from {0} to {1} without specifying an explicit default, Message: Multiple user mappings discovered for the mapping from A to B without specifying an explicit default, Category: Mapper