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 _mapPropertyAttributeTypeSymbol;
|
||||
private readonly INamedTypeSymbol _mapTypeConverterAttributeTypeSymbol;
|
||||
private readonly SemanticModel _semanticModel;
|
||||
private readonly SourceGenerationOptions _sourceGenerationOptions;
|
||||
private readonly INamedTypeSymbol _typeConverterInterfaceTypeSymbol;
|
||||
|
||||
|
@ -26,7 +25,6 @@ namespace MapTo
|
|||
_sourceGenerationOptions = sourceGenerationOptions;
|
||||
_classSyntax = classSyntax;
|
||||
_compilation = compilation;
|
||||
_semanticModel = _compilation.GetSemanticModel(_classSyntax.SyntaxTree);
|
||||
|
||||
_ignorePropertyAttributeTypeSymbol = compilation.GetTypeByMetadataNameOrThrow(IgnorePropertyAttributeSource.FullyQualifiedName);
|
||||
_mapTypeConverterAttributeTypeSymbol = compilation.GetTypeByMetadataNameOrThrow(MapTypeConverterAttributeSource.FullyQualifiedName);
|
||||
|
@ -43,13 +41,14 @@ namespace MapTo
|
|||
|
||||
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));
|
||||
return;
|
||||
}
|
||||
|
||||
var sourceTypeSymbol = GetSourceTypeSymbol(_classSyntax);
|
||||
var sourceTypeSymbol = GetSourceTypeSymbol(_classSyntax, semanticModel);
|
||||
if (sourceTypeSymbol is null)
|
||||
{
|
||||
ReportDiagnostic(DiagnosticProvider.MapFromAttributeNotFoundError(_classSyntax.GetLocation()));
|
||||
|
@ -132,8 +131,7 @@ namespace MapTo
|
|||
return false;
|
||||
}
|
||||
|
||||
var nestedAttributeSyntax = nestedSourceMapFromAttribute.ApplicationSyntaxReference?.GetSyntax() as AttributeSyntax;
|
||||
if (nestedAttributeSyntax is null)
|
||||
if (!(nestedSourceMapFromAttribute.ApplicationSyntaxReference?.GetSyntax() is AttributeSyntax nestedAttributeSyntax))
|
||||
{
|
||||
ReportDiagnostic(DiagnosticProvider.NoMatchingPropertyTypeFoundError(property));
|
||||
return false;
|
||||
|
@ -212,17 +210,23 @@ namespace MapTo
|
|||
Diagnostics = Diagnostics.Add(diagnostic);
|
||||
}
|
||||
|
||||
private INamedTypeSymbol? GetSourceTypeSymbol(ClassDeclarationSyntax classDeclarationSyntax) =>
|
||||
GetSourceTypeSymbol(classDeclarationSyntax.GetAttribute(MapFromAttributeSource.AttributeName));
|
||||
private INamedTypeSymbol? GetSourceTypeSymbol(ClassDeclarationSyntax classDeclarationSyntax, SemanticModel? semanticModel = null) =>
|
||||
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
|
||||
?.DescendantNodes()
|
||||
.DescendantNodes()
|
||||
.OfType<TypeOfExpressionSyntax>()
|
||||
.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"
|
||||
: $"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