Compare commits

...

30 Commits

Author SHA1 Message Date
CodeLiturgy 4e80156708 Remove EfGenerator, migrated to separate project 2022-09-05 23:18:15 +01:00
CodeLiturgy b8637c94ab Using GetOne and GetMany templates 2022-09-05 22:42:51 +01:00
CodeLiturgy 32bf670b9e Get one template working 2022-09-05 03:58:28 +01:00
CodeLiturgy 9743ff3740 Post-refactor 2022-09-05 02:16:32 +01:00
CodeLiturgy 8a664db1f7 pre-refacotring 2022-09-05 02:16:08 +01:00
CodeLiturgy 1de6350c7d Prepare getone template 2022-09-05 02:01:17 +01:00
CodeLiturgy fa3dfc8e86 Repair done 2022-09-05 01:42:08 +01:00
CodeLiturgy 4c4413da41 Before trying to replace with type syntax 2022-09-05 00:33:19 +01:00
CodeLiturgy 9211cde83c Use new model for ef method 2022-09-04 22:11:04 +01:00
CodeLiturgy 3917d231a2 Add generator attribute source 2022-09-04 19:07:59 +01:00
CodeLiturgy 9257d389ba add EfMethods directory 2022-09-04 16:36:46 +01:00
CodeLiturgy 07c8fc4853 Template ok 2022-09-02 00:07:56 +01:00
Wvader e23d932d13 update template using nameof to specify id 2022-08-29 17:30:32 +01:00
CodeLiturgy 654a54d5ef Update template working 2022-08-29 01:41:05 +01:00
CodeLiturgy f419c0f8ea ok state before introducing update 2022-08-28 19:39:47 +01:00
CodeLiturgy ca95c87839 Add model refactored 2022-08-28 18:14:08 +01:00
CodeLiturgy 12d5839f16 ok 2022-08-28 18:00:04 +01:00
CodeLiturgy fabfa562cf ok 2022-08-28 17:59:55 +01:00
CodeLiturgy 4d9cc4b029 End merge Add with template and Update 2022-08-27 21:01:52 +01:00
CodeLiturgy c34da98ad8 ef update wip 2022-08-27 20:56:19 +01:00
Wvader e232fa6e76 Add template working 2022-08-27 04:19:43 +01:00
Wvader 340a89bbd2 Support Ef Add method 2022-08-24 17:56:44 +01:00
Wvader 1400bd08e0 Prepare for Add/Update generators 2022-08-23 17:48:47 +01:00
Wvader 7de9b48c69 Inject empty constructor 2022-08-22 03:14:14 +01:00
Wvader a6f5116656 Supporting 'friendly constructors' 2022-08-21 22:15:00 +01:00
Wvader aa5b01cdc4 Fix UseUpdate usage 2022-08-20 03:47:55 +01:00
Wvader f7963d2d7e Addapt MapTo to support multiple mappings 2022-08-18 16:48:44 +01:00
Wvader ca82e6fb17 Fix docker errors 2022-08-13 06:33:39 +01:00
Wvader 77603643b4 Bump Nerdbank.GitVersioning 2022-08-13 01:15:25 +01:00
Wvader c241fd4cb5 summer changes 2022-08-02 22:02:26 +01:00
56 changed files with 994 additions and 681 deletions

View File

@ -29,7 +29,7 @@ jobs:
- name: Publish MapTo
uses: brandedoutcast/publish-nuget@v2.5.5
with:
PROJECT_FILE_PATH: src/MapTo/MapTo.csproj
PROJECT_FILE_PATH: src/BlueWest.MapTo/BlueWest.MapTo.csproj
NUGET_KEY: ${{secrets.NUGET_API_KEY}}
NUGET_SOURCE: https://api.nuget.org
TAG_COMMIT: false

View File

@ -1,6 +1,6 @@

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
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MapToTests", "test\MapTo.Tests\MapTo.Tests.csproj", "{797DA57B-AC7E-468B-8799-44C5A574C0E3}"
EndProject

View File

@ -4,15 +4,11 @@
<TargetFramework>netstandard2.0</TargetFramework>
<Nullable>enable</Nullable>
<LangVersion>9</LangVersion>
<AssemblyName>MapTo</AssemblyName>
<Description>An object to object mapping generator using Roslyn source generator.</Description>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<IncludeSymbols>true</IncludeSymbols>
<PackageId>MapTo</PackageId>
<PackageLicenseFile>LICENSE</PackageLicenseFile>
<PackageProjectUrl>https://github.com/mrtaikandi/mapto</PackageProjectUrl>
<PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance>
<PackageVersion>$(Version)</PackageVersion>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<RepositoryUrl>https://github.com/mrtaikandi/mapto</RepositoryUrl>
@ -22,6 +18,7 @@
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<DocumentationFile>bin\Release\MapTo.xml</DocumentationFile>
<Optimize>false</Optimize>
</PropertyGroup>
<ItemGroup>
@ -31,15 +28,17 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.2">
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.3">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="3.8.0" />
<PackageReference Update="Nerdbank.GitVersioning">
<Version>3.5.109</Version>
</PackageReference>
</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="MapTo.props" Pack="true" PackagePath="build" Visible="false" />
</ItemGroup>

View File

@ -30,17 +30,22 @@ namespace MapTo.Extensions
internal static SourceBuilder WriteModelInfo(this SourceBuilder builder, MappingModel model)
{
return 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 {model.SourceNamespace}")
.WriteComment($" SourceTypeFullName {model.SourceTypeFullName}")
.WriteComment($" SourceTypeIdentifierName {model.SourceTypeIdentifierName}");
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;
}

View File

@ -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;
}
}
}

View File

@ -31,13 +31,14 @@ namespace MapTo.Extensions
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)
.SingleOrDefault(a =>
(a.Name as IdentifierNameSyntax)?.Identifier.ValueText == attributeName ||
((a.Name as QualifiedNameSyntax)?.Right as IdentifierNameSyntax)?.Identifier.ValueText == attributeName);
.FirstOrDefault(x => x.Name.ToString().Contains(attributeName));
return selection;
}
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) =>
symbol.GetAttributes(attributeSymbol).FirstOrDefault();
public static string? GetNamespace(this TypeDeclarationSyntax typeDeclarationSyntax) => typeDeclarationSyntax
public static string? GetNamespace(this MemberDeclarationSyntax typeDeclarationSyntax) => typeDeclarationSyntax
.Ancestors()
.OfType<NamespaceDeclarationSyntax>()
.FirstOrDefault()
@ -86,7 +87,7 @@ namespace MapTo.Extensions
public static IPropertySymbol? FindProperty(this IEnumerable<IPropertySymbol> properties, IPropertySymbol targetProperty)
{
return properties.SingleOrDefault(p =>
return properties.FirstOrDefault(p =>
p.Name == targetProperty.Name &&
(p.NullableAnnotation != NullableAnnotation.Annotated ||
p.NullableAnnotation == NullableAnnotation.Annotated &&

View File

@ -1,6 +1,8 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using MapTo.Extensions;
using MapTo.Sources;
using Microsoft.CodeAnalysis;
@ -17,7 +19,9 @@ namespace MapTo
/// <inheritdoc />
public void Initialize(GeneratorInitializationContext context)
{
context.RegisterForSyntaxNotifications(() => new MapToSyntaxReceiver());
}
/// <inheritdoc />
@ -54,13 +58,14 @@ namespace MapTo
foreach (var typeDeclarationSyntax in candidateTypes)
{
var mappingContext = MappingContext.Create(compilation, options, typeDeclarationSyntax);
mappingContext.Diagnostics.ForEach(context.ReportDiagnostic);
if (mappingContext.Model is null)
{
continue;
}
var (source, hintName) = typeDeclarationSyntax switch
{
StructDeclarationSyntax => MapStructSource.Generate(mappingContext.Model),

View File

@ -20,7 +20,7 @@ namespace MapTo
var attributeSyntax = attributes
.SelectMany(a => a.Attributes)
.SingleOrDefault(a => a.Name is
.FirstOrDefault(a => a.Name is
IdentifierNameSyntax { Identifier: { ValueText: MapFromAttributeSource.AttributeName } } // For: [MapFrom]
or
QualifiedNameSyntax // For: [MapTo.MapFrom]

View File

@ -1,11 +1,14 @@
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
{
@ -52,7 +55,7 @@ namespace MapTo
protected INamedTypeSymbol UseUpdateAttributeTypeSymbol { get; }
public static INamedTypeSymbol JsonExtensionAttributeSymbol { get; set; }
public static INamedTypeSymbol JsonExtensionAttributeSymbol { get; set; } = null!;
protected INamedTypeSymbol MappingContextTypeSymbol { get; }
@ -70,6 +73,7 @@ namespace MapTo
public static MappingContext Create(Compilation compilation, SourceGenerationOptions sourceGenerationOptions, TypeDeclarationSyntax typeSyntax)
{
MappingContext context = typeSyntax switch
{
StructDeclarationSyntax => new StructMappingContext(compilation, sourceGenerationOptions, typeSyntax),
@ -107,20 +111,20 @@ namespace MapTo
var propertyName = property
.GetAttribute(MapPropertyAttributeTypeSymbol)
?.NamedArguments
.SingleOrDefault(a => a.Key == MapPropertyAttributeSource.SourcePropertyNamePropertyName)
.FirstOrDefault(a => a.Key == MapPropertyAttributeSource.SourcePropertyNamePropertyName)
.Value.Value as string ?? property.Name;
return sourceProperties.SingleOrDefault(p => p.Name == propertyName);
return sourceProperties.FirstOrDefault(p => p.Name == propertyName);
}
protected IFieldSymbol? FindSourceField(IEnumerable<IFieldSymbol> sourceProperties, ISymbol property)
{
var propertyName = property
.GetAttribute(MapPropertyAttributeTypeSymbol)
?.NamedArguments
.SingleOrDefault(a => a.Key == MapPropertyAttributeSource.SourcePropertyNamePropertyName)
.FirstOrDefault(a => a.Key == MapPropertyAttributeSource.SourcePropertyNamePropertyName)
.Value.Value as string ?? property.Name;
return sourceProperties.SingleOrDefault(p => p.Name == propertyName);
return sourceProperties.FirstOrDefault(p => p.Name == propertyName);
}
protected abstract ImmutableArray<MappedMember> GetSourceMappedProperties(ITypeSymbol typeSymbol, ITypeSymbol sourceTypeSymbol, bool isInheritFromMappedBaseClass);
@ -131,23 +135,41 @@ namespace MapTo
protected abstract ImmutableArray<MappedMember> GetTypeMappedFields(ITypeSymbol typeSymbol, ITypeSymbol sourceTypeSymbol, bool isInheritFromMappedBaseClass);
protected INamedTypeSymbol? GetSourceTypeSymbol(TypeDeclarationSyntax typeDeclarationSyntax, SemanticModel? semanticModel = null) =>
GetSourceTypeSymbol(typeDeclarationSyntax.GetAttribute(MapFromAttributeSource.AttributeName), semanticModel);
protected ImmutableArray<INamedTypeSymbol> GetSourceTypeSymbol(TypeDeclarationSyntax typeDeclarationSyntax, SemanticModel? semanticModel = null)
{
var attributeData = typeDeclarationSyntax.GetAttribute(MapFromAttributeSource.AttributeName);
var sourceSymbol = GetSourceTypeSymbol(attributeData, semanticModel);
return sourceSymbol;
}
protected INamedTypeSymbol? GetSourceTypeSymbol(SyntaxNode? attributeSyntax, SemanticModel? semanticModel = null)
// we need two possible InamedTypeSymbol
protected ImmutableArray<INamedTypeSymbol> GetSourceTypeSymbol(SyntaxNode? attributeSyntax, SemanticModel? semanticModel = null)
{
if (attributeSyntax is null)
{
return null;
return new ImmutableArray<INamedTypeSymbol>(){};
}
semanticModel ??= Compilation.GetSemanticModel(attributeSyntax.SyntaxTree);
var sourceTypeExpressionSyntax = attributeSyntax
.DescendantNodes()
.OfType<TypeOfExpressionSyntax>()
.SingleOrDefault();
var descendentNodes = attributeSyntax
.DescendantNodes();
return sourceTypeExpressionSyntax is not null ? semanticModel.GetTypeInfo(sourceTypeExpressionSyntax.Type).Type as INamedTypeSymbol : null;
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)
@ -249,7 +271,7 @@ namespace MapTo
return new MappedMember(
property.Name,
property.GetTypeSymbol().ToString(),
property.GetTypeSymbol()?.ToString() ?? throw new InvalidOperationException(),
ToQualifiedDisplayName(propertyType) ?? propertyType.Name,
converterFullyQualifiedName,
converterParameters.ToImmutableArray(),
@ -295,7 +317,9 @@ namespace MapTo
property,
namedType,
isEnumerable,
(property as IPropertySymbol).IsReadOnly);
#pragma warning disable CS8602
(property as IPropertySymbol).IsReadOnly);
#pragma warning restore CS8602
;
}
@ -470,54 +494,68 @@ namespace MapTo
return null;
}
var sourceTypeSymbol = GetSourceTypeSymbol(TypeSyntax, semanticModel);
if (sourceTypeSymbol is 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;
}
_ignoredNamespaces.Add(sourceTypeSymbol.ContainingNamespace.ToDisplayParts().First());
var typeIdentifierName = TypeSyntax.GetIdentifierName();
var sourceTypeIdentifierName = sourceTypeSymbol.Name;
var isTypeInheritFromMappedBaseClass = IsTypeInheritFromMappedBaseClass(semanticModel);
var isTypeUpdatable = IsTypeUpdatable();
var hasJsonExtension = HasJsonExtension();
var shouldGenerateSecondaryConstructor = ShouldGenerateSecondaryConstructor(semanticModel, sourceTypeSymbol);
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
var mappedProperties = GetSourceMappedProperties(typeSymbol, sourceTypeSymbol, isTypeInheritFromMappedBaseClass);
var mappedFields = GetSourceMappedFields(typeSymbol, sourceTypeSymbol, isTypeInheritFromMappedBaseClass);
/*if (!mappedProperties.Any())
{
AddDiagnostic(DiagnosticsFactory.NoMatchingPropertyFoundError(TypeSyntax.GetLocation(), typeSymbol, sourceTypeSymbol));
return null;
}*/
AddUsingIfRequired(mappedProperties.Any(p => p.IsEnumerable), "System.Linq");
var allProperties = GetTypeMappedProperties(sourceTypeSymbol, typeSymbol , isTypeInheritFromMappedBaseClass);
var allFields = GetTypeMappedFields(sourceTypeSymbol, typeSymbol, isTypeInheritFromMappedBaseClass);
return new MappingModel(
SourceGenerationOptions,
TypeSyntax.GetNamespace(),
TypeSyntax.Modifiers,
TypeSyntax.Keyword.Text,
typeIdentifierName,
sourceTypeSymbol.ContainingNamespace.ToDisplayString(),
sourceTypeIdentifierName,
sourceTypeSymbol.ToDisplayString(),
isTypeUpdatable,
hasJsonExtension,
mappedProperties,
allProperties,
mappedFields,
allFields,
mappedSourceTypes.ToImmutableArray(),
isTypeInheritFromMappedBaseClass,
Usings,
shouldGenerateSecondaryConstructor);
Usings);
}
@ -530,7 +568,7 @@ namespace MapTo
}
return converterTypeSymbol.AllInterfaces
.SingleOrDefault(i =>
.FirstOrDefault(i =>
i.TypeArguments.Length == 2 &&
SymbolEqualityComparer.Default.Equals(i.ConstructedFrom, TypeConverterInterfaceTypeSymbol) &&
SymbolEqualityComparer.Default.Equals(sourceProperty.Type, i.TypeArguments[0]) &&
@ -544,7 +582,7 @@ namespace MapTo
}
return converterTypeSymbol.AllInterfaces
.SingleOrDefault(i =>
.FirstOrDefault(i =>
i.TypeArguments.Length == 2 &&
SymbolEqualityComparer.Default.Equals(i.ConstructedFrom, TypeConverterInterfaceTypeSymbol) &&
SymbolEqualityComparer.Default.Equals(sourceProperty.Type, i.TypeArguments[0]) &&
@ -555,7 +593,7 @@ namespace MapTo
{
var constructorSyntax = TypeSyntax.DescendantNodes()
.OfType<ConstructorDeclarationSyntax>()
.SingleOrDefault(c =>
.FirstOrDefault(c =>
c.ParameterList.Parameters.Count == 1 &&
SymbolEqualityComparer.Default.Equals(semanticModel.GetTypeInfo(c.ParameterList.Parameters.Single().Type!).ConvertedType, sourceTypeSymbol));

View File

@ -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;
}
}

View File

@ -42,6 +42,13 @@ namespace MapTo.Sources
builder
.WriteLine($"public {AttributeName}Attribute(Type sourceType)")
.WriteOpeningBracket()
.WriteLine("SourceType = new [] { sourceType };")
.WriteClosingBracket()
.WriteLine();
builder
.WriteLine($"public {AttributeName}Attribute(Type[] sourceType)")
.WriteOpeningBracket()
.WriteLine("SourceType = sourceType;")
.WriteClosingBracket()
.WriteLine();
@ -55,7 +62,7 @@ namespace MapTo.Sources
}
builder
.WriteLine("public Type SourceType { get; }")
.WriteLine("public Type[] SourceType { get; }")
.WriteClosingBracket() // class
.WriteClosingBracket(); // namespace

View File

@ -23,6 +23,7 @@ namespace MapTo.Sources
if (options.GenerateXmlDocument)
{
builder
.WriteLine()
.WriteLine("/// <summary>")
.WriteLine("/// Specifies the mapping behavior of the annotated property.")
.WriteLine("/// </summary>")

View File

@ -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;
}
}
}

View File

@ -1,303 +0,0 @@
using MapTo.Sources;
using static MapTo.Sources.Constants;
using System;
using System.Collections.Generic;
using System.Text;
using System.Collections.Immutable;
using System.Diagnostics;
using Microsoft.CodeAnalysis;
namespace MapTo.Extensions
{
internal static class CommonSource
{
internal static SourceCode GenerateStructOrClass(this MappingModel model, string structOrClass)
{
const bool writeDebugInfo = true;
using var builder = new SourceBuilder()
.WriteLine(GeneratedFilesHeader)
.WriteNullableContextOptionIf(model.Options.SupportNullableReferenceTypes)
.WriteUsings(model.Usings)
.WriteLine()
// Namespace declaration
.WriteLine($"namespace {model.Namespace}")
.WriteOpeningBracket();
if (writeDebugInfo)
builder
.WriteModelInfo(model)
.WriteLine()
.WriteComment("Type properties")
.WriteComment()
.WriteMappedProperties(model.TypeProperties)
.WriteLine()
.WriteComment("Source properties")
.WriteLine()
.WriteComment("Type fields")
.WriteComment()
.WriteMappedProperties(model.TypeFields)
.WriteLine()
.WriteComment("Source fields")
.WriteMappedProperties(model.SourceFields)
.WriteLine();
builder
// Class declaration
.WriteLine($"partial {structOrClass} {model.TypeIdentifierName}")
.WriteOpeningBracket()
.WriteLine()
// Class body
.GeneratePublicConstructor(model);
if (model.IsJsonExtension) builder.WriteToJsonMethod(model);
if (model.IsTypeUpdatable && model.TypeProperties.GetWritableMappedProperties().Length > 0) builder.GenerateUpdateMethod(model);
if (model.IsTypeUpdatable && model.TypeFields.GetWritableMappedProperties().Length > 0) builder.GenerateUpdateMethod(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)
{
var sourceClassParameterName = model.SourceTypeIdentifierName.ToCamelCase();
const string mappingContextParameterName = "context";
var baseConstructor = /*model.HasMappedBaseClass ? $" : base({mappingContextParameterName}, {sourceClassParameterName})" :*/ string.Empty;
var stringBuilder = new StringBuilder();
var otherProperties = new List<MappedMember>();
foreach (var property in model.TypeProperties)
{
if (!model.SourceProperties.IsMappedProperty(property))
{
stringBuilder.Append(", ");
stringBuilder.Append($"{property.FullyQualifiedType} {property.SourcePropertyName.ToCamelCase()}");
otherProperties.Add(property);
}
}
foreach (var property in model.TypeFields)
{
if (!model.SourceFields.IsMappedProperty(property))
{
stringBuilder.Append(", ");
stringBuilder.Append($"{property.FullyQualifiedType} {property.SourcePropertyName.ToCamelCase()}");
otherProperties.Add(property);
}
}
var readOnlyPropertiesArguments = stringBuilder.ToString();
builder
.WriteLine($"public {model.TypeIdentifierName}({model.SourceType} {sourceClassParameterName}{readOnlyPropertiesArguments}){baseConstructor}")
.WriteOpeningBracket()
.WriteAssignmentMethod(model, otherProperties.ToArray().ToImmutableArray(), sourceClassParameterName, mappingContextParameterName, false);
// End constructor declaration
return builder.WriteClosingBracket();
}
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 property in model.TypeProperties)
{
if (!property.isEnumerable)
HandlePropertyEnumerable(builder, property);
else
{
builder = WriteJsonField(builder, property);
}
}
foreach (var property in model.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;
builder.WriteCommentArray(symbol.Parameters, nameof(symbol.Parameters));
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)
{
foreach (var property in model.SourceProperties)
{
if (property.isReadOnly && fromUpdate) continue;
builder.WriteLine($"{property.Name} = {sourceClassParameterName}.{property.SourcePropertyName};");
}
foreach (var property in model.SourceFields)
{
if (property.isReadOnly && fromUpdate) continue;
builder.WriteLine($"{property.Name} = {sourceClassParameterName}.{property.SourcePropertyName};");
}
if (otherProperties == null) return builder;
foreach (var property in otherProperties)
{
builder.WriteLine(property.MappedSourcePropertyTypeName is null
? $"{property.Name} = {property.SourcePropertyName.ToCamelCase()};"
: "");
}
return builder;
}
private static SourceBuilder GenerateUpdateMethod(this SourceBuilder builder, MappingModel model)
{
var sourceClassParameterName = model.SourceTypeIdentifierName.ToCamelCase();
builder
.GenerateUpdaterMethodsXmlDocs(model, sourceClassParameterName)
.WriteLine($"public void Update({model.SourceType} {sourceClassParameterName})")
.WriteOpeningBracket()
.WriteAssignmentMethod(model, null, sourceClassParameterName, "context", true)
.WriteClosingBracket();
return builder;
}
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 GenerateEnumerableJsonSourceTypeExtensionMethod(this SourceBuilder builder, MappingModel model)
{
var sourceClassParameterName = model.SourceTypeIdentifierName.ToCamelCase();
return builder
.WriteLineIf(model.Options.SupportNullableStaticAnalysis, $"[return: NotNullIfNotNull(\"{sourceClassParameterName}\")]")
.WriteLine($"{model.Options.GeneratedMethodsAccessModifier.ToLowercaseString()} static string ToJson(this IEnumerable<{model.SourceType}{model.Options.NullableReferenceSyntax}> {sourceClassParameterName}List)")
.WriteOpeningBracket()
.WriteLine($"return {sourceClassParameterName} == null ? null : new {model.TypeIdentifierName}({sourceClassParameterName});")
.WriteClosingBracket();
}
}
}

View File

@ -1,94 +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 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 MappingModel (
SourceGenerationOptions Options,
string? Namespace,
SyntaxTokenList Modifiers,
string Type,
string TypeIdentifierName,
string SourceNamespace,
string SourceTypeIdentifierName,
string SourceTypeFullName,
bool IsTypeUpdatable,
bool IsJsonExtension,
ImmutableArray<MappedMember> SourceProperties,
ImmutableArray<MappedMember> TypeProperties,
ImmutableArray<MappedMember> SourceFields,
ImmutableArray<MappedMember> TypeFields,
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;
}
}

View File

@ -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.SourceProperties.Length; i++)
{
var property = model.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 < model.SourceProperties.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();
}
}
}

View File

@ -1,37 +1,42 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<IsPackable>false</IsPackable>
<Nullable>enable</Nullable>
<RootNamespace>MapTo.Tests</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.1">
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.3">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="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>
<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="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
<PackageReference Include="xunit" Version="2.4.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="3.0.3">
<PackageReference Include="coverlet.collector" Version="3.1.2">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\MapTo\MapTo.csproj" />
<ProjectReference Include="..\..\src\BlueWest.MapTo\BlueWest.MapTo.csproj" />
</ItemGroup>
</Project>

View File

@ -8,7 +8,7 @@ namespace MapTo.Tests.Extensions
internal static class RoslynExtensions
{
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)
{

View File

@ -15,7 +15,7 @@ namespace MapTo.Tests.Extensions
{
var syntax = syntaxTree
.Select(s => s.ToString().Trim())
.SingleOrDefault(s => s.Contains(typeName));
.FirstOrDefault(s => s.Contains(typeName));
syntax.ShouldNotBeNullOrWhiteSpace();
syntax.ShouldBe(expectedSource, customMessage);
@ -25,7 +25,7 @@ namespace MapTo.Tests.Extensions
{
var syntax = syntaxTree
.Select(s => s.ToString().Trim())
.SingleOrDefault(s => s.Contains(typeName));
.FirstOrDefault(s => s.Contains(typeName));
syntax.ShouldNotBeNullOrWhiteSpace();
syntax.ShouldContainWithoutWhitespace(expectedSource, customMessage);
@ -68,7 +68,7 @@ namespace MapTo.Tests.Extensions
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 });
compilationDiagnostics.ShouldBeSuccessful();

View File

@ -11,6 +11,7 @@ namespace MapTo.Tests
{
public class IgnorePropertyAttributeTests
{
/*
[Fact]
public void VerifyIgnorePropertyAttribute()
{
@ -34,6 +35,7 @@ namespace MapTo
diagnostics.ShouldBeSuccessful();
compilation.SyntaxTrees.ShouldContainSource(IgnorePropertyAttributeSource.AttributeName, expectedAttribute);
}
*/
[Fact]
public void When_IgnorePropertyAttributeIsSpecified_Should_NotGenerateMappingsForThatProperty()

View File

@ -1,7 +1,9 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using MapTo.Tests.Extensions;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
@ -46,9 +48,9 @@ namespace MapTo.Tests.Infrastructure
// NB: fail tests when the injected program isn't valid _before_ running generators
compilation.GetDiagnostics().ShouldBeSuccessful();
}
var driver = CSharpGeneratorDriver.Create(
new[] { new MapToGenerator() },
new List<ISourceGenerator>() { new MapToGenerator()},
optionsProvider: new TestAnalyzerConfigOptionsProvider(analyzerConfigOptions),
parseOptions: new CSharpParseOptions(languageVersion)
);

View File

@ -234,7 +234,7 @@ namespace Test
const string source = "";
var expectedTypes = new[]
{
IgnorePropertyAttributeSource.AttributeName,
//IgnorePropertyAttributeSource.AttributeName,
MapFromAttributeSource.AttributeName,
ITypeConverterSource.InterfaceName,
MapPropertyAttributeSource.AttributeName

View File

@ -186,7 +186,7 @@ namespace SaleModel
diagnostics.ShouldBeSuccessful();
}
public static IEnumerable<object> SameSourceAndDestinationTypeNameData => new List<object>
public static IEnumerable<object[]> SameSourceAndDestinationTypeNameData => new List<object[]>
{
new object[]
{

View File

@ -6,7 +6,7 @@
</PropertyGroup>
<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>

View File

@ -8,10 +8,10 @@
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\MapTo\MapTo.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
<ProjectReference Include="..\..\src\BlueWest.MapTo\BlueWest.MapTo.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
</ItemGroup>
<Import Project="..\..\src\MapTo\MapTo.props" />
<Import Project="..\..\src\BlueWest.MapTo\MapTo.props" />
<PropertyGroup>
<MapTo_ConstructorAccessModifier>Internal</MapTo_ConstructorAccessModifier>
</PropertyGroup>