Move to MapTypeConverter.

This commit is contained in:
Mohammadreza Taikandi 2021-01-21 16:41:12 +00:00
parent dd9254ab74
commit 0fc9540c58
8 changed files with 53 additions and 53 deletions

View File

@ -23,7 +23,7 @@ namespace MapTo
Create($"{ErrorId}030", location, "Type Mismatch", $"No matching properties found between '{classType.ToDisplayString()}' and '{sourceType.ToDisplayString()}' types.");
internal static Diagnostic NoMatchingPropertyTypeFoundError(IPropertySymbol property) =>
Create($"{ErrorId}031", property.Locations.FirstOrDefault(), "Type Mismatch", $"Cannot create a map for '{property.ToDisplayString()}' property because source and destination types are not implicitly convertible. Consider using '{RootNamespace}.{MapPropertyAttributeSource.AttributeName}Attribute' to provide a type converter or ignore the property using '{RootNamespace}.{IgnorePropertyAttributeSource.AttributeName}Attribute'.");
Create($"{ErrorId}031", property.Locations.FirstOrDefault(), "Type Mismatch", $"Cannot create a map for '{property.ToDisplayString()}' property because source and destination types are not implicitly convertible. Consider using '{MapTypeConverterAttributeSource.FullyQualifiedName}' to provide a type converter or ignore the property using '{RootNamespace}.{IgnorePropertyAttributeSource.AttributeName}Attribute'.");
internal static Diagnostic InvalidTypeConverterGenericTypesError(IPropertySymbol property, IPropertySymbol sourceProperty) =>
Create($"{ErrorId}032", property.Locations.FirstOrDefault(), "Type Mismatch", $"Cannot map '{property.ToDisplayString()}' property because the annotated converter does not implement '{RootNamespace}.{TypeConverterSource.InterfaceName}<{sourceProperty.Type.ToDisplayString()}, {property.Type.ToDisplayString()}>'.");

View File

