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.");
|
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()}>'.");
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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())
|
||||||
{
|
{
|
||||||
|
|
|
@ -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));
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
{
|
{
|
||||||
|
|
|
@ -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");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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; }")));
|
||||||
|
|
Loading…
Reference in New Issue