Fix semantic model for nested objects.
This commit is contained in:
parent
4706bc22ef
commit
a54b15942a
|
@ -16,7 +16,6 @@ namespace MapTo
|
||||||
private readonly INamedTypeSymbol _mapFromAttributeTypeSymbol;
|
private readonly INamedTypeSymbol _mapFromAttributeTypeSymbol;
|
||||||
private readonly INamedTypeSymbol _mapPropertyAttributeTypeSymbol;
|
private readonly INamedTypeSymbol _mapPropertyAttributeTypeSymbol;
|
||||||
private readonly INamedTypeSymbol _mapTypeConverterAttributeTypeSymbol;
|
private readonly INamedTypeSymbol _mapTypeConverterAttributeTypeSymbol;
|
||||||
private readonly SemanticModel _semanticModel;
|
|
||||||
private readonly SourceGenerationOptions _sourceGenerationOptions;
|
private readonly SourceGenerationOptions _sourceGenerationOptions;
|
||||||
private readonly INamedTypeSymbol _typeConverterInterfaceTypeSymbol;
|
private readonly INamedTypeSymbol _typeConverterInterfaceTypeSymbol;
|
||||||
|
|
||||||
|
@ -26,7 +25,6 @@ namespace MapTo
|
||||||
_sourceGenerationOptions = sourceGenerationOptions;
|
_sourceGenerationOptions = sourceGenerationOptions;
|
||||||
_classSyntax = classSyntax;
|
_classSyntax = classSyntax;
|
||||||
_compilation = compilation;
|
_compilation = compilation;
|
||||||
_semanticModel = _compilation.GetSemanticModel(_classSyntax.SyntaxTree);
|
|
||||||
|
|
||||||
_ignorePropertyAttributeTypeSymbol = compilation.GetTypeByMetadataNameOrThrow(IgnorePropertyAttributeSource.FullyQualifiedName);
|
_ignorePropertyAttributeTypeSymbol = compilation.GetTypeByMetadataNameOrThrow(IgnorePropertyAttributeSource.FullyQualifiedName);
|
||||||
_mapTypeConverterAttributeTypeSymbol = compilation.GetTypeByMetadataNameOrThrow(MapTypeConverterAttributeSource.FullyQualifiedName);
|
_mapTypeConverterAttributeTypeSymbol = compilation.GetTypeByMetadataNameOrThrow(MapTypeConverterAttributeSource.FullyQualifiedName);
|
||||||
|
@ -43,13 +41,14 @@ namespace MapTo
|
||||||
|
|
||||||
private void Initialize()
|
private void Initialize()
|
||||||
{
|
{
|
||||||
if (!(_semanticModel.GetDeclaredSymbol(_classSyntax) is INamedTypeSymbol classTypeSymbol))
|
var semanticModel = _compilation.GetSemanticModel(_classSyntax.SyntaxTree);
|
||||||
|
if (!(semanticModel.GetDeclaredSymbol(_classSyntax) is INamedTypeSymbol classTypeSymbol))
|
||||||
{
|
{
|
||||||
ReportDiagnostic(DiagnosticProvider.TypeNotFoundError(_classSyntax.GetLocation(), _classSyntax.Identifier.ValueText));
|
ReportDiagnostic(DiagnosticProvider.TypeNotFoundError(_classSyntax.GetLocation(), _classSyntax.Identifier.ValueText));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var sourceTypeSymbol = GetSourceTypeSymbol(_classSyntax);
|
var sourceTypeSymbol = GetSourceTypeSymbol(_classSyntax, semanticModel);
|
||||||
if (sourceTypeSymbol is null)
|
if (sourceTypeSymbol is null)
|
||||||
{
|
{
|
||||||
ReportDiagnostic(DiagnosticProvider.MapFromAttributeNotFoundError(_classSyntax.GetLocation()));
|
ReportDiagnostic(DiagnosticProvider.MapFromAttributeNotFoundError(_classSyntax.GetLocation()));
|
||||||
|
@ -132,8 +131,7 @@ namespace MapTo
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
var nestedAttributeSyntax = nestedSourceMapFromAttribute.ApplicationSyntaxReference?.GetSyntax() as AttributeSyntax;
|
if (!(nestedSourceMapFromAttribute.ApplicationSyntaxReference?.GetSyntax() is AttributeSyntax nestedAttributeSyntax))
|
||||||
if (nestedAttributeSyntax is null)
|
|
||||||
{
|
{
|
||||||
ReportDiagnostic(DiagnosticProvider.NoMatchingPropertyTypeFoundError(property));
|
ReportDiagnostic(DiagnosticProvider.NoMatchingPropertyTypeFoundError(property));
|
||||||
return false;
|
return false;
|
||||||
|
@ -212,17 +210,23 @@ namespace MapTo
|
||||||
Diagnostics = Diagnostics.Add(diagnostic);
|
Diagnostics = Diagnostics.Add(diagnostic);
|
||||||
}
|
}
|
||||||
|
|
||||||
private INamedTypeSymbol? GetSourceTypeSymbol(ClassDeclarationSyntax classDeclarationSyntax) =>
|
private INamedTypeSymbol? GetSourceTypeSymbol(ClassDeclarationSyntax classDeclarationSyntax, SemanticModel? semanticModel = null) =>
|
||||||
GetSourceTypeSymbol(classDeclarationSyntax.GetAttribute(MapFromAttributeSource.AttributeName));
|
GetSourceTypeSymbol(classDeclarationSyntax.GetAttribute(MapFromAttributeSource.AttributeName), semanticModel);
|
||||||
|
|
||||||
private INamedTypeSymbol? GetSourceTypeSymbol(AttributeSyntax? attributeSyntax)
|
private INamedTypeSymbol? GetSourceTypeSymbol(AttributeSyntax? attributeSyntax, SemanticModel? semanticModel = null)
|
||||||
{
|
{
|
||||||
|
if (attributeSyntax is null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
semanticModel ??= _compilation.GetSemanticModel(attributeSyntax.SyntaxTree);
|
||||||
var sourceTypeExpressionSyntax = attributeSyntax
|
var sourceTypeExpressionSyntax = attributeSyntax
|
||||||
?.DescendantNodes()
|
.DescendantNodes()
|
||||||
.OfType<TypeOfExpressionSyntax>()
|
.OfType<TypeOfExpressionSyntax>()
|
||||||
.SingleOrDefault();
|
.SingleOrDefault();
|
||||||
|
|
||||||
return sourceTypeExpressionSyntax is not null ? _semanticModel.GetTypeInfo(sourceTypeExpressionSyntax.Type).Type as INamedTypeSymbol : null;
|
return sourceTypeExpressionSyntax is not null ? semanticModel.GetTypeInfo(sourceTypeExpressionSyntax.Type).Type as INamedTypeSymbol : null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -80,7 +80,7 @@ namespace MapTo.Sources
|
||||||
? "null"
|
? "null"
|
||||||
: $"new object[] {{ {string.Join(", ", property.TypeConverterParameters)} }}";
|
: $"new object[] {{ {string.Join(", ", property.TypeConverterParameters)} }}";
|
||||||
|
|
||||||
builder.WriteLine($"{property.Name} = new {property.TypeConverter}().Convert({sourceClassParameterName}.{property.Name}, {parameters});");
|
builder.WriteLine($"{property.Name} = new {property.TypeConverter}().Convert({sourceClassParameterName}.{property.SourcePropertyName}, {parameters});");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,106 @@
|
||||||
|
using MapTo.Tests.Extensions;
|
||||||
|
using MapTo.Tests.Infrastructure;
|
||||||
|
using Xunit;
|
||||||
|
using Xunit.Abstractions;
|
||||||
|
using static MapTo.Tests.Common;
|
||||||
|
|
||||||
|
namespace MapTo.Tests
|
||||||
|
{
|
||||||
|
public class MappedClassesTests
|
||||||
|
{
|
||||||
|
private readonly ITestOutputHelper _output;
|
||||||
|
|
||||||
|
public MappedClassesTests(ITestOutputHelper output)
|
||||||
|
{
|
||||||
|
_output = output;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void VerifyMappedClassSource()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var sources = new[] { MainSourceClass, NestedSourceClass, MainDestinationClass, NestedDestinationClass };
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(sources, analyzerConfigOptions: DefaultAnalyzerOptions);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
diagnostics.ShouldBeSuccessful();
|
||||||
|
_output.WriteLine(compilation.PrintSyntaxTree());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string NestedSourceClass => @"
|
||||||
|
namespace Test.Data.Models
|
||||||
|
{
|
||||||
|
public class Profile
|
||||||
|
{
|
||||||
|
public string FirstName { get; set; }
|
||||||
|
|
||||||
|
public string LastName { get; set; }
|
||||||
|
|
||||||
|
public string FullName => $""{FirstName} {LastName}"";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
".Trim();
|
||||||
|
|
||||||
|
private static string MainSourceClass => @"
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Test.Data.Models
|
||||||
|
{
|
||||||
|
public class User
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
|
||||||
|
public DateTimeOffset RegisteredAt { get; set; }
|
||||||
|
|
||||||
|
public Profile Profile { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
".Trim();
|
||||||
|
|
||||||
|
private static string NestedDestinationClass => @"
|
||||||
|
using MapTo;
|
||||||
|
using Test.Data.Models;
|
||||||
|
|
||||||
|
namespace Test.ViewModels
|
||||||
|
{
|
||||||
|
[MapFrom(typeof(Profile))]
|
||||||
|
public partial class ProfileViewModel
|
||||||
|
{
|
||||||
|
public string FirstName { get; }
|
||||||
|
|
||||||
|
public string LastName { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
".Trim();
|
||||||
|
|
||||||
|
|
||||||
|
private static string MainDestinationClass => @"
|
||||||
|
using System;
|
||||||
|
using MapTo;
|
||||||
|
using Test.Data.Models;
|
||||||
|
|
||||||
|
namespace Test.ViewModels
|
||||||
|
{
|
||||||
|
[MapFrom(typeof(User))]
|
||||||
|
public partial class UserViewModel
|
||||||
|
{
|
||||||
|
[MapProperty(SourcePropertyName = nameof(User.Id))]
|
||||||
|
[MapTypeConverter(typeof(IdConverter))]
|
||||||
|
public string Key { get; }
|
||||||
|
|
||||||
|
public DateTimeOffset RegisteredAt { get; set; }
|
||||||
|
|
||||||
|
// [IgnoreProperty]
|
||||||
|
public ProfileViewModel Profile { get; set; }
|
||||||
|
|
||||||
|
private class IdConverter : ITypeConverter<int, string>
|
||||||
|
{
|
||||||
|
public string Convert(int source, object[] converterParameters) => $""{source:X}"";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
".Trim();
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue