Compare commits
42 Commits
Author | SHA1 | Date |
---|---|---|
CodeLiturgy | 4e80156708 | |
CodeLiturgy | b8637c94ab | |
CodeLiturgy | 32bf670b9e | |
CodeLiturgy | 9743ff3740 | |
CodeLiturgy | 8a664db1f7 | |
CodeLiturgy | 1de6350c7d | |
CodeLiturgy | fa3dfc8e86 | |
CodeLiturgy | 4c4413da41 | |
CodeLiturgy | 9211cde83c | |
CodeLiturgy | 3917d231a2 | |
CodeLiturgy | 9257d389ba | |
CodeLiturgy | 07c8fc4853 | |
Wvader | e23d932d13 | |
CodeLiturgy | 654a54d5ef | |
CodeLiturgy | f419c0f8ea | |
CodeLiturgy | ca95c87839 | |
CodeLiturgy | 12d5839f16 | |
CodeLiturgy | fabfa562cf | |
CodeLiturgy | 4d9cc4b029 | |
CodeLiturgy | c34da98ad8 | |
Wvader | e232fa6e76 | |
Wvader | 340a89bbd2 | |
Wvader | 1400bd08e0 | |
Wvader | 7de9b48c69 | |
Wvader | a6f5116656 | |
Wvader | aa5b01cdc4 | |
Wvader | f7963d2d7e | |
Wvader | ca82e6fb17 | |
Wvader | 77603643b4 | |
Wvader | c241fd4cb5 | |
Wvader | ffc3dc7729 | |
Wvader | 60ccbd53ca | |
Wvader | 5faebd9141 | |
Wvader | 768dbb4532 | |
Wvader | 2b0b03bb60 | |
Wvader | 18f4ff408a | |
Wvader | 31ac58f274 | |
Wvader | 9470afb737 | |
Wvader | 63b2bcb359 | |
Wvader | 4bf3599d79 | |
Wvader | 735fd44ee1 | |
Wvader | 4077dfd692 |
|
@ -29,7 +29,7 @@ jobs:
|
||||||
- name: Publish MapTo
|
- name: Publish MapTo
|
||||||
uses: brandedoutcast/publish-nuget@v2.5.5
|
uses: brandedoutcast/publish-nuget@v2.5.5
|
||||||
with:
|
with:
|
||||||
PROJECT_FILE_PATH: src/MapTo/MapTo.csproj
|
PROJECT_FILE_PATH: src/BlueWest.MapTo/BlueWest.MapTo.csproj
|
||||||
NUGET_KEY: ${{secrets.NUGET_API_KEY}}
|
NUGET_KEY: ${{secrets.NUGET_API_KEY}}
|
||||||
NUGET_SOURCE: https://api.nuget.org
|
NUGET_SOURCE: https://api.nuget.org
|
||||||
TAG_COMMIT: false
|
TAG_COMMIT: false
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
|
|
||||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MapTo", "src\MapTo\MapTo.csproj", "{4DB371AC-48D0-4F01-8EF3-7707D06EF0A7}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MapTo", "src\BlueWest.MapTo\BlueWest.MapTo.csproj", "{4DB371AC-48D0-4F01-8EF3-7707D06EF0A7}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MapToTests", "test\MapTo.Tests\MapTo.Tests.csproj", "{797DA57B-AC7E-468B-8799-44C5A574C0E3}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MapToTests", "test\MapTo.Tests\MapTo.Tests.csproj", "{797DA57B-AC7E-468B-8799-44C5A574C0E3}"
|
||||||
EndProject
|
EndProject
|
||||||
|
|
|
@ -4,15 +4,11 @@
|
||||||
<TargetFramework>netstandard2.0</TargetFramework>
|
<TargetFramework>netstandard2.0</TargetFramework>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<LangVersion>9</LangVersion>
|
<LangVersion>9</LangVersion>
|
||||||
|
|
||||||
<AssemblyName>MapTo</AssemblyName>
|
|
||||||
<Description>An object to object mapping generator using Roslyn source generator.</Description>
|
<Description>An object to object mapping generator using Roslyn source generator.</Description>
|
||||||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||||
<IncludeSymbols>true</IncludeSymbols>
|
<IncludeSymbols>true</IncludeSymbols>
|
||||||
<PackageId>MapTo</PackageId>
|
<PackageId>MapTo</PackageId>
|
||||||
<PackageLicenseFile>LICENSE</PackageLicenseFile>
|
|
||||||
<PackageProjectUrl>https://github.com/mrtaikandi/mapto</PackageProjectUrl>
|
<PackageProjectUrl>https://github.com/mrtaikandi/mapto</PackageProjectUrl>
|
||||||
<PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance>
|
|
||||||
<PackageVersion>$(Version)</PackageVersion>
|
<PackageVersion>$(Version)</PackageVersion>
|
||||||
<PublishRepositoryUrl>true</PublishRepositoryUrl>
|
<PublishRepositoryUrl>true</PublishRepositoryUrl>
|
||||||
<RepositoryUrl>https://github.com/mrtaikandi/mapto</RepositoryUrl>
|
<RepositoryUrl>https://github.com/mrtaikandi/mapto</RepositoryUrl>
|
||||||
|
@ -22,6 +18,7 @@
|
||||||
|
|
||||||
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
|
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
|
||||||
<DocumentationFile>bin\Release\MapTo.xml</DocumentationFile>
|
<DocumentationFile>bin\Release\MapTo.xml</DocumentationFile>
|
||||||
|
<Optimize>false</Optimize>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -31,15 +28,17 @@
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.2">
|
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.3">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="3.8.0" />
|
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="3.8.0" />
|
||||||
|
<PackageReference Update="Nerdbank.GitVersioning">
|
||||||
|
<Version>3.5.109</Version>
|
||||||
|
</PackageReference>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Include="..\..\LICENSE" Pack="true" PackagePath="" Visible="false" />
|
|
||||||
<None Include="$(OutputPath)\$(AssemblyName).dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false" />
|
<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="MapTo.props" Pack="true" PackagePath="build" Visible="false" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
|
@ -0,0 +1,67 @@
|
||||||
|
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()!;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -25,7 +25,7 @@ namespace MapTo
|
||||||
Create($"{ErrorId}030", location, $"No matching properties found between '{classType.ToDisplayString()}' and '{sourceType.ToDisplayString()}' types.");
|
Create($"{ErrorId}030", location, $"No matching properties found between '{classType.ToDisplayString()}' and '{sourceType.ToDisplayString()}' types.");
|
||||||
|
|
||||||
internal static Diagnostic NoMatchingPropertyTypeFoundError(ISymbol property) =>
|
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 '{IgnorePropertyAttributeSource.FullyQualifiedName}'.");
|
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) =>
|
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()}>'.");
|
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()}>'.");
|
|
@ -0,0 +1,86 @@
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,379 @@
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -31,13 +31,14 @@ namespace MapTo.Extensions
|
||||||
|
|
||||||
public static string GetIdentifierName(this TypeDeclarationSyntax typeSyntax) => typeSyntax.Identifier.Text;
|
public static string GetIdentifierName(this TypeDeclarationSyntax typeSyntax) => typeSyntax.Identifier.Text;
|
||||||
|
|
||||||
public static AttributeSyntax? GetAttribute(this TypeDeclarationSyntax typeDeclarationSyntax, string attributeName)
|
public static AttributeSyntax? GetAttribute(this MemberDeclarationSyntax typeDeclarationSyntax, string attributeName)
|
||||||
{
|
{
|
||||||
return typeDeclarationSyntax.AttributeLists
|
var attributeLists = typeDeclarationSyntax.AttributeLists;
|
||||||
|
var selection = attributeLists
|
||||||
.SelectMany(al => al.Attributes)
|
.SelectMany(al => al.Attributes)
|
||||||
.SingleOrDefault(a =>
|
.FirstOrDefault(x => x.Name.ToString().Contains(attributeName));
|
||||||
(a.Name as IdentifierNameSyntax)?.Identifier.ValueText == attributeName ||
|
|
||||||
((a.Name as QualifiedNameSyntax)?.Right as IdentifierNameSyntax)?.Identifier.ValueText == attributeName);
|
return selection;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool HasAttribute(this ISymbol symbol, ITypeSymbol attributeSymbol) =>
|
public static bool HasAttribute(this ISymbol symbol, ITypeSymbol attributeSymbol) =>
|
||||||
|
@ -49,7 +50,7 @@ namespace MapTo.Extensions
|
||||||
public static AttributeData? GetAttribute(this ISymbol symbol, ITypeSymbol attributeSymbol) =>
|
public static AttributeData? GetAttribute(this ISymbol symbol, ITypeSymbol attributeSymbol) =>
|
||||||
symbol.GetAttributes(attributeSymbol).FirstOrDefault();
|
symbol.GetAttributes(attributeSymbol).FirstOrDefault();
|
||||||
|
|
||||||
public static string? GetNamespace(this TypeDeclarationSyntax typeDeclarationSyntax) => typeDeclarationSyntax
|
public static string? GetNamespace(this MemberDeclarationSyntax typeDeclarationSyntax) => typeDeclarationSyntax
|
||||||
.Ancestors()
|
.Ancestors()
|
||||||
.OfType<NamespaceDeclarationSyntax>()
|
.OfType<NamespaceDeclarationSyntax>()
|
||||||
.FirstOrDefault()
|
.FirstOrDefault()
|
||||||
|
@ -68,6 +69,10 @@ namespace MapTo.Extensions
|
||||||
typeSymbol = propertySymbol.Type;
|
typeSymbol = propertySymbol.Type;
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
|
case IFieldSymbol fieldSymbol:
|
||||||
|
typeSymbol = fieldSymbol.Type;
|
||||||
|
return true;
|
||||||
|
|
||||||
case IParameterSymbol parameterSymbol:
|
case IParameterSymbol parameterSymbol:
|
||||||
typeSymbol = parameterSymbol.Type;
|
typeSymbol = parameterSymbol.Type;
|
||||||
return true;
|
return true;
|
||||||
|
@ -82,7 +87,7 @@ namespace MapTo.Extensions
|
||||||
|
|
||||||
public static IPropertySymbol? FindProperty(this IEnumerable<IPropertySymbol> properties, IPropertySymbol targetProperty)
|
public static IPropertySymbol? FindProperty(this IEnumerable<IPropertySymbol> properties, IPropertySymbol targetProperty)
|
||||||
{
|
{
|
||||||
return properties.SingleOrDefault(p =>
|
return properties.FirstOrDefault(p =>
|
||||||
p.Name == targetProperty.Name &&
|
p.Name == targetProperty.Name &&
|
||||||
(p.NullableAnnotation != NullableAnnotation.Annotated ||
|
(p.NullableAnnotation != NullableAnnotation.Annotated ||
|
||||||
p.NullableAnnotation == NullableAnnotation.Annotated &&
|
p.NullableAnnotation == NullableAnnotation.Annotated &&
|
|
@ -1,6 +1,8 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
using MapTo.Extensions;
|
using MapTo.Extensions;
|
||||||
using MapTo.Sources;
|
using MapTo.Sources;
|
||||||
using Microsoft.CodeAnalysis;
|
using Microsoft.CodeAnalysis;
|
||||||
|
@ -17,7 +19,9 @@ namespace MapTo
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public void Initialize(GeneratorInitializationContext context)
|
public void Initialize(GeneratorInitializationContext context)
|
||||||
{
|
{
|
||||||
|
|
||||||
context.RegisterForSyntaxNotifications(() => new MapToSyntaxReceiver());
|
context.RegisterForSyntaxNotifications(() => new MapToSyntaxReceiver());
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
@ -28,8 +32,10 @@ namespace MapTo
|
||||||
var options = SourceGenerationOptions.From(context);
|
var options = SourceGenerationOptions.From(context);
|
||||||
|
|
||||||
var compilation = context.Compilation
|
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, MapFromAttributeSource.Generate(options))
|
||||||
.AddSource(ref context, IgnorePropertyAttributeSource.Generate(options))
|
.AddSource(ref context, IgnoreMemberAttributeSource.Generate(options))
|
||||||
.AddSource(ref context, ITypeConverterSource.Generate(options))
|
.AddSource(ref context, ITypeConverterSource.Generate(options))
|
||||||
.AddSource(ref context, MapTypeConverterAttributeSource.Generate(options))
|
.AddSource(ref context, MapTypeConverterAttributeSource.Generate(options))
|
||||||
.AddSource(ref context, MapPropertyAttributeSource.Generate(options))
|
.AddSource(ref context, MapPropertyAttributeSource.Generate(options))
|
||||||
|
@ -52,6 +58,7 @@ namespace MapTo
|
||||||
foreach (var typeDeclarationSyntax in candidateTypes)
|
foreach (var typeDeclarationSyntax in candidateTypes)
|
||||||
{
|
{
|
||||||
var mappingContext = MappingContext.Create(compilation, options, typeDeclarationSyntax);
|
var mappingContext = MappingContext.Create(compilation, options, typeDeclarationSyntax);
|
||||||
|
|
||||||
mappingContext.Diagnostics.ForEach(context.ReportDiagnostic);
|
mappingContext.Diagnostics.ForEach(context.ReportDiagnostic);
|
||||||
|
|
||||||
if (mappingContext.Model is null)
|
if (mappingContext.Model is null)
|
|
@ -20,7 +20,7 @@ namespace MapTo
|
||||||
|
|
||||||
var attributeSyntax = attributes
|
var attributeSyntax = attributes
|
||||||
.SelectMany(a => a.Attributes)
|
.SelectMany(a => a.Attributes)
|
||||||
.SingleOrDefault(a => a.Name is
|
.FirstOrDefault(a => a.Name is
|
||||||
IdentifierNameSyntax { Identifier: { ValueText: MapFromAttributeSource.AttributeName } } // For: [MapFrom]
|
IdentifierNameSyntax { Identifier: { ValueText: MapFromAttributeSource.AttributeName } } // For: [MapFrom]
|
||||||
or
|
or
|
||||||
QualifiedNameSyntax // For: [MapTo.MapFrom]
|
QualifiedNameSyntax // For: [MapTo.MapFrom]
|
|
@ -0,0 +1,630 @@
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,222 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Immutable;
|
||||||
|
using MapTo.Extensions;
|
||||||
|
using Microsoft.CodeAnalysis;
|
||||||
|
using Microsoft.CodeAnalysis.CSharp;
|
||||||
|
|
||||||
|
namespace MapTo
|
||||||
|
{
|
||||||
|
internal enum AccessModifier
|
||||||
|
{
|
||||||
|
Public,
|
||||||
|
Internal,
|
||||||
|
Private
|
||||||
|
}
|
||||||
|
|
||||||
|
internal enum NullStaticAnalysisState
|
||||||
|
{
|
||||||
|
Default,
|
||||||
|
Enabled,
|
||||||
|
Disabled
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
string Namespace,
|
||||||
|
string ContextTypeName,
|
||||||
|
string ContextFullType,
|
||||||
|
ImmutableArray<EfEntityDataModel> MethodsModels,
|
||||||
|
ImmutableArray<string> Usings
|
||||||
|
);
|
||||||
|
|
||||||
|
internal class EfEntityDataModel
|
||||||
|
{
|
||||||
|
public string PropertyName { get; set; }
|
||||||
|
public string EntityTypeFullName { get; set; }
|
||||||
|
public string EntityTypeIdentifierName { get; set; }
|
||||||
|
|
||||||
|
public EfEntityDataModel(string propertyName, string entityTypeFullName, string entityTypeIdentifierName)
|
||||||
|
{
|
||||||
|
PropertyName = propertyName;
|
||||||
|
EntityTypeFullName = entityTypeFullName;
|
||||||
|
EntityTypeIdentifierName = entityTypeIdentifierName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
internal class EfAddMethodsModel : EfEntityDataModel
|
||||||
|
{
|
||||||
|
public string CreateTypeFullName { get; set; }
|
||||||
|
public string CreateTypeIdentifierName { get; set; }
|
||||||
|
public string ReturnTypeFullName { get; set; }
|
||||||
|
public string ReturnTypeIdentifierName { get; set; }
|
||||||
|
|
||||||
|
public EfAddMethodsModel(EfEntityDataModel entity, string createTypeFullName,
|
||||||
|
string createTypeIdentifierName,
|
||||||
|
string returnTypeFullName,
|
||||||
|
string returnTypeIdentifierName) : base(entity.PropertyName, entity.EntityTypeFullName, entity.EntityTypeIdentifierName)
|
||||||
|
{
|
||||||
|
CreateTypeFullName = createTypeFullName;
|
||||||
|
CreateTypeIdentifierName = createTypeIdentifierName;
|
||||||
|
ReturnTypeIdentifierName = returnTypeIdentifierName;
|
||||||
|
ReturnTypeFullName = returnTypeFullName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class EfGetOneByModel : EfEntityDataModel
|
||||||
|
{
|
||||||
|
public string ByParamPropertyName { get; set; }
|
||||||
|
public string ByParamFullTypeName { get; set; }
|
||||||
|
public string ByParamTypeName { get; set; }
|
||||||
|
public string ReturnTypeIdentifierName { get; set; }
|
||||||
|
public string ReturnTypeFullName { get; set; }
|
||||||
|
|
||||||
|
|
||||||
|
public EfGetOneByModel(EfEntityDataModel entity, string byParamPropertyName,
|
||||||
|
string byParamFullTypeName,
|
||||||
|
string returnTypeFullName,
|
||||||
|
string returnTypeIdentifierName) : base(entity.PropertyName, entity.EntityTypeFullName, entity.EntityTypeIdentifierName)
|
||||||
|
{
|
||||||
|
ByParamPropertyName = byParamPropertyName;
|
||||||
|
ByParamFullTypeName = byParamFullTypeName;
|
||||||
|
ReturnTypeIdentifierName = returnTypeIdentifierName;
|
||||||
|
ReturnTypeFullName = returnTypeFullName;
|
||||||
|
ByParamTypeName = returnTypeFullName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class EfGetOneWithModel : EfEntityDataModel
|
||||||
|
{
|
||||||
|
public string ReturnTypeIdentifierName { get; set; }
|
||||||
|
public string ReturnTypeFullName { get; set; }
|
||||||
|
|
||||||
|
public EfGetOneWithModel(EfEntityDataModel entity, string returnTypeFullName,
|
||||||
|
string returnTypeIdentifierName) : base(entity.PropertyName, entity.EntityTypeFullName, entity.EntityTypeIdentifierName)
|
||||||
|
{
|
||||||
|
ReturnTypeIdentifierName = returnTypeIdentifierName;
|
||||||
|
ReturnTypeFullName = returnTypeFullName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class EfGetManyModel : EfEntityDataModel
|
||||||
|
{
|
||||||
|
public string ReturnTypeIdentifierName { get; set; }
|
||||||
|
public string ReturnTypeFullName { get; set; }
|
||||||
|
|
||||||
|
public EfGetManyModel(EfEntityDataModel entity, string returnTypeFullName,
|
||||||
|
string returnTypeIdentifierName) : base(entity.PropertyName, entity.EntityTypeFullName, entity.EntityTypeIdentifierName)
|
||||||
|
{
|
||||||
|
ReturnTypeIdentifierName = returnTypeIdentifierName;
|
||||||
|
ReturnTypeFullName = returnTypeFullName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
internal class EfUpdateMethodsModel : EfEntityDataModel
|
||||||
|
{
|
||||||
|
public string UpdateTypeFullName;
|
||||||
|
public string UpdateTypeIdentifierName;
|
||||||
|
public string ReturnTypeFullName;
|
||||||
|
public string ReturnTypeIdentifierName;
|
||||||
|
public string KeyPropertyName;
|
||||||
|
public string KeyFullTypeName;
|
||||||
|
public EfUpdateMethodsModel(EfEntityDataModel entity,
|
||||||
|
string updateTypeFullName,
|
||||||
|
string updateTypeIdentifierName,
|
||||||
|
string returnTypeFullName,
|
||||||
|
string returnTypeIdentifierName,
|
||||||
|
string keyPropertyName,
|
||||||
|
string keyFullTypeName) : base(entity.PropertyName, entity.EntityTypeFullName, entity.EntityTypeIdentifierName)
|
||||||
|
{
|
||||||
|
UpdateTypeFullName = updateTypeFullName;
|
||||||
|
UpdateTypeIdentifierName = updateTypeIdentifierName;
|
||||||
|
ReturnTypeFullName= returnTypeFullName;
|
||||||
|
ReturnTypeIdentifierName = returnTypeIdentifierName;
|
||||||
|
KeyPropertyName = keyPropertyName;
|
||||||
|
KeyFullTypeName = keyFullTypeName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal record SourceGenerationOptions(
|
||||||
|
AccessModifier ConstructorAccessModifier,
|
||||||
|
AccessModifier GeneratedMethodsAccessModifier,
|
||||||
|
bool GenerateXmlDocument,
|
||||||
|
bool SupportNullableReferenceTypes,
|
||||||
|
bool SupportNullableStaticAnalysis)
|
||||||
|
{
|
||||||
|
internal static SourceGenerationOptions From(GeneratorExecutionContext context)
|
||||||
|
{
|
||||||
|
const string allowNullAttributeName = "System.Diagnostics.CodeAnalysis.AllowNullAttribute";
|
||||||
|
var supportNullableStaticAnalysis = context.GetBuildGlobalOption(propertyName: nameof(SupportNullableStaticAnalysis), NullStaticAnalysisState.Default);
|
||||||
|
var supportNullableReferenceTypes = context.Compilation.Options.NullableContextOptions is NullableContextOptions.Warnings or NullableContextOptions.Enable;
|
||||||
|
|
||||||
|
return new(
|
||||||
|
ConstructorAccessModifier: context.GetBuildGlobalOption(propertyName: nameof(ConstructorAccessModifier), AccessModifier.Public),
|
||||||
|
GeneratedMethodsAccessModifier: context.GetBuildGlobalOption(propertyName: nameof(GeneratedMethodsAccessModifier), AccessModifier.Public),
|
||||||
|
GenerateXmlDocument: context.GetBuildGlobalOption(propertyName: nameof(GenerateXmlDocument), true),
|
||||||
|
SupportNullableReferenceTypes: supportNullableReferenceTypes,
|
||||||
|
SupportNullableStaticAnalysis: supportNullableStaticAnalysis switch
|
||||||
|
{
|
||||||
|
NullStaticAnalysisState.Enabled => true,
|
||||||
|
NullStaticAnalysisState.Disabled => false,
|
||||||
|
_ => context.Compilation is CSharpCompilation { LanguageVersion: >= LanguageVersion.CSharp8 } cs && cs.TypeByMetadataNameExists(allowNullAttributeName)
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string NullableReferenceSyntax => SupportNullableReferenceTypes ? "?" : string.Empty;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,52 @@
|
||||||
|
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()!;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,60 @@
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -26,7 +26,7 @@ namespace MapTo.Sources
|
||||||
}
|
}
|
||||||
|
|
||||||
builder
|
builder
|
||||||
.WriteLine("[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]")
|
.WriteLine("[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)]")
|
||||||
.WriteLine($"public sealed class {AttributeName}Attribute : Attribute")
|
.WriteLine($"public sealed class {AttributeName}Attribute : Attribute")
|
||||||
.WriteOpeningBracket();
|
.WriteOpeningBracket();
|
||||||
|
|
||||||
|
@ -42,6 +42,13 @@ namespace MapTo.Sources
|
||||||
builder
|
builder
|
||||||
.WriteLine($"public {AttributeName}Attribute(Type sourceType)")
|
.WriteLine($"public {AttributeName}Attribute(Type sourceType)")
|
||||||
.WriteOpeningBracket()
|
.WriteOpeningBracket()
|
||||||
|
.WriteLine("SourceType = new [] { sourceType };")
|
||||||
|
.WriteClosingBracket()
|
||||||
|
.WriteLine();
|
||||||
|
|
||||||
|
builder
|
||||||
|
.WriteLine($"public {AttributeName}Attribute(Type[] sourceType)")
|
||||||
|
.WriteOpeningBracket()
|
||||||
.WriteLine("SourceType = sourceType;")
|
.WriteLine("SourceType = sourceType;")
|
||||||
.WriteClosingBracket()
|
.WriteClosingBracket()
|
||||||
.WriteLine();
|
.WriteLine();
|
||||||
|
@ -55,7 +62,7 @@ namespace MapTo.Sources
|
||||||
}
|
}
|
||||||
|
|
||||||
builder
|
builder
|
||||||
.WriteLine("public Type SourceType { get; }")
|
.WriteLine("public Type[] SourceType { get; }")
|
||||||
.WriteClosingBracket() // class
|
.WriteClosingBracket() // class
|
||||||
.WriteClosingBracket(); // namespace
|
.WriteClosingBracket(); // namespace
|
||||||
|
|
|
@ -23,6 +23,7 @@ namespace MapTo.Sources
|
||||||
if (options.GenerateXmlDocument)
|
if (options.GenerateXmlDocument)
|
||||||
{
|
{
|
||||||
builder
|
builder
|
||||||
|
.WriteLine()
|
||||||
.WriteLine("/// <summary>")
|
.WriteLine("/// <summary>")
|
||||||
.WriteLine("/// Specifies the mapping behavior of the annotated property.")
|
.WriteLine("/// Specifies the mapping behavior of the annotated property.")
|
||||||
.WriteLine("/// </summary>")
|
.WriteLine("/// </summary>")
|
|
@ -0,0 +1,235 @@
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,9 +2,9 @@
|
||||||
|
|
||||||
namespace MapTo.Sources
|
namespace MapTo.Sources
|
||||||
{
|
{
|
||||||
internal static class IgnorePropertyAttributeSource
|
internal static class ReadOnlyPropertyAttributeSource
|
||||||
{
|
{
|
||||||
internal const string AttributeName = "IgnoreProperty";
|
internal const string AttributeName = "ReadOnlyProperty";
|
||||||
internal const string AttributeClassName = AttributeName + "Attribute";
|
internal const string AttributeClassName = AttributeName + "Attribute";
|
||||||
internal const string FullyQualifiedName = RootNamespace + "." + AttributeClassName;
|
internal const string FullyQualifiedName = RootNamespace + "." + AttributeClassName;
|
||||||
|
|
|
@ -0,0 +1,40 @@
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,67 @@
|
||||||
|
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,27 +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<MappedProperty> GetMappedProperties(ITypeSymbol typeSymbol, ITypeSymbol sourceTypeSymbol, bool isInheritFromMappedBaseClass)
|
|
||||||
{
|
|
||||||
var sourceProperties = sourceTypeSymbol.GetAllMembers().OfType<IPropertySymbol>().ToArray();
|
|
||||||
|
|
||||||
return typeSymbol
|
|
||||||
.GetAllMembers(!isInheritFromMappedBaseClass)
|
|
||||||
.OfType<IPropertySymbol>()
|
|
||||||
.Where(p => !p.HasAttribute(IgnorePropertyAttributeTypeSymbol))
|
|
||||||
.Select(property => MapProperty(sourceTypeSymbol, sourceProperties, property))
|
|
||||||
.Where(mappedProperty => mappedProperty is not null)
|
|
||||||
.ToImmutableArray()!;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,340 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Collections.Immutable;
|
|
||||||
using System.Linq;
|
|
||||||
using MapTo.Extensions;
|
|
||||||
using MapTo.Sources;
|
|
||||||
using Microsoft.CodeAnalysis;
|
|
||||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
|
||||||
|
|
||||||
namespace MapTo
|
|
||||||
{
|
|
||||||
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;
|
|
||||||
|
|
||||||
IgnorePropertyAttributeTypeSymbol = compilation.GetTypeByMetadataNameOrThrow(IgnorePropertyAttributeSource.FullyQualifiedName);
|
|
||||||
MapTypeConverterAttributeTypeSymbol = compilation.GetTypeByMetadataNameOrThrow(MapTypeConverterAttributeSource.FullyQualifiedName);
|
|
||||||
TypeConverterInterfaceTypeSymbol = compilation.GetTypeByMetadataNameOrThrow(ITypeConverterSource.FullyQualifiedName);
|
|
||||||
MapPropertyAttributeTypeSymbol = compilation.GetTypeByMetadataNameOrThrow(MapPropertyAttributeSource.FullyQualifiedName);
|
|
||||||
MapFromAttributeTypeSymbol = compilation.GetTypeByMetadataNameOrThrow(MapFromAttributeSource.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 IgnorePropertyAttributeTypeSymbol { get; }
|
|
||||||
|
|
||||||
protected INamedTypeSymbol MapFromAttributeTypeSymbol { get; }
|
|
||||||
|
|
||||||
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
|
|
||||||
{
|
|
||||||
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
|
|
||||||
.SingleOrDefault(a => a.Key == MapPropertyAttributeSource.SourcePropertyNamePropertyName)
|
|
||||||
.Value.Value as string ?? property.Name;
|
|
||||||
|
|
||||||
return sourceProperties.SingleOrDefault(p => p.Name == propertyName);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected abstract ImmutableArray<MappedProperty> GetMappedProperties(ITypeSymbol typeSymbol, ITypeSymbol sourceTypeSymbol, bool isInheritFromMappedBaseClass);
|
|
||||||
|
|
||||||
protected INamedTypeSymbol? GetSourceTypeSymbol(TypeDeclarationSyntax typeDeclarationSyntax, SemanticModel? semanticModel = null) =>
|
|
||||||
GetSourceTypeSymbol(typeDeclarationSyntax.GetAttribute(MapFromAttributeSource.AttributeName), semanticModel);
|
|
||||||
|
|
||||||
protected INamedTypeSymbol? GetSourceTypeSymbol(SyntaxNode? attributeSyntax, SemanticModel? semanticModel = null)
|
|
||||||
{
|
|
||||||
if (attributeSyntax is null)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
semanticModel ??= Compilation.GetSemanticModel(attributeSyntax.SyntaxTree);
|
|
||||||
var sourceTypeExpressionSyntax = attributeSyntax
|
|
||||||
.DescendantNodes()
|
|
||||||
.OfType<TypeOfExpressionSyntax>()
|
|
||||||
.SingleOrDefault();
|
|
||||||
|
|
||||||
return sourceTypeExpressionSyntax is not null ? semanticModel.GetTypeInfo(sourceTypeExpressionSyntax.Type).Type as INamedTypeSymbol : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
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 virtual MappedProperty? 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 (!TryGetMapTypeConverter(property, sourceProperty, out converterFullyQualifiedName, out converterParameters) &&
|
|
||||||
!TryGetNestedObjectMappings(property, out mappedSourcePropertyType, out enumerableTypeArgumentType))
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
AddUsingIfRequired(propertyType);
|
|
||||||
AddUsingIfRequired(enumerableTypeArgumentType);
|
|
||||||
AddUsingIfRequired(mappedSourcePropertyType);
|
|
||||||
|
|
||||||
return new MappedProperty(
|
|
||||||
property.Name,
|
|
||||||
ToQualifiedDisplayName(propertyType) ?? propertyType.Name,
|
|
||||||
converterFullyQualifiedName,
|
|
||||||
converterParameters.ToImmutableArray(),
|
|
||||||
sourceProperty.Name,
|
|
||||||
ToQualifiedDisplayName(mappedSourcePropertyType),
|
|
||||||
ToQualifiedDisplayName(enumerableTypeArgumentType));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected bool TryGetMapTypeConverter(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 = GetTypeConverterBaseInterface(converterTypeSymbol, property, sourceProperty);
|
|
||||||
if (baseInterface is null)
|
|
||||||
{
|
|
||||||
AddDiagnostic(DiagnosticsFactory.InvalidTypeConverterGenericTypesError(property, sourceProperty));
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
var sourceTypeSymbol = GetSourceTypeSymbol(TypeSyntax, semanticModel);
|
|
||||||
if (sourceTypeSymbol is null)
|
|
||||||
{
|
|
||||||
AddDiagnostic(DiagnosticsFactory.MapFromAttributeNotFoundError(TypeSyntax.GetLocation()));
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
_ignoredNamespaces.Add(sourceTypeSymbol.ContainingNamespace.ToDisplayParts().First());
|
|
||||||
|
|
||||||
var typeIdentifierName = TypeSyntax.GetIdentifierName();
|
|
||||||
var sourceTypeIdentifierName = sourceTypeSymbol.Name;
|
|
||||||
var isTypeInheritFromMappedBaseClass = IsTypeInheritFromMappedBaseClass(semanticModel);
|
|
||||||
var shouldGenerateSecondaryConstructor = ShouldGenerateSecondaryConstructor(semanticModel, sourceTypeSymbol);
|
|
||||||
|
|
||||||
var mappedProperties = GetMappedProperties(typeSymbol, sourceTypeSymbol, isTypeInheritFromMappedBaseClass);
|
|
||||||
if (!mappedProperties.Any())
|
|
||||||
{
|
|
||||||
AddDiagnostic(DiagnosticsFactory.NoMatchingPropertyFoundError(TypeSyntax.GetLocation(), typeSymbol, sourceTypeSymbol));
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
AddUsingIfRequired(mappedProperties.Any(p => p.IsEnumerable), "System.Linq");
|
|
||||||
|
|
||||||
return new MappingModel(
|
|
||||||
SourceGenerationOptions,
|
|
||||||
TypeSyntax.GetNamespace(),
|
|
||||||
TypeSyntax.Modifiers,
|
|
||||||
TypeSyntax.Keyword.Text,
|
|
||||||
typeIdentifierName,
|
|
||||||
sourceTypeSymbol.ContainingNamespace.ToDisplayString(),
|
|
||||||
sourceTypeIdentifierName,
|
|
||||||
sourceTypeSymbol.ToDisplayString(),
|
|
||||||
mappedProperties,
|
|
||||||
isTypeInheritFromMappedBaseClass,
|
|
||||||
Usings,
|
|
||||||
shouldGenerateSecondaryConstructor);
|
|
||||||
}
|
|
||||||
|
|
||||||
private INamedTypeSymbol? GetTypeConverterBaseInterface(ITypeSymbol converterTypeSymbol, ISymbol property, IPropertySymbol sourceProperty)
|
|
||||||
{
|
|
||||||
if (!property.TryGetTypeSymbol(out var propertyType))
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return converterTypeSymbol.AllInterfaces
|
|
||||||
.SingleOrDefault(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>()
|
|
||||||
.SingleOrDefault(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,84 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Immutable;
|
|
||||||
using MapTo.Extensions;
|
|
||||||
using Microsoft.CodeAnalysis;
|
|
||||||
using Microsoft.CodeAnalysis.CSharp;
|
|
||||||
|
|
||||||
namespace MapTo
|
|
||||||
{
|
|
||||||
internal enum AccessModifier
|
|
||||||
{
|
|
||||||
Public,
|
|
||||||
Internal,
|
|
||||||
Private
|
|
||||||
}
|
|
||||||
|
|
||||||
internal enum NullStaticAnalysisState
|
|
||||||
{
|
|
||||||
Default,
|
|
||||||
Enabled,
|
|
||||||
Disabled
|
|
||||||
}
|
|
||||||
|
|
||||||
internal record SourceCode(string Text, string HintName);
|
|
||||||
|
|
||||||
internal record MappedProperty(
|
|
||||||
string Name,
|
|
||||||
string Type,
|
|
||||||
string? TypeConverter,
|
|
||||||
ImmutableArray<string> TypeConverterParameters,
|
|
||||||
string SourcePropertyName,
|
|
||||||
string? MappedSourcePropertyTypeName,
|
|
||||||
string? EnumerableTypeArgument)
|
|
||||||
{
|
|
||||||
public bool IsEnumerable => EnumerableTypeArgument is not null;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal record MappingModel (
|
|
||||||
SourceGenerationOptions Options,
|
|
||||||
string? Namespace,
|
|
||||||
SyntaxTokenList Modifiers,
|
|
||||||
string Type,
|
|
||||||
string TypeIdentifierName,
|
|
||||||
string SourceNamespace,
|
|
||||||
string SourceTypeIdentifierName,
|
|
||||||
string SourceTypeFullName,
|
|
||||||
ImmutableArray<MappedProperty> MappedProperties,
|
|
||||||
bool HasMappedBaseClass,
|
|
||||||
ImmutableArray<string> Usings,
|
|
||||||
bool GenerateSecondaryConstructor
|
|
||||||
)
|
|
||||||
{
|
|
||||||
public string SourceType => SourceTypeFullName;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal record SourceGenerationOptions(
|
|
||||||
AccessModifier ConstructorAccessModifier,
|
|
||||||
AccessModifier GeneratedMethodsAccessModifier,
|
|
||||||
bool GenerateXmlDocument,
|
|
||||||
bool SupportNullableReferenceTypes,
|
|
||||||
bool SupportNullableStaticAnalysis)
|
|
||||||
{
|
|
||||||
internal static SourceGenerationOptions From(GeneratorExecutionContext context)
|
|
||||||
{
|
|
||||||
const string allowNullAttributeName = "System.Diagnostics.CodeAnalysis.AllowNullAttribute";
|
|
||||||
var supportNullableStaticAnalysis = context.GetBuildGlobalOption(propertyName: nameof(SupportNullableStaticAnalysis), NullStaticAnalysisState.Default);
|
|
||||||
var supportNullableReferenceTypes = context.Compilation.Options.NullableContextOptions is NullableContextOptions.Warnings or NullableContextOptions.Enable;
|
|
||||||
|
|
||||||
return new(
|
|
||||||
ConstructorAccessModifier: context.GetBuildGlobalOption(propertyName: nameof(ConstructorAccessModifier), AccessModifier.Public),
|
|
||||||
GeneratedMethodsAccessModifier: context.GetBuildGlobalOption(propertyName: nameof(GeneratedMethodsAccessModifier), AccessModifier.Public),
|
|
||||||
GenerateXmlDocument: context.GetBuildGlobalOption(propertyName: nameof(GenerateXmlDocument), true),
|
|
||||||
SupportNullableReferenceTypes: supportNullableReferenceTypes,
|
|
||||||
SupportNullableStaticAnalysis: supportNullableStaticAnalysis switch
|
|
||||||
{
|
|
||||||
NullStaticAnalysisState.Enabled => true,
|
|
||||||
NullStaticAnalysisState.Disabled => false,
|
|
||||||
_ => context.Compilation is CSharpCompilation { LanguageVersion: >= LanguageVersion.CSharp8 } cs && cs.TypeByMetadataNameExists(allowNullAttributeName)
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public string NullableReferenceSyntax => SupportNullableReferenceTypes ? "?" : string.Empty;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,28 +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<MappedProperty> GetMappedProperties(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(IgnorePropertyAttributeTypeSymbol))
|
|
||||||
.Select(property => MapProperty(sourceTypeSymbol, sourceProperties, property))
|
|
||||||
.Where(mappedProperty => mappedProperty is not null)
|
|
||||||
.ToImmutableArray()!;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,206 +0,0 @@
|
||||||
using MapTo.Extensions;
|
|
||||||
using static MapTo.Sources.Constants;
|
|
||||||
|
|
||||||
namespace MapTo.Sources
|
|
||||||
{
|
|
||||||
internal static class MapClassSource
|
|
||||||
{
|
|
||||||
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 class {model.TypeIdentifierName}")
|
|
||||||
.WriteOpeningBracket();
|
|
||||||
|
|
||||||
// Class body
|
|
||||||
if (model.GenerateSecondaryConstructor)
|
|
||||||
{
|
|
||||||
builder
|
|
||||||
.GenerateSecondaryConstructor(model)
|
|
||||||
.WriteLine();
|
|
||||||
}
|
|
||||||
|
|
||||||
builder
|
|
||||||
.GeneratePrivateConstructor(model)
|
|
||||||
.WriteLine()
|
|
||||||
.GenerateFactoryMethod(model)
|
|
||||||
.GenerateUpdateMethod(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)
|
|
||||||
{
|
|
||||||
var sourceClassParameterName = model.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>");
|
|
||||||
}
|
|
||||||
|
|
||||||
return builder
|
|
||||||
.WriteLine($"{model.Options.ConstructorAccessModifier.ToLowercaseString()} {model.TypeIdentifierName}({model.SourceType} {sourceClassParameterName})")
|
|
||||||
.WriteLine($" : this(new {MappingContextSource.ClassName}(), {sourceClassParameterName}) {{ }}");
|
|
||||||
}
|
|
||||||
|
|
||||||
private static SourceBuilder GeneratePrivateConstructor(this SourceBuilder builder, MappingModel model)
|
|
||||||
{
|
|
||||||
var sourceClassParameterName = model.SourceTypeIdentifierName.ToCamelCase();
|
|
||||||
const string mappingContextParameterName = "context";
|
|
||||||
|
|
||||||
var baseConstructor = model.HasMappedBaseClass ? $" : base({mappingContextParameterName}, {sourceClassParameterName})" : string.Empty;
|
|
||||||
|
|
||||||
builder
|
|
||||||
.WriteLine($"private protected {model.TypeIdentifierName}({MappingContextSource.ClassName} {mappingContextParameterName}, {model.SourceType} {sourceClassParameterName}){baseConstructor}")
|
|
||||||
.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);")
|
|
||||||
.WriteLine().
|
|
||||||
|
|
||||||
WriteProperties( model, sourceClassParameterName, mappingContextParameterName);
|
|
||||||
|
|
||||||
// End constructor declaration
|
|
||||||
return builder.WriteClosingBracket();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static SourceBuilder WriteProperties(this SourceBuilder builder, MappingModel model,
|
|
||||||
string? sourceClassParameterName, string mappingContextParameterName)
|
|
||||||
{
|
|
||||||
foreach (var property in model.MappedProperties)
|
|
||||||
{
|
|
||||||
if (property.TypeConverter is null)
|
|
||||||
{
|
|
||||||
if (property.IsEnumerable)
|
|
||||||
{
|
|
||||||
builder.WriteLine(
|
|
||||||
$"{property.Name} = {sourceClassParameterName}.{property.SourcePropertyName}.Select({mappingContextParameterName}.{MappingContextSource.MapMethodName}<{property.MappedSourcePropertyTypeName}, {property.EnumerableTypeArgument}>).ToList();");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
builder.WriteLine(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.WriteLine(
|
|
||||||
$"{property.Name} = new {property.TypeConverter}().Convert({sourceClassParameterName}.{property.SourcePropertyName}, {parameters});");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
return builder;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private static SourceBuilder GenerateFactoryMethod(this SourceBuilder builder, MappingModel model)
|
|
||||||
{
|
|
||||||
var sourceClassParameterName = model.SourceTypeIdentifierName.ToCamelCase();
|
|
||||||
|
|
||||||
return builder
|
|
||||||
.GenerateConvertorMethodsXmlDocs(model, sourceClassParameterName)
|
|
||||||
.WriteLineIf(model.Options.SupportNullableStaticAnalysis, $"[return: NotNullIfNotNull(\"{sourceClassParameterName}\")]")
|
|
||||||
.WriteLine($"{model.Options.GeneratedMethodsAccessModifier.ToLowercaseString()} static {model.TypeIdentifierName}{model.Options.NullableReferenceSyntax} From({model.SourceType}{model.Options.NullableReferenceSyntax} {sourceClassParameterName})")
|
|
||||||
.WriteOpeningBracket()
|
|
||||||
.WriteLine($"return {sourceClassParameterName} == null ? null : {MappingContextSource.ClassName}.{MappingContextSource.FactoryMethodName}<{model.SourceType}, {model.TypeIdentifierName}>({sourceClassParameterName});")
|
|
||||||
.WriteClosingBracket();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static SourceBuilder GenerateUpdateMethod(this SourceBuilder builder, MappingModel model)
|
|
||||||
{
|
|
||||||
var sourceClassParameterName = model.SourceTypeIdentifierName.ToCamelCase();
|
|
||||||
|
|
||||||
builder
|
|
||||||
.GenerateUpdaterMethodsXmlDocs(model, sourceClassParameterName)
|
|
||||||
.WriteLine($"public void {model.Options.NullableReferenceSyntax}Update({model.SourceType}{model.Options.NullableReferenceSyntax} {sourceClassParameterName})")
|
|
||||||
.WriteOpeningBracket()
|
|
||||||
.WriteProperties( model, sourceClassParameterName,"context" )
|
|
||||||
.WriteClosingBracket();
|
|
||||||
|
|
||||||
return builder;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static SourceBuilder GenerateConvertorMethodsXmlDocs(this SourceBuilder builder, MappingModel model, string sourceClassParameterName)
|
|
||||||
{
|
|
||||||
if (!model.Options.GenerateXmlDocument)
|
|
||||||
{
|
|
||||||
return builder;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 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=\"{model.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>");
|
|
||||||
}
|
|
||||||
|
|
||||||
private static SourceBuilder GenerateUpdaterMethodsXmlDocs(this SourceBuilder builder, MappingModel model, string sourceClassParameterName)
|
|
||||||
{
|
|
||||||
if (!model.Options.GenerateXmlDocument)
|
|
||||||
{
|
|
||||||
return builder;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 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=\"{model.SourceType}\"/> to use as source.</param>");
|
|
||||||
}
|
|
||||||
|
|
||||||
private static SourceBuilder GenerateSourceTypeExtensionClass(this SourceBuilder builder, MappingModel model)
|
|
||||||
{
|
|
||||||
return builder
|
|
||||||
.WriteLine($"{model.Options.GeneratedMethodsAccessModifier.ToLowercaseString()} static partial class {model.SourceTypeIdentifierName}To{model.TypeIdentifierName}Extensions")
|
|
||||||
.WriteOpeningBracket()
|
|
||||||
.GenerateSourceTypeExtensionMethod(model)
|
|
||||||
.WriteClosingBracket();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static SourceBuilder GenerateSourceTypeExtensionMethod(this SourceBuilder builder, MappingModel model)
|
|
||||||
{
|
|
||||||
var sourceClassParameterName = model.SourceTypeIdentifierName.ToCamelCase();
|
|
||||||
|
|
||||||
return 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 {model.SourceType}{model.Options.NullableReferenceSyntax} {sourceClassParameterName})")
|
|
||||||
.WriteOpeningBracket()
|
|
||||||
.WriteLine($"return {sourceClassParameterName} == null ? null : new {model.TypeIdentifierName}({sourceClassParameterName});")
|
|
||||||
.WriteClosingBracket();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,191 +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();
|
|
||||||
|
|
||||||
// Class body
|
|
||||||
if (model.GenerateSecondaryConstructor)
|
|
||||||
{
|
|
||||||
builder
|
|
||||||
.GenerateSecondaryConstructor(model)
|
|
||||||
.WriteLine();
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
var sourceClassParameterName = model.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>");
|
|
||||||
}
|
|
||||||
|
|
||||||
return builder
|
|
||||||
.WriteLine($"{model.Options.ConstructorAccessModifier.ToLowercaseString()} {model.TypeIdentifierName}({model.SourceType} {sourceClassParameterName})")
|
|
||||||
.WriteLine($" : this(new {MappingContextSource.ClassName}(), {sourceClassParameterName}) {{ }}");
|
|
||||||
}
|
|
||||||
|
|
||||||
private static SourceBuilder GeneratePrivateConstructor(this SourceBuilder builder, MappingModel model)
|
|
||||||
{
|
|
||||||
var sourceClassParameterName = model.SourceTypeIdentifierName.ToCamelCase();
|
|
||||||
const string mappingContextParameterName = "context";
|
|
||||||
|
|
||||||
builder
|
|
||||||
.WriteLine($"private protected {model.TypeIdentifierName}({MappingContextSource.ClassName} {mappingContextParameterName}, {model.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);");
|
|
||||||
|
|
||||||
// End constructor declaration
|
|
||||||
return builder.WriteClosingBracket();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static SourceBuilder WriteProperties(this SourceBuilder builder, MappingModel model, string sourceClassParameterName,
|
|
||||||
string mappingContextParameterName)
|
|
||||||
{
|
|
||||||
for (var i = 0; i < model.MappedProperties.Length; i++)
|
|
||||||
{
|
|
||||||
var property = model.MappedProperties[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 < model.MappedProperties.Length - 1)
|
|
||||||
{
|
|
||||||
builder.Write(", ");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return builder;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static SourceBuilder GenerateFactoryMethod(this SourceBuilder builder, MappingModel model)
|
|
||||||
{
|
|
||||||
var sourceClassParameterName = model.SourceTypeIdentifierName.ToCamelCase();
|
|
||||||
|
|
||||||
return builder
|
|
||||||
.GenerateConvertorMethodsXmlDocs(model, sourceClassParameterName)
|
|
||||||
.WriteLineIf(model.Options.SupportNullableStaticAnalysis, $"[return: NotNullIfNotNull(\"{sourceClassParameterName}\")]")
|
|
||||||
.WriteLine(
|
|
||||||
$"{model.Options.GeneratedMethodsAccessModifier.ToLowercaseString()} static {model.TypeIdentifierName}{model.Options.NullableReferenceSyntax} From({model.SourceType}{model.Options.NullableReferenceSyntax} {sourceClassParameterName})")
|
|
||||||
.WriteOpeningBracket()
|
|
||||||
.WriteLine(
|
|
||||||
$"return {sourceClassParameterName} == null ? null : {MappingContextSource.ClassName}.{MappingContextSource.FactoryMethodName}<{model.SourceType}, {model.TypeIdentifierName}>({sourceClassParameterName});")
|
|
||||||
.WriteClosingBracket();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static SourceBuilder GenerateConvertorMethodsXmlDocs(this SourceBuilder builder, MappingModel model, string sourceClassParameterName)
|
|
||||||
{
|
|
||||||
if (!model.Options.GenerateXmlDocument)
|
|
||||||
{
|
|
||||||
return builder;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 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=\"{model.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>");
|
|
||||||
}
|
|
||||||
|
|
||||||
private static SourceBuilder GenerateSourceTypeExtensionClass(this SourceBuilder builder, MappingModel model)
|
|
||||||
{
|
|
||||||
return builder
|
|
||||||
.WriteLine(
|
|
||||||
$"{model.Options.GeneratedMethodsAccessModifier.ToLowercaseString()} static partial class {model.SourceTypeIdentifierName}To{model.TypeIdentifierName}Extensions")
|
|
||||||
.WriteOpeningBracket()
|
|
||||||
.GenerateSourceTypeExtensionMethod(model)
|
|
||||||
.WriteClosingBracket();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static SourceBuilder GenerateSourceTypeExtensionMethod(this SourceBuilder builder, MappingModel model)
|
|
||||||
{
|
|
||||||
var sourceClassParameterName = model.SourceTypeIdentifierName.ToCamelCase();
|
|
||||||
|
|
||||||
return 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 {model.SourceType}{model.Options.NullableReferenceSyntax} {sourceClassParameterName})")
|
|
||||||
.WriteOpeningBracket()
|
|
||||||
.WriteLine($"return {sourceClassParameterName} == null ? null : new {model.TypeIdentifierName}({sourceClassParameterName});")
|
|
||||||
.WriteClosingBracket();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,172 +0,0 @@
|
||||||
using MapTo.Extensions;
|
|
||||||
using static MapTo.Sources.Constants;
|
|
||||||
|
|
||||||
namespace MapTo.Sources
|
|
||||||
{
|
|
||||||
internal static class MapStructSource
|
|
||||||
{
|
|
||||||
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 class {model.TypeIdentifierName}")
|
|
||||||
.WriteOpeningBracket();
|
|
||||||
|
|
||||||
// Class body
|
|
||||||
if (model.GenerateSecondaryConstructor)
|
|
||||||
{
|
|
||||||
builder
|
|
||||||
.GenerateSecondaryConstructor(model)
|
|
||||||
.WriteLine();
|
|
||||||
}
|
|
||||||
|
|
||||||
builder
|
|
||||||
.GeneratePrivateConstructor(model)
|
|
||||||
.WriteLine()
|
|
||||||
// End class declaration
|
|
||||||
.WriteClosingBracket()
|
|
||||||
.WriteLine()
|
|
||||||
|
|
||||||
// End namespace declaration
|
|
||||||
.WriteClosingBracket();
|
|
||||||
|
|
||||||
return new(builder.ToString(), $"{model.Namespace}.{model.TypeIdentifierName}.g.cs");
|
|
||||||
}
|
|
||||||
|
|
||||||
private static SourceBuilder GenerateSecondaryConstructor(this SourceBuilder builder, MappingModel model)
|
|
||||||
{
|
|
||||||
var sourceClassParameterName = model.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>");
|
|
||||||
}
|
|
||||||
|
|
||||||
return builder
|
|
||||||
.WriteLine($"{model.Options.ConstructorAccessModifier.ToLowercaseString()} {model.TypeIdentifierName}({model.SourceType} {sourceClassParameterName})")
|
|
||||||
.WriteLine($" : this(new {MappingContextSource.ClassName}(), {sourceClassParameterName}) {{ }}");
|
|
||||||
}
|
|
||||||
|
|
||||||
private static SourceBuilder GeneratePrivateConstructor(this SourceBuilder builder, MappingModel model)
|
|
||||||
{
|
|
||||||
var sourceClassParameterName = model.SourceTypeIdentifierName.ToCamelCase();
|
|
||||||
const string mappingContextParameterName = "context";
|
|
||||||
|
|
||||||
var baseConstructor = model.HasMappedBaseClass ? $" : base({mappingContextParameterName}, {sourceClassParameterName})" : string.Empty;
|
|
||||||
|
|
||||||
builder
|
|
||||||
.WriteLine($"private protected {model.TypeIdentifierName}({MappingContextSource.ClassName} {mappingContextParameterName}, {model.SourceType} {sourceClassParameterName}){baseConstructor}")
|
|
||||||
.WriteOpeningBracket()
|
|
||||||
.WriteLine()
|
|
||||||
.WriteLine($"{mappingContextParameterName}.{MappingContextSource.RegisterMethodName}({sourceClassParameterName}, this);")
|
|
||||||
.WriteLine().
|
|
||||||
|
|
||||||
WriteProperties( model, sourceClassParameterName, mappingContextParameterName);
|
|
||||||
|
|
||||||
// End constructor declaration
|
|
||||||
return builder.WriteClosingBracket();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static SourceBuilder WriteProperties(this SourceBuilder builder, MappingModel model,
|
|
||||||
string? sourceClassParameterName, string mappingContextParameterName)
|
|
||||||
{
|
|
||||||
foreach (var property in model.MappedProperties)
|
|
||||||
{
|
|
||||||
if (property.TypeConverter is null)
|
|
||||||
{
|
|
||||||
if (property.IsEnumerable)
|
|
||||||
{
|
|
||||||
builder.WriteLine(
|
|
||||||
$"{property.Name} = {sourceClassParameterName}.{property.SourcePropertyName}.Select({mappingContextParameterName}.{MappingContextSource.MapMethodName}<{property.MappedSourcePropertyTypeName}, {property.EnumerableTypeArgument}>).ToList();");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
builder.WriteLine(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.WriteLine(
|
|
||||||
$"{property.Name} = new {property.TypeConverter}().Convert({sourceClassParameterName}.{property.SourcePropertyName}, {parameters});");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
return builder;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private static SourceBuilder GenerateConvertorMethodsXmlDocs(this SourceBuilder builder, MappingModel model, string sourceClassParameterName)
|
|
||||||
{
|
|
||||||
if (!model.Options.GenerateXmlDocument)
|
|
||||||
{
|
|
||||||
return builder;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 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=\"{model.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>");
|
|
||||||
}
|
|
||||||
|
|
||||||
private static SourceBuilder GenerateUpdaterMethodsXmlDocs(this SourceBuilder builder, MappingModel model, string sourceClassParameterName)
|
|
||||||
{
|
|
||||||
if (!model.Options.GenerateXmlDocument)
|
|
||||||
{
|
|
||||||
return builder;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 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=\"{model.SourceType}\"/> to use as source.</param>");
|
|
||||||
}
|
|
||||||
|
|
||||||
private static SourceBuilder GenerateSourceTypeExtensionClass(this SourceBuilder builder, MappingModel model)
|
|
||||||
{
|
|
||||||
return builder
|
|
||||||
.WriteLine($"{model.Options.GeneratedMethodsAccessModifier.ToLowercaseString()} static partial class {model.SourceTypeIdentifierName}To{model.TypeIdentifierName}Extensions")
|
|
||||||
.WriteOpeningBracket()
|
|
||||||
.GenerateSourceTypeExtensionMethod(model)
|
|
||||||
.WriteClosingBracket();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static SourceBuilder GenerateSourceTypeExtensionMethod(this SourceBuilder builder, MappingModel model)
|
|
||||||
{
|
|
||||||
var sourceClassParameterName = model.SourceTypeIdentifierName.ToCamelCase();
|
|
||||||
|
|
||||||
return 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 {model.SourceType}{model.Options.NullableReferenceSyntax} {sourceClassParameterName})")
|
|
||||||
.WriteOpeningBracket()
|
|
||||||
.WriteLine($"return {sourceClassParameterName} == null ? null : new {model.TypeIdentifierName}({sourceClassParameterName});")
|
|
||||||
.WriteClosingBracket();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,37 +1,42 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net5.0</TargetFramework>
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
|
|
||||||
<IsPackable>false</IsPackable>
|
<IsPackable>false</IsPackable>
|
||||||
|
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
|
|
||||||
|
<RootNamespace>MapTo.Tests</RootNamespace>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.1">
|
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.3">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="3.8.0" />
|
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.2.0" />
|
||||||
|
<PackageReference Update="Nerdbank.GitVersioning">
|
||||||
|
<Version>3.5.109</Version>
|
||||||
|
</PackageReference>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.10.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.0" />
|
||||||
<PackageReference Include="Shouldly" Version="4.0.3" />
|
<PackageReference Include="Shouldly" Version="4.0.3" />
|
||||||
<PackageReference Include="xunit" Version="2.4.1" />
|
<PackageReference Include="xunit" Version="2.4.2" />
|
||||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
|
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="coverlet.collector" Version="3.0.3">
|
<PackageReference Include="coverlet.collector" Version="3.1.2">
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\..\src\MapTo\MapTo.csproj" />
|
<ProjectReference Include="..\..\src\BlueWest.MapTo\BlueWest.MapTo.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
|
@ -8,7 +8,7 @@ namespace MapTo.Tests.Extensions
|
||||||
internal static class RoslynExtensions
|
internal static class RoslynExtensions
|
||||||
{
|
{
|
||||||
internal static SyntaxTree? GetGeneratedSyntaxTree(this Compilation compilation, string className) =>
|
internal static SyntaxTree? GetGeneratedSyntaxTree(this Compilation compilation, string className) =>
|
||||||
compilation.SyntaxTrees.SingleOrDefault(s => s.FilePath.EndsWith($"{className}.g.cs"));
|
compilation.SyntaxTrees.FirstOrDefault(s => s.FilePath.EndsWith($"{className}.g.cs"));
|
||||||
|
|
||||||
internal static string PrintSyntaxTree(this Compilation compilation)
|
internal static string PrintSyntaxTree(this Compilation compilation)
|
||||||
{
|
{
|
|
@ -15,7 +15,7 @@ namespace MapTo.Tests.Extensions
|
||||||
{
|
{
|
||||||
var syntax = syntaxTree
|
var syntax = syntaxTree
|
||||||
.Select(s => s.ToString().Trim())
|
.Select(s => s.ToString().Trim())
|
||||||
.SingleOrDefault(s => s.Contains(typeName));
|
.FirstOrDefault(s => s.Contains(typeName));
|
||||||
|
|
||||||
syntax.ShouldNotBeNullOrWhiteSpace();
|
syntax.ShouldNotBeNullOrWhiteSpace();
|
||||||
syntax.ShouldBe(expectedSource, customMessage);
|
syntax.ShouldBe(expectedSource, customMessage);
|
||||||
|
@ -25,7 +25,7 @@ namespace MapTo.Tests.Extensions
|
||||||
{
|
{
|
||||||
var syntax = syntaxTree
|
var syntax = syntaxTree
|
||||||
.Select(s => s.ToString().Trim())
|
.Select(s => s.ToString().Trim())
|
||||||
.SingleOrDefault(s => s.Contains(typeName));
|
.FirstOrDefault(s => s.Contains(typeName));
|
||||||
|
|
||||||
syntax.ShouldNotBeNullOrWhiteSpace();
|
syntax.ShouldNotBeNullOrWhiteSpace();
|
||||||
syntax.ShouldContainWithoutWhitespace(expectedSource, customMessage);
|
syntax.ShouldContainWithoutWhitespace(expectedSource, customMessage);
|
||||||
|
@ -68,7 +68,7 @@ namespace MapTo.Tests.Extensions
|
||||||
|
|
||||||
internal static void ShouldNotBeSuccessful(this ImmutableArray<Diagnostic> diagnostics, Diagnostic expectedError)
|
internal static void ShouldNotBeSuccessful(this ImmutableArray<Diagnostic> diagnostics, Diagnostic expectedError)
|
||||||
{
|
{
|
||||||
var actualDiagnostics = diagnostics.SingleOrDefault(d => d.Id == expectedError.Id);
|
var actualDiagnostics = diagnostics.FirstOrDefault(d => d.Id == expectedError.Id);
|
||||||
var compilationDiagnostics = actualDiagnostics == null ? diagnostics : diagnostics.Except(new[] { actualDiagnostics });
|
var compilationDiagnostics = actualDiagnostics == null ? diagnostics : diagnostics.Except(new[] { actualDiagnostics });
|
||||||
|
|
||||||
compilationDiagnostics.ShouldBeSuccessful();
|
compilationDiagnostics.ShouldBeSuccessful();
|
|
@ -11,6 +11,7 @@ namespace MapTo.Tests
|
||||||
{
|
{
|
||||||
public class IgnorePropertyAttributeTests
|
public class IgnorePropertyAttributeTests
|
||||||
{
|
{
|
||||||
|
/*
|
||||||
[Fact]
|
[Fact]
|
||||||
public void VerifyIgnorePropertyAttribute()
|
public void VerifyIgnorePropertyAttribute()
|
||||||
{
|
{
|
||||||
|
@ -34,6 +35,7 @@ namespace MapTo
|
||||||
diagnostics.ShouldBeSuccessful();
|
diagnostics.ShouldBeSuccessful();
|
||||||
compilation.SyntaxTrees.ShouldContainSource(IgnorePropertyAttributeSource.AttributeName, expectedAttribute);
|
compilation.SyntaxTrees.ShouldContainSource(IgnorePropertyAttributeSource.AttributeName, expectedAttribute);
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void When_IgnorePropertyAttributeIsSpecified_Should_NotGenerateMappingsForThatProperty()
|
public void When_IgnorePropertyAttributeIsSpecified_Should_NotGenerateMappingsForThatProperty()
|
|
@ -1,7 +1,9 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Immutable;
|
using System.Collections.Immutable;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
using MapTo.Tests.Extensions;
|
using MapTo.Tests.Extensions;
|
||||||
using Microsoft.CodeAnalysis;
|
using Microsoft.CodeAnalysis;
|
||||||
using Microsoft.CodeAnalysis.CSharp;
|
using Microsoft.CodeAnalysis.CSharp;
|
||||||
|
@ -48,7 +50,7 @@ namespace MapTo.Tests.Infrastructure
|
||||||
}
|
}
|
||||||
|
|
||||||
var driver = CSharpGeneratorDriver.Create(
|
var driver = CSharpGeneratorDriver.Create(
|
||||||
new[] { new MapToGenerator() },
|
new List<ISourceGenerator>() { new MapToGenerator()},
|
||||||
optionsProvider: new TestAnalyzerConfigOptionsProvider(analyzerConfigOptions),
|
optionsProvider: new TestAnalyzerConfigOptionsProvider(analyzerConfigOptions),
|
||||||
parseOptions: new CSharpParseOptions(languageVersion)
|
parseOptions: new CSharpParseOptions(languageVersion)
|
||||||
);
|
);
|
|
@ -234,7 +234,7 @@ namespace Test
|
||||||
const string source = "";
|
const string source = "";
|
||||||
var expectedTypes = new[]
|
var expectedTypes = new[]
|
||||||
{
|
{
|
||||||
IgnorePropertyAttributeSource.AttributeName,
|
//IgnorePropertyAttributeSource.AttributeName,
|
||||||
MapFromAttributeSource.AttributeName,
|
MapFromAttributeSource.AttributeName,
|
||||||
ITypeConverterSource.InterfaceName,
|
ITypeConverterSource.InterfaceName,
|
||||||
MapPropertyAttributeSource.AttributeName
|
MapPropertyAttributeSource.AttributeName
|
|
@ -186,7 +186,7 @@ namespace SaleModel
|
||||||
diagnostics.ShouldBeSuccessful();
|
diagnostics.ShouldBeSuccessful();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IEnumerable<object> SameSourceAndDestinationTypeNameData => new List<object>
|
public static IEnumerable<object[]> SameSourceAndDestinationTypeNameData => new List<object[]>
|
||||||
{
|
{
|
||||||
new object[]
|
new object[]
|
||||||
{
|
{
|
|
@ -6,7 +6,7 @@
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\..\src\MapTo\MapTo.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
|
<ProjectReference Include="..\..\src\BlueWest.MapTo\BlueWest.MapTo.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
@ -0,0 +1,59 @@
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,12 +4,18 @@ using System.Text;
|
||||||
|
|
||||||
namespace TestConsoleApp.Data.Models
|
namespace TestConsoleApp.Data.Models
|
||||||
{
|
{
|
||||||
|
|
||||||
public class Employee
|
public class Employee
|
||||||
{
|
{
|
||||||
public int Id { get; set; }
|
public int Id { get; }
|
||||||
|
|
||||||
public string EmployeeCode { get; set; }
|
public string EmployeeCode { get; }
|
||||||
|
|
||||||
|
public Employee(int id, string employeeCode)
|
||||||
|
{
|
||||||
|
Id = id;
|
||||||
|
EmployeeCode = employeeCode;
|
||||||
|
}
|
||||||
|
|
||||||
public Manager Manager { get; set; }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace TestConsoleApp.Data.Models
|
|
||||||
{
|
|
||||||
public class Manager: Employee
|
|
||||||
{
|
|
||||||
public int Level { get; set; }
|
|
||||||
|
|
||||||
public IEnumerable<Employee> Employees { get; set; } = Array.Empty<Employee>();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
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,11 +0,0 @@
|
||||||
namespace TestConsoleApp.Data.Models
|
|
||||||
{
|
|
||||||
public class Profile
|
|
||||||
{
|
|
||||||
public string FirstName { get; set; }
|
|
||||||
|
|
||||||
public string LastName { get; set; }
|
|
||||||
|
|
||||||
public string FullName => $"{FirstName} {LastName}";
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,13 +0,0 @@
|
||||||
using System;
|
|
||||||
|
|
||||||
namespace TestConsoleApp.Data.Models
|
|
||||||
{
|
|
||||||
public class User
|
|
||||||
{
|
|
||||||
public int Id { get; set; }
|
|
||||||
|
|
||||||
public DateTimeOffset RegisteredAt { get; set; }
|
|
||||||
|
|
||||||
public Profile Profile { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,44 @@
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
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;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,7 +2,6 @@
|
||||||
using MapTo;
|
using MapTo;
|
||||||
using TestConsoleApp.Data.Models;
|
using TestConsoleApp.Data.Models;
|
||||||
using TestConsoleApp.ViewModels;
|
using TestConsoleApp.ViewModels;
|
||||||
using TestConsoleApp.ViewModels2;
|
|
||||||
|
|
||||||
namespace TestConsoleApp
|
namespace TestConsoleApp
|
||||||
{
|
{
|
||||||
|
@ -11,7 +10,6 @@ namespace TestConsoleApp
|
||||||
private static void Main(string[] args)
|
private static void Main(string[] args)
|
||||||
{
|
{
|
||||||
//UserTest();
|
//UserTest();
|
||||||
CyclicReferenceTest();
|
|
||||||
|
|
||||||
// EmployeeManagerTest();
|
// EmployeeManagerTest();
|
||||||
Console.WriteLine("done");
|
Console.WriteLine("done");
|
||||||
|
@ -19,74 +17,16 @@ namespace TestConsoleApp
|
||||||
|
|
||||||
private static void EmployeeManagerTest()
|
private static void EmployeeManagerTest()
|
||||||
{
|
{
|
||||||
var manager1 = new Manager
|
|
||||||
{
|
|
||||||
Id = 1,
|
|
||||||
EmployeeCode = "M001",
|
|
||||||
Level = 100
|
|
||||||
};
|
|
||||||
|
|
||||||
var manager2 = new Manager
|
|
||||||
{
|
|
||||||
Id = 2,
|
|
||||||
EmployeeCode = "M002",
|
|
||||||
Level = 100,
|
|
||||||
Manager = manager1
|
|
||||||
};
|
|
||||||
|
|
||||||
var employee1 = new Employee
|
var employee = new Employee(1, "hello");
|
||||||
{
|
|
||||||
Id = 101,
|
|
||||||
EmployeeCode = "E101",
|
|
||||||
Manager = manager1
|
|
||||||
};
|
|
||||||
|
|
||||||
var employee2 = new Employee
|
|
||||||
{
|
|
||||||
Id = 102,
|
|
||||||
EmployeeCode = "E102",
|
|
||||||
Manager = manager2
|
|
||||||
};
|
|
||||||
|
|
||||||
manager1.Employees = new[] { employee1, manager2 };
|
|
||||||
manager2.Employees = new[] { employee2 };
|
|
||||||
|
|
||||||
manager1.ToManagerViewModel();
|
|
||||||
employee1.ToEmployeeViewModel();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ManagerViewModel CyclicReferenceTest()
|
|
||||||
{
|
|
||||||
var manager1 = new Manager
|
|
||||||
{
|
|
||||||
Id = 1,
|
|
||||||
EmployeeCode = "M001",
|
|
||||||
Level = 100
|
|
||||||
};
|
|
||||||
|
|
||||||
manager1.Manager = manager1;
|
|
||||||
return manager1.ToManagerViewModel();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void UserTest()
|
|
||||||
{
|
|
||||||
var user = new User
|
|
||||||
{
|
|
||||||
Id = 1234,
|
|
||||||
RegisteredAt = DateTimeOffset.Now,
|
|
||||||
Profile = new Profile
|
|
||||||
{
|
|
||||||
FirstName = "John",
|
|
||||||
LastName = "Doe"
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var vm = user.ToUserViewModel();
|
|
||||||
|
|
||||||
Console.WriteLine("Key: {0}", vm.Key);
|
|
||||||
Console.WriteLine("RegisteredAt: {0}", vm.RegisteredAt);
|
|
||||||
Console.WriteLine("FirstName: {0}", vm.Profile.FirstName);
|
|
||||||
Console.WriteLine("LastName: {0}", vm.Profile.LastName);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -8,10 +8,10 @@
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\..\src\MapTo\MapTo.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
|
<ProjectReference Include="..\..\src\BlueWest.MapTo\BlueWest.MapTo.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<Import Project="..\..\src\MapTo\MapTo.props" />
|
<Import Project="..\..\src\BlueWest.MapTo\MapTo.props" />
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<MapTo_ConstructorAccessModifier>Internal</MapTo_ConstructorAccessModifier>
|
<MapTo_ConstructorAccessModifier>Internal</MapTo_ConstructorAccessModifier>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
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,16 +1,13 @@
|
||||||
using MapTo;
|
using MapTo;
|
||||||
using TestConsoleApp.Data.Models;
|
using TestConsoleApp.Data.Models;
|
||||||
using TestConsoleApp.ViewModels2;
|
|
||||||
|
|
||||||
namespace TestConsoleApp.ViewModels
|
namespace TestConsoleApp.ViewModels
|
||||||
{
|
{
|
||||||
[MapFrom(typeof(Employee))]
|
[MapFrom(typeof(Employee))]
|
||||||
public partial class EmployeeViewModel
|
public partial class EmployeeViewModel
|
||||||
{
|
{
|
||||||
public int Id { get; set; }
|
public int Id { get; }
|
||||||
|
|
||||||
public string EmployeeCode { get; set; }
|
|
||||||
|
|
||||||
public ManagerViewModel Manager { get; set; }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,16 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using MapTo;
|
|
||||||
using TestConsoleApp.Data.Models;
|
|
||||||
using TestConsoleApp.ViewModels;
|
|
||||||
|
|
||||||
namespace TestConsoleApp.ViewModels2
|
|
||||||
{
|
|
||||||
[MapFrom(typeof(Manager))]
|
|
||||||
public partial class ManagerViewModel : EmployeeViewModel
|
|
||||||
{
|
|
||||||
public int Level { get; set; }
|
|
||||||
|
|
||||||
public IEnumerable<EmployeeViewModel> Employees { get; set; } = Array.Empty<EmployeeViewModel>();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,13 +0,0 @@
|
||||||
using MapTo;
|
|
||||||
using TestConsoleApp.Data.Models;
|
|
||||||
|
|
||||||
namespace TestConsoleApp.ViewModels
|
|
||||||
{
|
|
||||||
[MapFrom(typeof(Profile))]
|
|
||||||
public partial class ProfileViewModel
|
|
||||||
{
|
|
||||||
public string FirstName { get; }
|
|
||||||
|
|
||||||
public string LastName { get; }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,24 +0,0 @@
|
||||||
using System;
|
|
||||||
using MapTo;
|
|
||||||
using TestConsoleApp.Data.Models;
|
|
||||||
|
|
||||||
namespace TestConsoleApp.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}";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json",
|
"$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json",
|
||||||
"version": "0.8",
|
"version": "0.9",
|
||||||
"semVer1NumericIdentifierPadding": 1,
|
"semVer1NumericIdentifierPadding": 1,
|
||||||
"publicReleaseRefSpec": [
|
"publicReleaseRefSpec": [
|
||||||
"^refs/heads/master$",
|
"^refs/heads/master$",
|
||||||
|
|
Loading…
Reference in New Issue