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."); Create($"{ErrorId}030", location, "Type Mismatch", $"No matching properties found between '{classType.ToDisplayString()}' and '{sourceType.ToDisplayString()}' types.");
internal static Diagnostic NoMatchingPropertyTypeFoundError(IPropertySymbol property) => 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) => 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()}>'."); 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) => public static IEnumerable<AttributeData> GetAttributes(this ISymbol symbol, ITypeSymbol attributeSymbol) =>
symbol.GetAttributes().Where(a => a.AttributeClass?.Equals(attributeSymbol, SymbolEqualityComparer.Default) == true); 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) public static string? GetNamespace(this ClassDeclarationSyntax classDeclarationSyntax)
{ {
return classDeclarationSyntax.Ancestors() return classDeclarationSyntax.Ancestors()

View File

@ -25,7 +25,7 @@ namespace MapTo
.AddSource(ref context, MapFromAttributeSource.Generate(options)) .AddSource(ref context, MapFromAttributeSource.Generate(options))
.AddSource(ref context, IgnorePropertyAttributeSource.Generate(options)) .AddSource(ref context, IgnorePropertyAttributeSource.Generate(options))
.AddSource(ref context, TypeConverterSource.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()) if (context.SyntaxReceiver is MapToSyntaxReceiver receiver && receiver.CandidateClasses.Any())
{ {

View File

@ -21,14 +21,14 @@ namespace MapTo
IgnorePropertyAttributeTypeSymbol = compilation.GetTypeByMetadataName(IgnorePropertyAttributeSource.FullyQualifiedName) IgnorePropertyAttributeTypeSymbol = compilation.GetTypeByMetadataName(IgnorePropertyAttributeSource.FullyQualifiedName)
?? throw new TypeLoadException($"Unable to find '{IgnorePropertyAttributeSource.FullyQualifiedName}' type."); ?? throw new TypeLoadException($"Unable to find '{IgnorePropertyAttributeSource.FullyQualifiedName}' type.");
MapPropertyAttributeTypeSymbol = compilation.GetTypeByMetadataName(MapPropertyAttributeSource.FullyQualifiedName) MapTypeConverterAttributeTypeSymbol = compilation.GetTypeByMetadataName(MapTypeConverterAttributeSource.FullyQualifiedName)
?? throw new TypeLoadException($"Unable to find '{MapPropertyAttributeSource.FullyQualifiedName}' type."); ?? throw new TypeLoadException($"Unable to find '{MapTypeConverterAttributeSource.FullyQualifiedName}' type.");
TypeConverterInterfaceTypeSymbol = compilation.GetTypeByMetadataName(TypeConverterSource.FullyQualifiedName) TypeConverterInterfaceTypeSymbol = compilation.GetTypeByMetadataName(TypeConverterSource.FullyQualifiedName)
?? throw new TypeLoadException($"Unable to find '{TypeConverterSource.FullyQualifiedName}' type."); ?? throw new TypeLoadException($"Unable to find '{TypeConverterSource.FullyQualifiedName}' type.");
} }
public INamedTypeSymbol MapPropertyAttributeTypeSymbol { get; } public INamedTypeSymbol MapTypeConverterAttributeTypeSymbol { get; }
public INamedTypeSymbol TypeConverterInterfaceTypeSymbol { get; } public INamedTypeSymbol TypeConverterInterfaceTypeSymbol { get; }
@ -90,7 +90,7 @@ namespace MapTo
?.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;
} }
@ -114,37 +114,28 @@ namespace MapTo
} }
string? converterFullyQualifiedName = null; 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); var converterTypeSymbol = property.GetAttribute(context.MapTypeConverterAttributeTypeSymbol)?.ConstructorArguments.First().Value as INamedTypeSymbol;
if (!conversionClassification.Exists || !conversionClassification.IsImplicit) if (converterTypeSymbol is null)
{ {
var mapPropertyAttribute = property.GetAttributes(context.MapPropertyAttributeTypeSymbol) context.ReportDiagnostic(DiagnosticProvider.NoMatchingPropertyTypeFoundError(property));
.FirstOrDefault(a => a.NamedArguments.Any(na => na.Key == MapPropertyAttributeSource.ConverterPropertyName)); continue;
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();
} }
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)); 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 ( internal record MappingModel (
SourceGenerationOptions Options, SourceGenerationOptions Options,

View File

@ -66,9 +66,9 @@ namespace MapTo.Sources
foreach (var property in model.MappedProperties) 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 else
{ {

View File

@ -3,10 +3,11 @@ using static MapTo.Sources.Constants;
namespace MapTo.Sources namespace MapTo.Sources
{ {
internal static class MapPropertyAttributeSource internal static class MapTypeConverterAttributeSource
{ {
internal const string AttributeName = "MapProperty"; internal const string AttributeName = "MapTypeConverter";
internal const string FullyQualifiedName = RootNamespace + "." + AttributeName + "Attribute"; internal const string AttributeClassName = AttributeName + "Attribute";
internal const string FullyQualifiedName = RootNamespace + "." + AttributeClassName;
internal const string ConverterPropertyName = "Converter"; internal const string ConverterPropertyName = "Converter";
internal static SourceCode Generate(SourceGenerationOptions options) internal static SourceCode Generate(SourceGenerationOptions options)
@ -28,19 +29,22 @@ namespace MapTo.Sources
builder builder
.WriteLine("[AttributeUsage(AttributeTargets.Property, AllowMultiple = true)]") .WriteLine("[AttributeUsage(AttributeTargets.Property, AllowMultiple = true)]")
.WriteLine($"public sealed class {AttributeName}Attribute : Attribute") .WriteLine($"public sealed class {AttributeClassName} : Attribute")
.WriteOpeningBracket(); .WriteOpeningBracket();
if (options.GenerateXmlDocument) if (options.GenerateXmlDocument)
{ {
builder builder
.WriteLine("/// <summary>") .WriteLine("/// <summary>")
.WriteLine("/// Initializes a new instance of <see cref=\"MapPropertyAttribute\"/>.") .WriteLine($"/// Initializes a new instance of <see cref=\"{AttributeClassName}\"/>.")
.WriteLine("/// </summary>"); .WriteLine("/// </summary>");
} }
builder builder
.WriteLine($"public {AttributeName}Attribute() {{ }}") .WriteLine($"public {AttributeClassName}(Type converter)")
.WriteOpeningBracket()
.WriteLine($"{ConverterPropertyName} = converter;")
.WriteClosingBracket()
.WriteLine(); .WriteLine();
if (options.GenerateXmlDocument) if (options.GenerateXmlDocument)
@ -52,11 +56,11 @@ namespace MapTo.Sources
} }
builder builder
.WriteLine($"public Type {ConverterPropertyName} {{ get; set; }}") .WriteLine($"public Type {ConverterPropertyName} {{ get; }}")
.WriteClosingBracket() .WriteClosingBracket()
.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] [Fact]
public void VerifyMapPropertyAttribute() public void VerifyMapTypeConverterAttribute()
{ {
// Arrange // Arrange
const string source = ""; const string source = "";
@ -528,11 +528,14 @@ using System;
namespace MapTo namespace MapTo
{{ {{
[AttributeUsage(AttributeTargets.Property, AllowMultiple = true)] [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(); ".Trim();
@ -542,7 +545,7 @@ namespace MapTo
// Assert // Assert
diagnostics.ShouldBeSuccessful(); diagnostics.ShouldBeSuccessful();
compilation.SyntaxTrees.ShouldContainSource(MapPropertyAttributeSource.AttributeName, expectedInterface); compilation.SyntaxTrees.ShouldContainSource(MapTypeConverterAttributeSource.AttributeName, expectedInterface);
} }
[Fact] [Fact]
@ -591,8 +594,7 @@ namespace MapTo
builder builder
.PadLeft(Indent2).AppendLine("[IgnoreProperty]") .PadLeft(Indent2).AppendLine("[IgnoreProperty]")
.PadLeft(Indent2).AppendLine("public long IgnoreMe { get; set; }") .PadLeft(Indent2).AppendLine("public long IgnoreMe { get; set; }")
.PadLeft(Indent2).AppendLine("[MapProperty]") .PadLeft(Indent2).AppendLine("[MapTypeConverter(typeof(Prop4Converter))]")
.PadLeft(Indent2).AppendLine("[MapProperty(Converter = typeof(Prop4Converter))]")
.PadLeft(Indent2).AppendLine("public long Prop4 { get; set; }"); .PadLeft(Indent2).AppendLine("public long Prop4 { get; set; }");
}, },
SourcePropertyBuilder: builder => builder.PadLeft(Indent2).AppendLine("public string Prop4 { get; set; }"))); SourcePropertyBuilder: builder => builder.PadLeft(Indent2).AppendLine("public string Prop4 { get; set; }")));
@ -626,7 +628,7 @@ namespace Test
PropertyBuilder: builder => PropertyBuilder: builder =>
{ {
builder builder
.PadLeft(Indent2).AppendLine("[MapProperty(Converter = typeof(Prop4Converter))]") .PadLeft(Indent2).AppendLine("[MapTypeConverter(typeof(Prop4Converter))]")
.PadLeft(Indent2).AppendLine("public long Prop4 { get; set; }"); .PadLeft(Indent2).AppendLine("public long Prop4 { get; set; }");
}, },
SourcePropertyBuilder: builder => builder.PadLeft(Indent2).AppendLine("public string Prop4 { get; set; }"))); SourcePropertyBuilder: builder => builder.PadLeft(Indent2).AppendLine("public string Prop4 { get; set; }")));