Working state
This commit is contained in:
parent
0f37ff9619
commit
33fbb36fb8
|
@ -26,10 +26,10 @@ jobs:
|
|||
run: dotnet build --configuration Release --no-restore /p:PublicRelease=true
|
||||
- name: Test
|
||||
run: dotnet test --configuration Release --no-build --verbosity normal
|
||||
- name: Publish MapTo
|
||||
- name: Publish BlueWest.EfMethods
|
||||
uses: brandedoutcast/publish-nuget@v2.5.5
|
||||
with:
|
||||
PROJECT_FILE_PATH: src/BlueWest.MapTo/BlueWest.MapTo.csproj
|
||||
PROJECT_FILE_PATH: src/BlueWest.EfMethods/BlueWest.EfMethods.csproj
|
||||
NUGET_KEY: ${{secrets.NUGET_API_KEY}}
|
||||
NUGET_SOURCE: https://api.nuget.org
|
||||
TAG_COMMIT: false
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BlueWest.EfMethods", "src\BlueWest.EfMethods\BlueWest.EfMethods.csproj", "{4DB371AC-48D0-4F01-8EF3-7707D06EF0A7}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{4DB371AC-48D0-4F01-8EF3-7707D06EF0A7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{4DB371AC-48D0-4F01-8EF3-7707D06EF0A7}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{4DB371AC-48D0-4F01-8EF3-7707D06EF0A7}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{4DB371AC-48D0-4F01-8EF3-7707D06EF0A7}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
EndGlobal
|
|
@ -1,5 +1,5 @@
|
|||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Mapto/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=BlueWest.EfMethods/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=paramref/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Shouldly/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=typeparam/@EntryIndexedValue">True</s:Boolean>
|
34
MapTo.sln
34
MapTo.sln
|
@ -1,34 +0,0 @@
|
|||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MapTo", "src\BlueWest.MapTo\BlueWest.MapTo.csproj", "{4DB371AC-48D0-4F01-8EF3-7707D06EF0A7}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MapToTests", "test\MapTo.Tests\MapTo.Tests.csproj", "{797DA57B-AC7E-468B-8799-44C5A574C0E3}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestConsoleApp", "test\TestConsoleApp\TestConsoleApp.csproj", "{5BE2551A-9EF9-42FA-B6D1-5B5E6A90CC85}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MapTo.Integration.Tests", "test\MapTo.Integration.Tests\MapTo.Integration.Tests.csproj", "{23B46FDF-6A1E-4287-88C9-C8C5D7EECB8C}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{4DB371AC-48D0-4F01-8EF3-7707D06EF0A7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{4DB371AC-48D0-4F01-8EF3-7707D06EF0A7}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{4DB371AC-48D0-4F01-8EF3-7707D06EF0A7}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{4DB371AC-48D0-4F01-8EF3-7707D06EF0A7}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{797DA57B-AC7E-468B-8799-44C5A574C0E3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{797DA57B-AC7E-468B-8799-44C5A574C0E3}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{797DA57B-AC7E-468B-8799-44C5A574C0E3}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{797DA57B-AC7E-468B-8799-44C5A574C0E3}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{5BE2551A-9EF9-42FA-B6D1-5B5E6A90CC85}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{5BE2551A-9EF9-42FA-B6D1-5B5E6A90CC85}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{5BE2551A-9EF9-42FA-B6D1-5B5E6A90CC85}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{5BE2551A-9EF9-42FA-B6D1-5B5E6A90CC85}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{23B46FDF-6A1E-4287-88C9-C8C5D7EECB8C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{23B46FDF-6A1E-4287-88C9-C8C5D7EECB8C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{23B46FDF-6A1E-4287-88C9-C8C5D7EECB8C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{23B46FDF-6A1E-4287-88C9-C8C5D7EECB8C}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
EndGlobal
|
18
README.md
18
README.md
|
@ -1,22 +1,22 @@
|
|||
# MapTo
|
||||
[![Nuget](https://img.shields.io/nuget/v/mapto?logo=nuget)](https://www.nuget.org/packages/MapTo/)
|
||||
![Publish Packages](https://github.com/mrtaikandi/MapTo/workflows/Publish%20Packages/badge.svg)
|
||||
# BlueWest.EfMethods
|
||||
[![Nuget](https://img.shields.io/nuget/v/BlueWest.EfMethods?logo=nuget)](https://www.nuget.org/packages/BlueWest.EfMethods/)
|
||||
![Publish Packages](https://github.com/mrtaikandi/BlueWest.EfMethods/workflows/Publish%20Packages/badge.svg)
|
||||
|
||||
A convention based object to object mapper using [Roslyn source generator](https://github.com/dotnet/roslyn/blob/master/docs/features/source-generators.md).
|
||||
|
||||
MapTo is a library to programmatically generate the necessary code to map one object to another during compile-time, eliminating the need to use reflection to map objects and make it much faster in runtime. It provides compile-time safety checks and ease of use by leveraging extension methods.
|
||||
BlueWest.EfMethods is a library to programmatically generate the necessary code to map one object to another during compile-time, eliminating the need to use reflection to map objects and make it much faster in runtime. It provides compile-time safety checks and ease of use by leveraging extension methods.
|
||||
|
||||
|
||||
## Installation
|
||||
```
|
||||
dotnet add package MapTo --prerelease
|
||||
dotnet add package BlueWest.EfMethods --prerelease
|
||||
```
|
||||
|
||||
## Usage
|
||||
MapTo relies on a set of attributes to instruct it on how to generate the mappings. To start, declare the destination class as `partial` and annotate it with `MapFrom` attribute. As its name implies, `MapFrom` attribute tells the library what the source class you want to map from is.
|
||||
BlueWest.EfMethods relies on a set of attributes to instruct it on how to generate the mappings. To start, declare the destination class as `partial` and annotate it with `MapFrom` attribute. As its name implies, `MapFrom` attribute tells the library what the source class you want to map from is.
|
||||
|
||||
```c#
|
||||
using MapTo;
|
||||
using BlueWest.EfMethods;
|
||||
|
||||
namespace App.ViewModels
|
||||
{
|
||||
|
@ -47,11 +47,11 @@ vm = new UserViewModel(user); // A generated contructor.
|
|||
vm = UserViewModel.From(user); // A generated factory method.
|
||||
```
|
||||
|
||||
> Please refer to [sample console app](https://github.com/mrtaikandi/MapTo/tree/master/test/TestConsoleApp) for a more complete example.
|
||||
> Please refer to [sample console app](https://github.com/mrtaikandi/BlueWest.EfMethods/tree/master/test/TestConsoleApp) for a more complete example.
|
||||
|
||||
## Available Attributes
|
||||
### IgnoreProperty
|
||||
By default, MapTo will include all properties with the same name (case-sensitive), whether read-only or not, in the mapping unless annotating them with the `IgnoreProperty` attribute.
|
||||
By default, BlueWest.EfMethods will include all properties with the same name (case-sensitive), whether read-only or not, in the mapping unless annotating them with the `IgnoreProperty` attribute.
|
||||
```c#
|
||||
[IgnoreProperty]
|
||||
public string FullName { get; set; }
|
||||
|
|
|
@ -7,17 +7,17 @@
|
|||
<Description>An object to object mapping generator using Roslyn source generator.</Description>
|
||||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||
<IncludeSymbols>true</IncludeSymbols>
|
||||
<PackageId>MapTo</PackageId>
|
||||
<PackageProjectUrl>https://github.com/mrtaikandi/mapto</PackageProjectUrl>
|
||||
<PackageId>BlueWest.EfMethods</PackageId>
|
||||
<PackageProjectUrl>https://github.com/mrtaikandi/BlueWest.EfMethods</PackageProjectUrl>
|
||||
<PackageVersion>$(Version)</PackageVersion>
|
||||
<PublishRepositoryUrl>true</PublishRepositoryUrl>
|
||||
<RepositoryUrl>https://github.com/mrtaikandi/mapto</RepositoryUrl>
|
||||
<RepositoryUrl>https://github.com/mrtaikandi/BlueWest.EfMethods</RepositoryUrl>
|
||||
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
|
||||
<RootNamespace>MapTo</RootNamespace>
|
||||
<RootNamespace>BlueWest.EfMethods</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
|
||||
<DocumentationFile>bin\Release\MapTo.xml</DocumentationFile>
|
||||
<DocumentationFile>bin\Release\BlueWest.EfMethods.xml</DocumentationFile>
|
||||
<Optimize>false</Optimize>
|
||||
</PropertyGroup>
|
||||
|
||||
|
@ -40,7 +40,7 @@
|
|||
|
||||
<ItemGroup>
|
||||
<None Include="$(OutputPath)\$(AssemblyName).dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false" />
|
||||
<None Include="MapTo.props" Pack="true" PackagePath="build" Visible="false" />
|
||||
<None Include="BlueWest.EfMethods.props" Pack="true" PackagePath="build" Visible="false" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
|
@ -0,0 +1,5 @@
|
|||
<Project>
|
||||
<ItemGroup>
|
||||
<CompilerVisibleProperty Include="BlueWest_EfMethods_ConstructorAccessModifier" />
|
||||
</ItemGroup>
|
||||
</Project>
|
|
@ -1,11 +1,11 @@
|
|||
using System.Linq;
|
||||
using MapTo.Extensions;
|
||||
using MapTo.Sources;
|
||||
using BlueWest.EfMethods.Extensions;
|
||||
using BlueWest.EfMethods.Sources;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using static MapTo.Sources.Constants;
|
||||
using static BlueWest.EfMethods.Sources.Constants;
|
||||
|
||||
namespace MapTo
|
||||
namespace BlueWest.EfMethods
|
||||
{
|
||||
internal static class DiagnosticsFactory
|
||||
{
|
||||
|
@ -17,19 +17,10 @@ namespace MapTo
|
|||
|
||||
internal static Diagnostic TypeNotFoundError(Location location, string syntaxName) =>
|
||||
Create($"{ErrorId}010", location, $"Unable to find '{syntaxName}' type.");
|
||||
|
||||
internal static Diagnostic MapFromAttributeNotFoundError(Location location) =>
|
||||
Create($"{ErrorId}020", location, $"Unable to find {MapFromAttributeSource.AttributeName} type.");
|
||||
|
||||
|
||||
internal static Diagnostic NoMatchingPropertyFoundError(Location location, INamedTypeSymbol classType, INamedTypeSymbol sourceType) =>
|
||||
Create($"{ErrorId}030", location, $"No matching properties found between '{classType.ToDisplayString()}' and '{sourceType.ToDisplayString()}' types.");
|
||||
|
||||
internal static Diagnostic NoMatchingPropertyTypeFoundError(ISymbol property) =>
|
||||
Create($"{ErrorId}031", property.Locations.FirstOrDefault(), $"Cannot create a map for '{property.ToDisplayString()}' property because source and destination types are not implicitly convertible. Consider using '{MapTypeConverterAttributeSource.FullyQualifiedName}' to provide a type converter or ignore the property using '{IgnoreMemberAttributeSource.FullyQualifiedName}'.");
|
||||
|
||||
internal static Diagnostic InvalidTypeConverterGenericTypesError(ISymbol property, IPropertySymbol sourceProperty) =>
|
||||
Create($"{ErrorId}032", property.Locations.FirstOrDefault(), $"Cannot map '{property.ToDisplayString()}' property because the annotated converter does not implement '{RootNamespace}.{ITypeConverterSource.InterfaceName}<{sourceProperty.Type.ToDisplayString()}, {property.GetTypeSymbol()?.ToDisplayString()}>'.");
|
||||
|
||||
internal static Diagnostic ConfigurationParseError(string error) =>
|
||||
Create($"{ErrorId}040", Location.None, error);
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
using static MapTo.Sources.Constants;
|
||||
using static BlueWest.EfMethods.Sources.Constants;
|
||||
|
||||
namespace MapTo.Sources
|
||||
namespace BlueWest.EfMethods.Sources
|
||||
{
|
||||
public class EfGeneratorAttributeSource
|
||||
{
|
|
@ -1,9 +1,9 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using static MapTo.Sources.Constants;
|
||||
using static BlueWest.EfMethods.Sources.Constants;
|
||||
|
||||
namespace MapTo.Sources
|
||||
namespace BlueWest.EfMethods.Sources
|
||||
{
|
||||
internal static class EfGetManyAttributeSource
|
||||
{
|
|
@ -1,9 +1,9 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using static MapTo.Sources.Constants;
|
||||
using static BlueWest.EfMethods.Sources.Constants;
|
||||
|
||||
namespace MapTo.Sources
|
||||
namespace BlueWest.EfMethods.Sources
|
||||
{
|
||||
internal class EfGetOneByAttributeSource
|
||||
{
|
|
@ -1,6 +1,6 @@
|
|||
using static MapTo.Sources.Constants;
|
||||
using static BlueWest.EfMethods.Sources.Constants;
|
||||
|
||||
namespace MapTo.Sources
|
||||
namespace BlueWest.EfMethods.Sources
|
||||
{
|
||||
public class EfUpdateMethodsAttributeSource
|
||||
{
|
|
@ -1,9 +1,9 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using static MapTo.Sources.Constants;
|
||||
using static BlueWest.EfMethods.Sources.Constants;
|
||||
|
||||
namespace MapTo.Sources
|
||||
namespace BlueWest.EfMethods.Sources
|
||||
{
|
||||
internal static class EfGetOneAttributeSource
|
||||
{
|
|
@ -1,7 +1,7 @@
|
|||
using static MapTo.Sources.Constants;
|
||||
using static BlueWest.EfMethods.Sources.Constants;
|
||||
|
||||
|
||||
namespace MapTo.Sources
|
||||
namespace BlueWest.EfMethods.Sources
|
||||
{
|
||||
internal static class EfAddMethodsAttributeSource
|
||||
{
|
|
@ -4,8 +4,8 @@ using System.Collections.Immutable;
|
|||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using MapTo.Extensions;
|
||||
using MapTo.Sources;
|
||||
using BlueWest.EfMethods.Extensions;
|
||||
using BlueWest.EfMethods.Sources;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
|
@ -13,7 +13,7 @@ using CSharpExtensions = Microsoft.CodeAnalysis.CSharpExtensions;
|
|||
|
||||
#pragma warning disable CS8602
|
||||
|
||||
namespace MapTo
|
||||
namespace BlueWest.EfMethods
|
||||
{
|
||||
internal class EfGeneratorContext
|
||||
{
|
||||
|
@ -213,7 +213,8 @@ namespace MapTo
|
|||
{
|
||||
if (!property.TryGetTypeSymbol(out var propertyType))
|
||||
{
|
||||
AddDiagnostic(DiagnosticsFactory.NoMatchingPropertyTypeFoundError(property));
|
||||
// Todo add diagnostic
|
||||
//AddDiagnostic(DiagnosticsFactory.NoMatchingPropertyTypeFoundError(property));
|
||||
namedTypeSymbolResult = null;
|
||||
return false;
|
||||
}
|
|
@ -3,12 +3,12 @@ using System.Collections.Generic;
|
|||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using MapTo.Extensions;
|
||||
using MapTo.Sources;
|
||||
using BlueWest.EfMethods.Extensions;
|
||||
using BlueWest.EfMethods.Sources;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
|
||||
namespace MapTo
|
||||
namespace BlueWest.EfMethods
|
||||
{
|
||||
/// <summary>
|
||||
/// Ef methods source generator
|
|
@ -3,11 +3,11 @@ using System.Collections.Generic;
|
|||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using MapTo.Extensions;
|
||||
using BlueWest.EfMethods.Extensions;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using static MapTo.Sources.Constants;
|
||||
using static BlueWest.EfMethods.Sources.Constants;
|
||||
|
||||
namespace MapTo.Sources
|
||||
namespace BlueWest.EfMethods.Sources
|
||||
{
|
||||
internal static class EfMethodsSource
|
||||
{
|
|
@ -1,9 +1,9 @@
|
|||
using MapTo.Extensions;
|
||||
using BlueWest.EfMethods.Extensions;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace MapTo.Sources
|
||||
namespace BlueWest.EfMethods.Sources
|
||||
{
|
||||
internal static class EfAddEntityTemplateSource
|
||||
{
|
|
@ -1,10 +1,10 @@
|
|||
using MapTo.Extensions;
|
||||
using MapTo.Sources;
|
||||
using BlueWest.EfMethods.Extensions;
|
||||
using BlueWest.EfMethods.Sources;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace MapTo
|
||||
namespace BlueWest.EfMethods
|
||||
{
|
||||
internal static class EfGetOneEntityByTemplate
|
||||
{
|
|
@ -1,10 +1,10 @@
|
|||
using MapTo.Extensions;
|
||||
using MapTo.Sources;
|
||||
using BlueWest.EfMethods.Extensions;
|
||||
using BlueWest.EfMethods.Sources;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace MapTo
|
||||
namespace BlueWest.EfMethods
|
||||
{
|
||||
internal static class EfGetOneWithTemplateSource
|
||||
{
|
|
@ -1,9 +1,9 @@
|
|||
using MapTo.Extensions;
|
||||
using BlueWest.EfMethods.Extensions;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace MapTo.Sources
|
||||
namespace BlueWest.EfMethods.Sources
|
||||
{
|
||||
internal static class EfUpdateEntityTemplateSource
|
||||
{
|
|
@ -1,10 +1,10 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using MapTo.Sources;
|
||||
using BlueWest.EfMethods.Sources;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
|
||||
namespace MapTo
|
||||
namespace BlueWest.EfMethods
|
||||
{
|
||||
internal enum EfMethodsAttributeType
|
||||
{
|
|
@ -0,0 +1,31 @@
|
|||
using BlueWest.EfMethods.Sources;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace BlueWest.EfMethods.Extensions
|
||||
{
|
||||
internal static class CommonExtensions
|
||||
{
|
||||
internal static SourceBuilder WriteComment(this SourceBuilder builder, string comment = "")
|
||||
{
|
||||
return builder.WriteLine($"// {comment}");
|
||||
}
|
||||
|
||||
internal static SourceBuilder WriteCommentArray(this SourceBuilder builder, IEnumerable<object> enumerable, string name = "")
|
||||
{
|
||||
builder.WriteComment($"Printing Array: {name}");
|
||||
foreach (var o in enumerable)
|
||||
{
|
||||
if (o != null)
|
||||
{
|
||||
builder.WriteComment($" {o.ToString()}");
|
||||
}
|
||||
}
|
||||
builder.WriteComment($"End printing Array: {name}");
|
||||
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
using System;
|
||||
|
||||
namespace MapTo.Extensions
|
||||
namespace BlueWest.EfMethods.Extensions
|
||||
{
|
||||
internal static class EnumExtensions
|
||||
{
|
|
@ -2,7 +2,7 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace MapTo.Extensions
|
||||
namespace BlueWest.EfMethods.Extensions
|
||||
{
|
||||
internal static class EnumerableExtensions
|
||||
{
|
|
@ -4,11 +4,11 @@ using Microsoft.CodeAnalysis;
|
|||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.Text;
|
||||
|
||||
namespace MapTo.Extensions
|
||||
namespace BlueWest.EfMethods.Extensions
|
||||
{
|
||||
internal static class GeneratorExecutionContextExtensions
|
||||
{
|
||||
private const string PropertyNameSuffix = "MapTo_";
|
||||
private const string PropertyNameSuffix = "BlueWest.EfMethods_";
|
||||
|
||||
internal static T GetBuildGlobalOption<T>(this GeneratorExecutionContext context, string propertyName, T defaultValue = default!) where T : notnull
|
||||
{
|
|
@ -5,7 +5,7 @@ using System.Linq;
|
|||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
|
||||
namespace MapTo.Extensions
|
||||
namespace BlueWest.EfMethods.Extensions
|
||||
{
|
||||
internal static class RoslynExtensions
|
||||
{
|
|
@ -1,7 +1,7 @@
|
|||
using System;
|
||||
using System.Text;
|
||||
|
||||
namespace MapTo.Extensions
|
||||
namespace BlueWest.EfMethods.Extensions
|
||||
{
|
||||
internal static class StringBuilderExtensions
|
||||
{
|
|
@ -1,6 +1,6 @@
|
|||
using System.Threading.Tasks;
|
||||
|
||||
namespace MapTo.Extensions
|
||||
namespace BlueWest.EfMethods.Extensions
|
||||
{
|
||||
internal static class StringExtensions
|
||||
{
|
|
@ -1,10 +1,10 @@
|
|||
using System;
|
||||
using System.Collections.Immutable;
|
||||
using MapTo.Extensions;
|
||||
using BlueWest.EfMethods.Extensions;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
|
||||
namespace MapTo
|
||||
namespace BlueWest.EfMethods
|
||||
{
|
||||
internal enum AccessModifier
|
||||
{
|
||||
|
@ -22,56 +22,6 @@ namespace MapTo
|
|||
|
||||
internal record SourceCode(string Text, string HintName);
|
||||
|
||||
internal record MappedMember(
|
||||
string Name,
|
||||
string FullyQualifiedType,
|
||||
string Type,
|
||||
string? TypeConverter,
|
||||
ImmutableArray<string> TypeConverterParameters,
|
||||
string SourcePropertyName,
|
||||
string? MappedSourcePropertyTypeName,
|
||||
string? EnumerableTypeArgument,
|
||||
ISymbol ActualSymbol,
|
||||
INamedTypeSymbol? NamedTypeSymbol,
|
||||
bool isEnumerable,
|
||||
bool isReadOnly)
|
||||
{
|
||||
public bool IsEnumerable => EnumerableTypeArgument is not null;
|
||||
}
|
||||
|
||||
internal record MappedSourceType
|
||||
(
|
||||
string SourceNamespace,
|
||||
string SourceTypeIdentifierName,
|
||||
string SourceTypeFullName,
|
||||
ImmutableArray<MappedMember> SourceProperties,
|
||||
ImmutableArray<MappedMember> SourceFields,
|
||||
ImmutableArray<MappedMember> TypeProperties,
|
||||
ImmutableArray<MappedMember> TypeFields,
|
||||
bool GenerateSecondaryConstructor
|
||||
)
|
||||
{
|
||||
public string SourceType => SourceTypeFullName;
|
||||
}
|
||||
|
||||
|
||||
internal record MappingModel(
|
||||
SourceGenerationOptions Options,
|
||||
string? Namespace,
|
||||
SyntaxTokenList Modifiers,
|
||||
string Type,
|
||||
string TypeIdentifierName,
|
||||
bool IsTypeUpdatable,
|
||||
bool IsJsonExtension,
|
||||
ImmutableArray<MappedSourceType> MappedSourceTypes,
|
||||
bool HasMappedBaseClass,
|
||||
ImmutableArray<string> Usings
|
||||
);
|
||||
|
||||
internal interface IEfMethodsModel { }
|
||||
|
||||
|
||||
|
||||
|
||||
internal record EfMethodsModel(
|
||||
SourceGenerationOptions Options,
|
|
@ -1,8 +1,8 @@
|
|||
namespace MapTo.Sources
|
||||
namespace BlueWest.EfMethods.Sources
|
||||
{
|
||||
internal class Constants
|
||||
{
|
||||
internal const string RootNamespace = "MapTo";
|
||||
internal const string RootNamespace = "BlueWest.EfMethods";
|
||||
internal const string GeneratedFilesHeader = "// <auto-generated />";
|
||||
}
|
||||
}
|
|
@ -4,7 +4,7 @@ using System.Collections.Generic;
|
|||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace MapTo.Sources
|
||||
namespace BlueWest.EfMethods.Sources
|
||||
{
|
||||
internal sealed class SourceBuilder : IDisposable
|
||||
{
|
|
@ -1,67 +0,0 @@
|
|||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using MapTo.Extensions;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
|
||||
namespace MapTo
|
||||
{
|
||||
internal class ClassMappingContext : MappingContext
|
||||
{
|
||||
internal ClassMappingContext(Compilation compilation, SourceGenerationOptions sourceGenerationOptions, TypeDeclarationSyntax typeSyntax)
|
||||
: base(compilation, sourceGenerationOptions, typeSyntax) { }
|
||||
|
||||
protected override ImmutableArray<MappedMember> GetSourceMappedFields(ITypeSymbol typeSymbol, ITypeSymbol sourceTypeSymbol, bool isInheritFromMappedBaseClass)
|
||||
{
|
||||
var sourceProperties = sourceTypeSymbol.GetAllMembers().OfType<IFieldSymbol>().ToArray();
|
||||
|
||||
return typeSymbol
|
||||
.GetAllMembers()
|
||||
.OfType<IFieldSymbol>()
|
||||
.Where(p => !p.HasAttribute(IgnoreMemberAttributeTypeSymbol))
|
||||
.Select(property => MapField(sourceTypeSymbol, sourceProperties, property))
|
||||
.Where(mappedProperty => mappedProperty is not null)
|
||||
.ToImmutableArray()!;
|
||||
}
|
||||
|
||||
protected override ImmutableArray<MappedMember> GetSourceMappedProperties(ITypeSymbol typeSymbol, ITypeSymbol sourceTypeSymbol, bool hasInheritedClass)
|
||||
{
|
||||
var sourceProperties = sourceTypeSymbol.GetAllMembers().OfType<IPropertySymbol>().ToArray();
|
||||
|
||||
return typeSymbol
|
||||
.GetAllMembers()
|
||||
.OfType<IPropertySymbol>()
|
||||
.Where(p => !p.HasAttribute(IgnoreMemberAttributeTypeSymbol))
|
||||
.Select(property => MapProperty(sourceTypeSymbol, sourceProperties, property))
|
||||
.Where(mappedProperty => mappedProperty is not null)
|
||||
.ToImmutableArray()!;
|
||||
}
|
||||
|
||||
protected override ImmutableArray<MappedMember> GetTypeMappedFields(ITypeSymbol typeSymbol, ITypeSymbol sourceTypeSymbol, bool isInheritFromMappedBaseClass)
|
||||
{
|
||||
var sourceProperties = sourceTypeSymbol.GetAllMembers().OfType<IFieldSymbol>().ToArray();
|
||||
|
||||
return sourceTypeSymbol
|
||||
.GetAllMembers()
|
||||
.OfType<IFieldSymbol>()
|
||||
.Where(p => !p.HasAttribute(IgnoreMemberAttributeTypeSymbol))
|
||||
.Select(property => MapFieldSimple(typeSymbol, property))
|
||||
.Where(mappedProperty => mappedProperty is not null)
|
||||
.ToImmutableArray()!;
|
||||
}
|
||||
|
||||
protected override ImmutableArray<MappedMember> GetTypeMappedProperties(ITypeSymbol typeSymbol, ITypeSymbol sourceTypeSymbol, bool hasInheritedClass)
|
||||
{
|
||||
var sourceProperties = sourceTypeSymbol.GetAllMembers().OfType<IPropertySymbol>().ToArray();
|
||||
|
||||
return sourceTypeSymbol
|
||||
.GetAllMembers()
|
||||
.OfType<IPropertySymbol>()
|
||||
.Where(p => !p.HasAttribute(IgnoreMemberAttributeTypeSymbol))
|
||||
.Select(property => MapPropertySimple(typeSymbol, property))
|
||||
.Where(mappedProperty => mappedProperty is not null)
|
||||
.ToImmutableArray()!;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -1,86 +0,0 @@
|
|||
using MapTo.Sources;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace MapTo.Extensions
|
||||
{
|
||||
internal static class CommonExtensions
|
||||
{
|
||||
internal static SourceBuilder WriteComment(this SourceBuilder builder, string comment = "")
|
||||
{
|
||||
return builder.WriteLine($"// {comment}");
|
||||
}
|
||||
|
||||
internal static SourceBuilder WriteCommentArray(this SourceBuilder builder, IEnumerable<object> enumerable, string name = "")
|
||||
{
|
||||
builder.WriteComment($"Printing Array: {name}");
|
||||
foreach (var o in enumerable)
|
||||
{
|
||||
if (o != null)
|
||||
{
|
||||
builder.WriteComment($" {o.ToString()}");
|
||||
}
|
||||
}
|
||||
builder.WriteComment($"End printing Array: {name}");
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
internal static SourceBuilder WriteModelInfo(this SourceBuilder builder, MappingModel model)
|
||||
{
|
||||
foreach (var targetSourceType in model.MappedSourceTypes)
|
||||
{
|
||||
builder
|
||||
.WriteLine()
|
||||
.WriteComment($" IsTypeUpdatable {model.IsTypeUpdatable}")
|
||||
.WriteComment($" HasMappedBaseClass {model.HasMappedBaseClass.ToString()}")
|
||||
.WriteComment($" Namespace {model.Namespace}")
|
||||
.WriteComment($" Options {model.Options.ToString()}")
|
||||
.WriteComment($" Type {model.Type}")
|
||||
.WriteComment($" TypeIdentifierName {model.TypeIdentifierName}")
|
||||
.WriteComment($" SourceNamespace {targetSourceType.SourceNamespace}")
|
||||
.WriteComment($" SourceTypeFullName {targetSourceType.SourceTypeFullName}")
|
||||
.WriteComment($" SourceTypeIdentifierName {targetSourceType.SourceTypeIdentifierName}");
|
||||
}
|
||||
|
||||
return builder;
|
||||
|
||||
}
|
||||
|
||||
internal static SourceBuilder WriteMappedProperties(this SourceBuilder builder, System.Collections.Immutable.ImmutableArray<MappedMember> mappedProperties)
|
||||
{
|
||||
foreach (var item in mappedProperties)
|
||||
{
|
||||
string str = "";
|
||||
|
||||
if (item.NamedTypeSymbol != null)
|
||||
foreach (var named in item.NamedTypeSymbol?.TypeArguments)
|
||||
{
|
||||
str += $"typeToString: {named.ToString()} ";
|
||||
bool? containedTypeIsJsonEXtension = named?.HasAttribute(MappingContext.JsonExtensionAttributeSymbol);
|
||||
str += $"typeArgumentTypeIsJsonExtensioN: {containedTypeIsJsonEXtension.ToString()}";
|
||||
}
|
||||
|
||||
builder .WriteComment($" Name {item.Name}")
|
||||
.WriteComment($" Type {item.Type}")
|
||||
.WriteComment($" MappedSourcePropertyTypeName {item.MappedSourcePropertyTypeName}")
|
||||
.WriteComment($" IsEnumerable {item.IsEnumerable}")
|
||||
.WriteComment($" FullyQualifiedType {item.FullyQualifiedType}")
|
||||
.WriteComment($" EnumerableTypeArgument {item.EnumerableTypeArgument}")
|
||||
.WriteComment($" SourcePropertyName {item.SourcePropertyName}")
|
||||
.WriteComment($" TypeSymbol {item.FullyQualifiedType.ToString()}")
|
||||
.WriteComment($" isReadOnly {item.isReadOnly.ToString()}")
|
||||
.WriteComment($" isEnumerable {item.isEnumerable.ToString()}")
|
||||
.WriteComment($" INamedTypeSymbol {item.NamedTypeSymbol?.ToString()}")
|
||||
.WriteComment($" INamedTypeSymbolTypeArguments {str}")
|
||||
|
||||
.WriteLine();
|
||||
}
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -1,379 +0,0 @@
|
|||
using MapTo.Sources;
|
||||
using static MapTo.Sources.Constants;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Collections.Immutable;
|
||||
using System.Diagnostics;
|
||||
using Microsoft.CodeAnalysis;
|
||||
#pragma warning disable CS8602
|
||||
|
||||
namespace MapTo.Extensions
|
||||
{
|
||||
internal static class CommonSource
|
||||
{
|
||||
internal static SourceCode GenerateStructOrClass(this MappingModel model, string structOrClass)
|
||||
{
|
||||
const bool writeDebugInfo = false;
|
||||
|
||||
List<string> constructorHeaders = new List<string>();
|
||||
|
||||
using var builder = new SourceBuilder()
|
||||
.WriteLine(GeneratedFilesHeader)
|
||||
.WriteNullableContextOptionIf(model.Options.SupportNullableReferenceTypes)
|
||||
.WriteUsings(model.Usings)
|
||||
.WriteLine()
|
||||
// Namespace declaration
|
||||
.WriteLine($"namespace {model.Namespace}")
|
||||
.WriteOpeningBracket();
|
||||
|
||||
builder
|
||||
// Class declaration
|
||||
.WriteLine($"partial {structOrClass} {model.TypeIdentifierName}")
|
||||
.WriteOpeningBracket()
|
||||
.WriteLine()
|
||||
// Class body
|
||||
.GeneratePublicEmptyConstructor(model, ref constructorHeaders, true)
|
||||
.GeneratePublicConstructor(model, ref constructorHeaders)
|
||||
.GeneratePublicConstructor(model, ref constructorHeaders, true);
|
||||
|
||||
|
||||
if (model.IsTypeUpdatable) builder.GenerateUpdateMethod(model);
|
||||
if (model.IsJsonExtension) builder.WriteToJsonMethod(model);
|
||||
|
||||
|
||||
builder
|
||||
.WriteLine()
|
||||
// End class declaration
|
||||
.WriteClosingBracket()
|
||||
.WriteLine()
|
||||
// End namespace declaration
|
||||
.WriteClosingBracket();
|
||||
|
||||
return new(builder.ToString(), $"{model.Namespace}.{model.TypeIdentifierName}.g.cs");
|
||||
}
|
||||
|
||||
private static SourceBuilder GeneratePublicConstructor(this SourceBuilder builder, MappingModel model, ref List<string> constructorHeaders, bool filterNonMapped = false)
|
||||
{
|
||||
const string mappingContextParameterName = "context";
|
||||
|
||||
foreach (var targetSourceType in model.MappedSourceTypes)
|
||||
{
|
||||
var sourceClassParameterName = targetSourceType.SourceTypeIdentifierName.ToCamelCase();
|
||||
var baseConstructor = /*model.HasMappedBaseClass ? $" : base({mappingContextParameterName}, {sourceClassParameterName})" :*/ string.Empty;
|
||||
var stringBuilder = new StringBuilder();
|
||||
var otherProperties = new List<MappedMember>();
|
||||
|
||||
foreach (var property in targetSourceType.TypeProperties)
|
||||
{
|
||||
if (!targetSourceType.SourceProperties.IsMappedProperty(property) && !filterNonMapped)
|
||||
{
|
||||
stringBuilder.Append(", ");
|
||||
stringBuilder.Append($"{property.FullyQualifiedType} {property.SourcePropertyName.ToCamelCase()}");
|
||||
otherProperties.Add(property);
|
||||
}
|
||||
|
||||
}
|
||||
foreach (var property in targetSourceType.TypeFields)
|
||||
{
|
||||
if (!targetSourceType.SourceFields.IsMappedProperty(property) && !filterNonMapped)
|
||||
{
|
||||
stringBuilder.Append(", ");
|
||||
stringBuilder.Append($"{property.FullyQualifiedType} {property.SourcePropertyName.ToCamelCase()}");
|
||||
otherProperties.Add(property);
|
||||
}
|
||||
}
|
||||
|
||||
var readOnlyPropertiesArguments = stringBuilder.ToString();
|
||||
|
||||
var constructorHeader =
|
||||
$"public {model.TypeIdentifierName}({targetSourceType.SourceType} {sourceClassParameterName}{readOnlyPropertiesArguments}){baseConstructor}";
|
||||
|
||||
|
||||
bool hasAlreadyConstructor = false;
|
||||
|
||||
foreach (var header in constructorHeaders)
|
||||
{
|
||||
if(constructorHeader.Contains(header)) hasAlreadyConstructor = true;
|
||||
}
|
||||
|
||||
if (hasAlreadyConstructor) continue;
|
||||
|
||||
constructorHeaders.Add(constructorHeader);
|
||||
|
||||
builder
|
||||
.WriteLine(constructorHeader)
|
||||
.WriteOpeningBracket()
|
||||
.WriteAssignmentMethod(model, filterNonMapped ? null : otherProperties.ToArray().ToImmutableArray(), sourceClassParameterName, mappingContextParameterName, filterNonMapped);
|
||||
|
||||
builder.WriteClosingBracket()
|
||||
.WriteLine();
|
||||
}
|
||||
|
||||
// End constructor declaration
|
||||
return builder;
|
||||
}
|
||||
|
||||
private static SourceBuilder GeneratePublicEmptyConstructor(this SourceBuilder builder, MappingModel model, ref List<string> constructorHeaders, bool filterNonMapped = false)
|
||||
{
|
||||
const string mappingContextParameterName = "context";
|
||||
|
||||
foreach (var targetSourceType in model.MappedSourceTypes)
|
||||
{
|
||||
var baseConstructor = /*model.HasMappedBaseClass ? $" : base({mappingContextParameterName}, {sourceClassParameterName})" :*/ string.Empty;
|
||||
|
||||
var constructorHeader =
|
||||
$"public {model.TypeIdentifierName}(){baseConstructor}";
|
||||
|
||||
|
||||
bool hasAlreadyConstructor = false;
|
||||
|
||||
foreach (var header in constructorHeaders)
|
||||
{
|
||||
if(constructorHeader.Contains(header)) hasAlreadyConstructor = true;
|
||||
}
|
||||
|
||||
if (hasAlreadyConstructor) continue;
|
||||
|
||||
constructorHeaders.Add(constructorHeader);
|
||||
|
||||
builder
|
||||
.WriteLine(constructorHeader)
|
||||
.WriteOpeningBracket()
|
||||
.WriteClosingBracket()
|
||||
.WriteLine();
|
||||
}
|
||||
|
||||
// End constructor declaration
|
||||
return builder;
|
||||
}
|
||||
|
||||
private static bool IsMappedProperty(this System.Collections.Immutable.ImmutableArray<MappedMember> properties, MappedMember property)
|
||||
{
|
||||
|
||||
foreach (var prop in properties)
|
||||
{
|
||||
if (prop.Name == property.Name) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static SourceBuilder WriteToJsonMethod(this SourceBuilder builder, MappingModel model)
|
||||
{
|
||||
builder
|
||||
.WriteLine($"public string ToJson()")
|
||||
.WriteOpeningBracket()
|
||||
.WriteLine("var stringBuilder = new System.Text.StringBuilder();")
|
||||
.WriteLine(GetStringBuilderAppendNoInterpolation("{"));
|
||||
|
||||
foreach (var targetSourceType in model.MappedSourceTypes)
|
||||
{
|
||||
foreach (var property in targetSourceType.TypeProperties)
|
||||
{
|
||||
if (!property.isEnumerable)
|
||||
HandlePropertyEnumerable(builder, property);
|
||||
else
|
||||
{
|
||||
builder = WriteJsonField(builder, property);
|
||||
}
|
||||
}
|
||||
foreach (var property in targetSourceType.TypeFields)
|
||||
{
|
||||
if (!property.isEnumerable)
|
||||
HandleFieldEnumerable(builder, property);
|
||||
else
|
||||
{
|
||||
builder.WriteLine(GetStringBuilderAppend($"\\\"{property.Name.ToCamelCase()}\\\" : [{GetJsonArrayValue(property, ref builder)}],"));
|
||||
}
|
||||
}
|
||||
|
||||
builder.WriteLine(GetStringBuilderAppendNoInterpolation("}"));
|
||||
builder.WriteLine("return stringBuilder.ToString();");
|
||||
builder.WriteClosingBracket();
|
||||
}
|
||||
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
private static SourceBuilder WriteJsonField(SourceBuilder builder, MappedMember property)
|
||||
{
|
||||
builder.WriteLine(
|
||||
GetStringBuilderAppend(
|
||||
$"\\\"{property.Name.ToCamelCase()}\\\" : [{GetJsonArrayValue(property, ref builder)}],"));
|
||||
return builder;
|
||||
}
|
||||
|
||||
private static void HandleEnumerable(SourceBuilder builder, MappedMember property)
|
||||
{
|
||||
var symbol = property.ActualSymbol as IPropertySymbol;
|
||||
#pragma warning disable CS8602
|
||||
builder.WriteCommentArray(symbol.Parameters, nameof(symbol.Parameters));
|
||||
#pragma warning restore CS8602
|
||||
builder.WriteCommentArray(symbol.TypeCustomModifiers, nameof(symbol.TypeCustomModifiers));
|
||||
|
||||
builder.WriteComment($"Is enumerable {(property.ActualSymbol as IPropertySymbol).Parameters}");
|
||||
builder.WriteLine(
|
||||
GetStringBuilderAppend($"\\\"{property.Name.ToCamelCase()}\\\" : {GetJsonValue(property, builder)},"));
|
||||
}
|
||||
|
||||
|
||||
private static void HandleFieldEnumerable(SourceBuilder builder, MappedMember property)
|
||||
{
|
||||
HandleEnumerable(builder, property);
|
||||
}
|
||||
|
||||
private static void HandlePropertyEnumerable(SourceBuilder builder, MappedMember property)
|
||||
{
|
||||
HandleEnumerable(builder, property);
|
||||
}
|
||||
|
||||
private static string GetJsonArrayValue(MappedMember member, ref SourceBuilder builder)
|
||||
{
|
||||
if (member.isEnumerable)
|
||||
{
|
||||
// get underlying type (check if is a json extension)
|
||||
|
||||
builder.WriteLine("var arrStrBuilder = new StringBuilder();");
|
||||
|
||||
foreach (var named in member.NamedTypeSymbol?.TypeArguments!)
|
||||
{
|
||||
bool? containedTypeIsJsonEXtension = named?.HasAttribute(MappingContext.JsonExtensionAttributeSymbol);
|
||||
if (!containedTypeIsJsonEXtension.HasValue) continue;
|
||||
builder.WriteLine($"foreach (var v in {member.SourcePropertyName.ToString()})");
|
||||
builder.WriteOpeningBracket();
|
||||
builder.WriteLine("arrStrBuilder.Append(v.ToJson());");
|
||||
builder.WriteLine("arrStrBuilder.Append(\", \");");
|
||||
builder.WriteClosingBracket();
|
||||
}
|
||||
builder.WriteLine("arrStrBuilder.Remove(arrStrBuilder.Length -1, 1);");
|
||||
}
|
||||
|
||||
return "{arrStrBuilder.ToString()}";
|
||||
}
|
||||
private static string GetJsonValue(MappedMember member, SourceBuilder builder)
|
||||
{
|
||||
|
||||
if (member.FullyQualifiedType == "string") return $"\\\"{{{member.SourcePropertyName}}}\\\"";
|
||||
if (member.FullyQualifiedType is "int" or "double" or "float" or "long") return $"{{{member.SourcePropertyName}}}";
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
private static string GetStringBuilderAppend(string stringToAppend)
|
||||
{
|
||||
return $"stringBuilder.Append($\"{stringToAppend}\");";
|
||||
}
|
||||
private static string GetStringBuilderAppendNoInterpolation(string stringToAppend)
|
||||
{
|
||||
return $"stringBuilder.Append(\"{stringToAppend}\");";
|
||||
}
|
||||
|
||||
private static SourceBuilder WriteAssignmentMethod(this SourceBuilder builder, MappingModel model, System.Collections.Immutable.ImmutableArray<MappedMember>? otherProperties,
|
||||
string? sourceClassParameterName, string mappingContextParameterName, bool fromUpdate)
|
||||
{
|
||||
|
||||
List<MappedMember> _addedMembers = new List<MappedMember>();
|
||||
|
||||
foreach (var targetSourceType in model.MappedSourceTypes)
|
||||
{
|
||||
foreach (var property in targetSourceType.SourceProperties)
|
||||
{
|
||||
if (property.isReadOnly && fromUpdate) continue;
|
||||
if(_addedMembers.Contains(property)) continue;
|
||||
|
||||
|
||||
builder.WriteLine($"{property.Name} = {sourceClassParameterName}.{property.SourcePropertyName};");
|
||||
_addedMembers.Add(property);
|
||||
|
||||
}
|
||||
|
||||
foreach (var property in targetSourceType.SourceFields)
|
||||
{
|
||||
if (property.isReadOnly && fromUpdate) continue;
|
||||
if(_addedMembers.Contains(property)) continue;
|
||||
|
||||
builder.WriteLine($"{property.Name} = {sourceClassParameterName}.{property.SourcePropertyName};");
|
||||
_addedMembers.Add(property);
|
||||
|
||||
}
|
||||
|
||||
if (otherProperties == null) return builder;
|
||||
|
||||
foreach (var property in otherProperties)
|
||||
{
|
||||
if(_addedMembers.Contains(property)) continue;
|
||||
|
||||
builder.WriteLine(property.MappedSourcePropertyTypeName is null
|
||||
? $"{property.Name} = {property.SourcePropertyName.ToCamelCase()};"
|
||||
: "");
|
||||
_addedMembers.Add(property);
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
return builder;
|
||||
|
||||
}
|
||||
|
||||
|
||||
private static SourceBuilder GenerateUpdateMethod(this SourceBuilder builder, MappingModel model)
|
||||
{
|
||||
|
||||
foreach (var targetSourceType in model.MappedSourceTypes)
|
||||
{
|
||||
var sourceClassParameterName = targetSourceType.SourceTypeIdentifierName.ToCamelCase();
|
||||
|
||||
builder
|
||||
.GenerateUpdaterMethodsXmlDocs(model, sourceClassParameterName, targetSourceType)
|
||||
.WriteLine($"public void Update({targetSourceType.SourceType} {sourceClassParameterName})")
|
||||
.WriteOpeningBracket()
|
||||
.WriteAssignmentMethod(model, null, sourceClassParameterName, "context", true)
|
||||
.WriteClosingBracket()
|
||||
.WriteLine();
|
||||
}
|
||||
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
private static SourceBuilder GenerateUpdaterMethodsXmlDocs(this SourceBuilder builder, MappingModel model, string sourceClassParameterName,
|
||||
MappedSourceType mappedSourceType)
|
||||
{
|
||||
if (!model.Options.GenerateXmlDocument)
|
||||
{
|
||||
return builder;
|
||||
}
|
||||
builder
|
||||
.WriteLine("/// <summary>")
|
||||
.WriteLine($"/// Updates <see cref=\"{model.TypeIdentifierName}\"/> and sets its participating properties")
|
||||
.WriteLine($"/// using the property values from <paramref name=\"{sourceClassParameterName}\"/>.")
|
||||
.WriteLine("/// </summary>")
|
||||
.WriteLine($"/// <param name=\"{sourceClassParameterName}\">The instance of <see cref=\"{mappedSourceType.SourceType}\"/> to use as source.</param>");
|
||||
return builder;
|
||||
}
|
||||
|
||||
private static SourceBuilder GenerateEnumerableJsonSourceTypeExtensionMethod(this SourceBuilder builder, MappingModel model)
|
||||
{
|
||||
|
||||
foreach (var targetSourceType in model.MappedSourceTypes)
|
||||
{
|
||||
var sourceClassParameterName = targetSourceType.SourceTypeIdentifierName.ToCamelCase();
|
||||
|
||||
builder
|
||||
.WriteLineIf(model.Options.SupportNullableStaticAnalysis, $"[return: NotNullIfNotNull(\"{sourceClassParameterName}\")]")
|
||||
.WriteLine($"{model.Options.GeneratedMethodsAccessModifier.ToLowercaseString()} static string ToJson(this IEnumerable<{targetSourceType.SourceType}{model.Options.NullableReferenceSyntax}> {sourceClassParameterName}List)")
|
||||
.WriteOpeningBracket()
|
||||
.WriteLine($"return {sourceClassParameterName} == null ? null : new {model.TypeIdentifierName}({sourceClassParameterName});")
|
||||
.WriteClosingBracket();
|
||||
}
|
||||
|
||||
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
<Project>
|
||||
<ItemGroup>
|
||||
<CompilerVisibleProperty Include="MapTo_ConstructorAccessModifier" />
|
||||
</ItemGroup>
|
||||
</Project>
|
|
@ -1,81 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using MapTo.Extensions;
|
||||
using MapTo.Sources;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
|
||||
namespace MapTo
|
||||
{
|
||||
/// <summary>
|
||||
/// MapTo source generator.
|
||||
/// </summary>
|
||||
[Generator]
|
||||
public class MapToGenerator : ISourceGenerator
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public void Initialize(GeneratorInitializationContext context)
|
||||
{
|
||||
|
||||
context.RegisterForSyntaxNotifications(() => new MapToSyntaxReceiver());
|
||||
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Execute(GeneratorExecutionContext context)
|
||||
{
|
||||
try
|
||||
{
|
||||
var options = SourceGenerationOptions.From(context);
|
||||
|
||||
var compilation = context.Compilation
|
||||
.AddSource(ref context, UseUpdateAttributeSource.Generate(options))
|
||||
.AddSource(ref context, JsonExtensionAttributeSource.Generate(options))
|
||||
.AddSource(ref context, MapFromAttributeSource.Generate(options))
|
||||
.AddSource(ref context, IgnoreMemberAttributeSource.Generate(options))
|
||||
.AddSource(ref context, ITypeConverterSource.Generate(options))
|
||||
.AddSource(ref context, MapTypeConverterAttributeSource.Generate(options))
|
||||
.AddSource(ref context, MapPropertyAttributeSource.Generate(options))
|
||||
.AddSource(ref context, MappingContextSource.Generate(options));
|
||||
|
||||
if (context.SyntaxReceiver is MapToSyntaxReceiver receiver && receiver.CandidateTypes.Any())
|
||||
{
|
||||
AddGeneratedMappingsClasses(context, compilation, receiver.CandidateTypes, options);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine(ex);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private static void AddGeneratedMappingsClasses(GeneratorExecutionContext context, Compilation compilation, IEnumerable<TypeDeclarationSyntax> candidateTypes, SourceGenerationOptions options)
|
||||
{
|
||||
foreach (var typeDeclarationSyntax in candidateTypes)
|
||||
{
|
||||
var mappingContext = MappingContext.Create(compilation, options, typeDeclarationSyntax);
|
||||
|
||||
mappingContext.Diagnostics.ForEach(context.ReportDiagnostic);
|
||||
|
||||
if (mappingContext.Model is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var (source, hintName) = typeDeclarationSyntax switch
|
||||
{
|
||||
StructDeclarationSyntax => MapStructSource.Generate(mappingContext.Model),
|
||||
ClassDeclarationSyntax => MapClassSource.Generate(mappingContext.Model),
|
||||
RecordDeclarationSyntax => MapRecordSource.Generate(mappingContext.Model),
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
};
|
||||
|
||||
context.AddSource(hintName, source);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,39 +0,0 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using MapTo.Sources;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
|
||||
namespace MapTo
|
||||
{
|
||||
internal class MapToSyntaxReceiver : ISyntaxReceiver
|
||||
{
|
||||
public List<TypeDeclarationSyntax> CandidateTypes { get; } = new();
|
||||
|
||||
/// <inheritdoc />
|
||||
public void OnVisitSyntaxNode(SyntaxNode syntaxNode)
|
||||
{
|
||||
if (syntaxNode is not TypeDeclarationSyntax { AttributeLists: { Count: >= 1 } attributes } typeDeclarationSyntax)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var attributeSyntax = attributes
|
||||
.SelectMany(a => a.Attributes)
|
||||
.FirstOrDefault(a => a.Name is
|
||||
IdentifierNameSyntax { Identifier: { ValueText: MapFromAttributeSource.AttributeName } } // For: [MapFrom]
|
||||
or
|
||||
QualifiedNameSyntax // For: [MapTo.MapFrom]
|
||||
{
|
||||
Left: IdentifierNameSyntax { Identifier: { ValueText: Constants.RootNamespace } },
|
||||
Right: IdentifierNameSyntax { Identifier: { ValueText: MapFromAttributeSource.AttributeName } }
|
||||
}
|
||||
);
|
||||
|
||||
if (attributeSyntax is not null)
|
||||
{
|
||||
CandidateTypes.Add(typeDeclarationSyntax);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,630 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using MapTo.Extensions;
|
||||
using MapTo.Sources;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
#pragma warning disable CS8602
|
||||
|
||||
namespace MapTo
|
||||
{
|
||||
internal static class MappingContextExtensions
|
||||
{
|
||||
internal static ImmutableArray<MappedMember> GetReadOnlyMappedProperties(this ImmutableArray<MappedMember> mappedProperties) => mappedProperties.Where(p => p.isReadOnly).ToImmutableArray()!;
|
||||
internal static ImmutableArray<MappedMember> GetWritableMappedProperties(this ImmutableArray<MappedMember> mappedProperties) => mappedProperties.Where(p => !p.isReadOnly).ToImmutableArray()!;
|
||||
}
|
||||
|
||||
internal abstract class MappingContext
|
||||
{
|
||||
private readonly List<SymbolDisplayPart> _ignoredNamespaces;
|
||||
|
||||
protected MappingContext(Compilation compilation, SourceGenerationOptions sourceGenerationOptions, TypeDeclarationSyntax typeSyntax)
|
||||
{
|
||||
_ignoredNamespaces = new();
|
||||
Diagnostics = ImmutableArray<Diagnostic>.Empty;
|
||||
Usings = ImmutableArray.Create("System", Constants.RootNamespace);
|
||||
SourceGenerationOptions = sourceGenerationOptions;
|
||||
TypeSyntax = typeSyntax;
|
||||
Compilation = compilation;
|
||||
|
||||
IgnoreMemberAttributeTypeSymbol = compilation.GetTypeByMetadataNameOrThrow(IgnoreMemberAttributeSource.FullyQualifiedName);
|
||||
MapTypeConverterAttributeTypeSymbol = compilation.GetTypeByMetadataNameOrThrow(MapTypeConverterAttributeSource.FullyQualifiedName);
|
||||
TypeConverterInterfaceTypeSymbol = compilation.GetTypeByMetadataNameOrThrow(ITypeConverterSource.FullyQualifiedName);
|
||||
MapPropertyAttributeTypeSymbol = compilation.GetTypeByMetadataNameOrThrow(MapPropertyAttributeSource.FullyQualifiedName);
|
||||
MapFromAttributeTypeSymbol = compilation.GetTypeByMetadataNameOrThrow(MapFromAttributeSource.FullyQualifiedName);
|
||||
UseUpdateAttributeTypeSymbol = compilation.GetTypeByMetadataNameOrThrow(UseUpdateAttributeSource.FullyQualifiedName);
|
||||
JsonExtensionAttributeSymbol = compilation.GetTypeByMetadataNameOrThrow(JsonExtensionAttributeSource.FullyQualifiedName);
|
||||
MappingContextTypeSymbol = compilation.GetTypeByMetadataNameOrThrow(MappingContextSource.FullyQualifiedName);
|
||||
|
||||
AddUsingIfRequired(sourceGenerationOptions.SupportNullableStaticAnalysis, "System.Diagnostics.CodeAnalysis");
|
||||
}
|
||||
|
||||
public ImmutableArray<Diagnostic> Diagnostics { get; private set; }
|
||||
|
||||
public MappingModel? Model { get; private set; }
|
||||
|
||||
protected Compilation Compilation { get; }
|
||||
|
||||
protected INamedTypeSymbol IgnoreMemberAttributeTypeSymbol { get; }
|
||||
|
||||
protected INamedTypeSymbol MapFromAttributeTypeSymbol { get; }
|
||||
|
||||
protected INamedTypeSymbol UseUpdateAttributeTypeSymbol { get; }
|
||||
|
||||
public static INamedTypeSymbol JsonExtensionAttributeSymbol { get; set; } = null!;
|
||||
|
||||
protected INamedTypeSymbol MappingContextTypeSymbol { get; }
|
||||
|
||||
protected INamedTypeSymbol MapPropertyAttributeTypeSymbol { get; }
|
||||
|
||||
protected INamedTypeSymbol MapTypeConverterAttributeTypeSymbol { get; }
|
||||
|
||||
protected SourceGenerationOptions SourceGenerationOptions { get; }
|
||||
|
||||
protected INamedTypeSymbol TypeConverterInterfaceTypeSymbol { get; }
|
||||
|
||||
protected TypeDeclarationSyntax TypeSyntax { get; }
|
||||
|
||||
protected ImmutableArray<string> Usings { get; private set; }
|
||||
|
||||
public static MappingContext Create(Compilation compilation, SourceGenerationOptions sourceGenerationOptions, TypeDeclarationSyntax typeSyntax)
|
||||
{
|
||||
|
||||
MappingContext context = typeSyntax switch
|
||||
{
|
||||
StructDeclarationSyntax => new StructMappingContext(compilation, sourceGenerationOptions, typeSyntax),
|
||||
ClassDeclarationSyntax => new ClassMappingContext(compilation, sourceGenerationOptions, typeSyntax),
|
||||
RecordDeclarationSyntax => new RecordMappingContext(compilation, sourceGenerationOptions, typeSyntax),
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
};
|
||||
|
||||
context.Model = context.CreateMappingModel();
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
protected void AddDiagnostic(Diagnostic diagnostic)
|
||||
{
|
||||
Diagnostics = Diagnostics.Add(diagnostic);
|
||||
}
|
||||
|
||||
protected void AddUsingIfRequired(ISymbol? namedTypeSymbol) =>
|
||||
AddUsingIfRequired(namedTypeSymbol?.ContainingNamespace.IsGlobalNamespace == false, namedTypeSymbol?.ContainingNamespace);
|
||||
|
||||
protected void AddUsingIfRequired(bool condition, INamespaceSymbol? ns) =>
|
||||
AddUsingIfRequired(condition && ns is not null && !_ignoredNamespaces.Contains(ns.ToDisplayParts().First()), ns?.ToDisplayString());
|
||||
|
||||
protected void AddUsingIfRequired(bool condition, string? ns)
|
||||
{
|
||||
if (ns is not null && condition && ns != TypeSyntax.GetNamespace() && !Usings.Contains(ns))
|
||||
{
|
||||
Usings = Usings.Add(ns);
|
||||
}
|
||||
}
|
||||
|
||||
protected IPropertySymbol? FindSourceProperty(IEnumerable<IPropertySymbol> sourceProperties, ISymbol property)
|
||||
{
|
||||
var propertyName = property
|
||||
.GetAttribute(MapPropertyAttributeTypeSymbol)
|
||||
?.NamedArguments
|
||||
.FirstOrDefault(a => a.Key == MapPropertyAttributeSource.SourcePropertyNamePropertyName)
|
||||
.Value.Value as string ?? property.Name;
|
||||
|
||||
return sourceProperties.FirstOrDefault(p => p.Name == propertyName);
|
||||
}
|
||||
protected IFieldSymbol? FindSourceField(IEnumerable<IFieldSymbol> sourceProperties, ISymbol property)
|
||||
{
|
||||
var propertyName = property
|
||||
.GetAttribute(MapPropertyAttributeTypeSymbol)
|
||||
?.NamedArguments
|
||||
.FirstOrDefault(a => a.Key == MapPropertyAttributeSource.SourcePropertyNamePropertyName)
|
||||
.Value.Value as string ?? property.Name;
|
||||
|
||||
return sourceProperties.FirstOrDefault(p => p.Name == propertyName);
|
||||
}
|
||||
|
||||
protected abstract ImmutableArray<MappedMember> GetSourceMappedProperties(ITypeSymbol typeSymbol, ITypeSymbol sourceTypeSymbol, bool isInheritFromMappedBaseClass);
|
||||
protected abstract ImmutableArray<MappedMember> GetTypeMappedProperties(ITypeSymbol typeSymbol, ITypeSymbol sourceTypeSymbol, bool isInheritFromMappedBaseClass);
|
||||
|
||||
|
||||
protected abstract ImmutableArray<MappedMember> GetSourceMappedFields(ITypeSymbol typeSymbol, ITypeSymbol sourceTypeSymbol, bool isInheritFromMappedBaseClass);
|
||||
protected abstract ImmutableArray<MappedMember> GetTypeMappedFields(ITypeSymbol typeSymbol, ITypeSymbol sourceTypeSymbol, bool isInheritFromMappedBaseClass);
|
||||
|
||||
|
||||
protected ImmutableArray<INamedTypeSymbol> GetSourceTypeSymbol(TypeDeclarationSyntax typeDeclarationSyntax, SemanticModel? semanticModel = null)
|
||||
{
|
||||
var attributeData = typeDeclarationSyntax.GetAttribute(MapFromAttributeSource.AttributeName);
|
||||
var sourceSymbol = GetSourceTypeSymbol(attributeData, semanticModel);
|
||||
return sourceSymbol;
|
||||
}
|
||||
|
||||
// we need two possible InamedTypeSymbol
|
||||
protected ImmutableArray<INamedTypeSymbol> GetSourceTypeSymbol(SyntaxNode? attributeSyntax, SemanticModel? semanticModel = null)
|
||||
{
|
||||
if (attributeSyntax is null)
|
||||
{
|
||||
return new ImmutableArray<INamedTypeSymbol>(){};
|
||||
}
|
||||
|
||||
semanticModel ??= Compilation.GetSemanticModel(attributeSyntax.SyntaxTree);
|
||||
var descendentNodes = attributeSyntax
|
||||
.DescendantNodes();
|
||||
|
||||
var sourceTypeExpressionSyntax = descendentNodes
|
||||
.OfType<TypeOfExpressionSyntax>()
|
||||
.ToImmutableArray();
|
||||
|
||||
// cast
|
||||
var resultList = new List<INamedTypeSymbol>();
|
||||
for (int i = 0; i < sourceTypeExpressionSyntax.Length; i++)
|
||||
{
|
||||
var sourceTypeExpression = sourceTypeExpressionSyntax[i];
|
||||
if (semanticModel.GetTypeInfo(sourceTypeExpression.Type).Type is INamedTypeSymbol namedTypeSymbol)
|
||||
{
|
||||
resultList.Add(namedTypeSymbol);
|
||||
}
|
||||
}
|
||||
|
||||
return resultList.ToImmutableArray();
|
||||
}
|
||||
|
||||
protected bool IsTypeInheritFromMappedBaseClass(SemanticModel semanticModel)
|
||||
{
|
||||
return TypeSyntax.BaseList is not null && TypeSyntax.BaseList.Types
|
||||
.Select(t => semanticModel.GetTypeInfo(t.Type).Type)
|
||||
.Any(t => t?.GetAttribute(MapFromAttributeTypeSymbol) != null);
|
||||
}
|
||||
|
||||
protected bool IsTypeUpdatable()
|
||||
{
|
||||
return TypeSyntax.GetAttribute("UseUpdate") != null;
|
||||
}
|
||||
protected bool HasJsonExtension()
|
||||
{
|
||||
return TypeSyntax.GetAttribute("JsonExtension") != null;
|
||||
}
|
||||
protected virtual MappedMember? MapProperty(ISymbol sourceTypeSymbol, IReadOnlyCollection<IPropertySymbol> sourceProperties, ISymbol property)
|
||||
{
|
||||
var sourceProperty = FindSourceProperty(sourceProperties, property);
|
||||
if (sourceProperty is null || !property.TryGetTypeSymbol(out var propertyType))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
string? converterFullyQualifiedName = null;
|
||||
var converterParameters = ImmutableArray<string>.Empty;
|
||||
ITypeSymbol? mappedSourcePropertyType = null;
|
||||
ITypeSymbol? enumerableTypeArgumentType = null;
|
||||
|
||||
if (!Compilation.HasCompatibleTypes(sourceProperty, property))
|
||||
{
|
||||
if (!TryGetMapTypeConverterForProperty(property, sourceProperty, out converterFullyQualifiedName, out converterParameters) &&
|
||||
!TryGetNestedObjectMappings(property, out mappedSourcePropertyType, out enumerableTypeArgumentType))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
AddUsingIfRequired(propertyType);
|
||||
AddUsingIfRequired(enumerableTypeArgumentType);
|
||||
AddUsingIfRequired(mappedSourcePropertyType);
|
||||
|
||||
INamedTypeSymbol? namedType;
|
||||
var isEnumerable = IsEnumerable(property, out namedType);
|
||||
|
||||
|
||||
return new MappedMember(
|
||||
property.Name,
|
||||
property.GetTypeSymbol().ToString(),
|
||||
ToQualifiedDisplayName(propertyType) ?? propertyType.Name,
|
||||
converterFullyQualifiedName,
|
||||
converterParameters.ToImmutableArray(),
|
||||
sourceProperty.Name,
|
||||
ToQualifiedDisplayName(mappedSourcePropertyType),
|
||||
ToQualifiedDisplayName(enumerableTypeArgumentType),
|
||||
property,
|
||||
namedType,
|
||||
isEnumerable,
|
||||
(property as IPropertySymbol).IsReadOnly);
|
||||
;
|
||||
}
|
||||
|
||||
protected virtual MappedMember? MapField(ISymbol sourceTypeSymbol, IReadOnlyCollection<IFieldSymbol> sourceProperties, ISymbol property)
|
||||
{
|
||||
var sourceProperty = FindSourceField(sourceProperties, property);
|
||||
if (sourceProperty is null || !property.TryGetTypeSymbol(out var propertyType))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (property is IFieldSymbol symbol)
|
||||
{
|
||||
if (symbol.AssociatedSymbol != null) return null;
|
||||
}
|
||||
|
||||
string? converterFullyQualifiedName = null;
|
||||
var converterParameters = ImmutableArray<string>.Empty;
|
||||
ITypeSymbol? mappedSourcePropertyType = null;
|
||||
ITypeSymbol? enumerableTypeArgumentType = null;
|
||||
|
||||
if (!Compilation.HasCompatibleTypes(sourceProperty, property))
|
||||
{
|
||||
if (!TryGetMapTypeConverterForField(property, sourceProperty, out converterFullyQualifiedName, out converterParameters) &&
|
||||
!TryGetNestedObjectMappings(property, out mappedSourcePropertyType, out enumerableTypeArgumentType))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
AddUsingIfRequired(propertyType);
|
||||
AddUsingIfRequired(enumerableTypeArgumentType);
|
||||
AddUsingIfRequired(mappedSourcePropertyType);
|
||||
|
||||
|
||||
INamedTypeSymbol? namedType;
|
||||
var isEnumerable = IsEnumerable(property, out namedType);
|
||||
|
||||
return new MappedMember(
|
||||
property.Name,
|
||||
property.GetTypeSymbol()?.ToString() ?? throw new InvalidOperationException(),
|
||||
ToQualifiedDisplayName(propertyType) ?? propertyType.Name,
|
||||
converterFullyQualifiedName,
|
||||
converterParameters.ToImmutableArray(),
|
||||
sourceProperty.Name,
|
||||
ToQualifiedDisplayName(mappedSourcePropertyType),
|
||||
ToQualifiedDisplayName(enumerableTypeArgumentType),
|
||||
property,
|
||||
namedType,
|
||||
isEnumerable,
|
||||
(property as IFieldSymbol).IsReadOnly);
|
||||
;
|
||||
}
|
||||
protected virtual MappedMember? MapPropertySimple(ISymbol sourceTypeSymbol, ISymbol property)
|
||||
{
|
||||
if (!property.TryGetTypeSymbol(out var propertyType))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
string? converterFullyQualifiedName = null;
|
||||
var converterParameters = ImmutableArray<string>.Empty;
|
||||
ITypeSymbol? mappedSourcePropertyType = null;
|
||||
ITypeSymbol? enumerableTypeArgumentType = null;
|
||||
|
||||
|
||||
AddUsingIfRequired(propertyType);
|
||||
AddUsingIfRequired(enumerableTypeArgumentType);
|
||||
AddUsingIfRequired(mappedSourcePropertyType);
|
||||
|
||||
INamedTypeSymbol? namedType;
|
||||
var isEnumerable = IsEnumerable(property, out namedType);
|
||||
|
||||
return new MappedMember(
|
||||
property.Name,
|
||||
property.GetTypeSymbol().ToString(),
|
||||
ToQualifiedDisplayName(propertyType) ?? propertyType.Name,
|
||||
converterFullyQualifiedName,
|
||||
converterParameters.ToImmutableArray(),
|
||||
property.Name,
|
||||
ToQualifiedDisplayName(mappedSourcePropertyType),
|
||||
ToQualifiedDisplayName(enumerableTypeArgumentType),
|
||||
property,
|
||||
namedType,
|
||||
isEnumerable,
|
||||
#pragma warning disable CS8602
|
||||
(property as IPropertySymbol).IsReadOnly);
|
||||
#pragma warning restore CS8602
|
||||
;
|
||||
}
|
||||
|
||||
protected virtual MappedMember? MapFieldSimple(ISymbol sourceTypeSymbol, ISymbol property)
|
||||
{
|
||||
if (!property.TryGetTypeSymbol(out var propertyType))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if(property is IFieldSymbol symbol)
|
||||
{
|
||||
if (symbol.AssociatedSymbol != null) return null;
|
||||
}
|
||||
|
||||
|
||||
string? converterFullyQualifiedName = null;
|
||||
var converterParameters = ImmutableArray<string>.Empty;
|
||||
ITypeSymbol? mappedSourcePropertyType = null;
|
||||
ITypeSymbol? enumerableTypeArgumentType = null;
|
||||
|
||||
|
||||
AddUsingIfRequired(propertyType);
|
||||
AddUsingIfRequired(enumerableTypeArgumentType);
|
||||
AddUsingIfRequired(mappedSourcePropertyType);
|
||||
|
||||
INamedTypeSymbol? namedType;
|
||||
var isEnumerable = IsEnumerable(property, out namedType);
|
||||
|
||||
|
||||
return new MappedMember(
|
||||
property.Name,
|
||||
property.GetTypeSymbol().ToString(),
|
||||
ToQualifiedDisplayName(propertyType) ?? propertyType.Name,
|
||||
converterFullyQualifiedName,
|
||||
converterParameters.ToImmutableArray(),
|
||||
property.Name,
|
||||
ToQualifiedDisplayName(mappedSourcePropertyType),
|
||||
ToQualifiedDisplayName(enumerableTypeArgumentType),
|
||||
property,
|
||||
namedType,
|
||||
isEnumerable,
|
||||
(property as IFieldSymbol).IsReadOnly);
|
||||
;
|
||||
}
|
||||
protected bool TryGetMapTypeConverterForProperty(ISymbol property, IPropertySymbol sourceProperty, out string? converterFullyQualifiedName,
|
||||
out ImmutableArray<string> converterParameters)
|
||||
{
|
||||
converterFullyQualifiedName = null;
|
||||
converterParameters = ImmutableArray<string>.Empty;
|
||||
|
||||
if (!Diagnostics.IsEmpty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var typeConverterAttribute = property.GetAttribute(MapTypeConverterAttributeTypeSymbol);
|
||||
if (typeConverterAttribute?.ConstructorArguments.First().Value is not INamedTypeSymbol converterTypeSymbol)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var baseInterface = GetTypeConverterBaseInterfaceForProperty(converterTypeSymbol, property, sourceProperty);
|
||||
if (baseInterface is null)
|
||||
{
|
||||
AddDiagnostic(DiagnosticsFactory.InvalidTypeConverterGenericTypesError(property, sourceProperty));
|
||||
return false;
|
||||
}
|
||||
|
||||
converterFullyQualifiedName = converterTypeSymbol.ToDisplayString();
|
||||
converterParameters = GetTypeConverterParameters(typeConverterAttribute);
|
||||
return true;
|
||||
}
|
||||
protected bool TryGetMapTypeConverterForField(ISymbol property, IFieldSymbol sourceProperty, out string? converterFullyQualifiedName,
|
||||
out ImmutableArray<string> converterParameters)
|
||||
{
|
||||
converterFullyQualifiedName = null;
|
||||
converterParameters = ImmutableArray<string>.Empty;
|
||||
|
||||
if (!Diagnostics.IsEmpty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var typeConverterAttribute = property.GetAttribute(MapTypeConverterAttributeTypeSymbol);
|
||||
if (typeConverterAttribute?.ConstructorArguments.First().Value is not INamedTypeSymbol converterTypeSymbol)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var baseInterface = GetTypeConverterBaseInterfaceForField(converterTypeSymbol, property, sourceProperty);
|
||||
if (baseInterface is null)
|
||||
{
|
||||
//AddDiagnostic(DiagnosticsFactory.InvalidTypeConverterGenericTypesError(property, null));
|
||||
return false;
|
||||
}
|
||||
|
||||
converterFullyQualifiedName = converterTypeSymbol.ToDisplayString();
|
||||
converterParameters = GetTypeConverterParameters(typeConverterAttribute);
|
||||
return true;
|
||||
}
|
||||
protected bool TryGetNestedObjectMappings(ISymbol property, out ITypeSymbol? mappedSourcePropertyType, out ITypeSymbol? enumerableTypeArgument)
|
||||
{
|
||||
mappedSourcePropertyType = null;
|
||||
enumerableTypeArgument = null;
|
||||
|
||||
if (!Diagnostics.IsEmpty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!property.TryGetTypeSymbol(out var propertyType))
|
||||
{
|
||||
AddDiagnostic(DiagnosticsFactory.NoMatchingPropertyTypeFoundError(property));
|
||||
return false;
|
||||
}
|
||||
|
||||
var mapFromAttribute = propertyType.GetAttribute(MapFromAttributeTypeSymbol);
|
||||
if (mapFromAttribute is null &&
|
||||
propertyType is INamedTypeSymbol namedTypeSymbol &&
|
||||
!propertyType.IsPrimitiveType() &&
|
||||
(Compilation.IsGenericEnumerable(propertyType) || propertyType.AllInterfaces.Any(i => Compilation.IsGenericEnumerable(i))))
|
||||
{
|
||||
enumerableTypeArgument = namedTypeSymbol.TypeArguments.First();
|
||||
mapFromAttribute = enumerableTypeArgument.GetAttribute(MapFromAttributeTypeSymbol);
|
||||
}
|
||||
|
||||
mappedSourcePropertyType = mapFromAttribute?.ConstructorArguments.First().Value as INamedTypeSymbol;
|
||||
|
||||
if (mappedSourcePropertyType is null && enumerableTypeArgument is null)
|
||||
{
|
||||
AddDiagnostic(DiagnosticsFactory.NoMatchingPropertyTypeFoundError(property));
|
||||
}
|
||||
|
||||
return Diagnostics.IsEmpty();
|
||||
}
|
||||
protected bool IsEnumerable(ISymbol property, out INamedTypeSymbol? namedTypeSymbolResult)
|
||||
{
|
||||
|
||||
if (!property.TryGetTypeSymbol(out var propertyType))
|
||||
{
|
||||
AddDiagnostic(DiagnosticsFactory.NoMatchingPropertyTypeFoundError(property));
|
||||
namedTypeSymbolResult = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
propertyType is INamedTypeSymbol namedTypeSymbol &&
|
||||
!propertyType.IsPrimitiveType() &&
|
||||
(Compilation.IsGenericEnumerable(propertyType) || propertyType.AllInterfaces.Any(i => Compilation.IsGenericEnumerable(i))))
|
||||
{
|
||||
namedTypeSymbolResult = namedTypeSymbol;
|
||||
return true;
|
||||
}
|
||||
namedTypeSymbolResult = null;
|
||||
return false;
|
||||
}
|
||||
private static ImmutableArray<string> GetTypeConverterParameters(AttributeData typeConverterAttribute)
|
||||
{
|
||||
var converterParameter = typeConverterAttribute.ConstructorArguments.Skip(1).FirstOrDefault();
|
||||
return converterParameter.IsNull
|
||||
? ImmutableArray<string>.Empty
|
||||
: converterParameter.Values.Where(v => v.Value is not null).Select(v => v.Value!.ToSourceCodeString()).ToImmutableArray();
|
||||
}
|
||||
|
||||
private MappingModel? CreateMappingModel()
|
||||
{
|
||||
var semanticModel = Compilation.GetSemanticModel(TypeSyntax.SyntaxTree);
|
||||
if (semanticModel.GetDeclaredSymbol(TypeSyntax) is not INamedTypeSymbol typeSymbol)
|
||||
{
|
||||
AddDiagnostic(DiagnosticsFactory.TypeNotFoundError(TypeSyntax.GetLocation(), TypeSyntax.Identifier.ValueText));
|
||||
return null;
|
||||
}
|
||||
|
||||
// We can have 2 sources...
|
||||
|
||||
var sourceTypeSymbols = GetSourceTypeSymbol(TypeSyntax, semanticModel);
|
||||
|
||||
|
||||
// lets pick one for now, and then think what to do with the second one
|
||||
if (sourceTypeSymbols.IsDefaultOrEmpty)
|
||||
{
|
||||
AddDiagnostic(DiagnosticsFactory.MapFromAttributeNotFoundError(TypeSyntax.GetLocation()));
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
var typeIdentifierName = TypeSyntax.GetIdentifierName();
|
||||
var isTypeInheritFromMappedBaseClass = IsTypeInheritFromMappedBaseClass(semanticModel);
|
||||
var isTypeUpdatable = true; //IsTypeUpdatable();
|
||||
var hasJsonExtension = false; // HasJsonExtension();
|
||||
|
||||
List<MappedSourceType> mappedSourceTypes = new List<MappedSourceType>();
|
||||
|
||||
foreach (var sourceTypeSymbol in sourceTypeSymbols)
|
||||
{
|
||||
_ignoredNamespaces.Add(sourceTypeSymbol.ContainingNamespace.ToDisplayParts().First());
|
||||
var sourceTypeIdentifierName = sourceTypeSymbol.Name;
|
||||
var shouldGenerateSecondaryConstructor = ShouldGenerateSecondaryConstructor(semanticModel, sourceTypeSymbol);
|
||||
var mappedProperties = GetSourceMappedProperties(typeSymbol, sourceTypeSymbol, isTypeInheritFromMappedBaseClass);
|
||||
var mappedFields = GetSourceMappedFields(typeSymbol, sourceTypeSymbol, isTypeInheritFromMappedBaseClass);
|
||||
AddUsingIfRequired(mappedProperties.Any(p => p.IsEnumerable), "System.Linq");
|
||||
var allProperties = GetTypeMappedProperties(sourceTypeSymbol, typeSymbol , isTypeInheritFromMappedBaseClass);
|
||||
var allFields = GetTypeMappedFields(sourceTypeSymbol, typeSymbol, isTypeInheritFromMappedBaseClass);
|
||||
|
||||
mappedSourceTypes.Add(new MappedSourceType(
|
||||
sourceTypeSymbol.ContainingNamespace.ToDisplayString(),
|
||||
sourceTypeIdentifierName,
|
||||
sourceTypeSymbol.ToDisplayString(),
|
||||
mappedProperties, mappedFields, allProperties, allFields, shouldGenerateSecondaryConstructor));
|
||||
}
|
||||
|
||||
//var sourceTypeSymbol = sourceTypeSymbols[0];
|
||||
|
||||
// Pick first one to avoid errors. TODO: Make possible to use different source types
|
||||
|
||||
|
||||
|
||||
|
||||
/*if (!mappedProperties.Any())
|
||||
{
|
||||
AddDiagnostic(DiagnosticsFactory.NoMatchingPropertyFoundError(TypeSyntax.GetLocation(), typeSymbol, sourceTypeSymbol));
|
||||
return null;
|
||||
}*/
|
||||
|
||||
return new MappingModel(
|
||||
SourceGenerationOptions,
|
||||
TypeSyntax.GetNamespace(),
|
||||
TypeSyntax.Modifiers,
|
||||
TypeSyntax.Keyword.Text,
|
||||
typeIdentifierName,
|
||||
isTypeUpdatable,
|
||||
hasJsonExtension,
|
||||
mappedSourceTypes.ToImmutableArray(),
|
||||
isTypeInheritFromMappedBaseClass,
|
||||
Usings);
|
||||
}
|
||||
|
||||
|
||||
|
||||
private INamedTypeSymbol? GetTypeConverterBaseInterfaceForProperty(ITypeSymbol converterTypeSymbol, ISymbol property, IPropertySymbol sourceProperty)
|
||||
{
|
||||
if (!property.TryGetTypeSymbol(out var propertyType))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return converterTypeSymbol.AllInterfaces
|
||||
.FirstOrDefault(i =>
|
||||
i.TypeArguments.Length == 2 &&
|
||||
SymbolEqualityComparer.Default.Equals(i.ConstructedFrom, TypeConverterInterfaceTypeSymbol) &&
|
||||
SymbolEqualityComparer.Default.Equals(sourceProperty.Type, i.TypeArguments[0]) &&
|
||||
SymbolEqualityComparer.Default.Equals(propertyType, i.TypeArguments[1]));
|
||||
}
|
||||
private INamedTypeSymbol? GetTypeConverterBaseInterfaceForField(ITypeSymbol converterTypeSymbol, ISymbol property, IFieldSymbol sourceProperty)
|
||||
{
|
||||
if (!property.TryGetTypeSymbol(out var propertyType))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return converterTypeSymbol.AllInterfaces
|
||||
.FirstOrDefault(i =>
|
||||
i.TypeArguments.Length == 2 &&
|
||||
SymbolEqualityComparer.Default.Equals(i.ConstructedFrom, TypeConverterInterfaceTypeSymbol) &&
|
||||
SymbolEqualityComparer.Default.Equals(sourceProperty.Type, i.TypeArguments[0]) &&
|
||||
SymbolEqualityComparer.Default.Equals(propertyType, i.TypeArguments[1]));
|
||||
}
|
||||
|
||||
private bool ShouldGenerateSecondaryConstructor(SemanticModel semanticModel, ISymbol sourceTypeSymbol)
|
||||
{
|
||||
var constructorSyntax = TypeSyntax.DescendantNodes()
|
||||
.OfType<ConstructorDeclarationSyntax>()
|
||||
.FirstOrDefault(c =>
|
||||
c.ParameterList.Parameters.Count == 1 &&
|
||||
SymbolEqualityComparer.Default.Equals(semanticModel.GetTypeInfo(c.ParameterList.Parameters.Single().Type!).ConvertedType, sourceTypeSymbol));
|
||||
|
||||
if (constructorSyntax is null)
|
||||
{
|
||||
// Secondary constructor is not defined.
|
||||
return true;
|
||||
}
|
||||
|
||||
if (constructorSyntax.Initializer?.ArgumentList.Arguments is not { Count: 2 } arguments ||
|
||||
!SymbolEqualityComparer.Default.Equals(semanticModel.GetTypeInfo(arguments[0].Expression).ConvertedType, MappingContextTypeSymbol) ||
|
||||
!SymbolEqualityComparer.Default.Equals(semanticModel.GetTypeInfo(arguments[1].Expression).ConvertedType, sourceTypeSymbol))
|
||||
{
|
||||
AddDiagnostic(DiagnosticsFactory.MissingConstructorArgument(constructorSyntax));
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private string? ToQualifiedDisplayName(ISymbol? symbol)
|
||||
{
|
||||
if (symbol is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var containingNamespace = TypeSyntax.GetNamespace();
|
||||
var symbolNamespace = symbol.ContainingNamespace.ToDisplayString();
|
||||
return containingNamespace != symbolNamespace && _ignoredNamespaces.Contains(symbol.ContainingNamespace.ToDisplayParts().First())
|
||||
? symbol.ToDisplayString()
|
||||
: symbol.Name;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,52 +0,0 @@
|
|||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using MapTo.Extensions;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
|
||||
namespace MapTo
|
||||
{
|
||||
internal class RecordMappingContext : MappingContext
|
||||
{
|
||||
internal RecordMappingContext(Compilation compilation, SourceGenerationOptions sourceGenerationOptions, TypeDeclarationSyntax typeSyntax)
|
||||
: base(compilation, sourceGenerationOptions, typeSyntax) { }
|
||||
|
||||
protected override ImmutableArray<MappedMember> GetSourceMappedFields(ITypeSymbol typeSymbol, ITypeSymbol sourceTypeSymbol, bool isInheritFromMappedBaseClass)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
||||
protected override ImmutableArray<MappedMember> GetSourceMappedProperties(ITypeSymbol typeSymbol, ITypeSymbol sourceTypeSymbol, bool isInheritFromMappedBaseClass)
|
||||
{
|
||||
var sourceProperties = sourceTypeSymbol.GetAllMembers().OfType<IPropertySymbol>().ToArray();
|
||||
return typeSymbol.GetMembers()
|
||||
.OfType<IMethodSymbol>()
|
||||
.OrderByDescending(s => s.Parameters.Length)
|
||||
.First(s => s.Name == ".ctor")
|
||||
.Parameters
|
||||
.Where(p => !p.HasAttribute(IgnoreMemberAttributeTypeSymbol))
|
||||
.Select(property => MapProperty(sourceTypeSymbol, sourceProperties, property))
|
||||
.Where(mappedProperty => mappedProperty is not null)
|
||||
.ToImmutableArray()!;
|
||||
}
|
||||
|
||||
protected override ImmutableArray<MappedMember> GetTypeMappedFields(ITypeSymbol typeSymbol, ITypeSymbol sourceTypeSymbol, bool isInheritFromMappedBaseClass)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
||||
protected override ImmutableArray<MappedMember> GetTypeMappedProperties(ITypeSymbol typeSymbol, ITypeSymbol sourceTypeSymbol, bool isInheritFromMappedBaseClass)
|
||||
{
|
||||
var sourceProperties = sourceTypeSymbol.GetAllMembers().OfType<IPropertySymbol>().ToArray();
|
||||
return typeSymbol.GetMembers()
|
||||
.OfType<IMethodSymbol>()
|
||||
.OrderByDescending(s => s.Parameters.Length)
|
||||
.First(s => s.Name == ".ctor")
|
||||
.Parameters
|
||||
.Where(p => !p.HasAttribute(IgnoreMemberAttributeTypeSymbol))
|
||||
.Select(property => MapProperty(typeSymbol, sourceProperties, property))
|
||||
.Where(mappedProperty => mappedProperty is not null)
|
||||
.ToImmutableArray()!;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,60 +0,0 @@
|
|||
using static MapTo.Sources.Constants;
|
||||
|
||||
namespace MapTo.Sources
|
||||
{
|
||||
internal static class DictionaryToListAttributeSource
|
||||
{
|
||||
internal const string AttributeName = "DictionaryToList";
|
||||
internal const string AttributeClassName = AttributeName + "Attribute";
|
||||
internal const string FullyQualifiedName = RootNamespace + "." + AttributeClassName;
|
||||
internal const string SourceMemberNameFieldOrPropertyName = "SourcePropertyName";
|
||||
|
||||
internal static SourceCode Generate(SourceGenerationOptions options)
|
||||
{
|
||||
using var builder = new SourceBuilder()
|
||||
.WriteLine(GeneratedFilesHeader)
|
||||
.WriteNullableContextOptionIf(options.SupportNullableReferenceTypes)
|
||||
.WriteLine()
|
||||
.WriteLine("using System;")
|
||||
.WriteLine()
|
||||
.WriteLine($"namespace {RootNamespace}")
|
||||
.WriteOpeningBracket();
|
||||
|
||||
if (options.GenerateXmlDocument)
|
||||
{
|
||||
builder
|
||||
.WriteLine("/// <summary>")
|
||||
.WriteLine("/// Specifies the mapping behavior of the annotated property.")
|
||||
.WriteLine("/// </summary>")
|
||||
.WriteLine("/// <remarks>")
|
||||
.WriteLine($"/// {AttributeClassName} has a number of uses:")
|
||||
.WriteLine("/// <list type=\"bullet\">")
|
||||
.WriteLine("/// <item><description>By default properties with same name will get mapped. This attribute allows the names to be different.</description></item>")
|
||||
.WriteLine("/// <item><description>Indicates that a property should be mapped when member serialization is set to opt-in.</description></item>")
|
||||
.WriteLine("/// </list>")
|
||||
.WriteLine("/// </remarks>");
|
||||
}
|
||||
|
||||
builder
|
||||
.WriteLine("[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = true)]")
|
||||
.WriteLine($"public sealed class {AttributeClassName} : Attribute")
|
||||
.WriteOpeningBracket();
|
||||
|
||||
if (options.GenerateXmlDocument)
|
||||
{
|
||||
builder
|
||||
.WriteLine("/// <summary>")
|
||||
.WriteLine("/// Gets or sets the property name of the object to mapping from.")
|
||||
.WriteLine("/// </summary>");
|
||||
}
|
||||
|
||||
builder
|
||||
.WriteLine($"public string{options.NullableReferenceSyntax} {SourceMemberNameFieldOrPropertyName} {{ get; set; }}")
|
||||
.WriteClosingBracket() // class
|
||||
.WriteClosingBracket(); // namespace
|
||||
|
||||
|
||||
return new(builder.ToString(), $"{AttributeClassName}.g.cs");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,58 +0,0 @@
|
|||
using System.Diagnostics.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using static MapTo.Sources.Constants;
|
||||
|
||||
namespace MapTo.Sources
|
||||
{
|
||||
[SuppressMessage("ReSharper", "InconsistentNaming")]
|
||||
internal static class ITypeConverterSource
|
||||
{
|
||||
internal const string InterfaceName = "ITypeConverter";
|
||||
internal const string FullyQualifiedName = RootNamespace + "." + InterfaceName + "`2";
|
||||
|
||||
internal static SourceCode Generate(SourceGenerationOptions options)
|
||||
{
|
||||
using var builder = new SourceBuilder()
|
||||
.WriteLine(GeneratedFilesHeader)
|
||||
.WriteNullableContextOptionIf(options.SupportNullableReferenceTypes)
|
||||
.WriteLine()
|
||||
.WriteLine($"namespace {RootNamespace}")
|
||||
.WriteOpeningBracket();
|
||||
|
||||
if (options.GenerateXmlDocument)
|
||||
{
|
||||
builder
|
||||
.WriteLine("/// <summary>")
|
||||
.WriteLine("/// Converts the value of <typeparamref name=\"TSource\"/> to <typeparamref name=\"TDestination\"/>.")
|
||||
.WriteLine("/// </summary>")
|
||||
.WriteLine("/// <typeparam name=\"TSource\">The type to convert from.</typeparam>")
|
||||
.WriteLine("/// <typeparam name=\"TDestination\">The type to convert to.</typeparam>");
|
||||
}
|
||||
|
||||
builder
|
||||
.WriteLine($"public interface {InterfaceName}<in TSource, out TDestination>")
|
||||
.WriteOpeningBracket();
|
||||
|
||||
if (options.GenerateXmlDocument)
|
||||
{
|
||||
builder
|
||||
.WriteLine("/// <summary>")
|
||||
.WriteLine("/// Converts the value of <paramref name=\"source\"/> object to <typeparamref name=\"TDestination\"/>.")
|
||||
.WriteLine("/// </summary>")
|
||||
.WriteLine("/// <param name=\"source\">The <see cref=\"TSource\"/> to convert.</param>")
|
||||
.WriteLine($"/// <param name=\"converterParameters\">The parameter list passed to the <see cref=\"{MapTypeConverterAttributeSource.AttributeClassName}\"/></param>")
|
||||
.WriteLine("/// <returns><typeparamref name=\"TDestination\"/> object.</returns>");
|
||||
}
|
||||
|
||||
builder
|
||||
.WriteLine($"TDestination Convert(TSource source, object[]{options.NullableReferenceSyntax} converterParameters);")
|
||||
.WriteClosingBracket()
|
||||
.WriteClosingBracket();
|
||||
|
||||
return new(builder.ToString(), $"{InterfaceName}.g.cs");
|
||||
}
|
||||
|
||||
internal static string GetFullyQualifiedName(ITypeSymbol sourceType, ITypeSymbol destinationType) =>
|
||||
$"{RootNamespace}.{InterfaceName}<{sourceType.ToDisplayString()}, {destinationType.ToDisplayString()}>";
|
||||
}
|
||||
}
|
|
@ -1,36 +0,0 @@
|
|||
using static MapTo.Sources.Constants;
|
||||
|
||||
namespace MapTo.Sources
|
||||
{
|
||||
internal static class IgnoreMemberAttributeSource
|
||||
{
|
||||
internal const string AttributeName = "IgnoreMemberMapTo";
|
||||
internal const string AttributeClassName = AttributeName + "Attribute";
|
||||
internal const string FullyQualifiedName = RootNamespace + "." + AttributeClassName;
|
||||
|
||||
internal static SourceCode Generate(SourceGenerationOptions options)
|
||||
{
|
||||
var builder = new SourceBuilder()
|
||||
.WriteLine(GeneratedFilesHeader)
|
||||
.WriteLine("using System;")
|
||||
.WriteLine()
|
||||
.WriteLine($"namespace {RootNamespace}")
|
||||
.WriteOpeningBracket();
|
||||
|
||||
if (options.GenerateXmlDocument)
|
||||
{
|
||||
builder
|
||||
.WriteLine("/// <summary>")
|
||||
.WriteLine("/// Specifies that the annotated property should be excluded.")
|
||||
.WriteLine("/// </summary>");
|
||||
}
|
||||
|
||||
builder
|
||||
.WriteLine("[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, Inherited = false, AllowMultiple = false)]")
|
||||
.WriteLine($"public sealed class {AttributeClassName} : Attribute {{ }}")
|
||||
.WriteClosingBracket();
|
||||
|
||||
return new(builder.ToString(), $"{AttributeClassName}.g.cs");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,40 +0,0 @@
|
|||
using static MapTo.Sources.Constants;
|
||||
|
||||
namespace MapTo.Sources
|
||||
{
|
||||
internal static class JsonExtensionAttributeSource
|
||||
{
|
||||
internal const string AttributeName = "JsonExtension";
|
||||
internal const string AttributeClassName = AttributeName + "Attribute";
|
||||
internal const string FullyQualifiedName = RootNamespace + "." + AttributeClassName;
|
||||
|
||||
internal static SourceCode Generate(SourceGenerationOptions options)
|
||||
{
|
||||
using var builder = new SourceBuilder()
|
||||
.WriteLine(GeneratedFilesHeader)
|
||||
.WriteLine("using System;")
|
||||
.WriteLine()
|
||||
.WriteLine($"namespace {RootNamespace}")
|
||||
.WriteOpeningBracket();
|
||||
|
||||
if (options.GenerateXmlDocument)
|
||||
{
|
||||
builder
|
||||
.WriteLine("/// <summary>")
|
||||
.WriteLine("/// Specifies that the annotated class has a json extension.")
|
||||
.WriteLine("/// </summary>");
|
||||
}
|
||||
|
||||
builder
|
||||
.WriteLine("[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)]")
|
||||
.WriteLine($"public sealed class {AttributeName}Attribute : Attribute")
|
||||
.WriteOpeningBracket();
|
||||
|
||||
builder
|
||||
.WriteClosingBracket() // class
|
||||
.WriteClosingBracket(); // namespace
|
||||
|
||||
return new(builder.ToString(), $"{AttributeName}Attribute.g.cs");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
using MapTo.Extensions;
|
||||
using System.Text;
|
||||
using static MapTo.Sources.Constants;
|
||||
|
||||
namespace MapTo.Sources
|
||||
{
|
||||
internal static class MapClassSource
|
||||
{
|
||||
internal static SourceCode Generate(MappingModel model)
|
||||
{
|
||||
return model.GenerateStructOrClass("class");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,72 +0,0 @@
|
|||
using static MapTo.Sources.Constants;
|
||||
|
||||
namespace MapTo.Sources
|
||||
{
|
||||
internal static class MapFromAttributeSource
|
||||
{
|
||||
internal const string AttributeName = "MapFrom";
|
||||
internal const string AttributeClassName = AttributeName + "Attribute";
|
||||
internal const string FullyQualifiedName = RootNamespace + "." + AttributeClassName;
|
||||
|
||||
internal static SourceCode Generate(SourceGenerationOptions options)
|
||||
{
|
||||
using var builder = new SourceBuilder()
|
||||
.WriteLine(GeneratedFilesHeader)
|
||||
.WriteLine("using System;")
|
||||
.WriteLine()
|
||||
.WriteLine($"namespace {RootNamespace}")
|
||||
.WriteOpeningBracket();
|
||||
|
||||
if (options.GenerateXmlDocument)
|
||||
{
|
||||
builder
|
||||
.WriteLine("/// <summary>")
|
||||
.WriteLine("/// Specifies that the annotated class can be mapped from the provided <see cref=\"SourceType\"/>.")
|
||||
.WriteLine("/// </summary>");
|
||||
}
|
||||
|
||||
builder
|
||||
.WriteLine("[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)]")
|
||||
.WriteLine($"public sealed class {AttributeName}Attribute : Attribute")
|
||||
.WriteOpeningBracket();
|
||||
|
||||
if (options.GenerateXmlDocument)
|
||||
{
|
||||
builder
|
||||
.WriteLine("/// <summary>")
|
||||
.WriteLine($"/// Initializes a new instance of the <see cref=\"{AttributeName}Attribute\"/> class with the specified <paramref name=\"sourceType\"/>.")
|
||||
.WriteLine("/// </summary>")
|
||||
.WriteLine("/// <param name=\"sourceType\">The type of to map from.</param>");
|
||||
}
|
||||
|
||||
builder
|
||||
.WriteLine($"public {AttributeName}Attribute(Type sourceType)")
|
||||
.WriteOpeningBracket()
|
||||
.WriteLine("SourceType = new [] { sourceType };")
|
||||
.WriteClosingBracket()
|
||||
.WriteLine();
|
||||
|
||||
builder
|
||||
.WriteLine($"public {AttributeName}Attribute(Type[] sourceType)")
|
||||
.WriteOpeningBracket()
|
||||
.WriteLine("SourceType = sourceType;")
|
||||
.WriteClosingBracket()
|
||||
.WriteLine();
|
||||
|
||||
if (options.GenerateXmlDocument)
|
||||
{
|
||||
builder
|
||||
.WriteLine("/// <summary>")
|
||||
.WriteLine("/// Gets the type to map from.")
|
||||
.WriteLine("/// </summary>");
|
||||
}
|
||||
|
||||
builder
|
||||
.WriteLine("public Type[] SourceType { get; }")
|
||||
.WriteClosingBracket() // class
|
||||
.WriteClosingBracket(); // namespace
|
||||
|
||||
return new(builder.ToString(), $"{AttributeName}Attribute.g.cs");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,61 +0,0 @@
|
|||
using static MapTo.Sources.Constants;
|
||||
|
||||
namespace MapTo.Sources
|
||||
{
|
||||
internal static class MapPropertyAttributeSource
|
||||
{
|
||||
internal const string AttributeName = "MapProperty";
|
||||
internal const string AttributeClassName = AttributeName + "Attribute";
|
||||
internal const string FullyQualifiedName = RootNamespace + "." + AttributeClassName;
|
||||
internal const string SourcePropertyNamePropertyName = "SourcePropertyName";
|
||||
|
||||
internal static SourceCode Generate(SourceGenerationOptions options)
|
||||
{
|
||||
using var builder = new SourceBuilder()
|
||||
.WriteLine(GeneratedFilesHeader)
|
||||
.WriteNullableContextOptionIf(options.SupportNullableReferenceTypes)
|
||||
.WriteLine()
|
||||
.WriteLine("using System;")
|
||||
.WriteLine()
|
||||
.WriteLine($"namespace {RootNamespace}")
|
||||
.WriteOpeningBracket();
|
||||
|
||||
if (options.GenerateXmlDocument)
|
||||
{
|
||||
builder
|
||||
.WriteLine()
|
||||
.WriteLine("/// <summary>")
|
||||
.WriteLine("/// Specifies the mapping behavior of the annotated property.")
|
||||
.WriteLine("/// </summary>")
|
||||
.WriteLine("/// <remarks>")
|
||||
.WriteLine($"/// {AttributeClassName} has a number of uses:")
|
||||
.WriteLine("/// <list type=\"bullet\">")
|
||||
.WriteLine("/// <item><description>By default properties with same name will get mapped. This attribute allows the names to be different.</description></item>")
|
||||
.WriteLine("/// <item><description>Indicates that a property should be mapped when member serialization is set to opt-in.</description></item>")
|
||||
.WriteLine("/// </list>")
|
||||
.WriteLine("/// </remarks>");
|
||||
}
|
||||
|
||||
builder
|
||||
.WriteLine("[AttributeUsage(AttributeTargets.Property | AttributeTargets.Parameter, AllowMultiple = true)]")
|
||||
.WriteLine($"public sealed class {AttributeClassName} : Attribute")
|
||||
.WriteOpeningBracket();
|
||||
|
||||
if (options.GenerateXmlDocument)
|
||||
{
|
||||
builder
|
||||
.WriteLine("/// <summary>")
|
||||
.WriteLine("/// Gets or sets the property name of the object to mapping from.")
|
||||
.WriteLine("/// </summary>");
|
||||
}
|
||||
|
||||
builder
|
||||
.WriteLine($"public string{options.NullableReferenceSyntax} {SourcePropertyNamePropertyName} {{ get; set; }}")
|
||||
.WriteClosingBracket() // class
|
||||
.WriteClosingBracket(); // namespace
|
||||
|
||||
|
||||
return new(builder.ToString(), $"{AttributeClassName}.g.cs");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,235 +0,0 @@
|
|||
using System;
|
||||
using MapTo.Extensions;
|
||||
using static MapTo.Sources.Constants;
|
||||
|
||||
namespace MapTo.Sources
|
||||
{
|
||||
internal static class MapRecordSource
|
||||
{
|
||||
internal static SourceCode Generate(MappingModel model)
|
||||
{
|
||||
using var builder = new SourceBuilder()
|
||||
.WriteLine(GeneratedFilesHeader)
|
||||
.WriteNullableContextOptionIf(model.Options.SupportNullableReferenceTypes)
|
||||
.WriteUsings(model.Usings)
|
||||
.WriteLine()
|
||||
|
||||
// Namespace declaration
|
||||
.WriteLine($"namespace {model.Namespace}")
|
||||
.WriteOpeningBracket()
|
||||
|
||||
// Class declaration
|
||||
.WriteLine($"partial record {model.TypeIdentifierName}")
|
||||
.WriteOpeningBracket();
|
||||
|
||||
foreach (var targetSourceType in model.MappedSourceTypes)
|
||||
{
|
||||
if (targetSourceType.GenerateSecondaryConstructor)
|
||||
{
|
||||
builder
|
||||
.GenerateSecondaryConstructor(model)
|
||||
.WriteLine();
|
||||
}
|
||||
}
|
||||
|
||||
// Class body
|
||||
|
||||
|
||||
builder
|
||||
.GeneratePrivateConstructor(model)
|
||||
|
||||
.WriteLine()
|
||||
.GenerateFactoryMethod(model)
|
||||
|
||||
// End class declaration
|
||||
.WriteClosingBracket()
|
||||
.WriteLine()
|
||||
|
||||
// Extension class declaration
|
||||
.GenerateSourceTypeExtensionClass(model)
|
||||
|
||||
|
||||
// End namespace declaration
|
||||
.WriteClosingBracket();
|
||||
|
||||
return new(builder.ToString(), $"{model.Namespace}.{model.TypeIdentifierName}.g.cs");
|
||||
}
|
||||
|
||||
private static SourceBuilder GenerateSecondaryConstructor(this SourceBuilder builder, MappingModel model)
|
||||
{
|
||||
// grab first data from array
|
||||
|
||||
foreach (var targetSourceType in model.MappedSourceTypes)
|
||||
{
|
||||
var sourceClassParameterName = targetSourceType.SourceTypeIdentifierName.ToCamelCase();
|
||||
if (model.Options.GenerateXmlDocument)
|
||||
{
|
||||
builder
|
||||
.WriteLine("/// <summary>")
|
||||
.WriteLine($"/// Initializes a new instance of the <see cref=\"{model.TypeIdentifierName}\"/> class")
|
||||
.WriteLine($"/// using the property values from the specified <paramref name=\"{sourceClassParameterName}\"/>.")
|
||||
.WriteLine("/// </summary>")
|
||||
.WriteLine($"/// <exception cref=\"ArgumentNullException\">{sourceClassParameterName} is null</exception>");
|
||||
}
|
||||
builder .WriteLine($"{model.Options.ConstructorAccessModifier.ToLowercaseString()} {model.TypeIdentifierName}({targetSourceType.SourceType} {sourceClassParameterName})")
|
||||
.WriteLine($" : this(new {MappingContextSource.ClassName}(), {sourceClassParameterName}) {{ }}");
|
||||
}
|
||||
|
||||
|
||||
|
||||
return builder;
|
||||
|
||||
}
|
||||
|
||||
private static SourceBuilder GeneratePrivateConstructor(this SourceBuilder builder, MappingModel model)
|
||||
{
|
||||
const string mappingContextParameterName = "context";
|
||||
|
||||
foreach (var targetSourceType in model.MappedSourceTypes)
|
||||
{
|
||||
var sourceClassParameterName = targetSourceType.SourceTypeIdentifierName.ToCamelCase();
|
||||
builder
|
||||
.WriteLine(
|
||||
$"private protected {model.TypeIdentifierName}({MappingContextSource.ClassName} {mappingContextParameterName}, {targetSourceType.SourceType} {sourceClassParameterName})")
|
||||
.Indent()
|
||||
.Write(": this(").WriteProperties(model, sourceClassParameterName, mappingContextParameterName)
|
||||
.WriteLine(")")
|
||||
.Unindent()
|
||||
.WriteOpeningBracket()
|
||||
.WriteLine($"if ({mappingContextParameterName} == null) throw new ArgumentNullException(nameof({mappingContextParameterName}));")
|
||||
.WriteLine($"if ({sourceClassParameterName} == null) throw new ArgumentNullException(nameof({sourceClassParameterName}));")
|
||||
.WriteLine()
|
||||
.WriteLine($"{mappingContextParameterName}.{MappingContextSource.RegisterMethodName}({sourceClassParameterName}, this);")
|
||||
.WriteClosingBracket();
|
||||
}
|
||||
// End constructor declaration
|
||||
return builder;
|
||||
}
|
||||
|
||||
private static SourceBuilder WriteProperties(this SourceBuilder builder, MappingModel model, string sourceClassParameterName,
|
||||
string mappingContextParameterName)
|
||||
{
|
||||
|
||||
foreach (var targetSourceType in model.MappedSourceTypes)
|
||||
{
|
||||
for (var i = 0; i < targetSourceType.SourceProperties.Length; i++)
|
||||
{
|
||||
var property = targetSourceType.SourceProperties[i];
|
||||
if (property.TypeConverter is null)
|
||||
{
|
||||
if (property.IsEnumerable)
|
||||
{
|
||||
builder.Write(
|
||||
$"{property.Name}: {sourceClassParameterName}.{property.SourcePropertyName}.Select({mappingContextParameterName}.{MappingContextSource.MapMethodName}<{property.MappedSourcePropertyTypeName}, {property.EnumerableTypeArgument}>).ToList()");
|
||||
}
|
||||
else
|
||||
{
|
||||
builder.Write(property.MappedSourcePropertyTypeName is null
|
||||
? $"{property.Name}: {sourceClassParameterName}.{property.SourcePropertyName}"
|
||||
: $"{property.Name}: {mappingContextParameterName}.{MappingContextSource.MapMethodName}<{property.MappedSourcePropertyTypeName}, {property.Type}>({sourceClassParameterName}.{property.SourcePropertyName})");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var parameters = property.TypeConverterParameters.IsEmpty
|
||||
? "null"
|
||||
: $"new object[] {{ {string.Join(", ", property.TypeConverterParameters)} }}";
|
||||
|
||||
builder.Write(
|
||||
$"{property.Name}: new {property.TypeConverter}().Convert({sourceClassParameterName}.{property.SourcePropertyName}, {parameters})");
|
||||
}
|
||||
|
||||
if (i < targetSourceType.SourceProperties.Length - 1)
|
||||
{
|
||||
builder.Write(", ");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
private static SourceBuilder GenerateFactoryMethod(this SourceBuilder builder, MappingModel model)
|
||||
{
|
||||
foreach (var targetSourceType in model.MappedSourceTypes)
|
||||
{
|
||||
var sourceClassParameterName = targetSourceType.SourceTypeIdentifierName.ToCamelCase();
|
||||
|
||||
builder
|
||||
.GenerateConvertorMethodsXmlDocs(model, sourceClassParameterName)
|
||||
.WriteLineIf(model.Options.SupportNullableStaticAnalysis, $"[return: NotNullIfNotNull(\"{sourceClassParameterName}\")]")
|
||||
.WriteLine(
|
||||
$"{model.Options.GeneratedMethodsAccessModifier.ToLowercaseString()} static {model.TypeIdentifierName}{model.Options.NullableReferenceSyntax} From({targetSourceType.SourceType}{model.Options.NullableReferenceSyntax} {sourceClassParameterName})")
|
||||
.WriteOpeningBracket()
|
||||
.WriteLine(
|
||||
$"return {sourceClassParameterName} == null ? null : {MappingContextSource.ClassName}.{MappingContextSource.FactoryMethodName}<{targetSourceType.SourceType}, {model.TypeIdentifierName}>({sourceClassParameterName});")
|
||||
.WriteClosingBracket();
|
||||
}
|
||||
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
private static SourceBuilder GenerateConvertorMethodsXmlDocs(this SourceBuilder builder, MappingModel model, string sourceClassParameterName)
|
||||
{
|
||||
if (!model.Options.GenerateXmlDocument)
|
||||
{
|
||||
return builder;
|
||||
}
|
||||
|
||||
foreach (var targetSourceType in model.MappedSourceTypes)
|
||||
{
|
||||
builder
|
||||
.WriteLine("/// <summary>")
|
||||
.WriteLine($"/// Creates a new instance of <see cref=\"{model.TypeIdentifierName}\"/> and sets its participating properties")
|
||||
.WriteLine($"/// using the property values from <paramref name=\"{sourceClassParameterName}\"/>.")
|
||||
.WriteLine("/// </summary>")
|
||||
.WriteLine($"/// <param name=\"{sourceClassParameterName}\">The instance of <see cref=\"{targetSourceType.SourceType}\"/> to use as source.</param>")
|
||||
.WriteLine(
|
||||
$"/// <returns>A new instance of <see cred=\"{model.TypeIdentifierName}\"/> -or- <c>null</c> if <paramref name=\"{sourceClassParameterName}\"/> is <c>null</c>.</returns>");
|
||||
}
|
||||
|
||||
return builder;
|
||||
|
||||
}
|
||||
|
||||
private static SourceBuilder GenerateSourceTypeExtensionClass(this SourceBuilder builder, MappingModel model)
|
||||
{
|
||||
foreach (var targetSourceType in model.MappedSourceTypes)
|
||||
{
|
||||
builder
|
||||
.WriteLine(
|
||||
$"{model.Options.GeneratedMethodsAccessModifier.ToLowercaseString()} static partial class {targetSourceType.SourceTypeIdentifierName}To{model.TypeIdentifierName}Extensions")
|
||||
.WriteOpeningBracket()
|
||||
.GenerateSourceTypeExtensionMethod(model)
|
||||
.WriteClosingBracket();
|
||||
}
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
private static SourceBuilder GenerateSourceTypeExtensionMethod(this SourceBuilder builder, MappingModel model)
|
||||
{
|
||||
|
||||
foreach (var targetSourceType in model.MappedSourceTypes)
|
||||
{
|
||||
var sourceClassParameterName = targetSourceType.SourceTypeIdentifierName.ToCamelCase();
|
||||
|
||||
builder
|
||||
.GenerateConvertorMethodsXmlDocs(model, sourceClassParameterName)
|
||||
.WriteLineIf(model.Options.SupportNullableStaticAnalysis, $"[return: NotNullIfNotNull(\"{sourceClassParameterName}\")]")
|
||||
.WriteLine(
|
||||
$"{model.Options.GeneratedMethodsAccessModifier.ToLowercaseString()} static {model.TypeIdentifierName}{model.Options.NullableReferenceSyntax} To{model.TypeIdentifierName}(this {targetSourceType.SourceType}{model.Options.NullableReferenceSyntax} {sourceClassParameterName})")
|
||||
.WriteOpeningBracket()
|
||||
.WriteLine($"return {sourceClassParameterName} == null ? null : new {model.TypeIdentifierName}({sourceClassParameterName});")
|
||||
.WriteClosingBracket();
|
||||
|
||||
}
|
||||
|
||||
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
using MapTo.Extensions;
|
||||
using static MapTo.Sources.Constants;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace MapTo.Sources
|
||||
{
|
||||
internal static class MapStructSource
|
||||
{
|
||||
internal static SourceCode Generate(MappingModel model)
|
||||
{
|
||||
return model.GenerateStructOrClass("struct");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,84 +0,0 @@
|
|||
using System;
|
||||
using static MapTo.Sources.Constants;
|
||||
|
||||
namespace MapTo.Sources
|
||||
{
|
||||
internal static class MapTypeConverterAttributeSource
|
||||
{
|
||||
internal const string AttributeName = "MapTypeConverter";
|
||||
internal const string AttributeClassName = AttributeName + "Attribute";
|
||||
internal const string FullyQualifiedName = RootNamespace + "." + AttributeClassName;
|
||||
internal const string ConverterPropertyName = "Converter";
|
||||
internal const string ConverterParametersPropertyName = "ConverterParameters";
|
||||
|
||||
internal static SourceCode Generate(SourceGenerationOptions options)
|
||||
{
|
||||
using var builder = new SourceBuilder()
|
||||
.WriteLine(GeneratedFilesHeader)
|
||||
.WriteNullableContextOptionIf(options.SupportNullableReferenceTypes)
|
||||
.WriteLine()
|
||||
.WriteLine("using System;")
|
||||
.WriteLine()
|
||||
.WriteLine($"namespace {RootNamespace}")
|
||||
.WriteOpeningBracket();
|
||||
|
||||
if (options.GenerateXmlDocument)
|
||||
{
|
||||
builder
|
||||
.WriteLine("/// <summary>")
|
||||
.WriteLine("/// Specifies what type to use as a converter for the property this attribute is bound to.")
|
||||
.WriteLine("/// </summary>");
|
||||
}
|
||||
|
||||
builder
|
||||
.WriteLine("[AttributeUsage(AttributeTargets.Property | AttributeTargets.Parameter, AllowMultiple = false)]")
|
||||
.WriteLine($"public sealed class {AttributeClassName} : Attribute")
|
||||
.WriteOpeningBracket();
|
||||
|
||||
if (options.GenerateXmlDocument)
|
||||
{
|
||||
builder
|
||||
.WriteLine("/// <summary>")
|
||||
.WriteLine($"/// Initializes a new instance of <see cref=\"{AttributeClassName}\"/>.")
|
||||
.WriteLine("/// </summary>")
|
||||
.WriteLine($"/// <param name=\"converter\">The <see cref=\"{ITypeConverterSource.InterfaceName}{{TSource,TDestination}}\" /> to be used to convert the source type.</param>")
|
||||
.WriteLine("/// <param name=\"converterParameters\">The list of parameters to pass to the <paramref name=\"converter\"/> during the type conversion.</param>");
|
||||
}
|
||||
|
||||
builder
|
||||
.WriteLine($"public {AttributeClassName}(Type converter, object[]{options.NullableReferenceSyntax} converterParameters = null)")
|
||||
.WriteOpeningBracket()
|
||||
.WriteLine($"{ConverterPropertyName} = converter;")
|
||||
.WriteLine($"{ConverterParametersPropertyName} = converterParameters;")
|
||||
.WriteClosingBracket()
|
||||
.WriteLine();
|
||||
|
||||
if (options.GenerateXmlDocument)
|
||||
{
|
||||
builder
|
||||
.WriteLine("/// <summary>")
|
||||
.WriteLine($"/// Gets or sets the <see cref=\"{ITypeConverterSource.InterfaceName}{{TSource,TDestination}}\" /> to be used to convert the source type.")
|
||||
.WriteLine("/// </summary>");
|
||||
}
|
||||
|
||||
builder
|
||||
.WriteLine($"public Type {ConverterPropertyName} {{ get; }}")
|
||||
.WriteLine();
|
||||
|
||||
if (options.GenerateXmlDocument)
|
||||
{
|
||||
builder
|
||||
.WriteLine("/// <summary>")
|
||||
.WriteLine($"/// Gets the list of parameters to pass to the <see cref=\"{ConverterPropertyName}\"/> during the type conversion.")
|
||||
.WriteLine("/// </summary>");
|
||||
}
|
||||
|
||||
builder
|
||||
.WriteLine($"public object[]{options.NullableReferenceSyntax} {ConverterParametersPropertyName} {{ get; }}")
|
||||
.WriteClosingBracket()
|
||||
.WriteClosingBracket();
|
||||
|
||||
return new(builder.ToString(), $"{AttributeClassName}.g.cs");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,115 +0,0 @@
|
|||
using System.Collections.Generic;
|
||||
using static MapTo.Sources.Constants;
|
||||
|
||||
namespace MapTo.Sources
|
||||
{
|
||||
internal static class MappingContextSource
|
||||
{
|
||||
internal const string ClassName = "MappingContext";
|
||||
internal const string FullyQualifiedName = RootNamespace + "." + ClassName;
|
||||
internal const string FactoryMethodName = "Create";
|
||||
internal const string RegisterMethodName = "Register";
|
||||
internal const string MapMethodName = "MapFromWithContext";
|
||||
|
||||
internal static SourceCode Generate(SourceGenerationOptions options)
|
||||
{
|
||||
var usings = new List<string> { "System", "System.Collections.Generic", "System.Reflection" };
|
||||
|
||||
using var builder = new SourceBuilder()
|
||||
.WriteLine(GeneratedFilesHeader)
|
||||
.WriteLine()
|
||||
.WriteUsings(usings)
|
||||
.WriteLine()
|
||||
|
||||
// Namespace declaration
|
||||
.WriteLine($"namespace {RootNamespace}")
|
||||
.WriteOpeningBracket()
|
||||
|
||||
// Class declaration
|
||||
.WriteLine($"internal sealed class {ClassName}")
|
||||
.WriteOpeningBracket()
|
||||
|
||||
.WriteLine("private readonly Dictionary<object, object> _cache;")
|
||||
.WriteLine()
|
||||
|
||||
// Constructor
|
||||
.WriteLine($"internal {ClassName}()")
|
||||
.WriteOpeningBracket()
|
||||
.WriteLine("_cache = new Dictionary<object, object>(1);")
|
||||
.WriteClosingBracket()
|
||||
.WriteLine()
|
||||
|
||||
// Factory
|
||||
.WriteLine($"internal static TMapped {FactoryMethodName}<TOriginal, TMapped>(TOriginal original)")
|
||||
.WriteOpeningBracket()
|
||||
.WriteLine("if (original == null) throw new ArgumentNullException(nameof(original));")
|
||||
.WriteLine()
|
||||
.WriteLine("var context = new MappingContext();")
|
||||
.WriteLine("var mapped = context.MapFromWithContext<TOriginal, TMapped>(original);")
|
||||
.WriteLine()
|
||||
.WriteLine("if (mapped == null)")
|
||||
.WriteOpeningBracket()
|
||||
.WriteLine("throw new InvalidOperationException();")
|
||||
.WriteClosingBracket()
|
||||
.WriteLine()
|
||||
.WriteLine("return mapped;")
|
||||
.WriteClosingBracket()
|
||||
.WriteLine()
|
||||
|
||||
// MapFromWithContext method
|
||||
.WriteLine($"internal TMapped MapFromWithContext<TOriginal, TMapped>(TOriginal original)")
|
||||
.WriteOpeningBracket()
|
||||
.WriteLine("if (original == null)")
|
||||
.WriteOpeningBracket()
|
||||
.WriteLine("return default(TMapped);")
|
||||
.WriteClosingBracket()
|
||||
.WriteLine()
|
||||
.WriteLine("if (!TryGetValue<TOriginal, TMapped>(original, out var mapped))")
|
||||
.WriteOpeningBracket()
|
||||
.WriteLine("var instance = Activator.CreateInstance(typeof(TMapped), BindingFlags.Instance | BindingFlags.NonPublic, null, new object[] { this, original }, null);")
|
||||
.WriteLine("if (instance != null)")
|
||||
.WriteOpeningBracket()
|
||||
.WriteLine("mapped = (TMapped)instance;")
|
||||
.WriteClosingBracket()
|
||||
.WriteClosingBracket()
|
||||
.WriteLine()
|
||||
.WriteLine("return mapped;")
|
||||
.WriteClosingBracket()
|
||||
.WriteLine()
|
||||
|
||||
// Register method
|
||||
.WriteLine("internal void Register<TOriginal, TMapped>(TOriginal original, TMapped mapped)")
|
||||
.WriteOpeningBracket()
|
||||
.WriteLine("if (original == null) throw new ArgumentNullException(nameof(original));")
|
||||
.WriteLine("if (mapped == null) throw new ArgumentNullException(nameof(mapped));")
|
||||
.WriteLine()
|
||||
.WriteLine("if (!_cache.ContainsKey(original))")
|
||||
.WriteOpeningBracket()
|
||||
.WriteLine("_cache.Add(original, mapped);")
|
||||
.WriteClosingBracket()
|
||||
.WriteClosingBracket()
|
||||
.WriteLine()
|
||||
|
||||
// TryGetValue method
|
||||
.WriteLine("private bool TryGetValue<TOriginal, TMapped>(TOriginal original, out TMapped mapped)")
|
||||
.WriteOpeningBracket()
|
||||
.WriteLine("if (original != null && _cache.TryGetValue(original, out var value))")
|
||||
.WriteOpeningBracket()
|
||||
.WriteLine("mapped = (TMapped)value;")
|
||||
.WriteLine("return true;")
|
||||
.WriteClosingBracket()
|
||||
.WriteLine()
|
||||
.WriteLine("mapped = default(TMapped);")
|
||||
.WriteLine("return false;")
|
||||
.WriteClosingBracket()
|
||||
|
||||
// End class declaration
|
||||
.WriteClosingBracket()
|
||||
|
||||
// End namespace declaration
|
||||
.WriteClosingBracket();
|
||||
|
||||
return new(builder.ToString(), $"{ClassName}.g.cs");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,36 +0,0 @@
|
|||
using static MapTo.Sources.Constants;
|
||||
|
||||
namespace MapTo.Sources
|
||||
{
|
||||
internal static class ReadOnlyPropertyAttributeSource
|
||||
{
|
||||
internal const string AttributeName = "ReadOnlyProperty";
|
||||
internal const string AttributeClassName = AttributeName + "Attribute";
|
||||
internal const string FullyQualifiedName = RootNamespace + "." + AttributeClassName;
|
||||
|
||||
internal static SourceCode Generate(SourceGenerationOptions options)
|
||||
{
|
||||
var builder = new SourceBuilder()
|
||||
.WriteLine(GeneratedFilesHeader)
|
||||
.WriteLine("using System;")
|
||||
.WriteLine()
|
||||
.WriteLine($"namespace {RootNamespace}")
|
||||
.WriteOpeningBracket();
|
||||
|
||||
if (options.GenerateXmlDocument)
|
||||
{
|
||||
builder
|
||||
.WriteLine("/// <summary>")
|
||||
.WriteLine("/// Specifies that the annotated property should be excluded.")
|
||||
.WriteLine("/// </summary>");
|
||||
}
|
||||
|
||||
builder
|
||||
.WriteLine("[AttributeUsage(AttributeTargets.Property, Inherited = false, AllowMultiple = false)]")
|
||||
.WriteLine($"public sealed class {AttributeClassName} : Attribute {{ }}")
|
||||
.WriteClosingBracket();
|
||||
|
||||
return new(builder.ToString(), $"{AttributeClassName}.g.cs");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,40 +0,0 @@
|
|||
using static MapTo.Sources.Constants;
|
||||
|
||||
namespace MapTo.Sources
|
||||
{
|
||||
internal static class UseUpdateAttributeSource
|
||||
{
|
||||
internal const string AttributeName = "UseUpdate";
|
||||
internal const string AttributeClassName = AttributeName + "Attribute";
|
||||
internal const string FullyQualifiedName = RootNamespace + "." + AttributeClassName;
|
||||
|
||||
internal static SourceCode Generate(SourceGenerationOptions options)
|
||||
{
|
||||
using var builder = new SourceBuilder()
|
||||
.WriteLine(GeneratedFilesHeader)
|
||||
.WriteLine("using System;")
|
||||
.WriteLine()
|
||||
.WriteLine($"namespace {RootNamespace}")
|
||||
.WriteOpeningBracket();
|
||||
|
||||
if (options.GenerateXmlDocument)
|
||||
{
|
||||
builder
|
||||
.WriteLine("/// <summary>")
|
||||
.WriteLine("/// Specifies that the annotated class can be updatable.")
|
||||
.WriteLine("/// </summary>");
|
||||
}
|
||||
|
||||
builder
|
||||
.WriteLine("[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)]")
|
||||
.WriteLine($"public sealed class {AttributeName}Attribute : Attribute")
|
||||
.WriteOpeningBracket();
|
||||
|
||||
builder
|
||||
.WriteClosingBracket() // class
|
||||
.WriteClosingBracket(); // namespace
|
||||
|
||||
return new(builder.ToString(), $"{AttributeName}Attribute.g.cs");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,67 +0,0 @@
|
|||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using MapTo.Extensions;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
|
||||
namespace MapTo
|
||||
{
|
||||
internal class StructMappingContext : MappingContext
|
||||
{
|
||||
internal StructMappingContext(Compilation compilation, SourceGenerationOptions sourceGenerationOptions, TypeDeclarationSyntax typeSyntax)
|
||||
: base(compilation, sourceGenerationOptions, typeSyntax) { }
|
||||
|
||||
protected override ImmutableArray<MappedMember> GetSourceMappedFields(ITypeSymbol typeSymbol, ITypeSymbol sourceTypeSymbol, bool isInheritFromMappedBaseClass)
|
||||
{
|
||||
var sourceProperties = sourceTypeSymbol.GetAllMembers().OfType<IFieldSymbol>().ToArray();
|
||||
|
||||
return typeSymbol
|
||||
.GetAllMembers()
|
||||
.OfType<IFieldSymbol>()
|
||||
.Where(p => !p.HasAttribute(IgnoreMemberAttributeTypeSymbol))
|
||||
.Select(property => MapField(sourceTypeSymbol, sourceProperties, property))
|
||||
.Where(mappedProperty => mappedProperty is not null)
|
||||
.ToImmutableArray()!;
|
||||
}
|
||||
|
||||
protected override ImmutableArray<MappedMember> GetSourceMappedProperties(ITypeSymbol typeSymbol, ITypeSymbol sourceTypeSymbol, bool hasInheritedClass)
|
||||
{
|
||||
var sourceProperties = sourceTypeSymbol.GetAllMembers().OfType<IPropertySymbol>().ToArray();
|
||||
|
||||
return typeSymbol
|
||||
.GetAllMembers()
|
||||
.OfType<IPropertySymbol>()
|
||||
.Where(p => !p.HasAttribute(IgnoreMemberAttributeTypeSymbol))
|
||||
.Select(property => MapProperty(sourceTypeSymbol, sourceProperties, property))
|
||||
.Where(mappedProperty => mappedProperty is not null)
|
||||
.ToImmutableArray()!;
|
||||
}
|
||||
|
||||
protected override ImmutableArray<MappedMember> GetTypeMappedFields(ITypeSymbol typeSymbol, ITypeSymbol sourceTypeSymbol, bool isInheritFromMappedBaseClass)
|
||||
{
|
||||
var sourceProperties = sourceTypeSymbol.GetAllMembers().OfType<IFieldSymbol>().ToArray();
|
||||
|
||||
return sourceTypeSymbol
|
||||
.GetAllMembers()
|
||||
.OfType<IFieldSymbol>()
|
||||
.Where(p => !p.HasAttribute(IgnoreMemberAttributeTypeSymbol))
|
||||
.Select(property => MapFieldSimple(typeSymbol, property))
|
||||
.Where(mappedProperty => mappedProperty is not null)
|
||||
.ToImmutableArray()!;
|
||||
}
|
||||
|
||||
protected override ImmutableArray<MappedMember> GetTypeMappedProperties(ITypeSymbol typeSymbol, ITypeSymbol sourceTypeSymbol, bool hasInheritedClass)
|
||||
{
|
||||
var sourceProperties = sourceTypeSymbol.GetAllMembers().OfType<IPropertySymbol>().ToArray();
|
||||
|
||||
return sourceTypeSymbol
|
||||
.GetAllMembers()
|
||||
.OfType<IPropertySymbol>()
|
||||
.Where(p => !p.HasAttribute(IgnoreMemberAttributeTypeSymbol))
|
||||
.Select(property => MapPropertySimple(typeSymbol, property))
|
||||
.Where(mappedProperty => mappedProperty is not null)
|
||||
.ToImmutableArray()!;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -1,42 +0,0 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
|
||||
<IsPackable>false</IsPackable>
|
||||
|
||||
<Nullable>enable</Nullable>
|
||||
|
||||
<RootNamespace>MapTo.Tests</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.3">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.2.0" />
|
||||
<PackageReference Update="Nerdbank.GitVersioning">
|
||||
<Version>3.5.109</Version>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.0" />
|
||||
<PackageReference Include="Shouldly" Version="4.0.3" />
|
||||
<PackageReference Include="xunit" Version="2.4.2" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="coverlet.collector" Version="3.1.2">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\BlueWest.MapTo\BlueWest.MapTo.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
|
@ -1,252 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using MapTo.Extensions;
|
||||
using MapTo.Sources;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using Shouldly;
|
||||
|
||||
namespace MapTo.Tests
|
||||
{
|
||||
internal static class Common
|
||||
{
|
||||
internal const int Indent1 = 4;
|
||||
internal const int Indent2 = Indent1 * 2;
|
||||
internal const int Indent3 = Indent1 * 3;
|
||||
internal static readonly Location IgnoreLocation = Location.None;
|
||||
|
||||
internal static readonly Dictionary<string, string> DefaultAnalyzerOptions = new()
|
||||
{
|
||||
[GeneratorExecutionContextExtensions.GetBuildPropertyName(nameof(SourceGenerationOptions.GenerateXmlDocument))] = "false"
|
||||
};
|
||||
|
||||
internal static string GetSourceText(SourceGeneratorOptions? options = null)
|
||||
{
|
||||
const string ns = "Test";
|
||||
options ??= new SourceGeneratorOptions();
|
||||
var hasDifferentSourceNamespace = options.SourceClassNamespace != ns;
|
||||
var builder = new SourceBuilder();
|
||||
|
||||
builder.WriteLine("//");
|
||||
builder.WriteLine("// Test source code.");
|
||||
builder.WriteLine("//");
|
||||
builder.WriteLine();
|
||||
|
||||
options.Usings?.ForEach(s => builder.WriteLine($"using {s};"));
|
||||
|
||||
if (options.UseMapToNamespace)
|
||||
{
|
||||
builder.WriteLine($"using {Constants.RootNamespace};");
|
||||
}
|
||||
|
||||
builder
|
||||
.WriteLine($"using {options.SourceClassNamespace};")
|
||||
.WriteLine()
|
||||
.WriteLine();
|
||||
|
||||
builder
|
||||
.WriteLine($"namespace {ns}")
|
||||
.WriteOpeningBracket();
|
||||
|
||||
if (hasDifferentSourceNamespace && options.UseMapToNamespace)
|
||||
{
|
||||
builder
|
||||
.WriteLine($"using {options.SourceClassNamespace};")
|
||||
.WriteLine()
|
||||
.WriteLine();
|
||||
}
|
||||
|
||||
builder
|
||||
.WriteLine(options.UseMapToNamespace ? "[MapFrom(typeof(Baz))]" : "[MapTo.MapFrom(typeof(Baz))]")
|
||||
.WriteLine("public partial class Foo")
|
||||
.WriteOpeningBracket();
|
||||
|
||||
for (var i = 1; i <= options.ClassPropertiesCount; i++)
|
||||
{
|
||||
builder.WriteLine(i % 2 == 0 ? $"public int Prop{i} {{ get; set; }}" : $"public int Prop{i} {{ get; }}");
|
||||
}
|
||||
|
||||
options.PropertyBuilder?.Invoke(builder);
|
||||
|
||||
builder
|
||||
.WriteClosingBracket()
|
||||
.WriteClosingBracket()
|
||||
.WriteLine()
|
||||
.WriteLine();
|
||||
|
||||
builder
|
||||
.WriteLine($"namespace {options.SourceClassNamespace}")
|
||||
.WriteOpeningBracket()
|
||||
.WriteLine("public class Baz")
|
||||
.WriteOpeningBracket();
|
||||
|
||||
for (var i = 1; i <= options.SourceClassPropertiesCount; i++)
|
||||
{
|
||||
builder.WriteLine(i % 2 == 0 ? $"public int Prop{i} {{ get; set; }}" : $"public int Prop{i} {{ get; }}");
|
||||
}
|
||||
|
||||
options.SourcePropertyBuilder?.Invoke(builder);
|
||||
|
||||
builder
|
||||
.WriteClosingBracket()
|
||||
.WriteClosingBracket();
|
||||
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
internal static string[] GetEmployeeManagerSourceText(
|
||||
Func<string>? employeeClassSource = null,
|
||||
Func<string>? managerClassSource = null,
|
||||
Func<string>? employeeViewModelSource = null,
|
||||
Func<string>? managerViewModelSource = null,
|
||||
bool useDifferentViewModelNamespace = false)
|
||||
{
|
||||
return new[]
|
||||
{
|
||||
employeeClassSource?.Invoke() ?? DefaultEmployeeClassSource(),
|
||||
managerClassSource?.Invoke() ?? DefaultManagerClassSource(),
|
||||
employeeViewModelSource?.Invoke() ??
|
||||
DefaultEmployeeViewModelSource(useDifferentViewModelNamespace),
|
||||
managerViewModelSource?.Invoke() ?? DefaultManagerViewModelSource(useDifferentViewModelNamespace)
|
||||
};
|
||||
|
||||
static string DefaultEmployeeClassSource() =>
|
||||
@"
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Test.Data.Models
|
||||
{
|
||||
public class Employee
|
||||
{
|
||||
public int Id { get; set; }
|
||||
|
||||
public string EmployeeCode { get; set; }
|
||||
|
||||
public Manager Manager { get; set; }
|
||||
}
|
||||
}".Trim();
|
||||
|
||||
static string DefaultManagerClassSource() =>
|
||||
@"using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Test.Data.Models
|
||||
{
|
||||
public class Manager: Employee
|
||||
{
|
||||
public int Level { get; set; }
|
||||
|
||||
public IEnumerable<Employee> Employees { get; set; } = Array.Empty<Employee>();
|
||||
}
|
||||
}
|
||||
".Trim();
|
||||
|
||||
static string DefaultEmployeeViewModelSource(bool useDifferentNamespace) => useDifferentNamespace
|
||||
? @"
|
||||
using MapTo;
|
||||
using Test.Data.Models;
|
||||
using Test.ViewModels2;
|
||||
|
||||
namespace Test.ViewModels
|
||||
{
|
||||
[MapFrom(typeof(Employee))]
|
||||
public partial class EmployeeViewModel
|
||||
{
|
||||
public int Id { get; set; }
|
||||
|
||||
public string EmployeeCode { get; set; }
|
||||
|
||||
public ManagerViewModel Manager { get; set; }
|
||||
}
|
||||
}
|
||||
".Trim()
|
||||
: @"
|
||||
using MapTo;
|
||||
using Test.Data.Models;
|
||||
|
||||
namespace Test.ViewModels
|
||||
{
|
||||
[MapFrom(typeof(Employee))]
|
||||
public partial class EmployeeViewModel
|
||||
{
|
||||
public int Id { get; set; }
|
||||
|
||||
public string EmployeeCode { get; set; }
|
||||
|
||||
public ManagerViewModel Manager { get; set; }
|
||||
}
|
||||
}
|
||||
".Trim();
|
||||
|
||||
static string DefaultManagerViewModelSource(bool useDifferentNamespace) => useDifferentNamespace
|
||||
? @"
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using MapTo;
|
||||
using Test.Data.Models;
|
||||
using Test.ViewModels;
|
||||
|
||||
namespace Test.ViewModels2
|
||||
{
|
||||
[MapFrom(typeof(Manager))]
|
||||
public partial class ManagerViewModel : EmployeeViewModel
|
||||
{
|
||||
public int Level { get; set; }
|
||||
|
||||
public IEnumerable<EmployeeViewModel> Employees { get; set; } = Array.Empty<EmployeeViewModel>();
|
||||
}
|
||||
}
|
||||
".Trim()
|
||||
: @"
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using MapTo;
|
||||
using Test.Data.Models;
|
||||
|
||||
namespace Test.ViewModels
|
||||
{
|
||||
[MapFrom(typeof(Manager))]
|
||||
public partial class ManagerViewModel : EmployeeViewModel
|
||||
{
|
||||
public int Level { get; set; }
|
||||
|
||||
public IEnumerable<EmployeeViewModel> Employees { get; set; } = Array.Empty<EmployeeViewModel>();
|
||||
}
|
||||
}".Trim();
|
||||
}
|
||||
|
||||
internal static PropertyDeclarationSyntax GetPropertyDeclarationSyntax(SyntaxTree syntaxTree, string targetPropertyName, string targetClass = "Foo")
|
||||
{
|
||||
return syntaxTree.GetRoot()
|
||||
.DescendantNodes()
|
||||
.OfType<ClassDeclarationSyntax>()
|
||||
.Single(c => c.Identifier.ValueText == targetClass)
|
||||
.DescendantNodes()
|
||||
.OfType<PropertyDeclarationSyntax>()
|
||||
.Single(p => p.Identifier.ValueText == targetPropertyName);
|
||||
}
|
||||
|
||||
internal static IPropertySymbol GetSourcePropertySymbol(string propertyName, Compilation compilation, string targetClass = "Foo")
|
||||
{
|
||||
var syntaxTree = compilation.SyntaxTrees.First();
|
||||
var propSyntax = GetPropertyDeclarationSyntax(syntaxTree, propertyName, targetClass);
|
||||
|
||||
var semanticModel = compilation.GetSemanticModel(syntaxTree);
|
||||
return semanticModel.GetDeclaredSymbol(propSyntax).ShouldNotBeNull();
|
||||
}
|
||||
|
||||
internal record SourceGeneratorOptions(
|
||||
bool UseMapToNamespace = false,
|
||||
string SourceClassNamespace = "Test.Models",
|
||||
int ClassPropertiesCount = 3,
|
||||
int SourceClassPropertiesCount = 3,
|
||||
Action<SourceBuilder>? PropertyBuilder = null,
|
||||
Action<SourceBuilder>? SourcePropertyBuilder = null,
|
||||
IEnumerable<string>? Usings = null);
|
||||
}
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
// ReSharper disable UnusedType.Global
|
||||
// ReSharper disable CheckNamespace
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace System.Runtime.CompilerServices
|
||||
{
|
||||
/// <summary>
|
||||
/// Reserved to be used by the compiler for tracking metadata.
|
||||
/// This class should not be used by developers in source code.
|
||||
/// </summary>
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
internal static class IsExternalInit { }
|
||||
}
|
|
@ -1,42 +0,0 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Microsoft.CodeAnalysis;
|
||||
|
||||
namespace MapTo.Tests.Extensions
|
||||
{
|
||||
internal static class RoslynExtensions
|
||||
{
|
||||
internal static SyntaxTree? GetGeneratedSyntaxTree(this Compilation compilation, string className) =>
|
||||
compilation.SyntaxTrees.FirstOrDefault(s => s.FilePath.EndsWith($"{className}.g.cs"));
|
||||
|
||||
internal static string PrintSyntaxTree(this Compilation compilation)
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
|
||||
return string.Join(
|
||||
Environment.NewLine,
|
||||
compilation.SyntaxTrees
|
||||
.Reverse()
|
||||
.Select((s, i) =>
|
||||
{
|
||||
builder
|
||||
.Clear()
|
||||
.AppendLine("----------------------------------------")
|
||||
.AppendFormat("File Path: \"{0}\"", s.FilePath).AppendLine()
|
||||
.AppendFormat("Index: \"{0}\"", i).AppendLine()
|
||||
.AppendLine();
|
||||
|
||||
var lines = s.ToString().Split(Environment.NewLine);
|
||||
var lineNumber = 0;
|
||||
foreach (var line in lines)
|
||||
{
|
||||
builder.AppendFormat("{0:00}: {1}", lineNumber, line).AppendLine();
|
||||
lineNumber++;
|
||||
}
|
||||
|
||||
return builder.ToString();
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,88 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
|
||||
namespace MapTo.Tests.Extensions
|
||||
{
|
||||
internal static class ShouldlyExtensions
|
||||
{
|
||||
internal static void ShouldContainSource(this IEnumerable<SyntaxTree> syntaxTree, string typeName, string expectedSource, string? customMessage = null)
|
||||
{
|
||||
var syntax = syntaxTree
|
||||
.Select(s => s.ToString().Trim())
|
||||
.FirstOrDefault(s => s.Contains(typeName));
|
||||
|
||||
syntax.ShouldNotBeNullOrWhiteSpace();
|
||||
syntax.ShouldBe(expectedSource, customMessage);
|
||||
}
|
||||
|
||||
internal static void ShouldContainPartialSource(this IEnumerable<SyntaxTree> syntaxTree, string typeName, string expectedSource, string? customMessage = null)
|
||||
{
|
||||
var syntax = syntaxTree
|
||||
.Select(s => s.ToString().Trim())
|
||||
.FirstOrDefault(s => s.Contains(typeName));
|
||||
|
||||
syntax.ShouldNotBeNullOrWhiteSpace();
|
||||
syntax.ShouldContainWithoutWhitespace(expectedSource, customMessage);
|
||||
}
|
||||
|
||||
internal static void ShouldContainPartialSource(this SyntaxTree syntaxTree, string expectedSource, string? customMessage = null)
|
||||
{
|
||||
var syntax = syntaxTree.ToString();
|
||||
syntax.ShouldNotBeNullOrWhiteSpace();
|
||||
syntax.ShouldContainWithoutWhitespace(expectedSource, customMessage);
|
||||
}
|
||||
|
||||
internal static void ShouldBeSuccessful(this IEnumerable<Diagnostic> diagnostics, Compilation? compilation = null, IEnumerable<string>? ignoreDiagnosticsIds = null)
|
||||
{
|
||||
var actual = diagnostics
|
||||
.Where(d => (ignoreDiagnosticsIds is null || ignoreDiagnosticsIds.All(i => !d.Id.StartsWith(i) )) && (d.Severity is DiagnosticSeverity.Warning or DiagnosticSeverity.Error))
|
||||
.Select(c => $"{c.Severity}: {c.Location.GetLineSpan()} - {c.GetMessage()}").ToArray();
|
||||
|
||||
if (!actual.Any())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var builder = new StringBuilder();
|
||||
builder.AppendLine("Failed");
|
||||
|
||||
foreach (var d in actual)
|
||||
{
|
||||
builder.AppendFormat("- {0}", d).AppendLine();
|
||||
}
|
||||
|
||||
if (compilation is not null)
|
||||
{
|
||||
builder.AppendLine("Generated Sources:");
|
||||
builder.AppendLine(compilation.PrintSyntaxTree());
|
||||
}
|
||||
|
||||
Assert.False(true, builder.ToString());
|
||||
}
|
||||
|
||||
internal static void ShouldNotBeSuccessful(this ImmutableArray<Diagnostic> diagnostics, Diagnostic expectedError)
|
||||
{
|
||||
var actualDiagnostics = diagnostics.FirstOrDefault(d => d.Id == expectedError.Id);
|
||||
var compilationDiagnostics = actualDiagnostics == null ? diagnostics : diagnostics.Except(new[] { actualDiagnostics });
|
||||
|
||||
compilationDiagnostics.ShouldBeSuccessful();
|
||||
|
||||
Assert.NotNull(actualDiagnostics);
|
||||
Assert.Equal(expectedError.Id, actualDiagnostics?.Id);
|
||||
Assert.Equal(expectedError.Descriptor.Id, actualDiagnostics?.Descriptor.Id);
|
||||
Assert.Equal(expectedError.Descriptor.Description, actualDiagnostics?.Descriptor.Description);
|
||||
Assert.Equal(expectedError.Descriptor.Title, actualDiagnostics?.Descriptor.Title);
|
||||
|
||||
if (expectedError.Location != Location.None)
|
||||
{
|
||||
Assert.Equal(expectedError.Location, actualDiagnostics?.Location);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,81 +0,0 @@
|
|||
using System.Linq;
|
||||
using MapTo.Extensions;
|
||||
using MapTo.Sources;
|
||||
using MapTo.Tests.Extensions;
|
||||
using MapTo.Tests.Infrastructure;
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
using static MapTo.Tests.Common;
|
||||
|
||||
namespace MapTo.Tests
|
||||
{
|
||||
public class IgnorePropertyAttributeTests
|
||||
{
|
||||
/*
|
||||
[Fact]
|
||||
public void VerifyIgnorePropertyAttribute()
|
||||
{
|
||||
// Arrange
|
||||
const string source = "";
|
||||
var expectedAttribute = $@"
|
||||
{Constants.GeneratedFilesHeader}
|
||||
using System;
|
||||
|
||||
namespace MapTo
|
||||
{{
|
||||
[AttributeUsage(AttributeTargets.Property, Inherited = false, AllowMultiple = false)]
|
||||
public sealed class IgnorePropertyAttribute : Attribute {{ }}
|
||||
}}
|
||||
".Trim();
|
||||
|
||||
// Act
|
||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions);
|
||||
|
||||
// Assert
|
||||
diagnostics.ShouldBeSuccessful();
|
||||
compilation.SyntaxTrees.ShouldContainSource(IgnorePropertyAttributeSource.AttributeName, expectedAttribute);
|
||||
}
|
||||
*/
|
||||
|
||||
[Fact]
|
||||
public void When_IgnorePropertyAttributeIsSpecified_Should_NotGenerateMappingsForThatProperty()
|
||||
{
|
||||
// Arrange
|
||||
var source = GetSourceText(new SourceGeneratorOptions(
|
||||
true,
|
||||
PropertyBuilder: builder =>
|
||||
{
|
||||
builder
|
||||
.WriteLine("[IgnoreProperty]")
|
||||
.WriteLine("public int Prop4 { get; set; }");
|
||||
},
|
||||
SourcePropertyBuilder: builder => builder.WriteLine("public int Prop4 { get; set; }")));
|
||||
|
||||
var expectedResult = @"
|
||||
partial class Foo
|
||||
{
|
||||
public Foo(Test.Models.Baz baz)
|
||||
: this(new MappingContext(), baz) { }
|
||||
|
||||
private protected Foo(MappingContext context, Test.Models.Baz baz)
|
||||
{
|
||||
if (context == null) throw new ArgumentNullException(nameof(context));
|
||||
if (baz == null) throw new ArgumentNullException(nameof(baz));
|
||||
|
||||
context.Register(baz, this);
|
||||
|
||||
Prop1 = baz.Prop1;
|
||||
Prop2 = baz.Prop2;
|
||||
Prop3 = baz.Prop3;
|
||||
}
|
||||
".Trim();
|
||||
|
||||
// Act
|
||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions);
|
||||
|
||||
// Assert
|
||||
diagnostics.ShouldBeSuccessful();
|
||||
compilation.SyntaxTrees.Last().ShouldContainPartialSource(expectedResult);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,66 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using MapTo.Tests.Extensions;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
|
||||
namespace MapTo.Tests.Infrastructure
|
||||
{
|
||||
internal static class CSharpGenerator
|
||||
{
|
||||
internal static (Compilation compilation, ImmutableArray<Diagnostic> diagnostics) GetOutputCompilation(
|
||||
string source,
|
||||
bool assertCompilation = false,
|
||||
IDictionary<string, string>? analyzerConfigOptions = null,
|
||||
NullableContextOptions nullableContextOptions = NullableContextOptions.Disable,
|
||||
LanguageVersion languageVersion = LanguageVersion.CSharp7_3) =>
|
||||
GetOutputCompilation(
|
||||
new[] { source },
|
||||
assertCompilation,
|
||||
analyzerConfigOptions,
|
||||
nullableContextOptions,
|
||||
languageVersion);
|
||||
|
||||
internal static (Compilation compilation, ImmutableArray<Diagnostic> diagnostics) GetOutputCompilation(
|
||||
IEnumerable<string> sources,
|
||||
bool assertCompilation = false,
|
||||
IDictionary<string, string>? analyzerConfigOptions = null,
|
||||
NullableContextOptions nullableContextOptions = NullableContextOptions.Disable,
|
||||
LanguageVersion languageVersion = LanguageVersion.CSharp7_3)
|
||||
{
|
||||
var references = AppDomain.CurrentDomain.GetAssemblies()
|
||||
.Where(a => !a.IsDynamic && !string.IsNullOrWhiteSpace(a.Location))
|
||||
.Select(a => MetadataReference.CreateFromFile(a.Location))
|
||||
.ToList();
|
||||
|
||||
var compilation = CSharpCompilation.Create(
|
||||
$"{typeof(CSharpGenerator).Assembly.GetName().Name}.Dynamic",
|
||||
sources.Select((source, index) => CSharpSyntaxTree.ParseText(source, path: $"Test{index:00}.g.cs", options: new CSharpParseOptions(languageVersion))),
|
||||
references,
|
||||
new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary, nullableContextOptions: nullableContextOptions));
|
||||
|
||||
if (assertCompilation)
|
||||
{
|
||||
// NB: fail tests when the injected program isn't valid _before_ running generators
|
||||
compilation.GetDiagnostics().ShouldBeSuccessful();
|
||||
}
|
||||
|
||||
var driver = CSharpGeneratorDriver.Create(
|
||||
new List<ISourceGenerator>() { new MapToGenerator(), new EfMethodsGenerator() },
|
||||
optionsProvider: new TestAnalyzerConfigOptionsProvider(analyzerConfigOptions),
|
||||
parseOptions: new CSharpParseOptions(languageVersion)
|
||||
);
|
||||
|
||||
driver.RunGeneratorsAndUpdateCompilation(compilation, out var outputCompilation, out var generateDiagnostics);
|
||||
|
||||
generateDiagnostics.ShouldBeSuccessful(ignoreDiagnosticsIds: new[] { "MT" });
|
||||
outputCompilation.GetDiagnostics().ShouldBeSuccessful(outputCompilation);
|
||||
|
||||
return (outputCompilation, generateDiagnostics);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.Diagnostics;
|
||||
|
||||
namespace MapTo.Tests.Infrastructure
|
||||
{
|
||||
internal sealed class TestAnalyzerConfigOptions : AnalyzerConfigOptions
|
||||
{
|
||||
private readonly ImmutableDictionary<string, string> _backing;
|
||||
|
||||
public TestAnalyzerConfigOptions(IDictionary<string, string>? properties)
|
||||
{
|
||||
_backing = properties?.ToImmutableDictionary(KeyComparer) ?? ImmutableDictionary.Create<string, string>(KeyComparer);
|
||||
}
|
||||
|
||||
public override bool TryGetValue(string key, out string? value) => _backing.TryGetValue(key, out value);
|
||||
}
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.Diagnostics;
|
||||
|
||||
namespace MapTo.Tests.Infrastructure
|
||||
{
|
||||
internal sealed class TestAnalyzerConfigOptionsProvider : AnalyzerConfigOptionsProvider
|
||||
{
|
||||
public TestAnalyzerConfigOptionsProvider(IDictionary<string, string>? options)
|
||||
{
|
||||
GlobalOptions = new TestAnalyzerConfigOptions(options);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override AnalyzerConfigOptions GlobalOptions { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override AnalyzerConfigOptions GetOptions(SyntaxTree tree) => throw new NotImplementedException();
|
||||
|
||||
/// <inheritdoc />
|
||||
public override AnalyzerConfigOptions GetOptions(AdditionalText textFile) => throw new NotImplementedException();
|
||||
}
|
||||
}
|
|
@ -1,202 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using MapTo.Sources;
|
||||
using MapTo.Tests.Extensions;
|
||||
using MapTo.Tests.Infrastructure;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Xunit;
|
||||
using static MapTo.Tests.Common;
|
||||
|
||||
namespace MapTo.Tests
|
||||
{
|
||||
public class MapPropertyTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(NullableContextOptions.Disable)]
|
||||
[InlineData(NullableContextOptions.Enable)]
|
||||
public void VerifyMapPropertyAttribute(NullableContextOptions nullableContextOptions)
|
||||
{
|
||||
// Arrange
|
||||
const string source = "";
|
||||
var nullableSyntax = nullableContextOptions == NullableContextOptions.Enable ? "?" : string.Empty;
|
||||
var languageVersion = nullableContextOptions == NullableContextOptions.Enable ? LanguageVersion.CSharp8 : LanguageVersion.CSharp7_3;
|
||||
var expectedInterface = $@"
|
||||
{Constants.GeneratedFilesHeader}
|
||||
{(nullableContextOptions == NullableContextOptions.Enable ? $"#nullable enable{Environment.NewLine}" : string.Empty)}
|
||||
using System;
|
||||
|
||||
namespace MapTo
|
||||
{{
|
||||
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Parameter, AllowMultiple = true)]
|
||||
public sealed class MapPropertyAttribute : Attribute
|
||||
{{
|
||||
public string{nullableSyntax} SourcePropertyName {{ get; set; }}
|
||||
}}
|
||||
}}
|
||||
".Trim();
|
||||
|
||||
// Act
|
||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions, nullableContextOptions: nullableContextOptions, languageVersion: languageVersion);
|
||||
|
||||
|
||||
// Assert
|
||||
diagnostics.ShouldBeSuccessful();
|
||||
compilation.SyntaxTrees.ShouldContainSource(MapPropertyAttributeSource.AttributeName, expectedInterface);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void When_MapPropertyFound_Should_UseItToMapToSourceProperty()
|
||||
{
|
||||
// Arrange
|
||||
var source = GetSourceText(new SourceGeneratorOptions(
|
||||
true,
|
||||
PropertyBuilder: builder =>
|
||||
{
|
||||
builder
|
||||
.WriteLine("[MapProperty(SourcePropertyName = nameof(Baz.Prop3))]")
|
||||
.WriteLine("public int Prop4 { get; set; }");
|
||||
},
|
||||
SourcePropertyBuilder: builder => builder.WriteLine("public int Prop4 { get; set; }")));
|
||||
|
||||
var expectedResult = @"
|
||||
partial class Foo
|
||||
{
|
||||
public Foo(Test.Models.Baz baz)
|
||||
: this(new MappingContext(), baz) { }
|
||||
|
||||
private protected Foo(MappingContext context, Test.Models.Baz baz)
|
||||
{
|
||||
if (context == null) throw new ArgumentNullException(nameof(context));
|
||||
if (baz == null) throw new ArgumentNullException(nameof(baz));
|
||||
|
||||
context.Register(baz, this);
|
||||
|
||||
Prop1 = baz.Prop1;
|
||||
Prop2 = baz.Prop2;
|
||||
Prop3 = baz.Prop3;
|
||||
Prop4 = baz.Prop3;
|
||||
}
|
||||
".Trim();
|
||||
|
||||
// Act
|
||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions);
|
||||
|
||||
// Assert
|
||||
diagnostics.ShouldBeSuccessful();
|
||||
compilation.SyntaxTrees.Last().ShouldContainPartialSource(expectedResult);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(MapPropertyWithImplicitConversionFoundData))]
|
||||
public void When_MapPropertyWithImplicitConversionFound_Should_UseItToMapToSourceProperty(string source, string expectedResult, LanguageVersion languageVersion)
|
||||
{
|
||||
// Arrange
|
||||
source = source.Trim();
|
||||
|
||||
// Act
|
||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions, languageVersion: languageVersion);
|
||||
|
||||
// Assert
|
||||
diagnostics.ShouldBeSuccessful();
|
||||
compilation.SyntaxTrees.Last().ShouldContainPartialSource(expectedResult);
|
||||
}
|
||||
|
||||
public static IEnumerable<object[]> MapPropertyWithImplicitConversionFoundData => new List<object[]>
|
||||
{
|
||||
new object[]
|
||||
{
|
||||
@"
|
||||
namespace Test
|
||||
{
|
||||
using System.Collections.Generic;
|
||||
|
||||
public class InnerClass { public int Prop1 { get; set; } }
|
||||
public class OuterClass
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public List<InnerClass> InnerProp { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
namespace Test.Models
|
||||
{
|
||||
using MapTo;
|
||||
using System.Collections.Generic;
|
||||
|
||||
[MapFrom(typeof(Test.InnerClass))]
|
||||
public partial class InnerClass { public int Prop1 { get; set; } }
|
||||
|
||||
[MapFrom(typeof(Test.OuterClass))]
|
||||
public partial class OuterClass
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public IReadOnlyList<InnerClass> InnerProp { get; set; }
|
||||
}
|
||||
}
|
||||
",
|
||||
@"
|
||||
private protected OuterClass(MappingContext context, Test.OuterClass outerClass)
|
||||
{
|
||||
if (context == null) throw new ArgumentNullException(nameof(context));
|
||||
if (outerClass == null) throw new ArgumentNullException(nameof(outerClass));
|
||||
|
||||
context.Register(outerClass, this);
|
||||
|
||||
Id = outerClass.Id;
|
||||
InnerProp = outerClass.InnerProp.Select(context.MapFromWithContext<Test.InnerClass, InnerClass>).ToList();
|
||||
}
|
||||
",
|
||||
LanguageVersion.CSharp7_3
|
||||
},
|
||||
new object[]
|
||||
{
|
||||
@"
|
||||
namespace Test
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
public class InnerClass
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string Name { get; set; }
|
||||
}
|
||||
|
||||
public class OuterClass
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public List<InnerClass> InnerClasses { get; set; }
|
||||
public DateTime? SomeDate { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
namespace Test.Models
|
||||
{
|
||||
using MapTo;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
[MapFrom(typeof(Test.InnerClass))]
|
||||
public partial record InnerClass(int Id, string Name);
|
||||
|
||||
[MapFrom(typeof(Test.OuterClass))]
|
||||
public partial record OuterClass(int Id, DateTime? SomeDate, IReadOnlyList<InnerClass> InnerClasses);
|
||||
}
|
||||
",
|
||||
@"
|
||||
private protected OuterClass(MappingContext context, Test.OuterClass outerClass)
|
||||
: this(Id: outerClass.Id, SomeDate: outerClass.SomeDate, InnerClasses: outerClass.InnerClasses.Select(context.MapFromWithContext<Test.InnerClass, InnerClass>).ToList())
|
||||
{
|
||||
if (context == null) throw new ArgumentNullException(nameof(context));
|
||||
if (outerClass == null) throw new ArgumentNullException(nameof(outerClass));
|
||||
|
||||
context.Register(outerClass, this);
|
||||
}
|
||||
",
|
||||
LanguageVersion.CSharp9
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
|
@ -1,601 +0,0 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using MapTo.Sources;
|
||||
using MapTo.Tests.Extensions;
|
||||
using MapTo.Tests.Infrastructure;
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
using static MapTo.Extensions.GeneratorExecutionContextExtensions;
|
||||
using static MapTo.Tests.Common;
|
||||
|
||||
namespace MapTo.Tests
|
||||
{
|
||||
public class MapToTests
|
||||
{
|
||||
private static readonly string ExpectedAttribute = $@"{Constants.GeneratedFilesHeader}
|
||||
using System;
|
||||
|
||||
namespace MapTo
|
||||
{{
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
|
||||
public sealed class MapFromAttribute : Attribute
|
||||
{{
|
||||
public MapFromAttribute(Type sourceType)
|
||||
{{
|
||||
SourceType = sourceType;
|
||||
}}
|
||||
|
||||
public Type SourceType {{ get; }}
|
||||
}}
|
||||
}}";
|
||||
|
||||
[Fact]
|
||||
public void VerifyMapToAttribute()
|
||||
{
|
||||
// Arrange
|
||||
const string source = "";
|
||||
|
||||
// Act
|
||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions);
|
||||
|
||||
// Assert
|
||||
diagnostics.ShouldBeSuccessful();
|
||||
compilation.SyntaxTrees.ShouldContainSource(MapFromAttributeSource.AttributeClassName, ExpectedAttribute);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void When_FoundMatchingPropertyNameWithDifferentTypes_Should_ReportError()
|
||||
{
|
||||
// Arrange
|
||||
var source = GetSourceText(new SourceGeneratorOptions(
|
||||
true,
|
||||
PropertyBuilder: builder => { builder.WriteLine("public string Prop4 { get; set; }"); },
|
||||
SourcePropertyBuilder: builder => builder.WriteLine("public int Prop4 { get; set; }")));
|
||||
|
||||
// Act
|
||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions);
|
||||
|
||||
// Assert
|
||||
var expectedError = DiagnosticsFactory.NoMatchingPropertyTypeFoundError(GetSourcePropertySymbol("Prop4", compilation));
|
||||
|
||||
diagnostics.ShouldNotBeSuccessful(expectedError);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void When_MappingsModifierOptionIsSetToInternal_Should_GenerateThoseMethodsWithInternalAccessModifier()
|
||||
{
|
||||
// Arrange
|
||||
var source = GetSourceText();
|
||||
var configOptions = new Dictionary<string, string>
|
||||
{
|
||||
[GetBuildPropertyName(nameof(SourceGenerationOptions.GeneratedMethodsAccessModifier))] = "Internal",
|
||||
[GetBuildPropertyName(nameof(SourceGenerationOptions.GenerateXmlDocument))] = "false"
|
||||
};
|
||||
|
||||
var expectedExtension = @"
|
||||
internal static partial class BazToFooExtensions
|
||||
{
|
||||
internal static Foo ToFoo(this Test.Models.Baz baz)
|
||||
{
|
||||
return baz == null ? null : new Foo(baz);
|
||||
}
|
||||
}".Trim();
|
||||
|
||||
var expectedFactory = @"
|
||||
internal static Foo From(Test.Models.Baz baz)
|
||||
{
|
||||
return baz == null ? null : MappingContext.Create<Test.Models.Baz, Foo>(baz);
|
||||
}".Trim();
|
||||
|
||||
// Act
|
||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: configOptions);
|
||||
|
||||
// Assert
|
||||
diagnostics.ShouldBeSuccessful();
|
||||
|
||||
var syntaxTree = compilation.SyntaxTrees.Last().ToString();
|
||||
syntaxTree.ShouldContain(expectedFactory);
|
||||
syntaxTree.ShouldContain(expectedExtension);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void When_MapToAttributeFound_Should_GenerateTheClass()
|
||||
{
|
||||
// Arrange
|
||||
const string source = @"
|
||||
using MapTo;
|
||||
|
||||
namespace Test
|
||||
{
|
||||
[MapFrom(typeof(Baz))]
|
||||
public partial class Foo
|
||||
{
|
||||
public int Prop1 { get; set; }
|
||||
}
|
||||
|
||||
public class Baz
|
||||
{
|
||||
public int Prop1 { get; set; }
|
||||
}
|
||||
}
|
||||
";
|
||||
|
||||
const string expectedResult = @"
|
||||
// <auto-generated />
|
||||
using MapTo;
|
||||
using System;
|
||||
|
||||
namespace Test
|
||||
{
|
||||
partial class Foo
|
||||
{
|
||||
public Foo(Test.Baz baz)
|
||||
: this(new MappingContext(), baz) { }
|
||||
|
||||
private protected Foo(MappingContext context, Test.Baz baz)
|
||||
{
|
||||
if (context == null) throw new ArgumentNullException(nameof(context));
|
||||
if (baz == null) throw new ArgumentNullException(nameof(baz));
|
||||
|
||||
context.Register(baz, this);
|
||||
|
||||
Prop1 = baz.Prop1;
|
||||
}
|
||||
";
|
||||
|
||||
// Act
|
||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions);
|
||||
|
||||
// Assert
|
||||
diagnostics.ShouldBeSuccessful();
|
||||
compilation.SyntaxTrees.Last().ToString().ShouldStartWith(expectedResult.Trim());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void When_MapToAttributeFoundWithoutMatchingProperties_Should_ReportError()
|
||||
{
|
||||
// Arrange
|
||||
const string source = @"
|
||||
using MapTo;
|
||||
|
||||
namespace Test
|
||||
{
|
||||
[MapFrom(typeof(Baz))]
|
||||
public partial class Foo { }
|
||||
|
||||
public class Baz { public int Prop1 { get; set; } }
|
||||
}
|
||||
";
|
||||
|
||||
// Act
|
||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source);
|
||||
|
||||
// Assert
|
||||
var fooType = compilation.GetTypeByMetadataName("Test.Foo");
|
||||
fooType.ShouldNotBeNull();
|
||||
|
||||
var bazType = compilation.GetTypeByMetadataName("Test.Baz");
|
||||
bazType.ShouldNotBeNull();
|
||||
|
||||
var expectedDiagnostic = DiagnosticsFactory.NoMatchingPropertyFoundError(fooType.Locations.Single(), fooType, bazType);
|
||||
var error = diagnostics.FirstOrDefault(d => d.Id == expectedDiagnostic.Id);
|
||||
error.ShouldNotBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void When_MapToAttributeWithNamespaceFound_Should_GenerateTheClass()
|
||||
{
|
||||
// Arrange
|
||||
const string source = @"
|
||||
namespace Test
|
||||
{
|
||||
[MapTo.MapFrom(typeof(Baz))]
|
||||
public partial class Foo { public int Prop1 { get; set; } }
|
||||
|
||||
public class Baz { public int Prop1 { get; set; } }
|
||||
}
|
||||
";
|
||||
|
||||
const string expectedResult = @"
|
||||
// <auto-generated />
|
||||
using MapTo;
|
||||
using System;
|
||||
|
||||
namespace Test
|
||||
{
|
||||
partial class Foo
|
||||
{
|
||||
public Foo(Test.Baz baz)
|
||||
: this(new MappingContext(), baz) { }
|
||||
|
||||
private protected Foo(MappingContext context, Test.Baz baz)
|
||||
{
|
||||
if (context == null) throw new ArgumentNullException(nameof(context));
|
||||
if (baz == null) throw new ArgumentNullException(nameof(baz));
|
||||
|
||||
context.Register(baz, this);
|
||||
|
||||
Prop1 = baz.Prop1;
|
||||
}
|
||||
";
|
||||
|
||||
// Act
|
||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions);
|
||||
|
||||
// Assert
|
||||
diagnostics.ShouldBeSuccessful();
|
||||
compilation.SyntaxTrees.Last().ToString().ShouldStartWith(expectedResult.Trim());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void When_NoMapToAttributeFound_Should_GenerateOnlyTheAttribute()
|
||||
{
|
||||
// Arrange
|
||||
const string source = "";
|
||||
var expectedTypes = new[]
|
||||
{
|
||||
//IgnorePropertyAttributeSource.AttributeName,
|
||||
MapFromAttributeSource.AttributeName,
|
||||
ITypeConverterSource.InterfaceName,
|
||||
MapPropertyAttributeSource.AttributeName
|
||||
};
|
||||
|
||||
// Act
|
||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source);
|
||||
|
||||
// Assert
|
||||
diagnostics.ShouldBeSuccessful();
|
||||
compilation.SyntaxTrees
|
||||
.Select(s => s.ToString())
|
||||
.Where(s => !string.IsNullOrWhiteSpace(s.ToString()))
|
||||
.All(s => expectedTypes.Any(s.Contains))
|
||||
.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void When_SourceTypeHasDifferentNamespace_Should_NotAddToUsings()
|
||||
{
|
||||
// Arrange
|
||||
var source = GetSourceText(new SourceGeneratorOptions(SourceClassNamespace: "Bazaar"));
|
||||
|
||||
const string expectedResult = @"
|
||||
// <auto-generated />
|
||||
using MapTo;
|
||||
using System;
|
||||
|
||||
namespace Test
|
||||
{
|
||||
";
|
||||
|
||||
// Act
|
||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source);
|
||||
|
||||
// Assert
|
||||
diagnostics.ShouldBeSuccessful();
|
||||
compilation.SyntaxTrees.Last().ToString().ShouldStartWith(expectedResult.Trim());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void When_SourceTypeHasMatchingProperties_Should_CreateConstructorAndAssignSrcToDest()
|
||||
{
|
||||
// Arrange
|
||||
var source = GetSourceText();
|
||||
|
||||
const string expectedResult = @"
|
||||
partial class Foo
|
||||
{
|
||||
public Foo(Test.Models.Baz baz)
|
||||
: this(new MappingContext(), baz) { }
|
||||
|
||||
private protected Foo(MappingContext context, Test.Models.Baz baz)
|
||||
{
|
||||
if (context == null) throw new ArgumentNullException(nameof(context));
|
||||
if (baz == null) throw new ArgumentNullException(nameof(baz));
|
||||
|
||||
context.Register(baz, this);
|
||||
|
||||
Prop1 = baz.Prop1;
|
||||
Prop2 = baz.Prop2;
|
||||
Prop3 = baz.Prop3;
|
||||
}
|
||||
";
|
||||
|
||||
// Act
|
||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions);
|
||||
|
||||
// Assert
|
||||
diagnostics.ShouldBeSuccessful();
|
||||
compilation.SyntaxTrees.Last().ShouldContainPartialSource(expectedResult.Trim());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void When_SourceTypeHasMatchingProperties_Should_CreateFromStaticMethod()
|
||||
{
|
||||
// Arrange
|
||||
var source = GetSourceText();
|
||||
|
||||
const string expectedResult = @"
|
||||
public static Foo From(Test.Models.Baz baz)
|
||||
{
|
||||
return baz == null ? null : MappingContext.Create<Test.Models.Baz, Foo>(baz);
|
||||
}
|
||||
";
|
||||
|
||||
// Act
|
||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source);
|
||||
|
||||
// Assert
|
||||
diagnostics.ShouldBeSuccessful();
|
||||
compilation.SyntaxTrees.Last().ShouldContainPartialSource(expectedResult.Trim());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void When_SourceTypeHasMatchingProperties_Should_GenerateToExtensionMethodOnSourceType()
|
||||
{
|
||||
// Arrange
|
||||
var source = GetSourceText();
|
||||
|
||||
const string expectedResult = @"
|
||||
public static partial class BazToFooExtensions
|
||||
{
|
||||
public static Foo ToFoo(this Test.Models.Baz baz)
|
||||
{
|
||||
return baz == null ? null : new Foo(baz);
|
||||
}
|
||||
}
|
||||
";
|
||||
|
||||
// Act
|
||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions);
|
||||
|
||||
// Assert
|
||||
diagnostics.ShouldBeSuccessful();
|
||||
compilation.SyntaxTrees.Last().ShouldContainPartialSource(expectedResult.Trim());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void When_HasNestedObjectPropertyTypeHasMapFromAttribute_Should_UseContinueToMap()
|
||||
{
|
||||
// Arrange
|
||||
var source = GetSourceText(new SourceGeneratorOptions(
|
||||
SourceClassNamespace: "Test",
|
||||
PropertyBuilder: b => b.WriteLine("public B InnerProp1 { get; }"),
|
||||
SourcePropertyBuilder: b => b.WriteLine("public A InnerProp1 { get; }")));
|
||||
|
||||
source += @"
|
||||
namespace Test
|
||||
{
|
||||
public class A { public int Prop1 { get; } }
|
||||
|
||||
[MapTo.MapFrom(typeof(A))]
|
||||
public partial class B { public int Prop1 { get; }}
|
||||
}
|
||||
".Trim();
|
||||
|
||||
var expectedResult = @"
|
||||
partial class Foo
|
||||
{
|
||||
public Foo(Test.Baz baz)
|
||||
: this(new MappingContext(), baz) { }
|
||||
|
||||
private protected Foo(MappingContext context, Test.Baz baz)
|
||||
{
|
||||
if (context == null) throw new ArgumentNullException(nameof(context));
|
||||
if (baz == null) throw new ArgumentNullException(nameof(baz));
|
||||
|
||||
context.Register(baz, this);
|
||||
|
||||
Prop1 = baz.Prop1;
|
||||
Prop2 = baz.Prop2;
|
||||
Prop3 = baz.Prop3;
|
||||
InnerProp1 = context.MapFromWithContext<A, B>(baz.InnerProp1);
|
||||
}
|
||||
".Trim();
|
||||
|
||||
// Act
|
||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions);
|
||||
|
||||
// Assert
|
||||
diagnostics.ShouldBeSuccessful();
|
||||
compilation.SyntaxTrees.ToArray()[^2].ShouldContainPartialSource(expectedResult);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void When_HasNestedObjectPropertyTypeDoesNotHaveMapFromAttribute_Should_ReportError()
|
||||
{
|
||||
// Arrange
|
||||
var source = GetSourceText(new SourceGeneratorOptions(
|
||||
SourceClassNamespace: "Test",
|
||||
PropertyBuilder: b => b.WriteLine("public FooInner1 InnerProp1 { get; }"),
|
||||
SourcePropertyBuilder: b => b.WriteLine("public BazInner1 InnerProp1 { get; }")));
|
||||
|
||||
source += @"
|
||||
namespace Test
|
||||
{
|
||||
public class FooInner1 { public int Prop1 { get; } }
|
||||
|
||||
public partial class BazInner1 { public int Prop1 { get; }}
|
||||
}
|
||||
".Trim();
|
||||
|
||||
// Act
|
||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions);
|
||||
|
||||
// Assert
|
||||
var expectedError = DiagnosticsFactory.NoMatchingPropertyTypeFoundError(GetSourcePropertySymbol("InnerProp1", compilation));
|
||||
diagnostics.ShouldNotBeSuccessful(expectedError);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void When_HasNestedObjectPropertyTypeHasMapFromAttributeToDifferentType_Should_ReportError()
|
||||
{
|
||||
// Arrange
|
||||
var source = GetSourceText(new SourceGeneratorOptions(
|
||||
SourceClassNamespace: "Test",
|
||||
PropertyBuilder: b => b.WriteLine("public FooInner1 InnerProp1 { get; }"),
|
||||
SourcePropertyBuilder: b => b.WriteLine("public BazInner1 InnerProp1 { get; }")));
|
||||
|
||||
source += @"
|
||||
namespace Test
|
||||
{
|
||||
public class FooInner1 { public int Prop1 { get; } }
|
||||
|
||||
public class FooInner2 { public int Prop1 { get; } }
|
||||
|
||||
[MapTo.MapFrom(typeof(FooInner2))]
|
||||
public partial class BazInner1 { public int Prop1 { get; }}
|
||||
}
|
||||
".Trim();
|
||||
|
||||
// Act
|
||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions);
|
||||
|
||||
// Assert
|
||||
var expectedError = DiagnosticsFactory.NoMatchingPropertyTypeFoundError(GetSourcePropertySymbol("InnerProp1", compilation));
|
||||
diagnostics.ShouldNotBeSuccessful(expectedError);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void When_SourceTypeEnumerableProperties_Should_CreateConstructorAndAssignSrcToDest()
|
||||
{
|
||||
// Arrange
|
||||
var source = GetSourceText(new SourceGeneratorOptions(
|
||||
Usings: new[] { "System.Collections.Generic"},
|
||||
PropertyBuilder: builder => builder.WriteLine("public IEnumerable<int> Prop4 { get; }"),
|
||||
SourcePropertyBuilder: builder => builder.WriteLine("public IEnumerable<int> Prop4 { get; }")));
|
||||
|
||||
const string expectedResult = @"
|
||||
partial class Foo
|
||||
{
|
||||
public Foo(Test.Models.Baz baz)
|
||||
: this(new MappingContext(), baz) { }
|
||||
|
||||
private protected Foo(MappingContext context, Test.Models.Baz baz)
|
||||
{
|
||||
if (context == null) throw new ArgumentNullException(nameof(context));
|
||||
if (baz == null) throw new ArgumentNullException(nameof(baz));
|
||||
|
||||
context.Register(baz, this);
|
||||
|
||||
Prop1 = baz.Prop1;
|
||||
Prop2 = baz.Prop2;
|
||||
Prop3 = baz.Prop3;
|
||||
Prop4 = baz.Prop4;
|
||||
}
|
||||
";
|
||||
|
||||
// Act
|
||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions);
|
||||
|
||||
// Assert
|
||||
diagnostics.ShouldBeSuccessful();
|
||||
compilation.SyntaxTrees.Last().ShouldContainPartialSource(expectedResult.Trim());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void When_DestinationTypeHasBaseClass_Should_CallBaseConstructor()
|
||||
{
|
||||
// Arrange
|
||||
var sources = GetEmployeeManagerSourceText();
|
||||
|
||||
const string expectedResult = @"
|
||||
private protected ManagerViewModel(MappingContext context, Test.Data.Models.Manager manager) : base(context, manager)
|
||||
{
|
||||
if (context == null) throw new ArgumentNullException(nameof(context));
|
||||
if (manager == null) throw new ArgumentNullException(nameof(manager));
|
||||
|
||||
context.Register(manager, this);
|
||||
|
||||
Level = manager.Level;
|
||||
Employees = manager.Employees.Select(context.MapFromWithContext<Test.Data.Models.Employee, EmployeeViewModel>).ToList();
|
||||
}
|
||||
";
|
||||
|
||||
// Act
|
||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(sources, analyzerConfigOptions: DefaultAnalyzerOptions);
|
||||
|
||||
// Assert
|
||||
diagnostics.ShouldBeSuccessful();
|
||||
compilation.SyntaxTrees.Last().ShouldContainPartialSource(expectedResult);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void When_SourceTypeHasEnumerablePropertiesWithMapFromAttribute_Should_CreateANewEnumerableWithMappedObjects()
|
||||
{
|
||||
// Arrange
|
||||
var sources = GetEmployeeManagerSourceText();
|
||||
|
||||
const string expectedResult = @"
|
||||
// <auto-generated />
|
||||
using MapTo;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Test.ViewModels
|
||||
{
|
||||
partial class ManagerViewModel
|
||||
{
|
||||
public ManagerViewModel(Test.Data.Models.Manager manager)
|
||||
: this(new MappingContext(), manager) { }
|
||||
|
||||
private protected ManagerViewModel(MappingContext context, Test.Data.Models.Manager manager) : base(context, manager)
|
||||
{
|
||||
if (context == null) throw new ArgumentNullException(nameof(context));
|
||||
if (manager == null) throw new ArgumentNullException(nameof(manager));
|
||||
|
||||
context.Register(manager, this);
|
||||
|
||||
Level = manager.Level;
|
||||
Employees = manager.Employees.Select(context.MapFromWithContext<Test.Data.Models.Employee, EmployeeViewModel>).ToList();
|
||||
}
|
||||
";
|
||||
|
||||
// Act
|
||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(sources, analyzerConfigOptions: DefaultAnalyzerOptions);
|
||||
|
||||
// Assert
|
||||
diagnostics.ShouldBeSuccessful();
|
||||
compilation.SyntaxTrees.Last().ShouldContainPartialSource(expectedResult);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void When_SourceTypeHasEnumerablePropertiesWithMapFromAttributeInDifferentNamespaces_Should_CreateANewEnumerableWithMappedObjectsAndImportNamespace()
|
||||
{
|
||||
// Arrange
|
||||
var sources = GetEmployeeManagerSourceText(useDifferentViewModelNamespace: true);
|
||||
|
||||
const string expectedResult = @"
|
||||
using MapTo;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Test.ViewModels2
|
||||
{
|
||||
partial class ManagerViewModel
|
||||
{
|
||||
public ManagerViewModel(Test.Data.Models.Manager manager)
|
||||
: this(new MappingContext(), manager) { }
|
||||
|
||||
private protected ManagerViewModel(MappingContext context, Test.Data.Models.Manager manager) : base(context, manager)
|
||||
{
|
||||
if (context == null) throw new ArgumentNullException(nameof(context));
|
||||
if (manager == null) throw new ArgumentNullException(nameof(manager));
|
||||
|
||||
context.Register(manager, this);
|
||||
|
||||
Level = manager.Level;
|
||||
Employees = manager.Employees.Select(context.MapFromWithContext<Test.Data.Models.Employee, Test.ViewModels.EmployeeViewModel>).ToList();
|
||||
}
|
||||
";
|
||||
|
||||
// Act
|
||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(sources, analyzerConfigOptions: DefaultAnalyzerOptions);
|
||||
|
||||
// Assert
|
||||
diagnostics.ShouldBeSuccessful();
|
||||
compilation.SyntaxTrees.Last().ShouldContainPartialSource(expectedResult);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,283 +0,0 @@
|
|||
using System.Linq;
|
||||
using MapTo.Sources;
|
||||
using MapTo.Tests.Extensions;
|
||||
using MapTo.Tests.Infrastructure;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Xunit;
|
||||
using static MapTo.Tests.Common;
|
||||
|
||||
namespace MapTo.Tests
|
||||
{
|
||||
public class MapTypeConverterTests
|
||||
{
|
||||
[Fact]
|
||||
public void VerifyMapTypeConverterAttribute()
|
||||
{
|
||||
// Arrange
|
||||
const string source = "";
|
||||
var expectedInterface = $@"
|
||||
{Constants.GeneratedFilesHeader}
|
||||
|
||||
using System;
|
||||
|
||||
namespace MapTo
|
||||
{{
|
||||
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Parameter, AllowMultiple = false)]
|
||||
public sealed class MapTypeConverterAttribute : Attribute
|
||||
{{
|
||||
public MapTypeConverterAttribute(Type converter, object[] converterParameters = null)
|
||||
{{
|
||||
Converter = converter;
|
||||
ConverterParameters = converterParameters;
|
||||
}}
|
||||
|
||||
public Type Converter {{ get; }}
|
||||
|
||||
public object[] ConverterParameters {{ get; }}
|
||||
}}
|
||||
}}
|
||||
".Trim();
|
||||
|
||||
// Act
|
||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions);
|
||||
|
||||
// Assert
|
||||
diagnostics.ShouldBeSuccessful();
|
||||
compilation.SyntaxTrees.ShouldContainSource(MapTypeConverterAttributeSource.AttributeName, expectedInterface);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void VerifyMapTypeConverterAttributeWithNullableOptionOn()
|
||||
{
|
||||
// Arrange
|
||||
const string source = "";
|
||||
var expectedInterface = $@"
|
||||
{Constants.GeneratedFilesHeader}
|
||||
#nullable enable
|
||||
|
||||
using System;
|
||||
|
||||
namespace MapTo
|
||||
{{
|
||||
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Parameter, AllowMultiple = false)]
|
||||
public sealed class MapTypeConverterAttribute : Attribute
|
||||
{{
|
||||
public MapTypeConverterAttribute(Type converter, object[]? converterParameters = null)
|
||||
{{
|
||||
Converter = converter;
|
||||
ConverterParameters = converterParameters;
|
||||
}}
|
||||
|
||||
public Type Converter {{ get; }}
|
||||
|
||||
public object[]? ConverterParameters {{ get; }}
|
||||
}}
|
||||
}}
|
||||
".Trim();
|
||||
|
||||
// Act
|
||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions, nullableContextOptions: NullableContextOptions.Enable, languageVersion: LanguageVersion.CSharp8);
|
||||
|
||||
// Assert
|
||||
diagnostics.ShouldBeSuccessful();
|
||||
compilation.SyntaxTrees.ShouldContainSource(MapTypeConverterAttributeSource.AttributeName, expectedInterface);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void VerifyTypeConverterInterface()
|
||||
{
|
||||
// Arrange
|
||||
const string source = "";
|
||||
var expectedInterface = $@"
|
||||
{Constants.GeneratedFilesHeader}
|
||||
|
||||
namespace MapTo
|
||||
{{
|
||||
public interface ITypeConverter<in TSource, out TDestination>
|
||||
{{
|
||||
TDestination Convert(TSource source, object[] converterParameters);
|
||||
}}
|
||||
}}
|
||||
".Trim();
|
||||
|
||||
// Act
|
||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions);
|
||||
|
||||
// Assert
|
||||
diagnostics.ShouldBeSuccessful();
|
||||
compilation.SyntaxTrees.ShouldContainSource(ITypeConverterSource.InterfaceName, expectedInterface);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void VerifyTypeConverterInterfaceWithNullableOptionOn()
|
||||
{
|
||||
// Arrange
|
||||
const string source = "";
|
||||
var expectedInterface = $@"
|
||||
{Constants.GeneratedFilesHeader}
|
||||
#nullable enable
|
||||
|
||||
namespace MapTo
|
||||
{{
|
||||
public interface ITypeConverter<in TSource, out TDestination>
|
||||
{{
|
||||
TDestination Convert(TSource source, object[]? converterParameters);
|
||||
}}
|
||||
}}
|
||||
".Trim();
|
||||
|
||||
// Act
|
||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions, nullableContextOptions: NullableContextOptions.Enable, languageVersion: LanguageVersion.CSharp8);
|
||||
|
||||
// Assert
|
||||
diagnostics.ShouldBeSuccessful();
|
||||
compilation.SyntaxTrees.ShouldContainSource(ITypeConverterSource.InterfaceName, expectedInterface);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void When_FoundMatchingPropertyNameWithConverterType_ShouldUseTheConverterAndItsParametersToAssignProperties()
|
||||
{
|
||||
// Arrange
|
||||
var source = GetSourceText(new SourceGeneratorOptions(
|
||||
true,
|
||||
PropertyBuilder: builder =>
|
||||
{
|
||||
builder
|
||||
.WriteLine("[MapTypeConverter(typeof(Prop4Converter), new object[]{\"G\", 'C', 10})]")
|
||||
.WriteLine("public string Prop4 { get; set; }");
|
||||
},
|
||||
SourcePropertyBuilder: builder => builder.WriteLine("public long Prop4 { get; set; }")));
|
||||
|
||||
source += @"
|
||||
namespace Test
|
||||
{
|
||||
using MapTo;
|
||||
|
||||
public class Prop4Converter: ITypeConverter<long, string>
|
||||
{
|
||||
public string Convert(long source, object[] converterParameters) => source.ToString(converterParameters[0] as string);
|
||||
}
|
||||
}
|
||||
";
|
||||
|
||||
const string expectedSyntax = "Prop4 = new Test.Prop4Converter().Convert(baz.Prop4, new object[] { \"G\", 'C', 10 });";
|
||||
|
||||
// Act
|
||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions);
|
||||
|
||||
// Assert
|
||||
diagnostics.ShouldBeSuccessful();
|
||||
compilation.SyntaxTrees.Last().ShouldContainPartialSource(expectedSyntax);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void When_FoundMatchingPropertyNameWithConverterType_ShouldUseTheConverterToAssignProperties()
|
||||
{
|
||||
// Arrange
|
||||
var source = GetSourceText(new SourceGeneratorOptions(
|
||||
true,
|
||||
PropertyBuilder: builder =>
|
||||
{
|
||||
builder
|
||||
.WriteLine("[MapTypeConverter(typeof(Prop4Converter))]")
|
||||
.WriteLine("public long Prop4 { get; set; }");
|
||||
},
|
||||
SourcePropertyBuilder: builder => builder.WriteLine("public string Prop4 { get; set; }")));
|
||||
|
||||
source += @"
|
||||
namespace Test
|
||||
{
|
||||
using MapTo;
|
||||
|
||||
public class Prop4Converter: ITypeConverter<string, long>
|
||||
{
|
||||
public long Convert(string source, object[] converterParameters) => long.Parse(source);
|
||||
}
|
||||
}
|
||||
";
|
||||
|
||||
const string expectedSyntax = "Prop4 = new Test.Prop4Converter().Convert(baz.Prop4, null);";
|
||||
|
||||
// Act
|
||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions);
|
||||
|
||||
// Assert
|
||||
diagnostics.ShouldBeSuccessful();
|
||||
compilation.SyntaxTrees.Last().ShouldContainPartialSource(expectedSyntax);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void When_FoundMatchingPropertyNameWithDifferentImplicitlyConvertibleType_Should_GenerateTheProperty()
|
||||
{
|
||||
// Arrange
|
||||
var source = GetSourceText(new SourceGeneratorOptions(
|
||||
true,
|
||||
PropertyBuilder: builder => { builder.WriteLine("public long Prop4 { get; set; }"); },
|
||||
SourcePropertyBuilder: builder => builder.WriteLine("public int Prop4 { get; set; }")));
|
||||
|
||||
var expectedResult = @"
|
||||
partial class Foo
|
||||
{
|
||||
public Foo(Test.Models.Baz baz)
|
||||
: this(new MappingContext(), baz) { }
|
||||
|
||||
private protected Foo(MappingContext context, Test.Models.Baz baz)
|
||||
{
|
||||
if (context == null) throw new ArgumentNullException(nameof(context));
|
||||
if (baz == null) throw new ArgumentNullException(nameof(baz));
|
||||
|
||||
context.Register(baz, this);
|
||||
|
||||
Prop1 = baz.Prop1;
|
||||
Prop2 = baz.Prop2;
|
||||
Prop3 = baz.Prop3;
|
||||
Prop4 = baz.Prop4;
|
||||
}
|
||||
".Trim();
|
||||
|
||||
// Act
|
||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions);
|
||||
|
||||
// Assert
|
||||
diagnostics.ShouldBeSuccessful();
|
||||
compilation.SyntaxTrees.Last().ShouldContainPartialSource(expectedResult);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void When_FoundMatchingPropertyNameWithIncorrectConverterType_ShouldReportError()
|
||||
{
|
||||
// Arrange
|
||||
var source = GetSourceText(new SourceGeneratorOptions(
|
||||
true,
|
||||
PropertyBuilder: builder =>
|
||||
{
|
||||
builder
|
||||
.WriteLine("[IgnoreProperty]")
|
||||
.WriteLine("public long IgnoreMe { get; set; }")
|
||||
.WriteLine("[MapTypeConverter(typeof(Prop4Converter))]")
|
||||
.WriteLine("public long Prop4 { get; set; }");
|
||||
},
|
||||
SourcePropertyBuilder: builder => builder.WriteLine("public string Prop4 { get; set; }")));
|
||||
|
||||
source += @"
|
||||
namespace Test
|
||||
{
|
||||
using MapTo;
|
||||
|
||||
public class Prop4Converter: ITypeConverter<string, int>
|
||||
{
|
||||
public int Convert(string source, object[] converterParameters) => int.Parse(source);
|
||||
}
|
||||
}
|
||||
";
|
||||
|
||||
// Act
|
||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions);
|
||||
|
||||
// Assert
|
||||
var expectedError = DiagnosticsFactory.InvalidTypeConverterGenericTypesError(GetSourcePropertySymbol("Prop4", compilation), GetSourcePropertySymbol("Prop4", compilation, "Baz"));
|
||||
diagnostics.ShouldNotBeSuccessful(expectedError);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,562 +0,0 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using MapTo.Tests.Extensions;
|
||||
using MapTo.Tests.Infrastructure;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
using static MapTo.Tests.Common;
|
||||
|
||||
namespace MapTo.Tests
|
||||
{
|
||||
public class MappedClassesTests
|
||||
{
|
||||
private readonly ITestOutputHelper _output;
|
||||
|
||||
public MappedClassesTests(ITestOutputHelper output)
|
||||
{
|
||||
_output = output;
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(SecondaryConstructorCheckData))]
|
||||
public void When_SecondaryConstructorExists_Should_NotGenerateOne(string source, LanguageVersion languageVersion)
|
||||
{
|
||||
// Arrange
|
||||
source = source.Trim();
|
||||
|
||||
// Act
|
||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions, languageVersion: languageVersion);
|
||||
|
||||
// Assert
|
||||
diagnostics.ShouldBeSuccessful();
|
||||
compilation
|
||||
.GetGeneratedSyntaxTree("DestinationClass")
|
||||
.ShouldNotBeNull()
|
||||
.GetRoot()
|
||||
.DescendantNodes()
|
||||
.OfType<ConstructorDeclarationSyntax>()
|
||||
.Count()
|
||||
.ShouldBe(1);
|
||||
}
|
||||
|
||||
public static IEnumerable<object[]> SecondaryConstructorCheckData => new List<object[]>
|
||||
{
|
||||
new object[]
|
||||
{
|
||||
@"
|
||||
using MapTo;
|
||||
namespace Test.Data.Models
|
||||
{
|
||||
public class SourceClass { public string Prop1 { get; set; } }
|
||||
|
||||
[MapFrom(typeof(SourceClass))]
|
||||
public partial class DestinationClass
|
||||
{
|
||||
public DestinationClass(SourceClass source) : this(new MappingContext(), source) { }
|
||||
public string Prop1 { get; set; }
|
||||
}
|
||||
}
|
||||
",
|
||||
LanguageVersion.CSharp7_3
|
||||
},
|
||||
new object[]
|
||||
{
|
||||
@"
|
||||
using MapTo;
|
||||
namespace Test.Data.Models
|
||||
{
|
||||
public record SourceClass(string Prop1);
|
||||
|
||||
[MapFrom(typeof(SourceClass))]
|
||||
public partial record DestinationClass(string Prop1)
|
||||
{
|
||||
public DestinationClass(SourceClass source) : this(new MappingContext(), source) { }
|
||||
}
|
||||
}
|
||||
",
|
||||
LanguageVersion.CSharp9
|
||||
}
|
||||
};
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(SecondaryCtorWithoutPrivateCtorData))]
|
||||
public void When_SecondaryConstructorExistsButDoNotReferencePrivateConstructor_Should_ReportError(string source, LanguageVersion languageVersion)
|
||||
{
|
||||
// Arrange
|
||||
source = source.Trim();
|
||||
|
||||
// Act
|
||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions, languageVersion: languageVersion);
|
||||
|
||||
// Assert
|
||||
var constructorSyntax = compilation.SyntaxTrees
|
||||
.First()
|
||||
.GetRoot()
|
||||
.DescendantNodes()
|
||||
.OfType<ConstructorDeclarationSyntax>()
|
||||
.Single();
|
||||
|
||||
diagnostics.ShouldNotBeSuccessful(DiagnosticsFactory.MissingConstructorArgument(constructorSyntax));
|
||||
}
|
||||
|
||||
public static IEnumerable<object[]> SecondaryCtorWithoutPrivateCtorData => new List<object[]>
|
||||
{
|
||||
new object[]
|
||||
{
|
||||
@"
|
||||
using MapTo;
|
||||
namespace Test.Data.Models
|
||||
{
|
||||
public class SourceClass { public string Prop1 { get; set; } }
|
||||
|
||||
[MapFrom(typeof(SourceClass))]
|
||||
public partial class DestinationClass
|
||||
{
|
||||
public DestinationClass(SourceClass source) { }
|
||||
public string Prop1 { get; set; }
|
||||
}
|
||||
}
|
||||
",
|
||||
LanguageVersion.CSharp7_3
|
||||
},
|
||||
new object[]
|
||||
{
|
||||
@"
|
||||
using MapTo;
|
||||
namespace Test.Data.Models
|
||||
{
|
||||
public record SourceClass(string Prop1);
|
||||
|
||||
[MapFrom(typeof(SourceClass))]
|
||||
public partial record DestinationClass(string Prop1)
|
||||
{
|
||||
public DestinationClass(SourceClass source) : this(""invalid"") { }
|
||||
}
|
||||
}
|
||||
",
|
||||
LanguageVersion.CSharp9
|
||||
}
|
||||
};
|
||||
|
||||
[Fact]
|
||||
public void When_PropertyNameIsTheSameAsClassName_Should_MapAccordingly()
|
||||
{
|
||||
// Arrange
|
||||
var source = @"
|
||||
namespace Sale
|
||||
{
|
||||
public class Sale { public Sale Prop1 { get; set; } }
|
||||
}
|
||||
|
||||
namespace SaleModel
|
||||
{
|
||||
using MapTo;
|
||||
using Sale;
|
||||
|
||||
[MapFrom(typeof(Sale))]
|
||||
public partial class SaleModel
|
||||
{
|
||||
[MapProperty(SourcePropertyName = nameof(global::Sale.Sale.Prop1))]
|
||||
public Sale Sale { get; set; }
|
||||
}
|
||||
}
|
||||
".Trim();
|
||||
|
||||
// Act
|
||||
var (_, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions);
|
||||
|
||||
// Assert
|
||||
diagnostics.ShouldBeSuccessful();
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(SameSourceAndDestinationTypeNameData))]
|
||||
public void When_SourceAndDestinationNamesAreTheSame_Should_MapAccordingly(string source, LanguageVersion languageVersion)
|
||||
{
|
||||
// Arrange
|
||||
source = source.Trim();
|
||||
|
||||
// Act
|
||||
var (_, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions, languageVersion: languageVersion);
|
||||
|
||||
// Assert
|
||||
diagnostics.ShouldBeSuccessful();
|
||||
}
|
||||
|
||||
public static IEnumerable<object[]> SameSourceAndDestinationTypeNameData => new List<object[]>
|
||||
{
|
||||
new object[]
|
||||
{
|
||||
@"
|
||||
namespace Test
|
||||
{
|
||||
public class TypeName { public int Prop2 { get; set; } }
|
||||
}
|
||||
|
||||
namespace Test2
|
||||
{
|
||||
using MapTo;
|
||||
|
||||
[MapFrom(typeof(Test.TypeName))]
|
||||
public partial class TypeName
|
||||
{
|
||||
[MapProperty(SourcePropertyName=""Prop2"")]
|
||||
public int Prop1 { get; set; }
|
||||
}
|
||||
}",
|
||||
LanguageVersion.CSharp7_3
|
||||
},
|
||||
new object[]
|
||||
{
|
||||
@"
|
||||
namespace Test
|
||||
{
|
||||
public record TypeName(int Prop2);
|
||||
}
|
||||
|
||||
namespace Test2
|
||||
{
|
||||
using MapTo;
|
||||
|
||||
[MapFrom(typeof(Test.TypeName))]
|
||||
public partial record TypeName([MapProperty(SourcePropertyName=""Prop2"")] int Prop1);
|
||||
}",
|
||||
LanguageVersion.CSharp9
|
||||
},
|
||||
new object[]
|
||||
{
|
||||
@"
|
||||
namespace Test
|
||||
{
|
||||
using System.Collections.Generic;
|
||||
|
||||
public class SourceType2 { public int Id { get; set; } }
|
||||
public class SourceType
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public List<SourceType2> Prop1 { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
namespace Test2
|
||||
{
|
||||
using MapTo;
|
||||
using System.Collections.Generic;
|
||||
|
||||
[MapFrom(typeof(Test.SourceType2))]
|
||||
public partial class SourceType2 { public int Id { get; set; } }
|
||||
|
||||
[MapFrom(typeof(Test.SourceType))]
|
||||
public partial class SourceType
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public IReadOnlyList<SourceType2> Prop1 { get; set; }
|
||||
}
|
||||
}",
|
||||
LanguageVersion.CSharp7_3
|
||||
},
|
||||
new object[]
|
||||
{
|
||||
@"
|
||||
namespace Test
|
||||
{
|
||||
using System.Collections.Generic;
|
||||
|
||||
public record SourceType(int Id, List<SourceType2> Prop1);
|
||||
public record SourceType2(int Id);
|
||||
}
|
||||
|
||||
namespace Test2
|
||||
{
|
||||
using MapTo;
|
||||
using System.Collections.Generic;
|
||||
|
||||
[MapFrom(typeof(Test.SourceType2))]
|
||||
public partial record SourceType2(int Id);
|
||||
|
||||
[MapFrom(typeof(Test.SourceType))]
|
||||
public partial record SourceType(int Id, IReadOnlyList<SourceType2> Prop1);
|
||||
}",
|
||||
LanguageVersion.CSharp9
|
||||
},
|
||||
new object[]
|
||||
{
|
||||
@"
|
||||
namespace Test
|
||||
{
|
||||
using System.Collections.Generic;
|
||||
|
||||
public record SourceType1(int Id);
|
||||
public record SourceType2(int Id, List<SourceType1> Prop1);
|
||||
}
|
||||
|
||||
namespace Test
|
||||
{
|
||||
using MapTo;
|
||||
using System.Collections.Generic;
|
||||
|
||||
[MapFrom(typeof(Test.SourceType1))]
|
||||
public partial record SourceType3(int Id);
|
||||
|
||||
[MapFrom(typeof(Test.SourceType2))]
|
||||
public partial record SourceType4(int Id, IReadOnlyList<SourceType3> Prop1);
|
||||
}",
|
||||
LanguageVersion.CSharp9
|
||||
}
|
||||
};
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(VerifyMappedTypesData))]
|
||||
public void VerifyMappedTypes(string[] sources, LanguageVersion languageVersion)
|
||||
{
|
||||
// Arrange
|
||||
// Act
|
||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(sources, analyzerConfigOptions: DefaultAnalyzerOptions, languageVersion: languageVersion);
|
||||
|
||||
// Assert
|
||||
diagnostics.ShouldBeSuccessful();
|
||||
_output.WriteLine(compilation.PrintSyntaxTree());
|
||||
}
|
||||
|
||||
public static IEnumerable<object[]> VerifyMappedTypesData => new List<object[]>
|
||||
{
|
||||
new object[] { new[] { MainSourceClass, NestedSourceClass, MainDestinationClass, NestedDestinationClass }, LanguageVersion.CSharp7_3 },
|
||||
new object[] { new[] { MainSourceRecord, NestedSourceRecord, MainDestinationRecord, NestedDestinationRecord }, LanguageVersion.CSharp9 },
|
||||
new object[]
|
||||
{
|
||||
new[]
|
||||
{
|
||||
@"
|
||||
namespace Test.Classes.Classes1
|
||||
{
|
||||
public class Class1
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string Name { get; set; }
|
||||
}
|
||||
}",
|
||||
@"
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Test.Classes.Classes1;
|
||||
|
||||
namespace Test.Classes.Classes2
|
||||
{
|
||||
public class Class2
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public List<Class1> Genres { get; set; }
|
||||
public DateTime? ReleaseDate { get; set; }
|
||||
}
|
||||
}",
|
||||
@"
|
||||
using MapTo;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using TC = Test.Classes;
|
||||
|
||||
namespace Tests.Records
|
||||
{
|
||||
[MapFrom(typeof(Test.Classes.Classes1.Class1))]
|
||||
public partial record Class1(int Id, string Name);
|
||||
|
||||
[MapFrom(typeof(Test.Classes.Classes2.Class2))]
|
||||
public partial record Class2(int Id, IReadOnlyList<Class1> Genres);
|
||||
}"
|
||||
},
|
||||
LanguageVersion.CSharp9
|
||||
}
|
||||
};
|
||||
|
||||
[Fact]
|
||||
public void VerifySelfReferencingRecords()
|
||||
{
|
||||
// Arrange
|
||||
var source = @"
|
||||
namespace Tests.Data.Models
|
||||
{
|
||||
using System.Collections.Generic;
|
||||
|
||||
public record Employee(int Id, string EmployeeCode, Manager Manager);
|
||||
|
||||
public record Manager(int Id, string EmployeeCode, Manager Manager, int Level, List<Employee> Employees) : Employee(Id, EmployeeCode, Manager);
|
||||
}
|
||||
|
||||
namespace Tests.Data.ViewModels
|
||||
{
|
||||
using System.Collections.Generic;
|
||||
using Tests.Data.Models;
|
||||
using MapTo;
|
||||
|
||||
[MapFrom(typeof(Employee))]
|
||||
public partial record EmployeeViewModel(int Id, string EmployeeCode, ManagerViewModel Manager);
|
||||
|
||||
[MapFrom(typeof(Manager))]
|
||||
public partial record ManagerViewModel(int Id, string EmployeeCode, ManagerViewModel Manager, int Level, List<EmployeeViewModel> Employees) : EmployeeViewModel(Id, EmployeeCode, Manager);
|
||||
}
|
||||
".Trim();
|
||||
|
||||
// Act
|
||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions, languageVersion: LanguageVersion.CSharp9);
|
||||
|
||||
// Assert
|
||||
diagnostics.ShouldBeSuccessful();
|
||||
_output.WriteLine(compilation.PrintSyntaxTree());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void VerifySystemNamespaceConflict()
|
||||
{
|
||||
// Arrange
|
||||
var source = @"
|
||||
namespace Test
|
||||
{
|
||||
public record SomeRecord(int Id);
|
||||
}
|
||||
|
||||
namespace Test.Models
|
||||
{
|
||||
using MapTo;
|
||||
|
||||
[MapFrom(typeof(Test.SomeRecord))]
|
||||
public partial record SomeRecordModel(int Id);
|
||||
}
|
||||
|
||||
namespace Test.System
|
||||
{
|
||||
public interface IMyInterface { }
|
||||
}
|
||||
".Trim();
|
||||
|
||||
// Act
|
||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions, languageVersion: LanguageVersion.CSharp9);
|
||||
|
||||
// Assert
|
||||
diagnostics.ShouldBeSuccessful();
|
||||
_output.WriteLine(compilation.PrintSyntaxTree());
|
||||
}
|
||||
|
||||
private static string MainSourceClass => @"
|
||||
using System;
|
||||
|
||||
namespace Test.Data.Models
|
||||
{
|
||||
public class User
|
||||
{
|
||||
public int Id { get; set; }
|
||||
|
||||
public DateTimeOffset RegisteredAt { get; set; }
|
||||
|
||||
public Profile Profile { get; set; }
|
||||
}
|
||||
}
|
||||
".Trim();
|
||||
|
||||
private static string NestedSourceClass => @"
|
||||
namespace Test.Data.Models
|
||||
{
|
||||
public class Profile
|
||||
{
|
||||
public string FirstName { get; set; }
|
||||
|
||||
public string LastName { get; set; }
|
||||
|
||||
public string FullName => $""{FirstName} {LastName}"";
|
||||
}
|
||||
}
|
||||
".Trim();
|
||||
|
||||
private static string MainDestinationClass => @"
|
||||
using System;
|
||||
using MapTo;
|
||||
using Test.Data.Models;
|
||||
|
||||
namespace Test.ViewModels
|
||||
{
|
||||
[MapFrom(typeof(User))]
|
||||
public partial class UserViewModel
|
||||
{
|
||||
[MapProperty(SourcePropertyName = nameof(User.Id))]
|
||||
[MapTypeConverter(typeof(IdConverter))]
|
||||
public string Key { get; }
|
||||
|
||||
public DateTimeOffset RegisteredAt { get; set; }
|
||||
|
||||
// [IgnoreProperty]
|
||||
public ProfileViewModel Profile { get; set; }
|
||||
|
||||
private class IdConverter : ITypeConverter<int, string>
|
||||
{
|
||||
public string Convert(int source, object[] converterParameters) => $""{source:X}"";
|
||||
}
|
||||
}
|
||||
}
|
||||
".Trim();
|
||||
|
||||
private static string NestedDestinationClass => @"
|
||||
using MapTo;
|
||||
using Test.Data.Models;
|
||||
|
||||
namespace Test.ViewModels
|
||||
{
|
||||
[MapFrom(typeof(Profile))]
|
||||
public partial class ProfileViewModel
|
||||
{
|
||||
public string FirstName { get; }
|
||||
|
||||
public string LastName { get; }
|
||||
}
|
||||
}
|
||||
".Trim();
|
||||
|
||||
private static string MainSourceRecord => BuildSourceRecord("public record User(int Id, DateTimeOffset RegisteredAt, Profile Profile);");
|
||||
|
||||
private static string MainDestinationRecord => BuildDestinationRecord(@"
|
||||
[MapFrom(typeof(User))]
|
||||
public partial record UserViewModel(
|
||||
[MapProperty(SourcePropertyName = nameof(User.Id))]
|
||||
[MapTypeConverter(typeof(UserViewModel.IdConverter))]
|
||||
string Key,
|
||||
DateTimeOffset RegisteredAt,
|
||||
Profile Profile)
|
||||
{
|
||||
private class IdConverter : ITypeConverter<int, string>
|
||||
{
|
||||
public string Convert(int source, object[] converterParameters) => $""{source:X}"";
|
||||
}
|
||||
}");
|
||||
|
||||
private static string NestedSourceRecord => BuildSourceRecord("public record Profile(string FirstName, string LastName) { public string FullName => $\"{FirstName} {LastName}\"; }");
|
||||
|
||||
private static string NestedDestinationRecord => BuildDestinationRecord("[MapFrom(typeof(Profile))] public partial record ProfileViewModel(string FirstName, string LastName);");
|
||||
|
||||
private static string BuildSourceRecord(string record)
|
||||
{
|
||||
return $@"
|
||||
using System;
|
||||
|
||||
namespace RecordTest.Data.Models
|
||||
{{
|
||||
{record}
|
||||
}}
|
||||
".Trim();
|
||||
}
|
||||
|
||||
private static string BuildDestinationRecord(string record)
|
||||
{
|
||||
return $@"
|
||||
using System;
|
||||
using MapTo;
|
||||
using RecordTest.Data.Models;
|
||||
|
||||
namespace RecordTest.ViewModels
|
||||
{{
|
||||
{record}
|
||||
}}
|
||||
".Trim();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,102 +0,0 @@
|
|||
using MapTo.Sources;
|
||||
using MapTo.Tests.Extensions;
|
||||
using MapTo.Tests.Infrastructure;
|
||||
using Xunit;
|
||||
using static MapTo.Tests.Common;
|
||||
|
||||
namespace MapTo.Tests
|
||||
{
|
||||
public class MappingContextTests
|
||||
{
|
||||
[Fact]
|
||||
public void VerifyMappingContextSource()
|
||||
{
|
||||
// Arrange
|
||||
const string source = "";
|
||||
var expected = @"
|
||||
// <auto-generated />
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
|
||||
namespace MapTo
|
||||
{
|
||||
internal sealed class MappingContext
|
||||
{
|
||||
private readonly Dictionary<object, object> _cache;
|
||||
|
||||
internal MappingContext()
|
||||
{
|
||||
_cache = new Dictionary<object, object>(1);
|
||||
}
|
||||
|
||||
internal static TMapped Create<TOriginal, TMapped>(TOriginal original)
|
||||
{
|
||||
if (original == null) throw new ArgumentNullException(nameof(original));
|
||||
|
||||
var context = new MappingContext();
|
||||
var mapped = context.MapFromWithContext<TOriginal, TMapped>(original);
|
||||
|
||||
if (mapped == null)
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
return mapped;
|
||||
}
|
||||
|
||||
internal TMapped MapFromWithContext<TOriginal, TMapped>(TOriginal original)
|
||||
{
|
||||
if (original == null)
|
||||
{
|
||||
return default(TMapped);
|
||||
}
|
||||
|
||||
if (!TryGetValue<TOriginal, TMapped>(original, out var mapped))
|
||||
{
|
||||
var instance = Activator.CreateInstance(typeof(TMapped), BindingFlags.Instance | BindingFlags.NonPublic, null, new object[] { this, original }, null);
|
||||
if (instance != null)
|
||||
{
|
||||
mapped = (TMapped)instance;
|
||||
}
|
||||
}
|
||||
|
||||
return mapped;
|
||||
}
|
||||
|
||||
internal void Register<TOriginal, TMapped>(TOriginal original, TMapped mapped)
|
||||
{
|
||||
if (original == null) throw new ArgumentNullException(nameof(original));
|
||||
if (mapped == null) throw new ArgumentNullException(nameof(mapped));
|
||||
|
||||
if (!_cache.ContainsKey(original))
|
||||
{
|
||||
_cache.Add(original, mapped);
|
||||
}
|
||||
}
|
||||
|
||||
private bool TryGetValue<TOriginal, TMapped>(TOriginal original, out TMapped mapped)
|
||||
{
|
||||
if (original != null && _cache.TryGetValue(original, out var value))
|
||||
{
|
||||
mapped = (TMapped)value;
|
||||
return true;
|
||||
}
|
||||
|
||||
mapped = default(TMapped);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
".Trim();
|
||||
|
||||
// Act
|
||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions);
|
||||
|
||||
// Assert
|
||||
diagnostics.ShouldBeSuccessful();
|
||||
compilation.SyntaxTrees.ShouldContainSource(MappingContextSource.ClassName, expected);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,85 +0,0 @@
|
|||
using System.Linq;
|
||||
using MapTo.Integration.Tests.Data.Models;
|
||||
using MapTo.Integration.Tests.Data.ViewModels;
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
|
||||
namespace MapTo.Integration.Tests
|
||||
{
|
||||
public class CyclicReferenceTests
|
||||
{
|
||||
[Fact]
|
||||
public void VerifySelfReference()
|
||||
{
|
||||
// Arrange
|
||||
var manager = new Manager { Id = 1, EmployeeCode = "M001", Level = 100 };
|
||||
manager.Manager = manager;
|
||||
|
||||
// Act
|
||||
var result = manager.ToManagerViewModel();
|
||||
|
||||
// Assert
|
||||
result.Id.ShouldBe(manager.Id);
|
||||
result.EmployeeCode.ShouldBe(manager.EmployeeCode);
|
||||
result.Level.ShouldBe(manager.Level);
|
||||
result.Manager.ShouldBeSameAs(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void VerifyNestedReference()
|
||||
{
|
||||
// Arrange
|
||||
var manager1 = new Manager { Id = 100, EmployeeCode = "M001", Level = 100 };
|
||||
var manager2 = new Manager { Id = 102, EmployeeCode = "M002", Level = 100 };
|
||||
|
||||
var employee1 = new Employee { Id = 200, EmployeeCode = "E001"};
|
||||
var employee2 = new Employee { Id = 201, EmployeeCode = "E002"};
|
||||
|
||||
employee1.Manager = manager1;
|
||||
employee2.Manager = manager2;
|
||||
|
||||
manager2.Manager = manager1;
|
||||
|
||||
// Act
|
||||
var manager1ViewModel = manager1.ToManagerViewModel();
|
||||
|
||||
// Assert
|
||||
manager1ViewModel.Id.ShouldBe(manager1.Id);
|
||||
manager1ViewModel.Manager.ShouldBeNull();
|
||||
manager1ViewModel.Employees.Count.ShouldBe(2);
|
||||
manager1ViewModel.Employees[0].Id.ShouldBe(employee1.Id);
|
||||
manager1ViewModel.Employees[0].Manager.ShouldBeSameAs(manager1ViewModel);
|
||||
manager1ViewModel.Employees[1].Id.ShouldBe(manager2.Id);
|
||||
manager1ViewModel.Employees[1].Manager.ShouldBeSameAs(manager1ViewModel);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void VerifyNestedSelfReference()
|
||||
{
|
||||
// Arrange
|
||||
var manager1 = new Manager { Id = 100, EmployeeCode = "M001", Level = 100 };
|
||||
var manager3 = new Manager { Id = 101, EmployeeCode = "M003", Level = 100 };
|
||||
var manager2 = new Manager { Id = 102, EmployeeCode = "M002", Level = 100 };
|
||||
|
||||
var employee1 = new Employee { Id = 200, EmployeeCode = "E001"};
|
||||
var employee2 = new Employee { Id = 201, EmployeeCode = "E002"};
|
||||
var employee3 = new Employee { Id = 202, EmployeeCode = "E003"};
|
||||
|
||||
employee1.Manager = manager1;
|
||||
employee2.Manager = manager2;
|
||||
employee3.Manager = manager3;
|
||||
|
||||
manager2.Manager = manager1;
|
||||
manager3.Manager = manager2;
|
||||
|
||||
// Act
|
||||
var manager3ViewModel = manager3.ToManagerViewModel();
|
||||
|
||||
// Assert
|
||||
manager3ViewModel.Manager.ShouldNotBeNull();
|
||||
manager3ViewModel.Manager.Id.ShouldBe(manager2.Id);
|
||||
manager3ViewModel.Manager.Manager.Id.ShouldBe(manager1.Id);
|
||||
manager3ViewModel.Employees.All(e => ReferenceEquals(e.Manager, manager3ViewModel)).ShouldBeTrue();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
namespace MapTo.Integration.Tests.Data.Models
|
||||
{
|
||||
public class Employee
|
||||
{
|
||||
private Manager _manager;
|
||||
|
||||
public int Id { get; set; }
|
||||
|
||||
public string EmployeeCode { get; set; }
|
||||
|
||||
public Manager Manager
|
||||
{
|
||||
get => _manager;
|
||||
set
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
_manager.Employees.Remove(this);
|
||||
}
|
||||
else
|
||||
{
|
||||
value.Employees.Add(this);
|
||||
}
|
||||
|
||||
_manager = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace MapTo.Integration.Tests.Data.Models
|
||||
{
|
||||
public class Manager : Employee
|
||||
{
|
||||
public int Level { get; set; }
|
||||
|
||||
public List<Employee> Employees { get; set; } = new();
|
||||
}
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
using MapTo.Integration.Tests.Data.Models;
|
||||
|
||||
namespace MapTo.Integration.Tests.Data.ViewModels
|
||||
{
|
||||
[MapFrom(typeof(Employee))]
|
||||
public partial class EmployeeViewModel
|
||||
{
|
||||
public int Id { get; set; }
|
||||
|
||||
public string EmployeeCode { get; set; }
|
||||
|
||||
public ManagerViewModel Manager { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
using System.Collections.Generic;
|
||||
using MapTo.Integration.Tests.Data.Models;
|
||||
|
||||
namespace MapTo.Integration.Tests.Data.ViewModels
|
||||
{
|
||||
[MapFrom(typeof(Manager))]
|
||||
public partial class ManagerViewModel : EmployeeViewModel
|
||||
{
|
||||
public int Level { get; set; }
|
||||
|
||||
public List<EmployeeViewModel> Employees { get; set; } = new();
|
||||
}
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\BlueWest.MapTo\BlueWest.MapTo.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.10.0" />
|
||||
<PackageReference Include="Shouldly" Version="4.0.3" />
|
||||
<PackageReference Include="xunit" Version="2.4.1" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="coverlet.collector" Version="3.0.3">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
|
@ -1,59 +0,0 @@
|
|||
using System;
|
||||
using MapTo;
|
||||
|
||||
namespace BlueWest.Data
|
||||
{
|
||||
public enum FinanceSymbol
|
||||
{
|
||||
BTC_EUR,
|
||||
BTC_BUSD,
|
||||
BTC_USD,
|
||||
BTC_USDT,
|
||||
LTC_EUR,
|
||||
LTC_BUSD,
|
||||
LTC_USDT
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
public enum FinanceTransactionType
|
||||
{
|
||||
Buy,
|
||||
Sell
|
||||
}
|
||||
|
||||
[JsonExtension]
|
||||
[MapFrom(typeof(FinanceTransactionInsertDto))]
|
||||
public partial struct FinanceTransaction
|
||||
{
|
||||
public readonly int Id;
|
||||
public readonly int UserId;
|
||||
public readonly FinanceTransactionType FinanceTransactionType;
|
||||
public readonly FinanceSymbol FinanceSymbol;
|
||||
public readonly double Amount; // To Buy
|
||||
public readonly double Quantity; // Bought
|
||||
public readonly double Fee;
|
||||
public readonly DateTime DateTime;
|
||||
|
||||
|
||||
public FinanceTransaction(int id, int userId, FinanceTransactionType financeTransactionType,
|
||||
FinanceSymbol financeSymbol, double amount, double quantity, double fee, DateTime dateTime)
|
||||
{
|
||||
Id = id;
|
||||
UserId = userId;
|
||||
FinanceTransactionType = financeTransactionType;
|
||||
FinanceSymbol = financeSymbol;
|
||||
Amount = amount;
|
||||
Quantity = quantity;
|
||||
Fee = fee;
|
||||
DateTime = dateTime;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
using System;
|
||||
|
||||
namespace BlueWest.Data
|
||||
{
|
||||
|
||||
public partial struct FinanceTransactionInsertDto
|
||||
{
|
||||
public readonly int UserId;
|
||||
public readonly FinanceTransactionType FinanceTransactionType;
|
||||
public readonly FinanceSymbol FinanceSymbol;
|
||||
public readonly double Amount; // To Buy
|
||||
public readonly double Quantity; // Bought
|
||||
public readonly double Fee;
|
||||
public readonly DateTime DateTime;
|
||||
}
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using MapTo;
|
||||
|
||||
namespace BlueWest.Data
|
||||
{
|
||||
[MapFrom(typeof(FinanceTransaction))]
|
||||
|
||||
partial struct FinanceTransactionReadDto
|
||||
{
|
||||
public readonly int UserId;
|
||||
public readonly FinanceTransactionType FinanceTransactionType;
|
||||
public readonly FinanceSymbol FinanceSymbol;
|
||||
public readonly double Amount; // To Buy
|
||||
public readonly double Quantity; // Bought
|
||||
public readonly double Fee;
|
||||
public readonly DateTime DateTime;
|
||||
|
||||
public readonly string ReadData;
|
||||
}
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using MapTo;
|
||||
using TestConsoleApp.ViewModels;
|
||||
|
||||
namespace TestConsoleApp.Data.Models
|
||||
{
|
||||
[MapFrom(typeof(CarReadDto))]
|
||||
[UseUpdate]
|
||||
partial class Car
|
||||
{
|
||||
public int Size { get; }
|
||||
public int Id { get; }
|
||||
|
||||
public string Brand { get; }
|
||||
|
||||
public Car(int size, int id, string brand)
|
||||
{
|
||||
Size = size;
|
||||
Id = id;
|
||||
Brand = brand;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace TestConsoleApp.Data.Models
|
||||
{
|
||||
|
||||
public class Employee
|
||||
{
|
||||
public int Id { get; }
|
||||
|
||||
public string EmployeeCode { get; }
|
||||
|
||||
public Employee(int id, string employeeCode)
|
||||
{
|
||||
Id = id;
|
||||
EmployeeCode = employeeCode;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using TestConsoleApp.ViewModels;
|
||||
using MapTo;
|
||||
|
||||
namespace TestConsoleApp.Data.Models
|
||||
{
|
||||
[MapFrom(typeof(MyStructViewModel))]
|
||||
[UseUpdate]
|
||||
public partial struct MyStruct
|
||||
{
|
||||
public int SomeInt { get; set; }
|
||||
|
||||
public string ReadOnlyString { get; }
|
||||
|
||||
public MyStruct(int someInt, string readOnlyString)
|
||||
{
|
||||
SomeInt = someInt;
|
||||
ReadOnlyString = readOnlyString;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -1,44 +0,0 @@
|
|||
|
||||
using System.Collections.Generic;
|
||||
using MapTo;
|
||||
|
||||
|
||||
namespace BlueWest.Data
|
||||
{
|
||||
[MapFrom(typeof(UserUpdateDto))]
|
||||
[JsonExtension]
|
||||
public partial class User
|
||||
{
|
||||
public readonly int Id;
|
||||
public string Name;
|
||||
public string Address;
|
||||
|
||||
public string BTCAddress;
|
||||
public string LTCAddress;
|
||||
|
||||
public double BTCAmount;
|
||||
public double LTCAmount;
|
||||
|
||||
public readonly List<FinanceTransaction> FinanceTransactions;
|
||||
|
||||
public User(int id, string name, string address, string btcAddress, string ltcAddress, double btcAmount, double ltcAmount, List<FinanceTransaction> financeTransactions)
|
||||
{
|
||||
Id = id;
|
||||
Name = name;
|
||||
Address = address;
|
||||
BTCAddress = btcAddress;
|
||||
LTCAddress = ltcAddress;
|
||||
BTCAmount = btcAmount;
|
||||
LTCAmount = ltcAmount;
|
||||
FinanceTransactions = financeTransactions;
|
||||
}
|
||||
|
||||
public void AddTransaction(FinanceTransaction financeTransaction)
|
||||
{
|
||||
FinanceTransactions.Add(financeTransaction);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace BlueWest.Data
|
||||
{
|
||||
public class UserList
|
||||
{
|
||||
public List<User> Users;
|
||||
|
||||
public UserList(List<User> users)
|
||||
{
|
||||
Users = users;
|
||||
}
|
||||
|
||||
public int Length => Users.Count;
|
||||
}
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
using MapTo;
|
||||
|
||||
namespace BlueWest.Data
|
||||
{
|
||||
[MapFrom(typeof(User))]
|
||||
|
||||
public partial class UserUpdateDto
|
||||
{
|
||||
public string Name;
|
||||
public string Address;
|
||||
|
||||
public string BTCAddress;
|
||||
public string LTCAddress;
|
||||
|
||||
public double BTCAmount;
|
||||
public double LTCAmount;
|
||||
|
||||
}
|
||||
}
|
|
@ -1,32 +0,0 @@
|
|||
using System;
|
||||
using MapTo;
|
||||
using TestConsoleApp.Data.Models;
|
||||
using TestConsoleApp.ViewModels;
|
||||
|
||||
namespace TestConsoleApp
|
||||
{
|
||||
internal class Program
|
||||
{
|
||||
private static void Main(string[] args)
|
||||
{
|
||||
//UserTest();
|
||||
|
||||
// EmployeeManagerTest();
|
||||
Console.WriteLine("done");
|
||||
}
|
||||
|
||||
private static void EmployeeManagerTest()
|
||||
{
|
||||
|
||||
|
||||
var employee = new Employee(1, "hello");
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net471</TargetFramework>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\BlueWest.MapTo\BlueWest.MapTo.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
|
||||
</ItemGroup>
|
||||
|
||||
<Import Project="..\..\src\BlueWest.MapTo\MapTo.props" />
|
||||
<PropertyGroup>
|
||||
<MapTo_ConstructorAccessModifier>Internal</MapTo_ConstructorAccessModifier>
|
||||
</PropertyGroup>
|
||||
</Project>
|
|
@ -1,23 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using MapTo;
|
||||
using TestConsoleApp.Data.Models;
|
||||
|
||||
namespace TestConsoleApp.ViewModels
|
||||
{
|
||||
[MapFrom(typeof(Car))]
|
||||
partial class CarReadDto
|
||||
{
|
||||
public int Size { get; }
|
||||
public string Brand { get; }
|
||||
|
||||
public CarReadDto(int size, string brand)
|
||||
{
|
||||
Size = size;
|
||||
Brand = brand;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
using MapTo;
|
||||
using TestConsoleApp.Data.Models;
|
||||
|
||||
namespace TestConsoleApp.ViewModels
|
||||
{
|
||||
[MapFrom(typeof(Employee))]
|
||||
public partial class EmployeeViewModel
|
||||
{
|
||||
public int Id { get; }
|
||||
|
||||
|
||||
}
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using TestConsoleApp.Data.Models;
|
||||
using MapTo;
|
||||
|
||||
namespace TestConsoleApp.ViewModels
|
||||
{
|
||||
[MapFrom(typeof(MyStruct))]
|
||||
|
||||
public partial struct MyStructViewModel
|
||||
{
|
||||
public int SomeInt { get; set; }
|
||||
|
||||
public MyStructViewModel(int someInt)
|
||||
{
|
||||
SomeInt = someInt;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue