Move to MapTypeConverter.
This commit is contained in:
parent
dd9254ab74
commit
0fc9540c58
|
@ -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()}>'.");
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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())
|
||||
{
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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; }")));
|
||||
|
|
Loading…
Reference in New Issue