@ -42,6 +42,9 @@ namespace MapTo.Extensions
public static IEnumerable<AttributeData> GetAttributes(this ISymbol symbol, ITypeSymbol attributeSymbol) =>
symbol.GetAttributes().Where(a => a.AttributeClass?.Equals(attributeSymbol, SymbolEqualityComparer.Default) == true);
public static AttributeData? GetAttribute(this ISymbol symbol, ITypeSymbol attributeSymbol) =>
symbol.GetAttributes(attributeSymbol).FirstOrDefault();
public static string? GetNamespace(this ClassDeclarationSyntax classDeclarationSyntax)
{
return classDeclarationSyntax.Ancestors()

View File

@ -25,7 +25,7 @@ namespace MapTo
.AddSource(ref context, MapFromAttributeSource.Generate(options))
.AddSource(ref context, IgnorePropertyAttributeSource.Generate(options))
.AddSource(ref context, TypeConverterSource.Generate(options))
.AddSource(ref context, MapPropertyAttributeSource.Generate(options));
.AddSource(ref context, MapTypeConverterAttributeSource.Generate(options));
if (context.SyntaxReceiver is MapToSyntaxReceiver receiver && receiver.CandidateClasses.Any())
{

View File

@ -21,14 +21,14 @@ namespace MapTo
IgnorePropertyAttributeTypeSymbol = compilation.GetTypeByMetadataName(IgnorePropertyAttributeSource.FullyQualifiedName)
?? throw new TypeLoadException($"Unable to find '{IgnorePropertyAttributeSource.FullyQualifiedName}' type.");
MapPropertyAttributeTypeSymbol = compilation.GetTypeByMetadataName(MapPropertyAttributeSource.FullyQualifiedName)
?? throw new TypeLoadException($"Unable to find '{MapPropertyAttributeSource.FullyQualifiedName}' type.");
MapTypeConverterAttributeTypeSymbol = compilation.GetTypeByMetadataName(MapTypeConverterAttributeSource.FullyQualifiedName)
?? throw new TypeLoadException($"Unable to find '{MapTypeConverterAttributeSource.FullyQualifiedName}' type.");
TypeConverterInterfaceTypeSymbol = compilation.GetTypeByMetadataName(TypeConverterSource.FullyQualifiedName)
?? throw new TypeLoadException($"Unable to find '{TypeConverterSource.FullyQualifiedName}' type.");
}
public INamedTypeSymbol MapPropertyAttributeTypeSymbol { get; }
public INamedTypeSymbol MapTypeConverterAttributeTypeSymbol { get; }
public INamedTypeSymbol TypeConverterInterfaceTypeSymbol { get; }
@ -90,7 +90,7 @@ namespace MapTo
?.DescendantNodes()
.OfType<TypeOfExpressionSyntax>()
.SingleOrDefault();
return sourceTypeExpressionSyntax is not null ? semanticModel.GetTypeInfo(sourceTypeExpressionSyntax.Type).Type as INamedTypeSymbol : null;
}
@ -114,37 +114,28 @@ namespace MapTo
}
string? converterFullyQualifiedName = null;
if (!SymbolEqualityComparer.Default.Equals(property.Type, sourceProperty.Type))
if (!SymbolEqualityComparer.Default.Equals(property.Type, sourceProperty.Type) && !context.Compilation.HasImplicitConversion(sourceProperty.Type, property.Type))
{
var conversionClassification = context.Compilation.ClassifyCommonConversion(sourceProperty.Type, property.Type);
if (!conversionClassification.Exists || !conversionClassification.IsImplicit)
var converterTypeSymbol = property.GetAttribute(context.MapTypeConverterAttributeTypeSymbol)?.ConstructorArguments.First().Value as INamedTypeSymbol;
if (converterTypeSymbol is null)
{
var mapPropertyAttribute = property.GetAttributes(context.MapPropertyAttributeTypeSymbol)
.FirstOrDefault(a => a.NamedArguments.Any(na => na.Key == MapPropertyAttributeSource.ConverterPropertyName));
var converterTypeSymbol = mapPropertyAttribute?.NamedArguments
.SingleOrDefault(na => na.Key == MapPropertyAttributeSource.ConverterPropertyName).Value.Value as INamedTypeSymbol;
if (mapPropertyAttribute is null || converterTypeSymbol is null)
{
context.ReportDiagnostic(DiagnosticProvider.NoMatchingPropertyTypeFoundError(property));
continue;
}
var baseInterface = converterTypeSymbol.AllInterfaces
.SingleOrDefault(i => SymbolEqualityComparer.Default.Equals(i.ConstructedFrom, context.TypeConverterInterfaceTypeSymbol) &&
i.TypeArguments.Length == 2 &&
SymbolEqualityComparer.Default.Equals(sourceProperty.Type, i.TypeArguments[0]) &&
SymbolEqualityComparer.Default.Equals(property.Type, i.TypeArguments[1]));
if (baseInterface is null)
{
context.ReportDiagnostic(DiagnosticProvider.InvalidTypeConverterGenericTypesError(property, sourceProperty));
continue;
}
converterFullyQualifiedName = converterTypeSymbol.ToDisplayString();
context.ReportDiagnostic(DiagnosticProvider.NoMatchingPropertyTypeFoundError(property));
continue;
}
var baseInterface = converterTypeSymbol.AllInterfaces
.SingleOrDefault(i => SymbolEqualityComparer.Default.Equals(i.ConstructedFrom, context.TypeConverterInterfaceTypeSymbol) &&
i.TypeArguments.Length == 2 &&
SymbolEqualityComparer.Default.Equals(sourceProperty.Type, i.TypeArguments[0]) &&
SymbolEqualityComparer.Default.Equals(property.Type, i.TypeArguments[1]));
if (baseInterface is null)
{
context.ReportDiagnostic(DiagnosticProvider.InvalidTypeConverterGenericTypesError(property, sourceProperty));
continue;
}
converterFullyQualifiedName = converterTypeSymbol.ToDisplayString();
}
mappedProperties.Add(new MappedProperty(property.Name, converterFullyQualifiedName));

View File

