Addapt MapTo to support multiple mappings

This commit is contained in:
Wvader 2022-08-18 16:48:44 +01:00
parent ca82e6fb17
commit f7963d2d7e
27 changed files with 445 additions and 284 deletions

View File

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

View File

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

View File

@ -18,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>

View File

@ -30,7 +30,9 @@ namespace MapTo.Extensions
internal static SourceBuilder WriteModelInfo(this SourceBuilder builder, MappingModel model) internal static SourceBuilder WriteModelInfo(this SourceBuilder builder, MappingModel model)
{ {
return builder foreach (var targetSourceType in model.MappedSourceTypes)
{
builder
.WriteLine() .WriteLine()
.WriteComment($" IsTypeUpdatable {model.IsTypeUpdatable}") .WriteComment($" IsTypeUpdatable {model.IsTypeUpdatable}")
.WriteComment($" HasMappedBaseClass {model.HasMappedBaseClass.ToString()}") .WriteComment($" HasMappedBaseClass {model.HasMappedBaseClass.ToString()}")
@ -38,9 +40,12 @@ namespace MapTo.Extensions
.WriteComment($" Options {model.Options.ToString()}") .WriteComment($" Options {model.Options.ToString()}")
.WriteComment($" Type {model.Type}") .WriteComment($" Type {model.Type}")
.WriteComment($" TypeIdentifierName {model.TypeIdentifierName}") .WriteComment($" TypeIdentifierName {model.TypeIdentifierName}")
.WriteComment($" SourceNamespace {model.SourceNamespace}") .WriteComment($" SourceNamespace {targetSourceType.SourceNamespace}")
.WriteComment($" SourceTypeFullName {model.SourceTypeFullName}") .WriteComment($" SourceTypeFullName {targetSourceType.SourceTypeFullName}")
.WriteComment($" SourceTypeIdentifierName {model.SourceTypeIdentifierName}"); .WriteComment($" SourceTypeIdentifierName {targetSourceType.SourceTypeIdentifierName}");
}
return builder;
} }

View File

@ -15,6 +15,7 @@ namespace MapTo.Extensions
{ {
const bool writeDebugInfo = true; const bool writeDebugInfo = true;
using var builder = new SourceBuilder() using var builder = new SourceBuilder()
.WriteLine(GeneratedFilesHeader) .WriteLine(GeneratedFilesHeader)
.WriteNullableContextOptionIf(model.Options.SupportNullableReferenceTypes) .WriteNullableContextOptionIf(model.Options.SupportNullableReferenceTypes)
@ -25,24 +26,31 @@ namespace MapTo.Extensions
.WriteLine($"namespace {model.Namespace}") .WriteLine($"namespace {model.Namespace}")
.WriteOpeningBracket(); .WriteOpeningBracket();
foreach (var targetSourceType in model.MappedSourceTypes)
{
if (writeDebugInfo) if (writeDebugInfo)
builder builder
.WriteModelInfo(model) .WriteModelInfo(model)
.WriteLine() .WriteLine()
.WriteComment("Type properties") .WriteComment("Type properties")
.WriteComment() .WriteComment()
.WriteMappedProperties(model.TypeProperties) .WriteMappedProperties(targetSourceType.TypeProperties)
.WriteLine() .WriteLine()
.WriteComment("Source properties") .WriteComment("Source properties")
.WriteLine() .WriteLine()
.WriteComment("Type fields") .WriteComment("Type fields")
.WriteComment() .WriteComment()
.WriteMappedProperties(model.TypeFields) .WriteMappedProperties(targetSourceType.TypeFields)
.WriteLine() .WriteLine()
.WriteComment("Source fields") .WriteComment("Source fields")
.WriteMappedProperties(model.SourceFields) .WriteMappedProperties(targetSourceType.SourceFields)
.WriteLine(); .WriteLine();
}
builder builder
// Class declaration // Class declaration
.WriteLine($"partial {structOrClass} {model.TypeIdentifierName}") .WriteLine($"partial {structOrClass} {model.TypeIdentifierName}")
@ -50,10 +58,14 @@ namespace MapTo.Extensions
.WriteLine() .WriteLine()
// Class body // Class body
.GeneratePublicConstructor(model); .GeneratePublicConstructor(model);
foreach (var targetSourceType in model.MappedSourceTypes)
{
if (model.IsTypeUpdatable && targetSourceType.TypeProperties.GetWritableMappedProperties().Length > 0) builder.GenerateUpdateMethod(model);
if (model.IsTypeUpdatable && targetSourceType.TypeFields.GetWritableMappedProperties().Length > 0) builder.GenerateUpdateMethod(model);
}
if (model.IsJsonExtension) builder.WriteToJsonMethod(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 builder
.WriteLine() .WriteLine()
@ -68,18 +80,28 @@ namespace MapTo.Extensions
private static SourceBuilder GeneratePublicConstructor(this SourceBuilder builder, MappingModel model) private static SourceBuilder GeneratePublicConstructor(this SourceBuilder builder, MappingModel model)
{ {
var sourceClassParameterName = model.SourceTypeIdentifierName.ToCamelCase();
const string mappingContextParameterName = "context"; 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 baseConstructor = /*model.HasMappedBaseClass ? $" : base({mappingContextParameterName}, {sourceClassParameterName})" :*/ string.Empty;
var stringBuilder = new StringBuilder(); var stringBuilder = new StringBuilder();
var otherProperties = new List<MappedMember>(); var otherProperties = new List<MappedMember>();
foreach (var property in model.TypeProperties) foreach (var property in targetSourceType.TypeProperties)
{ {
if (!model.SourceProperties.IsMappedProperty(property)) if (!targetSourceType.SourceProperties.IsMappedProperty(property))
{
stringBuilder.Append(", ");
stringBuilder.Append($"{property.FullyQualifiedType} {property.SourcePropertyName.ToCamelCase()}");
otherProperties.Add(property);
}
}
foreach (var property in targetSourceType.TypeFields)
{
if (!targetSourceType.SourceFields.IsMappedProperty(property))
{ {
stringBuilder.Append(", "); stringBuilder.Append(", ");
stringBuilder.Append($"{property.FullyQualifiedType} {property.SourcePropertyName.ToCamelCase()}"); stringBuilder.Append($"{property.FullyQualifiedType} {property.SourcePropertyName.ToCamelCase()}");
@ -87,26 +109,18 @@ namespace MapTo.Extensions
} }
} }
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(); var readOnlyPropertiesArguments = stringBuilder.ToString();
builder builder
.WriteLine($"public {model.TypeIdentifierName}({model.SourceType} {sourceClassParameterName}{readOnlyPropertiesArguments}){baseConstructor}") .WriteLine($"public {model.TypeIdentifierName}({targetSourceType.SourceType} {sourceClassParameterName}{readOnlyPropertiesArguments}){baseConstructor}")
.WriteOpeningBracket() .WriteOpeningBracket()
.WriteAssignmentMethod(model, otherProperties.ToArray().ToImmutableArray(), sourceClassParameterName, mappingContextParameterName, false); .WriteAssignmentMethod(model, otherProperties.ToArray().ToImmutableArray(), sourceClassParameterName, mappingContextParameterName, false);
builder.WriteClosingBracket();
}
// End constructor declaration // End constructor declaration
return builder.WriteClosingBracket(); return builder;
} }
private static bool IsMappedProperty(this System.Collections.Immutable.ImmutableArray<MappedMember> properties, MappedMember property) private static bool IsMappedProperty(this System.Collections.Immutable.ImmutableArray<MappedMember> properties, MappedMember property)
@ -128,7 +142,9 @@ namespace MapTo.Extensions
.WriteLine("var stringBuilder = new System.Text.StringBuilder();") .WriteLine("var stringBuilder = new System.Text.StringBuilder();")
.WriteLine(GetStringBuilderAppendNoInterpolation("{")); .WriteLine(GetStringBuilderAppendNoInterpolation("{"));
foreach (var property in model.TypeProperties) foreach (var targetSourceType in model.MappedSourceTypes)
{
foreach (var property in targetSourceType.TypeProperties)
{ {
if (!property.isEnumerable) if (!property.isEnumerable)
HandlePropertyEnumerable(builder, property); HandlePropertyEnumerable(builder, property);
@ -137,7 +153,7 @@ namespace MapTo.Extensions
builder = WriteJsonField(builder, property); builder = WriteJsonField(builder, property);
} }
} }
foreach (var property in model.TypeFields) foreach (var property in targetSourceType.TypeFields)
{ {
if (!property.isEnumerable) if (!property.isEnumerable)
HandleFieldEnumerable(builder, property); HandleFieldEnumerable(builder, property);
@ -150,6 +166,9 @@ namespace MapTo.Extensions
builder.WriteLine(GetStringBuilderAppendNoInterpolation("}")); builder.WriteLine(GetStringBuilderAppendNoInterpolation("}"));
builder.WriteLine("return stringBuilder.ToString();"); builder.WriteLine("return stringBuilder.ToString();");
builder.WriteClosingBracket(); builder.WriteClosingBracket();
}
return builder; return builder;
} }
@ -228,19 +247,28 @@ namespace MapTo.Extensions
string? sourceClassParameterName, string mappingContextParameterName, bool fromUpdate) string? sourceClassParameterName, string mappingContextParameterName, bool fromUpdate)
{ {
foreach (var property in model.SourceProperties) List<MappedMember> _addedMembers = new List<MappedMember>();
foreach (var targetSourceType in model.MappedSourceTypes)
{
foreach (var property in targetSourceType.SourceProperties)
{ {
if (property.isReadOnly && fromUpdate) continue; if (property.isReadOnly && fromUpdate) continue;
if(_addedMembers.Contains(property)) continue;
builder.WriteLine($"{property.Name} = {sourceClassParameterName}.{property.SourcePropertyName};"); builder.WriteLine($"{property.Name} = {sourceClassParameterName}.{property.SourcePropertyName};");
_addedMembers.Add(property);
} }
foreach (var property in model.SourceFields) foreach (var property in targetSourceType.SourceFields)
{ {
if (property.isReadOnly && fromUpdate) continue; if (property.isReadOnly && fromUpdate) continue;
if(_addedMembers.Contains(property)) continue;
builder.WriteLine($"{property.Name} = {sourceClassParameterName}.{property.SourcePropertyName};"); builder.WriteLine($"{property.Name} = {sourceClassParameterName}.{property.SourcePropertyName};");
_addedMembers.Add(property);
} }
@ -248,11 +276,18 @@ namespace MapTo.Extensions
foreach (var property in otherProperties) foreach (var property in otherProperties)
{ {
if(_addedMembers.Contains(property)) continue;
builder.WriteLine(property.MappedSourcePropertyTypeName is null builder.WriteLine(property.MappedSourcePropertyTypeName is null
? $"{property.Name} = {property.SourcePropertyName.ToCamelCase()};" ? $"{property.Name} = {property.SourcePropertyName.ToCamelCase()};"
: ""); : "");
_addedMembers.Add(property);
} }
}
return builder; return builder;
@ -261,14 +296,19 @@ namespace MapTo.Extensions
private static SourceBuilder GenerateUpdateMethod(this SourceBuilder builder, MappingModel model) private static SourceBuilder GenerateUpdateMethod(this SourceBuilder builder, MappingModel model)
{ {
var sourceClassParameterName = model.SourceTypeIdentifierName.ToCamelCase();
foreach (var targetSourceType in model.MappedSourceTypes)
{
var sourceClassParameterName = targetSourceType.SourceTypeIdentifierName.ToCamelCase();
builder builder
.GenerateUpdaterMethodsXmlDocs(model, sourceClassParameterName) .GenerateUpdaterMethodsXmlDocs(model, sourceClassParameterName)
.WriteLine($"public void Update({model.SourceType} {sourceClassParameterName})") .WriteLine($"public void Update({targetSourceType.SourceType} {sourceClassParameterName})")
.WriteOpeningBracket() .WriteOpeningBracket()
.WriteAssignmentMethod(model, null, sourceClassParameterName, "context", true) .WriteAssignmentMethod(model, null, sourceClassParameterName, "context", true)
.WriteClosingBracket(); .WriteClosingBracket();
}
return builder; return builder;
} }
@ -280,24 +320,37 @@ namespace MapTo.Extensions
return builder; return builder;
} }
return builder foreach (var targetSourceType in model.MappedSourceTypes)
{
builder
.WriteLine("/// <summary>") .WriteLine("/// <summary>")
.WriteLine($"/// Updates <see cref=\"{model.TypeIdentifierName}\"/> and sets its participating properties") .WriteLine($"/// Updates <see cref=\"{model.TypeIdentifierName}\"/> and sets its participating properties")
.WriteLine($"/// using the property values from <paramref name=\"{sourceClassParameterName}\"/>.") .WriteLine($"/// using the property values from <paramref name=\"{sourceClassParameterName}\"/>.")
.WriteLine("/// </summary>") .WriteLine("/// </summary>")
.WriteLine($"/// <param name=\"{sourceClassParameterName}\">The instance of <see cref=\"{model.SourceType}\"/> to use as source.</param>"); .WriteLine($"/// <param name=\"{sourceClassParameterName}\">The instance of <see cref=\"{targetSourceType.SourceType}\"/> to use as source.</param>");
}
return builder;
} }
private static SourceBuilder GenerateEnumerableJsonSourceTypeExtensionMethod(this SourceBuilder builder, MappingModel model) private static SourceBuilder GenerateEnumerableJsonSourceTypeExtensionMethod(this SourceBuilder builder, MappingModel model)
{ {
var sourceClassParameterName = model.SourceTypeIdentifierName.ToCamelCase();
return builder foreach (var targetSourceType in model.MappedSourceTypes)
{
var sourceClassParameterName = targetSourceType.SourceTypeIdentifierName.ToCamelCase();
builder
.WriteLineIf(model.Options.SupportNullableStaticAnalysis, $"[return: NotNullIfNotNull(\"{sourceClassParameterName}\")]") .WriteLineIf(model.Options.SupportNullableStaticAnalysis, $"[return: NotNullIfNotNull(\"{sourceClassParameterName}\")]")
.WriteLine($"{model.Options.GeneratedMethodsAccessModifier.ToLowercaseString()} static string ToJson(this IEnumerable<{model.SourceType}{model.Options.NullableReferenceSyntax}> {sourceClassParameterName}List)") .WriteLine($"{model.Options.GeneratedMethodsAccessModifier.ToLowercaseString()} static string ToJson(this IEnumerable<{targetSourceType.SourceType}{model.Options.NullableReferenceSyntax}> {sourceClassParameterName}List)")
.WriteOpeningBracket() .WriteOpeningBracket()
.WriteLine($"return {sourceClassParameterName} == null ? null : new {model.TypeIdentifierName}({sourceClassParameterName});") .WriteLine($"return {sourceClassParameterName} == null ? null : new {model.TypeIdentifierName}({sourceClassParameterName});")
.WriteClosingBracket(); .WriteClosingBracket();
} }
return builder;
}
} }
} }

View File

@ -33,11 +33,13 @@ namespace MapTo.Extensions
public static AttributeSyntax? GetAttribute(this TypeDeclarationSyntax typeDeclarationSyntax, string attributeName) public static AttributeSyntax? GetAttribute(this TypeDeclarationSyntax typeDeclarationSyntax, string attributeName)
{ {
return typeDeclarationSyntax.AttributeLists var attributeLists = typeDeclarationSyntax.AttributeLists;
.SelectMany(al => al.Attributes) var selection = attributeLists
.SingleOrDefault(a => .SelectMany(al => al.Attributes);
(a.Name as IdentifierNameSyntax)?.Identifier.ValueText == attributeName || var result = selection
((a.Name as QualifiedNameSyntax)?.Right as IdentifierNameSyntax)?.Identifier.ValueText == attributeName); .FirstOrDefault();
return result;
} }
public static bool HasAttribute(this ISymbol symbol, ITypeSymbol attributeSymbol) => public static bool HasAttribute(this ISymbol symbol, ITypeSymbol attributeSymbol) =>
@ -86,7 +88,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 &&

View File

@ -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]

View File

@ -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.Extensions; using MapTo.Extensions;
using MapTo.Sources; using MapTo.Sources;
using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis;
@ -70,6 +72,8 @@ namespace MapTo
public static MappingContext Create(Compilation compilation, SourceGenerationOptions sourceGenerationOptions, TypeDeclarationSyntax typeSyntax) public static MappingContext Create(Compilation compilation, SourceGenerationOptions sourceGenerationOptions, TypeDeclarationSyntax typeSyntax)
{ {
//SpinWait.SpinUntil(() => Debugger.IsAttached);
MappingContext context = typeSyntax switch MappingContext context = typeSyntax switch
{ {
StructDeclarationSyntax => new StructMappingContext(compilation, sourceGenerationOptions, typeSyntax), StructDeclarationSyntax => new StructMappingContext(compilation, sourceGenerationOptions, typeSyntax),
@ -107,20 +111,20 @@ namespace MapTo
var propertyName = property var propertyName = property
.GetAttribute(MapPropertyAttributeTypeSymbol) .GetAttribute(MapPropertyAttributeTypeSymbol)
?.NamedArguments ?.NamedArguments
.SingleOrDefault(a => a.Key == MapPropertyAttributeSource.SourcePropertyNamePropertyName) .FirstOrDefault(a => a.Key == MapPropertyAttributeSource.SourcePropertyNamePropertyName)
.Value.Value as string ?? property.Name; .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) protected IFieldSymbol? FindSourceField(IEnumerable<IFieldSymbol> sourceProperties, ISymbol property)
{ {
var propertyName = property var propertyName = property
.GetAttribute(MapPropertyAttributeTypeSymbol) .GetAttribute(MapPropertyAttributeTypeSymbol)
?.NamedArguments ?.NamedArguments
.SingleOrDefault(a => a.Key == MapPropertyAttributeSource.SourcePropertyNamePropertyName) .FirstOrDefault(a => a.Key == MapPropertyAttributeSource.SourcePropertyNamePropertyName)
.Value.Value as string ?? property.Name; .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); 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 abstract ImmutableArray<MappedMember> GetTypeMappedFields(ITypeSymbol typeSymbol, ITypeSymbol sourceTypeSymbol, bool isInheritFromMappedBaseClass);
protected INamedTypeSymbol? GetSourceTypeSymbol(TypeDeclarationSyntax typeDeclarationSyntax, SemanticModel? semanticModel = null) => protected ImmutableArray<INamedTypeSymbol> GetSourceTypeSymbol(TypeDeclarationSyntax typeDeclarationSyntax, SemanticModel? semanticModel = null)
GetSourceTypeSymbol(typeDeclarationSyntax.GetAttribute(MapFromAttributeSource.AttributeName), semanticModel); {
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) if (attributeSyntax is null)
{ {
return null; return new ImmutableArray<INamedTypeSymbol>(){};
} }
semanticModel ??= Compilation.GetSemanticModel(attributeSyntax.SyntaxTree); semanticModel ??= Compilation.GetSemanticModel(attributeSyntax.SyntaxTree);
var sourceTypeExpressionSyntax = attributeSyntax var descendentNodes = attributeSyntax
.DescendantNodes() .DescendantNodes();
.OfType<TypeOfExpressionSyntax>()
.SingleOrDefault();
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) protected bool IsTypeInheritFromMappedBaseClass(SemanticModel semanticModel)
@ -470,24 +492,50 @@ namespace MapTo
return null; return null;
} }
var sourceTypeSymbol = GetSourceTypeSymbol(TypeSyntax, semanticModel); // We can have 2 sources...
if (sourceTypeSymbol is null)
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())); AddDiagnostic(DiagnosticsFactory.MapFromAttributeNotFoundError(TypeSyntax.GetLocation()));
return null; return null;
} }
_ignoredNamespaces.Add(sourceTypeSymbol.ContainingNamespace.ToDisplayParts().First());
var typeIdentifierName = TypeSyntax.GetIdentifierName(); var typeIdentifierName = TypeSyntax.GetIdentifierName();
var sourceTypeIdentifierName = sourceTypeSymbol.Name;
var isTypeInheritFromMappedBaseClass = IsTypeInheritFromMappedBaseClass(semanticModel); var isTypeInheritFromMappedBaseClass = IsTypeInheritFromMappedBaseClass(semanticModel);
var isTypeUpdatable = IsTypeUpdatable(); var isTypeUpdatable = false; //IsTypeUpdatable();
var hasJsonExtension = HasJsonExtension(); var hasJsonExtension = false; // HasJsonExtension();
var shouldGenerateSecondaryConstructor = ShouldGenerateSecondaryConstructor(semanticModel, sourceTypeSymbol);
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 mappedProperties = GetSourceMappedProperties(typeSymbol, sourceTypeSymbol, isTypeInheritFromMappedBaseClass);
var mappedFields = GetSourceMappedFields(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()) /*if (!mappedProperties.Any())
{ {
@ -495,29 +543,17 @@ namespace MapTo
return null; 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( return new MappingModel(
SourceGenerationOptions, SourceGenerationOptions,
TypeSyntax.GetNamespace(), TypeSyntax.GetNamespace(),
TypeSyntax.Modifiers, TypeSyntax.Modifiers,
TypeSyntax.Keyword.Text, TypeSyntax.Keyword.Text,
typeIdentifierName, typeIdentifierName,
sourceTypeSymbol.ContainingNamespace.ToDisplayString(),
sourceTypeIdentifierName,
sourceTypeSymbol.ToDisplayString(),
isTypeUpdatable, isTypeUpdatable,
hasJsonExtension, hasJsonExtension,
mappedProperties, mappedSourceTypes.ToImmutableArray(),
allProperties,
mappedFields,
allFields,
isTypeInheritFromMappedBaseClass, isTypeInheritFromMappedBaseClass,
Usings, Usings);
shouldGenerateSecondaryConstructor);
} }
@ -530,7 +566,7 @@ namespace MapTo
} }
return converterTypeSymbol.AllInterfaces return converterTypeSymbol.AllInterfaces
.SingleOrDefault(i => .FirstOrDefault(i =>
i.TypeArguments.Length == 2 && i.TypeArguments.Length == 2 &&
SymbolEqualityComparer.Default.Equals(i.ConstructedFrom, TypeConverterInterfaceTypeSymbol) && SymbolEqualityComparer.Default.Equals(i.ConstructedFrom, TypeConverterInterfaceTypeSymbol) &&
SymbolEqualityComparer.Default.Equals(sourceProperty.Type, i.TypeArguments[0]) && SymbolEqualityComparer.Default.Equals(sourceProperty.Type, i.TypeArguments[0]) &&
@ -544,7 +580,7 @@ namespace MapTo
} }
return converterTypeSymbol.AllInterfaces return converterTypeSymbol.AllInterfaces
.SingleOrDefault(i => .FirstOrDefault(i =>
i.TypeArguments.Length == 2 && i.TypeArguments.Length == 2 &&
SymbolEqualityComparer.Default.Equals(i.ConstructedFrom, TypeConverterInterfaceTypeSymbol) && SymbolEqualityComparer.Default.Equals(i.ConstructedFrom, TypeConverterInterfaceTypeSymbol) &&
SymbolEqualityComparer.Default.Equals(sourceProperty.Type, i.TypeArguments[0]) && SymbolEqualityComparer.Default.Equals(sourceProperty.Type, i.TypeArguments[0]) &&
@ -555,7 +591,7 @@ namespace MapTo
{ {
var constructorSyntax = TypeSyntax.DescendantNodes() var constructorSyntax = TypeSyntax.DescendantNodes()
.OfType<ConstructorDeclarationSyntax>() .OfType<ConstructorDeclarationSyntax>()
.SingleOrDefault(c => .FirstOrDefault(c =>
c.ParameterList.Parameters.Count == 1 && c.ParameterList.Parameters.Count == 1 &&
SymbolEqualityComparer.Default.Equals(semanticModel.GetTypeInfo(c.ParameterList.Parameters.Single().Type!).ConvertedType, sourceTypeSymbol)); SymbolEqualityComparer.Default.Equals(semanticModel.GetTypeInfo(c.ParameterList.Parameters.Single().Type!).ConvertedType, sourceTypeSymbol));

View File

@ -39,28 +39,34 @@ namespace MapTo
public bool IsEnumerable => EnumerableTypeArgument is not null; 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( internal record MappingModel(
SourceGenerationOptions Options, SourceGenerationOptions Options,
string? Namespace, string? Namespace,
SyntaxTokenList Modifiers, SyntaxTokenList Modifiers,
string Type, string Type,
string TypeIdentifierName, string TypeIdentifierName,
string SourceNamespace,
string SourceTypeIdentifierName,
string SourceTypeFullName,
bool IsTypeUpdatable, bool IsTypeUpdatable,
bool IsJsonExtension, bool IsJsonExtension,
ImmutableArray<MappedMember> SourceProperties, ImmutableArray<MappedSourceType> MappedSourceTypes,
ImmutableArray<MappedMember> TypeProperties,
ImmutableArray<MappedMember> SourceFields,
ImmutableArray<MappedMember> TypeFields,
bool HasMappedBaseClass, bool HasMappedBaseClass,
ImmutableArray<string> Usings, ImmutableArray<string> Usings
bool GenerateSecondaryConstructor );
)
{
public string SourceType => SourceTypeFullName;
}
internal record SourceGenerationOptions( internal record SourceGenerationOptions(
AccessModifier ConstructorAccessModifier, AccessModifier ConstructorAccessModifier,

View File

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

View File

@ -22,13 +22,18 @@ namespace MapTo.Sources
.WriteLine($"partial record {model.TypeIdentifierName}") .WriteLine($"partial record {model.TypeIdentifierName}")
.WriteOpeningBracket(); .WriteOpeningBracket();
// Class body foreach (var targetSourceType in model.MappedSourceTypes)
if (model.GenerateSecondaryConstructor) {
if (targetSourceType.GenerateSecondaryConstructor)
{ {
builder builder
.GenerateSecondaryConstructor(model) .GenerateSecondaryConstructor(model)
.WriteLine(); .WriteLine();
} }
}
// Class body
builder builder
.GeneratePrivateConstructor(model) .GeneratePrivateConstructor(model)
@ -52,8 +57,11 @@ namespace MapTo.Sources
private static SourceBuilder GenerateSecondaryConstructor(this SourceBuilder builder, MappingModel model) private static SourceBuilder GenerateSecondaryConstructor(this SourceBuilder builder, MappingModel model)
{ {
var sourceClassParameterName = model.SourceTypeIdentifierName.ToCamelCase(); // grab first data from array
foreach (var targetSourceType in model.MappedSourceTypes)
{
var sourceClassParameterName = targetSourceType.SourceTypeIdentifierName.ToCamelCase();
if (model.Options.GenerateXmlDocument) if (model.Options.GenerateXmlDocument)
{ {
builder builder
@ -63,42 +71,50 @@ namespace MapTo.Sources
.WriteLine("/// </summary>") .WriteLine("/// </summary>")
.WriteLine($"/// <exception cref=\"ArgumentNullException\">{sourceClassParameterName} is null</exception>"); .WriteLine($"/// <exception cref=\"ArgumentNullException\">{sourceClassParameterName} is null</exception>");
} }
builder .WriteLine($"{model.Options.ConstructorAccessModifier.ToLowercaseString()} {model.TypeIdentifierName}({targetSourceType.SourceType} {sourceClassParameterName})")
return builder
.WriteLine($"{model.Options.ConstructorAccessModifier.ToLowercaseString()} {model.TypeIdentifierName}({model.SourceType} {sourceClassParameterName})")
.WriteLine($" : this(new {MappingContextSource.ClassName}(), {sourceClassParameterName}) {{ }}"); .WriteLine($" : this(new {MappingContextSource.ClassName}(), {sourceClassParameterName}) {{ }}");
} }
return builder;
}
private static SourceBuilder GeneratePrivateConstructor(this SourceBuilder builder, MappingModel model) private static SourceBuilder GeneratePrivateConstructor(this SourceBuilder builder, MappingModel model)
{ {
var sourceClassParameterName = model.SourceTypeIdentifierName.ToCamelCase();
const string mappingContextParameterName = "context"; const string mappingContextParameterName = "context";
foreach (var targetSourceType in model.MappedSourceTypes)
{
var sourceClassParameterName = targetSourceType.SourceTypeIdentifierName.ToCamelCase();
builder builder
.WriteLine($"private protected {model.TypeIdentifierName}({MappingContextSource.ClassName} {mappingContextParameterName}, {model.SourceType} {sourceClassParameterName})") .WriteLine(
$"private protected {model.TypeIdentifierName}({MappingContextSource.ClassName} {mappingContextParameterName}, {targetSourceType.SourceType} {sourceClassParameterName})")
.Indent() .Indent()
.Write(": this("). .Write(": this(").WriteProperties(model, sourceClassParameterName, mappingContextParameterName)
WriteProperties(model, sourceClassParameterName, mappingContextParameterName)
.WriteLine(")") .WriteLine(")")
.Unindent() .Unindent()
.WriteOpeningBracket() .WriteOpeningBracket()
.WriteLine($"if ({mappingContextParameterName} == null) throw new ArgumentNullException(nameof({mappingContextParameterName}));") .WriteLine($"if ({mappingContextParameterName} == null) throw new ArgumentNullException(nameof({mappingContextParameterName}));")
.WriteLine($"if ({sourceClassParameterName} == null) throw new ArgumentNullException(nameof({sourceClassParameterName}));") .WriteLine($"if ({sourceClassParameterName} == null) throw new ArgumentNullException(nameof({sourceClassParameterName}));")
.WriteLine() .WriteLine()
.WriteLine($"{mappingContextParameterName}.{MappingContextSource.RegisterMethodName}({sourceClassParameterName}, this);"); .WriteLine($"{mappingContextParameterName}.{MappingContextSource.RegisterMethodName}({sourceClassParameterName}, this);")
.WriteClosingBracket();
}
// End constructor declaration // End constructor declaration
return builder.WriteClosingBracket(); return builder;
} }
private static SourceBuilder WriteProperties(this SourceBuilder builder, MappingModel model, string sourceClassParameterName, private static SourceBuilder WriteProperties(this SourceBuilder builder, MappingModel model, string sourceClassParameterName,
string mappingContextParameterName) string mappingContextParameterName)
{ {
for (var i = 0; i < model.SourceProperties.Length; i++)
foreach (var targetSourceType in model.MappedSourceTypes)
{ {
var property = model.SourceProperties[i]; for (var i = 0; i < targetSourceType.SourceProperties.Length; i++)
{
var property = targetSourceType.SourceProperties[i];
if (property.TypeConverter is null) if (property.TypeConverter is null)
{ {
if (property.IsEnumerable) if (property.IsEnumerable)
@ -123,30 +139,39 @@ namespace MapTo.Sources
$"{property.Name}: new {property.TypeConverter}().Convert({sourceClassParameterName}.{property.SourcePropertyName}, {parameters})"); $"{property.Name}: new {property.TypeConverter}().Convert({sourceClassParameterName}.{property.SourcePropertyName}, {parameters})");
} }
if (i < model.SourceProperties.Length - 1) if (i < targetSourceType.SourceProperties.Length - 1)
{ {
builder.Write(", "); builder.Write(", ");
} }
} }
}
return builder; return builder;
} }
private static SourceBuilder GenerateFactoryMethod(this SourceBuilder builder, MappingModel model) private static SourceBuilder GenerateFactoryMethod(this SourceBuilder builder, MappingModel model)
{ {
var sourceClassParameterName = model.SourceTypeIdentifierName.ToCamelCase(); foreach (var targetSourceType in model.MappedSourceTypes)
{
var sourceClassParameterName = targetSourceType.SourceTypeIdentifierName.ToCamelCase();
return builder builder
.GenerateConvertorMethodsXmlDocs(model, sourceClassParameterName) .GenerateConvertorMethodsXmlDocs(model, sourceClassParameterName)
.WriteLineIf(model.Options.SupportNullableStaticAnalysis, $"[return: NotNullIfNotNull(\"{sourceClassParameterName}\")]") .WriteLineIf(model.Options.SupportNullableStaticAnalysis, $"[return: NotNullIfNotNull(\"{sourceClassParameterName}\")]")
.WriteLine( .WriteLine(
$"{model.Options.GeneratedMethodsAccessModifier.ToLowercaseString()} static {model.TypeIdentifierName}{model.Options.NullableReferenceSyntax} From({model.SourceType}{model.Options.NullableReferenceSyntax} {sourceClassParameterName})") $"{model.Options.GeneratedMethodsAccessModifier.ToLowercaseString()} static {model.TypeIdentifierName}{model.Options.NullableReferenceSyntax} From({targetSourceType.SourceType}{model.Options.NullableReferenceSyntax} {sourceClassParameterName})")
.WriteOpeningBracket() .WriteOpeningBracket()
.WriteLine( .WriteLine(
$"return {sourceClassParameterName} == null ? null : {MappingContextSource.ClassName}.{MappingContextSource.FactoryMethodName}<{model.SourceType}, {model.TypeIdentifierName}>({sourceClassParameterName});") $"return {sourceClassParameterName} == null ? null : {MappingContextSource.ClassName}.{MappingContextSource.FactoryMethodName}<{targetSourceType.SourceType}, {model.TypeIdentifierName}>({sourceClassParameterName});")
.WriteClosingBracket(); .WriteClosingBracket();
} }
return builder;
}
private static SourceBuilder GenerateConvertorMethodsXmlDocs(this SourceBuilder builder, MappingModel model, string sourceClassParameterName) private static SourceBuilder GenerateConvertorMethodsXmlDocs(this SourceBuilder builder, MappingModel model, string sourceClassParameterName)
{ {
if (!model.Options.GenerateXmlDocument) if (!model.Options.GenerateXmlDocument)
@ -154,38 +179,57 @@ namespace MapTo.Sources
return builder; return builder;
} }
return builder foreach (var targetSourceType in model.MappedSourceTypes)
{
builder
.WriteLine("/// <summary>") .WriteLine("/// <summary>")
.WriteLine($"/// Creates a new instance of <see cref=\"{model.TypeIdentifierName}\"/> and sets its participating properties") .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($"/// using the property values from <paramref name=\"{sourceClassParameterName}\"/>.")
.WriteLine("/// </summary>") .WriteLine("/// </summary>")
.WriteLine($"/// <param name=\"{sourceClassParameterName}\">The instance of <see cref=\"{model.SourceType}\"/> to use as source.</param>") .WriteLine($"/// <param name=\"{sourceClassParameterName}\">The instance of <see cref=\"{targetSourceType.SourceType}\"/> to use as source.</param>")
.WriteLine( .WriteLine(
$"/// <returns>A new instance of <see cred=\"{model.TypeIdentifierName}\"/> -or- <c>null</c> if <paramref name=\"{sourceClassParameterName}\"/> is <c>null</c>.</returns>"); $"/// <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) private static SourceBuilder GenerateSourceTypeExtensionClass(this SourceBuilder builder, MappingModel model)
{ {
return builder foreach (var targetSourceType in model.MappedSourceTypes)
{
builder
.WriteLine( .WriteLine(
$"{model.Options.GeneratedMethodsAccessModifier.ToLowercaseString()} static partial class {model.SourceTypeIdentifierName}To{model.TypeIdentifierName}Extensions") $"{model.Options.GeneratedMethodsAccessModifier.ToLowercaseString()} static partial class {targetSourceType.SourceTypeIdentifierName}To{model.TypeIdentifierName}Extensions")
.WriteOpeningBracket() .WriteOpeningBracket()
.GenerateSourceTypeExtensionMethod(model) .GenerateSourceTypeExtensionMethod(model)
.WriteClosingBracket(); .WriteClosingBracket();
} }
return builder;
}
private static SourceBuilder GenerateSourceTypeExtensionMethod(this SourceBuilder builder, MappingModel model) private static SourceBuilder GenerateSourceTypeExtensionMethod(this SourceBuilder builder, MappingModel model)
{ {
var sourceClassParameterName = model.SourceTypeIdentifierName.ToCamelCase();
return builder foreach (var targetSourceType in model.MappedSourceTypes)
{
var sourceClassParameterName = targetSourceType.SourceTypeIdentifierName.ToCamelCase();
builder
.GenerateConvertorMethodsXmlDocs(model, sourceClassParameterName) .GenerateConvertorMethodsXmlDocs(model, sourceClassParameterName)
.WriteLineIf(model.Options.SupportNullableStaticAnalysis, $"[return: NotNullIfNotNull(\"{sourceClassParameterName}\")]") .WriteLineIf(model.Options.SupportNullableStaticAnalysis, $"[return: NotNullIfNotNull(\"{sourceClassParameterName}\")]")
.WriteLine( .WriteLine(
$"{model.Options.GeneratedMethodsAccessModifier.ToLowercaseString()} static {model.TypeIdentifierName}{model.Options.NullableReferenceSyntax} To{model.TypeIdentifierName}(this {model.SourceType}{model.Options.NullableReferenceSyntax} {sourceClassParameterName})") $"{model.Options.GeneratedMethodsAccessModifier.ToLowercaseString()} static {model.TypeIdentifierName}{model.Options.NullableReferenceSyntax} To{model.TypeIdentifierName}(this {targetSourceType.SourceType}{model.Options.NullableReferenceSyntax} {sourceClassParameterName})")
.WriteOpeningBracket() .WriteOpeningBracket()
.WriteLine($"return {sourceClassParameterName} == null ? null : new {model.TypeIdentifierName}({sourceClassParameterName});") .WriteLine($"return {sourceClassParameterName} == null ? null : new {model.TypeIdentifierName}({sourceClassParameterName});")
.WriteClosingBracket(); .WriteClosingBracket();
}
return builder;
} }
} }
} }

View File

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

View File

@ -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)
{ {

View File

@ -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();

View File

@ -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()

View File

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

View File

@ -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[]
{ {

View File

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

View File

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