Add support for nested object property map.
This commit is contained in:
parent
8ed331ed95
commit
4693dcfa55
|
@ -1,5 +1,7 @@
|
||||||
using System.Collections.Generic;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using MapTo.Sources;
|
||||||
using Microsoft.CodeAnalysis;
|
using Microsoft.CodeAnalysis;
|
||||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||||
|
|
||||||
|
@ -63,5 +65,8 @@ namespace MapTo.Extensions
|
||||||
p.NullableAnnotation == NullableAnnotation.Annotated &&
|
p.NullableAnnotation == NullableAnnotation.Annotated &&
|
||||||
targetProperty.NullableAnnotation == NullableAnnotation.Annotated));
|
targetProperty.NullableAnnotation == NullableAnnotation.Annotated));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static INamedTypeSymbol GetTypeByMetadataNameOrThrow(this Compilation compilation, string fullyQualifiedMetadataName) =>
|
||||||
|
compilation.GetTypeByMetadataName(fullyQualifiedMetadataName) ?? throw new TypeLoadException($"Unable to find '{fullyQualifiedMetadataName}' type.");
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -41,7 +41,7 @@ namespace MapTo
|
||||||
{
|
{
|
||||||
foreach (var classSyntax in candidateClasses)
|
foreach (var classSyntax in candidateClasses)
|
||||||
{
|
{
|
||||||
var mappingContext = MappingContext.Create(compilation, classSyntax, options);
|
var mappingContext = new MappingContext(compilation, options, classSyntax);
|
||||||
mappingContext.Diagnostics.ForEach(context.ReportDiagnostic);
|
mappingContext.Diagnostics.ForEach(context.ReportDiagnostic);
|
||||||
|
|
||||||
if (mappingContext.Model is not null)
|
if (mappingContext.Model is not null)
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
using System;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Collections.Immutable;
|
using System.Collections.Immutable;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using MapTo.Extensions;
|
using MapTo.Extensions;
|
||||||
|
@ -11,148 +10,178 @@ namespace MapTo
|
||||||
{
|
{
|
||||||
internal class MappingContext
|
internal class MappingContext
|
||||||
{
|
{
|
||||||
private MappingContext(Compilation compilation)
|
private readonly ClassDeclarationSyntax _classSyntax;
|
||||||
|
private readonly Compilation _compilation;
|
||||||
|
private readonly INamedTypeSymbol _ignorePropertyAttributeTypeSymbol;
|
||||||
|
private readonly INamedTypeSymbol _mapFromAttributeTypeSymbol;
|
||||||
|
private readonly INamedTypeSymbol _mapPropertyAttributeTypeSymbol;
|
||||||
|
private readonly INamedTypeSymbol _mapTypeConverterAttributeTypeSymbol;
|
||||||
|
private readonly SemanticModel _semanticModel;
|
||||||
|
private readonly SourceGenerationOptions _sourceGenerationOptions;
|
||||||
|
private readonly INamedTypeSymbol _typeConverterInterfaceTypeSymbol;
|
||||||
|
|
||||||
|
internal MappingContext(Compilation compilation, SourceGenerationOptions sourceGenerationOptions, ClassDeclarationSyntax classSyntax)
|
||||||
{
|
{
|
||||||
Diagnostics = ImmutableArray<Diagnostic>.Empty;
|
Diagnostics = ImmutableArray<Diagnostic>.Empty;
|
||||||
Compilation = compilation;
|
_sourceGenerationOptions = sourceGenerationOptions;
|
||||||
|
_classSyntax = classSyntax;
|
||||||
|
_compilation = compilation;
|
||||||
|
_semanticModel = _compilation.GetSemanticModel(_classSyntax.SyntaxTree);
|
||||||
|
|
||||||
IgnorePropertyAttributeTypeSymbol = compilation.GetTypeByMetadataName(IgnorePropertyAttributeSource.FullyQualifiedName)
|
_ignorePropertyAttributeTypeSymbol = compilation.GetTypeByMetadataNameOrThrow(IgnorePropertyAttributeSource.FullyQualifiedName);
|
||||||
?? throw new TypeLoadException($"Unable to find '{IgnorePropertyAttributeSource.FullyQualifiedName}' type.");
|
_mapTypeConverterAttributeTypeSymbol = compilation.GetTypeByMetadataNameOrThrow(MapTypeConverterAttributeSource.FullyQualifiedName);
|
||||||
|
_typeConverterInterfaceTypeSymbol = compilation.GetTypeByMetadataNameOrThrow(ITypeConverterSource.FullyQualifiedName);
|
||||||
|
_mapPropertyAttributeTypeSymbol = compilation.GetTypeByMetadataNameOrThrow(MapPropertyAttributeSource.FullyQualifiedName);
|
||||||
|
_mapFromAttributeTypeSymbol = compilation.GetTypeByMetadataNameOrThrow(MapFromAttributeSource.FullyQualifiedName);
|
||||||
|
|
||||||
MapTypeConverterAttributeTypeSymbol = compilation.GetTypeByMetadataName(MapTypeConverterAttributeSource.FullyQualifiedName)
|
Initialize();
|
||||||
?? throw new TypeLoadException($"Unable to find '{MapTypeConverterAttributeSource.FullyQualifiedName}' type.");
|
|
||||||
|
|
||||||
TypeConverterInterfaceTypeSymbol = compilation.GetTypeByMetadataName(ITypeConverterSource.FullyQualifiedName)
|
|
||||||
?? throw new TypeLoadException($"Unable to find '{ITypeConverterSource.FullyQualifiedName}' type.");
|
|
||||||
|
|
||||||
MapPropertyAttributeTypeSymbol = compilation.GetTypeByMetadataName(MapPropertyAttributeSource.FullyQualifiedName)
|
|
||||||
?? throw new TypeLoadException($"Unable to find '{MapPropertyAttributeSource.FullyQualifiedName}' type.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Compilation Compilation { get; }
|
|
||||||
|
|
||||||
public MappingModel? Model { get; private set; }
|
public MappingModel? Model { get; private set; }
|
||||||
|
|
||||||
public ImmutableArray<Diagnostic> Diagnostics { get; private set; }
|
public ImmutableArray<Diagnostic> Diagnostics { get; private set; }
|
||||||
|
|
||||||
public INamedTypeSymbol IgnorePropertyAttributeTypeSymbol { get; }
|
private void Initialize()
|
||||||
|
|
||||||
public INamedTypeSymbol MapTypeConverterAttributeTypeSymbol { get; }
|
|
||||||
|
|
||||||
public INamedTypeSymbol TypeConverterInterfaceTypeSymbol { get; }
|
|
||||||
|
|
||||||
public INamedTypeSymbol MapPropertyAttributeTypeSymbol { get; }
|
|
||||||
|
|
||||||
internal static MappingContext Create(Compilation compilation, ClassDeclarationSyntax classSyntax, SourceGenerationOptions sourceGenerationOptions)
|
|
||||||
{
|
{
|
||||||
var context = new MappingContext(compilation);
|
if (!(_semanticModel.GetDeclaredSymbol(_classSyntax) is INamedTypeSymbol classTypeSymbol))
|
||||||
var root = classSyntax.GetCompilationUnit();
|
|
||||||
|
|
||||||
var semanticModel = compilation.GetSemanticModel(classSyntax.SyntaxTree);
|
|
||||||
if (!(semanticModel.GetDeclaredSymbol(classSyntax) is INamedTypeSymbol classTypeSymbol))
|
|
||||||
{
|
{
|
||||||
return context.ReportDiagnostic(DiagnosticProvider.TypeNotFoundError(classSyntax.GetLocation(), classSyntax.Identifier.ValueText));
|
ReportDiagnostic(DiagnosticProvider.TypeNotFoundError(_classSyntax.GetLocation(), _classSyntax.Identifier.ValueText));
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var sourceTypeSymbol = GetSourceTypeSymbol(semanticModel, classSyntax);
|
var sourceTypeSymbol = GetSourceTypeSymbol(_classSyntax);
|
||||||
if (sourceTypeSymbol is null)
|
if (sourceTypeSymbol is null)
|
||||||
{
|
{
|
||||||
return context.ReportDiagnostic(DiagnosticProvider.MapFromAttributeNotFoundError(classSyntax.GetLocation()));
|
ReportDiagnostic(DiagnosticProvider.MapFromAttributeNotFoundError(_classSyntax.GetLocation()));
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var className = classSyntax.GetClassName();
|
var className = _classSyntax.GetClassName();
|
||||||
var sourceClassName = sourceTypeSymbol.Name;
|
var sourceClassName = sourceTypeSymbol.Name;
|
||||||
|
|
||||||
var mappedProperties = GetMappedProperties(context, classTypeSymbol, sourceTypeSymbol);
|
var mappedProperties = GetMappedProperties(classTypeSymbol, sourceTypeSymbol);
|
||||||
if (!mappedProperties.Any())
|
if (!mappedProperties.Any())
|
||||||
{
|
{
|
||||||
return context.ReportDiagnostic(DiagnosticProvider.NoMatchingPropertyFoundError(classSyntax.GetLocation(), classTypeSymbol, sourceTypeSymbol));
|
ReportDiagnostic(DiagnosticProvider.NoMatchingPropertyFoundError(_classSyntax.GetLocation(), classTypeSymbol, sourceTypeSymbol));
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
context.Model = new MappingModel(
|
Model = new MappingModel(
|
||||||
sourceGenerationOptions,
|
_sourceGenerationOptions,
|
||||||
classSyntax.GetNamespace(),
|
_classSyntax.GetNamespace(),
|
||||||
classSyntax.Modifiers,
|
_classSyntax.Modifiers,
|
||||||
className,
|
className,
|
||||||
sourceTypeSymbol.ContainingNamespace.ToString(),
|
sourceTypeSymbol.ContainingNamespace.ToString(),
|
||||||
sourceClassName,
|
sourceClassName,
|
||||||
sourceTypeSymbol.ToString(),
|
sourceTypeSymbol.ToString(),
|
||||||
mappedProperties.ToImmutableArray());
|
mappedProperties.ToImmutableArray());
|
||||||
|
|
||||||
return context;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private MappingContext ReportDiagnostic(Diagnostic diagnostic)
|
private ImmutableArray<MappedProperty> GetMappedProperties(ITypeSymbol classSymbol, ITypeSymbol sourceTypeSymbol)
|
||||||
{
|
|
||||||
Diagnostics = Diagnostics.Add(diagnostic);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static INamedTypeSymbol? GetSourceTypeSymbol(SemanticModel semanticModel, ClassDeclarationSyntax classSyntax)
|
|
||||||
{
|
|
||||||
var sourceTypeExpressionSyntax = classSyntax
|
|
||||||
.GetAttribute(MapFromAttributeSource.AttributeName)
|
|
||||||
?.DescendantNodes()
|
|
||||||
.OfType<TypeOfExpressionSyntax>()
|
|
||||||
.SingleOrDefault();
|
|
||||||
|
|
||||||
return sourceTypeExpressionSyntax is not null ? semanticModel.GetTypeInfo(sourceTypeExpressionSyntax.Type).Type as INamedTypeSymbol : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static ImmutableArray<MappedProperty> GetMappedProperties(MappingContext context, ITypeSymbol classSymbol, ITypeSymbol sourceTypeSymbol)
|
|
||||||
{
|
{
|
||||||
var mappedProperties = new List<MappedProperty>();
|
var mappedProperties = new List<MappedProperty>();
|
||||||
var sourceProperties = sourceTypeSymbol.GetAllMembers().OfType<IPropertySymbol>().ToArray();
|
var sourceProperties = sourceTypeSymbol.GetAllMembers().OfType<IPropertySymbol>().ToArray();
|
||||||
var classProperties = classSymbol.GetAllMembers().OfType<IPropertySymbol>().Where(p => !p.HasAttribute(context.IgnorePropertyAttributeTypeSymbol));
|
var classProperties = classSymbol.GetAllMembers().OfType<IPropertySymbol>().Where(p => !p.HasAttribute(_ignorePropertyAttributeTypeSymbol));
|
||||||
|
|
||||||
foreach (var property in classProperties)
|
foreach (var property in classProperties)
|
||||||
{
|
{
|
||||||
var sourceProperty = FindSourceProperty(context, sourceProperties, property);
|
var sourceProperty = FindSourceProperty(sourceProperties, property);
|
||||||
if (sourceProperty is null)
|
if (sourceProperty is null)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
string? converterFullyQualifiedName = null;
|
string? converterFullyQualifiedName = null;
|
||||||
var converterParameters = new List<string>();
|
var converterParameters = ImmutableArray<string>.Empty;
|
||||||
|
string? mappedSourcePropertyType = null;
|
||||||
|
|
||||||
if (!context.Compilation.HasCompatibleTypes(sourceProperty, property))
|
if (!_compilation.HasCompatibleTypes(sourceProperty, property))
|
||||||
{
|
{
|
||||||
var typeConverterAttribute = property.GetAttribute(context.MapTypeConverterAttributeTypeSymbol);
|
if (!TryGetMapTypeConverter(property, sourceProperty, out converterFullyQualifiedName, out converterParameters) &&
|
||||||
if (typeConverterAttribute is null)
|
!TryGetNestedObjectMappings(property, out mappedSourcePropertyType))
|
||||||
{
|
{
|
||||||
context.ReportDiagnostic(DiagnosticProvider.NoMatchingPropertyTypeFoundError(property));
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
var converterTypeSymbol = typeConverterAttribute.ConstructorArguments.First().Value as INamedTypeSymbol;
|
|
||||||
if (converterTypeSymbol is null)
|
|
||||||
{
|
|
||||||
context.ReportDiagnostic(DiagnosticProvider.NoMatchingPropertyTypeFoundError(property));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var baseInterface = GetTypeConverterBaseInterface(context, converterTypeSymbol, property, sourceProperty);
|
|
||||||
if (baseInterface is null)
|
|
||||||
{
|
|
||||||
context.ReportDiagnostic(DiagnosticProvider.InvalidTypeConverterGenericTypesError(property, sourceProperty));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
converterFullyQualifiedName = converterTypeSymbol.ToDisplayString();
|
|
||||||
converterParameters.AddRange(GetTypeConverterParameters(typeConverterAttribute));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
mappedProperties.Add(new MappedProperty(property.Name, converterFullyQualifiedName, converterParameters.ToImmutableArray(), sourceProperty.Name));
|
mappedProperties.Add(new MappedProperty(
|
||||||
|
property.Name,
|
||||||
|
property.Type.Name,
|
||||||
|
converterFullyQualifiedName,
|
||||||
|
converterParameters.ToImmutableArray(),
|
||||||
|
sourceProperty.Name,
|
||||||
|
mappedSourcePropertyType));
|
||||||
}
|
}
|
||||||
|
|
||||||
return mappedProperties.ToImmutableArray();
|
return mappedProperties.ToImmutableArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static IPropertySymbol? FindSourceProperty(MappingContext context, IEnumerable<IPropertySymbol> sourceProperties, IPropertySymbol property)
|
private bool TryGetNestedObjectMappings(IPropertySymbol property, out string? mappedSourcePropertyType)
|
||||||
|
{
|
||||||
|
mappedSourcePropertyType = null;
|
||||||
|
|
||||||
|
if (!Diagnostics.IsEmpty)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var nestedSourceMapFromAttribute = property.Type.GetAttribute(_mapFromAttributeTypeSymbol);
|
||||||
|
if (nestedSourceMapFromAttribute is null)
|
||||||
|
{
|
||||||
|
ReportDiagnostic(DiagnosticProvider.NoMatchingPropertyTypeFoundError(property));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var nestedAttributeSyntax = nestedSourceMapFromAttribute.ApplicationSyntaxReference?.GetSyntax() as AttributeSyntax;
|
||||||
|
if (nestedAttributeSyntax is null)
|
||||||
|
{
|
||||||
|
ReportDiagnostic(DiagnosticProvider.NoMatchingPropertyTypeFoundError(property));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var nestedSourceTypeSymbol = GetSourceTypeSymbol(nestedAttributeSyntax);
|
||||||
|
if (nestedSourceTypeSymbol is null)
|
||||||
|
{
|
||||||
|
ReportDiagnostic(DiagnosticProvider.NoMatchingPropertyTypeFoundError(property));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
mappedSourcePropertyType = nestedSourceTypeSymbol.Name;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool TryGetMapTypeConverter(IPropertySymbol property, IPropertySymbol sourceProperty, out string? converterFullyQualifiedName, out ImmutableArray<string> converterParameters)
|
||||||
|
{
|
||||||
|
converterFullyQualifiedName = null;
|
||||||
|
converterParameters = ImmutableArray<string>.Empty;
|
||||||
|
|
||||||
|
if (!Diagnostics.IsEmpty)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var typeConverterAttribute = property.GetAttribute(_mapTypeConverterAttributeTypeSymbol);
|
||||||
|
if (!(typeConverterAttribute?.ConstructorArguments.First().Value is INamedTypeSymbol converterTypeSymbol))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var baseInterface = GetTypeConverterBaseInterface(converterTypeSymbol, property, sourceProperty);
|
||||||
|
if (baseInterface is null)
|
||||||
|
{
|
||||||
|
ReportDiagnostic(DiagnosticProvider.InvalidTypeConverterGenericTypesError(property, sourceProperty));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
converterFullyQualifiedName = converterTypeSymbol.ToDisplayString();
|
||||||
|
converterParameters = GetTypeConverterParameters(typeConverterAttribute);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private IPropertySymbol? FindSourceProperty(IEnumerable<IPropertySymbol> sourceProperties, IPropertySymbol property)
|
||||||
{
|
{
|
||||||
var propertyName = property
|
var propertyName = property
|
||||||
.GetAttribute(context.MapPropertyAttributeTypeSymbol)
|
.GetAttribute(_mapPropertyAttributeTypeSymbol)
|
||||||
?.NamedArguments
|
?.NamedArguments
|
||||||
.SingleOrDefault(a => a.Key == MapPropertyAttributeSource.SourcePropertyNamePropertyName)
|
.SingleOrDefault(a => a.Key == MapPropertyAttributeSource.SourcePropertyNamePropertyName)
|
||||||
.Value.Value as string ?? property.Name;
|
.Value.Value as string ?? property.Name;
|
||||||
|
@ -160,22 +189,40 @@ namespace MapTo
|
||||||
return sourceProperties.SingleOrDefault(p => p.Name == propertyName);
|
return sourceProperties.SingleOrDefault(p => p.Name == propertyName);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static INamedTypeSymbol? GetTypeConverterBaseInterface(MappingContext context, ITypeSymbol converterTypeSymbol, IPropertySymbol property, IPropertySymbol sourceProperty)
|
private INamedTypeSymbol? GetTypeConverterBaseInterface(ITypeSymbol converterTypeSymbol, IPropertySymbol property, IPropertySymbol sourceProperty)
|
||||||
{
|
{
|
||||||
return converterTypeSymbol.AllInterfaces
|
return converterTypeSymbol.AllInterfaces
|
||||||
.SingleOrDefault(i =>
|
.SingleOrDefault(i =>
|
||||||
i.TypeArguments.Length == 2 &&
|
i.TypeArguments.Length == 2 &&
|
||||||
SymbolEqualityComparer.Default.Equals(i.ConstructedFrom, context.TypeConverterInterfaceTypeSymbol) &&
|
SymbolEqualityComparer.Default.Equals(i.ConstructedFrom, _typeConverterInterfaceTypeSymbol) &&
|
||||||
SymbolEqualityComparer.Default.Equals(sourceProperty.Type, i.TypeArguments[0]) &&
|
SymbolEqualityComparer.Default.Equals(sourceProperty.Type, i.TypeArguments[0]) &&
|
||||||
SymbolEqualityComparer.Default.Equals(property.Type, i.TypeArguments[1]));
|
SymbolEqualityComparer.Default.Equals(property.Type, i.TypeArguments[1]));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static IEnumerable<string> GetTypeConverterParameters(AttributeData typeConverterAttribute)
|
private static ImmutableArray<string> GetTypeConverterParameters(AttributeData typeConverterAttribute)
|
||||||
{
|
{
|
||||||
var converterParameter = typeConverterAttribute.ConstructorArguments.Skip(1).FirstOrDefault();
|
var converterParameter = typeConverterAttribute.ConstructorArguments.Skip(1).FirstOrDefault();
|
||||||
return converterParameter.IsNull
|
return converterParameter.IsNull
|
||||||
? Enumerable.Empty<string>()
|
? ImmutableArray<string>.Empty
|
||||||
: converterParameter.Values.Where(v => v.Value is not null).Select(v => v.Value!.ToSourceCodeString());
|
: converterParameter.Values.Where(v => v.Value is not null).Select(v => v.Value!.ToSourceCodeString()).ToImmutableArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ReportDiagnostic(Diagnostic diagnostic)
|
||||||
|
{
|
||||||
|
Diagnostics = Diagnostics.Add(diagnostic);
|
||||||
|
}
|
||||||
|
|
||||||
|
private INamedTypeSymbol? GetSourceTypeSymbol(ClassDeclarationSyntax classDeclarationSyntax) =>
|
||||||
|
GetSourceTypeSymbol(classDeclarationSyntax.GetAttribute(MapFromAttributeSource.AttributeName));
|
||||||
|
|
||||||
|
private INamedTypeSymbol? GetSourceTypeSymbol(AttributeSyntax? attributeSyntax)
|
||||||
|
{
|
||||||
|
var sourceTypeExpressionSyntax = attributeSyntax
|
||||||
|
?.DescendantNodes()
|
||||||
|
.OfType<TypeOfExpressionSyntax>()
|
||||||
|
.SingleOrDefault();
|
||||||
|
|
||||||
|
return sourceTypeExpressionSyntax is not null ? _semanticModel.GetTypeInfo(sourceTypeExpressionSyntax.Type).Type as INamedTypeSymbol : null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -9,9 +9,11 @@ namespace MapTo
|
||||||
|
|
||||||
internal record MappedProperty(
|
internal record MappedProperty(
|
||||||
string Name,
|
string Name,
|
||||||
|
string Type,
|
||||||
string? TypeConverter,
|
string? TypeConverter,
|
||||||
ImmutableArray<string> TypeConverterParameters,
|
ImmutableArray<string> TypeConverterParameters,
|
||||||
string SourcePropertyName);
|
string SourcePropertyName,
|
||||||
|
string? MappedSourcePropertyTypeName);
|
||||||
|
|
||||||
internal record MappingModel (
|
internal record MappingModel (
|
||||||
SourceGenerationOptions Options,
|
SourceGenerationOptions Options,
|
||||||
|
|
|
@ -70,7 +70,9 @@ namespace MapTo.Sources
|
||||||
{
|
{
|
||||||
if (property.TypeConverter is null)
|
if (property.TypeConverter is null)
|
||||||
{
|
{
|
||||||
builder.WriteLine($"{property.Name} = {sourceClassParameterName}.{property.SourcePropertyName};");
|
builder.WriteLine(property.MappedSourcePropertyTypeName is null
|
||||||
|
? $"{property.Name} = {sourceClassParameterName}.{property.SourcePropertyName};"
|
||||||
|
: $"{property.Name} = new {property.Type}({sourceClassParameterName}.{property.SourcePropertyName});");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
|
@ -333,5 +333,101 @@ namespace Test
|
||||||
diagnostics.ShouldBeSuccessful();
|
diagnostics.ShouldBeSuccessful();
|
||||||
compilation.SyntaxTrees.Last().ToString().ShouldContain(expectedResult.Trim());
|
compilation.SyntaxTrees.Last().ToString().ShouldContain(expectedResult.Trim());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void When_HasNestedObjectPropertyTypeHasMapFromAttribute_Should_UseContinueToMap()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var source = GetSourceText(new SourceGeneratorOptions(
|
||||||
|
SourceClassNamespace: "Test",
|
||||||
|
PropertyBuilder: b => b.WriteLine("public B InnerProp1 { get; }"),
|
||||||
|
SourcePropertyBuilder: b => b.WriteLine("public A InnerProp1 { get; }")));
|
||||||
|
|
||||||
|
source += @"
|
||||||
|
namespace Test
|
||||||
|
{
|
||||||
|
public class A { public int Prop1 { get; } }
|
||||||
|
|
||||||
|
[MapTo.MapFrom(typeof(A))]
|
||||||
|
public partial class B { public int Prop1 { get; }}
|
||||||
|
}
|
||||||
|
".Trim();
|
||||||
|
|
||||||
|
var expectedResult = @"
|
||||||
|
partial class Foo
|
||||||
|
{
|
||||||
|
public Foo(Test.Baz baz)
|
||||||
|
{
|
||||||
|
if (baz == null) throw new ArgumentNullException(nameof(baz));
|
||||||
|
|
||||||
|
Prop1 = baz.Prop1;
|
||||||
|
Prop2 = baz.Prop2;
|
||||||
|
Prop3 = baz.Prop3;
|
||||||
|
InnerProp1 = new B(baz.InnerProp1);
|
||||||
|
}
|
||||||
|
".Trim();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
diagnostics.ShouldBeSuccessful();
|
||||||
|
compilation.SyntaxTrees.ToArray()[^2].ToString().ShouldContain(expectedResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void When_HasNestedObjectPropertyTypeDoesNotHaveMapFromAttribute_Should_ReportError()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var source = GetSourceText(new SourceGeneratorOptions(
|
||||||
|
SourceClassNamespace: "Test",
|
||||||
|
PropertyBuilder: b => b.WriteLine("public FooInner1 InnerProp1 { get; }"),
|
||||||
|
SourcePropertyBuilder: b => b.WriteLine("public BazInner1 InnerProp1 { get; }")));
|
||||||
|
|
||||||
|
source += @"
|
||||||
|
namespace Test
|
||||||
|
{
|
||||||
|
public class FooInner1 { public int Prop1 { get; } }
|
||||||
|
|
||||||
|
public partial class BazInner1 { public int Prop1 { get; }}
|
||||||
|
}
|
||||||
|
".Trim();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var expectedError = DiagnosticProvider.NoMatchingPropertyTypeFoundError(GetSourcePropertySymbol("InnerProp1", compilation));
|
||||||
|
diagnostics.ShouldBeUnsuccessful(expectedError);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void When_HasNestedObjectPropertyTypeHasMapFromAttributeToDifferentType_Should_ReportError()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var source = GetSourceText(new SourceGeneratorOptions(
|
||||||
|
SourceClassNamespace: "Test",
|
||||||
|
PropertyBuilder: b => b.WriteLine("public FooInner1 InnerProp1 { get; }"),
|
||||||
|
SourcePropertyBuilder: b => b.WriteLine("public BazInner1 InnerProp1 { get; }")));
|
||||||
|
|
||||||
|
source += @"
|
||||||
|
namespace Test
|
||||||
|
{
|
||||||
|
public class FooInner1 { public int Prop1 { get; } }
|
||||||
|
|
||||||
|
public class FooInner2 { public int Prop1 { get; } }
|
||||||
|
|
||||||
|
[MapTo.MapFrom(typeof(FooInner2))]
|
||||||
|
public partial class BazInner1 { public int Prop1 { get; }}
|
||||||
|
}
|
||||||
|
".Trim();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var expectedError = DiagnosticProvider.NoMatchingPropertyTypeFoundError(GetSourcePropertySymbol("InnerProp1", compilation));
|
||||||
|
diagnostics.ShouldBeUnsuccessful(expectedError);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
Reference in New Issue