@ -16,7 +16,7 @@ namespace MapTo
);
}
internal record MappedProperty(string Name, string? ConverterFullyQualifiedName);
internal record MappedProperty(string Name, string? TypeConverter);
internal record MappingModel (
SourceGenerationOptions Options,

View File

@ -66,9 +66,9 @@ namespace MapTo.Sources
foreach (var property in model.MappedProperties)
{
if (property.ConverterFullyQualifiedName is not null)
if (property.TypeConverter is not null)
{
builder.WriteLine($"{property.Name} = new {property.ConverterFullyQualifiedName}().Convert({sourceClassParameterName}.{property.Name});");
builder.WriteLine($"{property.Name} = new {property.TypeConverter}().Convert({sourceClassParameterName}.{property.Name});");
}
else
{

View File

@ -3,10 +3,11 @@ using static MapTo.Sources.Constants;
namespace MapTo.Sources
{
internal static class MapPropertyAttributeSource
internal static class MapTypeConverterAttributeSource
{
internal const string AttributeName = "MapProperty";
internal const string FullyQualifiedName = RootNamespace + "." + AttributeName + "Attribute";
internal const string AttributeName = "MapTypeConverter";
internal const string AttributeClassName = AttributeName + "Attribute";
internal const string FullyQualifiedName = RootNamespace + "." + AttributeClassName;
internal const string ConverterPropertyName = "Converter";
internal static SourceCode Generate(SourceGenerationOptions options)
@ -28,19 +29,22 @@ namespace MapTo.Sources
builder
.WriteLine("[AttributeUsage(AttributeTargets.Property, AllowMultiple = true)]")
.WriteLine($"public sealed class {AttributeName}Attribute : Attribute")
.WriteLine($"public sealed class {AttributeClassName} : Attribute")
.WriteOpeningBracket();
if (options.GenerateXmlDocument)
{
builder
.WriteLine("/// <summary>")
.WriteLine("/// Initializes a new instance of <see cref=\"MapPropertyAttribute\"/>.")
.WriteLine($"/// Initializes a new instance of <see cref=\"{AttributeClassName}\"/>.")
.WriteLine("/// </summary>");
}
builder
.WriteLine($"public {AttributeName}Attribute() {{ }}")
.WriteLine($"public {AttributeClassName}(Type converter)")
.WriteOpeningBracket()
.WriteLine($"{ConverterPropertyName} = converter;")
.WriteClosingBracket()
.WriteLine();
if (options.GenerateXmlDocument)
@ -52,11 +56,11 @@ namespace MapTo.Sources
}
builder
.WriteLine($"public Type {ConverterPropertyName} {{ get; set; }}")
.WriteLine($"public Type {ConverterPropertyName} {{ get; }}")
.WriteClosingBracket()
.WriteClosingBracket();
return new(builder.ToString(), $"{AttributeName}Attribute.g.cs");
return new(builder.ToString(), $"{AttributeClassName}.g.cs");
}
}
}

View File

@ -517,7 +517,7 @@ namespace MapTo
}
[Fact]
public void VerifyMapPropertyAttribute()
public void VerifyMapTypeConverterAttribute()
{
// Arrange
const string source = "";
@ -528,11 +528,14 @@ using System;
namespace MapTo
{{
[AttributeUsage(AttributeTargets.Property, AllowMultiple = true)]
public sealed class MapPropertyAttribute : Attribute
public sealed class MapTypeConverterAttribute : Attribute
{{
public MapPropertyAttribute() {{ }}
public MapTypeConverterAttribute(Type converter)
{{
Converter = converter;
}}
public Type Converter {{ get; set; }}
public Type Converter {{ get; }}
}}
}}
".Trim();
@ -542,7 +545,7 @@ namespace MapTo
// Assert
diagnostics.ShouldBeSuccessful();
compilation.SyntaxTrees.ShouldContainSource(MapPropertyAttributeSource.AttributeName, expectedInterface);
compilation.SyntaxTrees.ShouldContainSource(MapTypeConverterAttributeSource.AttributeName, expectedInterface);
}
[Fact]
@ -591,8 +594,7 @@ namespace MapTo
builder
.PadLeft(Indent2).AppendLine("[IgnoreProperty]")
.PadLeft(Indent2).AppendLine("public long IgnoreMe { get; set; }")
.PadLeft(Indent2).AppendLine("[MapProperty]")
.PadLeft(Indent2).AppendLine("[MapProperty(Converter = typeof(Prop4Converter))]")
.PadLeft(Indent2).AppendLine("[MapTypeConverter(typeof(Prop4Converter))]")
.PadLeft(Indent2).AppendLine("public long Prop4 { get; set; }");
},
SourcePropertyBuilder: builder => builder.PadLeft(Indent2).AppendLine("public string Prop4 { get; set; }")));
@ -626,7 +628,7 @@ namespace Test
PropertyBuilder: builder =>
{
builder
.PadLeft(Indent2).AppendLine("[MapProperty(Converter = typeof(Prop4Converter))]")
.PadLeft(Indent2).AppendLine("[MapTypeConverter(typeof(Prop4Converter))]")
.PadLeft(Indent2).AppendLine("public long Prop4 { get; set; }");
},
SourcePropertyBuilder: builder => builder.PadLeft(Indent2).AppendLine("public string Prop4 { get; set; }")));