Fix semantic model for nested objects.

This commit is contained in:
Mohammadreza Taikandi 2021-02-07 10:49:42 +00:00
parent 4706bc22ef
commit a54b15942a
3 changed files with 122 additions and 12 deletions

View File

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

View File

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

View File

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