From efdd3ada9424623e6949a175ba1a54fcc8039dbe Mon Sep 17 00:00:00 2001 From: Wvader <34067397+wvader@users.noreply.github.com> Date: Sat, 11 Dec 2021 21:06:58 +0000 Subject: [PATCH] k --- src/MapTo/ClassMappingContext.cs | 11 +-- src/MapTo/DiagnosticsFactory.cs | 8 +- src/MapTo/Extensions/CommonSource.cs | 4 +- src/MapTo/Extensions/RoslynExtensions.cs | 3 + src/MapTo/MappingContext.cs | 93 +++++++++++++++---- src/MapTo/Models.cs | 1 + src/MapTo/RecordMappingContext.cs | 4 +- .../Sources/JsonExtensionAttributeSource.cs | 40 ++++++++ src/MapTo/StructMappingContext.cs | 6 +- test/TestConsoleApp/Data/User.cs | 18 ++-- test/TestConsoleApp/Data/UserUpdateDto.cs | 16 ++-- 11 files changed, 152 insertions(+), 52 deletions(-) create mode 100644 src/MapTo/Sources/JsonExtensionAttributeSource.cs diff --git a/src/MapTo/ClassMappingContext.cs b/src/MapTo/ClassMappingContext.cs index 3abc4fc..ca66268 100644 --- a/src/MapTo/ClassMappingContext.cs +++ b/src/MapTo/ClassMappingContext.cs @@ -11,26 +11,25 @@ namespace MapTo internal ClassMappingContext(Compilation compilation, SourceGenerationOptions sourceGenerationOptions, TypeDeclarationSyntax typeSyntax) : base(compilation, sourceGenerationOptions, typeSyntax) { } - protected override ImmutableArray GetSourceMappedProperties(ITypeSymbol typeSymbol, ITypeSymbol sourceTypeSymbol, bool isInheritFromMappedBaseClass) + protected override ImmutableArray GetSourceMappedMembers(ITypeSymbol typeSymbol, ITypeSymbol sourceTypeSymbol, bool isInheritFromMappedBaseClass) { - var sourceProperties = sourceTypeSymbol.GetAllMembers().OfType().ToArray(); + var sourceProperties = sourceTypeSymbol.GetAllMembers().ToArray(); return typeSymbol .GetAllMembers(!isInheritFromMappedBaseClass) - .OfType() .Where(p => !p.HasAttribute(IgnorePropertyAttributeTypeSymbol)) .Select(property => MapProperty(sourceTypeSymbol, sourceProperties, property)) .Where(mappedProperty => mappedProperty is not null) .ToImmutableArray()!; } - protected override ImmutableArray GetTypeMappedProperties(ITypeSymbol typeSymbol, ITypeSymbol sourceTypeSymbol, bool isInheritFromMappedBaseClass) + protected override ImmutableArray GetTypeMappedMembers(ITypeSymbol typeSymbol, ITypeSymbol sourceTypeSymbol, bool isInheritFromMappedBaseClass) { - var sourceProperties = sourceTypeSymbol.GetAllMembers().OfType().ToArray(); + var sourceProperties = sourceTypeSymbol.GetAllMembers().ToArray(); + return typeSymbol .GetAllMembers() - .OfType() .Where(p => !p.HasAttribute(IgnorePropertyAttributeTypeSymbol)) .Select(property => MapProperty(typeSymbol, sourceProperties, property)) .Where(mappedProperty => mappedProperty is not null) diff --git a/src/MapTo/DiagnosticsFactory.cs b/src/MapTo/DiagnosticsFactory.cs index 3b29a8a..150fc45 100644 --- a/src/MapTo/DiagnosticsFactory.cs +++ b/src/MapTo/DiagnosticsFactory.cs @@ -27,8 +27,12 @@ namespace MapTo internal static Diagnostic NoMatchingPropertyTypeFoundError(ISymbol property) => Create($"{ErrorId}031", property.Locations.FirstOrDefault(), $"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 '{IgnorePropertyAttributeSource.FullyQualifiedName}'."); - internal static Diagnostic InvalidTypeConverterGenericTypesError(ISymbol property, IPropertySymbol sourceProperty) => - Create($"{ErrorId}032", property.Locations.FirstOrDefault(), $"Cannot map '{property.ToDisplayString()}' property because the annotated converter does not implement '{RootNamespace}.{ITypeConverterSource.InterfaceName}<{sourceProperty.Type.ToDisplayString()}, {property.GetTypeSymbol()?.ToDisplayString()}>'."); + internal static Diagnostic InvalidTypeConverterGenericTypesError(ISymbol property, ISymbol memberSymbol) + { + + return Create($"{ErrorId}032", property.Locations.FirstOrDefault(), $"Cannot map '{property.ToDisplayString()}' property because the annotated converter does not implement '{RootNamespace}.{ITypeConverterSource.InterfaceName}<{memberSymbol.ToString()}, {property.GetTypeSymbol()?.ToDisplayString()}>'."); + + } internal static Diagnostic ConfigurationParseError(string error) => Create($"{ErrorId}040", Location.None, error); diff --git a/src/MapTo/Extensions/CommonSource.cs b/src/MapTo/Extensions/CommonSource.cs index 2de8fa6..becea90 100644 --- a/src/MapTo/Extensions/CommonSource.cs +++ b/src/MapTo/Extensions/CommonSource.cs @@ -88,7 +88,7 @@ namespace MapTo.Extensions return builder.WriteClosingBracket(); } - private static bool IsMappedProperty(this System.Collections.Immutable.ImmutableArray properties, MappedProperty property) { + private static bool IsMappedProperty(this ImmutableArray properties, MappedProperty property) { foreach(var prop in properties) { @@ -98,7 +98,7 @@ namespace MapTo.Extensions return false; } - private static SourceBuilder TryWriteProperties(this SourceBuilder builder, System.Collections.Immutable.ImmutableArray properties, System.Collections.Immutable.ImmutableArray? otherProperties, + private static SourceBuilder TryWriteProperties(this SourceBuilder builder, ImmutableArray properties, System.Collections.Immutable.ImmutableArray? otherProperties, string? sourceClassParameterName, string mappingContextParameterName, bool fromUpdate) { if (fromUpdate) diff --git a/src/MapTo/Extensions/RoslynExtensions.cs b/src/MapTo/Extensions/RoslynExtensions.cs index edae4c8..73d8a3b 100644 --- a/src/MapTo/Extensions/RoslynExtensions.cs +++ b/src/MapTo/Extensions/RoslynExtensions.cs @@ -67,6 +67,9 @@ namespace MapTo.Extensions case IPropertySymbol propertySymbol: typeSymbol = propertySymbol.Type; return true; + case IFieldSymbol fieldSymbol: + typeSymbol = fieldSymbol.Type; + return true; case IParameterSymbol parameterSymbol: typeSymbol = parameterSymbol.Type; diff --git a/src/MapTo/MappingContext.cs b/src/MapTo/MappingContext.cs index 892e2c5..99fe97e 100644 --- a/src/MapTo/MappingContext.cs +++ b/src/MapTo/MappingContext.cs @@ -34,6 +34,8 @@ namespace MapTo MapPropertyAttributeTypeSymbol = compilation.GetTypeByMetadataNameOrThrow(MapPropertyAttributeSource.FullyQualifiedName); MapFromAttributeTypeSymbol = compilation.GetTypeByMetadataNameOrThrow(MapFromAttributeSource.FullyQualifiedName); UseUpdateAttributeTypeSymbol = compilation.GetTypeByMetadataNameOrThrow(UseUpdateAttributeSource.FullyQualifiedName); + JsonExtensionAttributeTypeSymbol = compilation.GetTypeByMetadataNameOrThrow(UseUpdateAttributeSource.FullyQualifiedName); + MappingContextTypeSymbol = compilation.GetTypeByMetadataNameOrThrow(MappingContextSource.FullyQualifiedName); AddUsingIfRequired(sourceGenerationOptions.SupportNullableStaticAnalysis, "System.Diagnostics.CodeAnalysis"); @@ -51,6 +53,8 @@ namespace MapTo protected INamedTypeSymbol UseUpdateAttributeTypeSymbol { get; } + protected INamedTypeSymbol JsonExtensionAttributeTypeSymbol { get; } + protected INamedTypeSymbol MappingContextTypeSymbol { get; } protected INamedTypeSymbol MapPropertyAttributeTypeSymbol { get; } @@ -99,19 +103,31 @@ namespace MapTo } } - protected IPropertySymbol? FindSourceProperty(IEnumerable sourceProperties, ISymbol property) + protected ISymbol? FindSourceProperty(IEnumerable sourceMembers, ISymbol member) { - var propertyName = property + var propertyName = member .GetAttribute(MapPropertyAttributeTypeSymbol) ?.NamedArguments .SingleOrDefault(a => a.Key == MapPropertyAttributeSource.SourcePropertyNamePropertyName) - .Value.Value as string ?? property.Name; + .Value.Value as string ?? member.Name; + foreach(var sourceProperty in sourceMembers) + { + if(sourceProperty is IPropertySymbol propertySymbol) + { + if (propertySymbol.Name == propertyName) return sourceProperty; + } + if (sourceProperty is IFieldSymbol fieldSymbol) + { + if (fieldSymbol.Name == propertyName) return sourceProperty; + } - return sourceProperties.SingleOrDefault(p => p.Name == propertyName); + } + + return sourceMembers.SingleOrDefault(p => p.Name == propertyName); } - protected abstract ImmutableArray GetSourceMappedProperties(ITypeSymbol typeSymbol, ITypeSymbol sourceTypeSymbol, bool isInheritFromMappedBaseClass); - protected abstract ImmutableArray GetTypeMappedProperties(ITypeSymbol typeSymbol, ITypeSymbol sourceTypeSymbol, bool isInheritFromMappedBaseClass); + protected abstract ImmutableArray GetSourceMappedMembers(ITypeSymbol typeSymbol, ITypeSymbol sourceTypeSymbol, bool isInheritFromMappedBaseClass); + protected abstract ImmutableArray GetTypeMappedMembers(ITypeSymbol typeSymbol, ITypeSymbol sourceTypeSymbol, bool isInheritFromMappedBaseClass); protected INamedTypeSymbol? GetSourceTypeSymbol(TypeDeclarationSyntax typeDeclarationSyntax, SemanticModel? semanticModel = null) => GetSourceTypeSymbol(typeDeclarationSyntax.GetAttribute(MapFromAttributeSource.AttributeName), semanticModel); @@ -144,7 +160,12 @@ namespace MapTo return TypeSyntax.GetAttribute("UseUpdate") != null; } - protected virtual MappedProperty? MapProperty(ISymbol sourceTypeSymbol, IReadOnlyCollection sourceProperties, ISymbol property) + protected bool IsJsonExtension() + { + return TypeSyntax.GetAttribute("JsonExtension") != null; + } + + protected virtual MappedProperty? MapProperty(ISymbol sourceTypeSymbol, IReadOnlyCollection sourceProperties, ISymbol property) { var sourceProperty = FindSourceProperty(sourceProperties, property); if (sourceProperty is null || !property.TryGetTypeSymbol(out var propertyType)) @@ -171,6 +192,16 @@ namespace MapTo AddUsingIfRequired(enumerableTypeArgumentType); AddUsingIfRequired(mappedSourcePropertyType); + bool isReadOnly = false; + + if(property is IPropertySymbol pSymbol) + { + isReadOnly = pSymbol.IsReadOnly; + } + if (property is IFieldSymbol fSymbol) + { + isReadOnly = fSymbol.IsReadOnly; + } return new MappedProperty( property.Name, @@ -181,7 +212,7 @@ namespace MapTo sourceProperty.Name, ToQualifiedDisplayName(mappedSourcePropertyType), ToQualifiedDisplayName(enumerableTypeArgumentType), - (property as IPropertySymbol).IsReadOnly); + isReadOnly); ; } protected virtual MappedProperty? MapPropertySimple(ISymbol sourceTypeSymbol, ISymbol property) @@ -202,7 +233,16 @@ namespace MapTo AddUsingIfRequired(enumerableTypeArgumentType); AddUsingIfRequired(mappedSourcePropertyType); - + bool isReadOnly = false; + + if (property is IPropertySymbol pSymbol) + { + isReadOnly = pSymbol.IsReadOnly; + } + if (property is IFieldSymbol fSymbol) + { + isReadOnly = fSymbol.IsReadOnly; + } return new MappedProperty( property.Name, property.GetTypeSymbol().ToString(), @@ -212,10 +252,10 @@ namespace MapTo property.Name, ToQualifiedDisplayName(mappedSourcePropertyType), ToQualifiedDisplayName(enumerableTypeArgumentType), - (property as IPropertySymbol).IsReadOnly); + isReadOnly); ; } - protected bool TryGetMapTypeConverter(ISymbol property, IPropertySymbol sourceProperty, out string? converterFullyQualifiedName, + protected bool TryGetMapTypeConverter(ISymbol property, ISymbol sourceMember, out string? converterFullyQualifiedName, out ImmutableArray converterParameters) { converterFullyQualifiedName = null; @@ -232,10 +272,10 @@ namespace MapTo return false; } - var baseInterface = GetTypeConverterBaseInterface(converterTypeSymbol, property, sourceProperty); + var baseInterface = GetTypeConverterBaseInterface(converterTypeSymbol, property, sourceMember); if (baseInterface is null) { - AddDiagnostic(DiagnosticsFactory.InvalidTypeConverterGenericTypesError(property, sourceProperty)); + AddDiagnostic(DiagnosticsFactory.InvalidTypeConverterGenericTypesError(property, sourceMember)); return false; } @@ -310,18 +350,19 @@ namespace MapTo var sourceTypeIdentifierName = sourceTypeSymbol.Name; var isTypeInheritFromMappedBaseClass = IsTypeInheritFromMappedBaseClass(semanticModel); var isTypeUpdatable = IsTypeUpdatable(); + var isJsonExtension = IsJsonExtension(); var shouldGenerateSecondaryConstructor = ShouldGenerateSecondaryConstructor(semanticModel, sourceTypeSymbol); - var mappedProperties = GetSourceMappedProperties(typeSymbol, sourceTypeSymbol, isTypeInheritFromMappedBaseClass); - if (!mappedProperties.Any()) + var mappedProperties = GetSourceMappedMembers(typeSymbol, sourceTypeSymbol, isTypeInheritFromMappedBaseClass); + /* if (!mappedProperties.Any()) { AddDiagnostic(DiagnosticsFactory.NoMatchingPropertyFoundError(TypeSyntax.GetLocation(), typeSymbol, sourceTypeSymbol)); return null; - } + }*/ AddUsingIfRequired(mappedProperties.Any(p => p.IsEnumerable), "System.Linq"); - var allProperties = GetTypeMappedProperties(sourceTypeSymbol, typeSymbol , isTypeInheritFromMappedBaseClass); + var allProperties = GetTypeMappedMembers(sourceTypeSymbol, typeSymbol , isTypeInheritFromMappedBaseClass); return new MappingModel( SourceGenerationOptions, @@ -333,6 +374,7 @@ namespace MapTo sourceTypeIdentifierName, sourceTypeSymbol.ToDisplayString(), isTypeUpdatable, + isJsonExtension, mappedProperties, allProperties, isTypeInheritFromMappedBaseClass, @@ -342,18 +384,31 @@ namespace MapTo - private INamedTypeSymbol? GetTypeConverterBaseInterface(ITypeSymbol converterTypeSymbol, ISymbol property, IPropertySymbol sourceProperty) + private INamedTypeSymbol? GetTypeConverterBaseInterface(ITypeSymbol converterTypeSymbol, ISymbol property, ISymbol sourceMember) { if (!property.TryGetTypeSymbol(out var propertyType)) { return null; } + ISymbol? type = null; + + if(sourceMember is IPropertySymbol propertySymbol) + { + type = propertySymbol.Type; + } + + if (sourceMember is IFieldSymbol fieldSymbol) + { + type = fieldSymbol.Type; + } + + if (type == null) return null; return converterTypeSymbol.AllInterfaces .SingleOrDefault(i => i.TypeArguments.Length == 2 && SymbolEqualityComparer.Default.Equals(i.ConstructedFrom, TypeConverterInterfaceTypeSymbol) && - SymbolEqualityComparer.Default.Equals(sourceProperty.Type, i.TypeArguments[0]) && + SymbolEqualityComparer.Default.Equals(type, i.TypeArguments[0]) && SymbolEqualityComparer.Default.Equals(propertyType, i.TypeArguments[1])); } diff --git a/src/MapTo/Models.cs b/src/MapTo/Models.cs index e8997bd..8c8903a 100644 --- a/src/MapTo/Models.cs +++ b/src/MapTo/Models.cs @@ -46,6 +46,7 @@ namespace MapTo string SourceTypeIdentifierName, string SourceTypeFullName, bool IsTypeUpdatable, + bool IsJsonExtension, ImmutableArray SourceProperties, ImmutableArray TypeProperties, bool HasMappedBaseClass, diff --git a/src/MapTo/RecordMappingContext.cs b/src/MapTo/RecordMappingContext.cs index 5aab2de..5d06c44 100644 --- a/src/MapTo/RecordMappingContext.cs +++ b/src/MapTo/RecordMappingContext.cs @@ -11,7 +11,7 @@ namespace MapTo internal RecordMappingContext(Compilation compilation, SourceGenerationOptions sourceGenerationOptions, TypeDeclarationSyntax typeSyntax) : base(compilation, sourceGenerationOptions, typeSyntax) { } - protected override ImmutableArray GetSourceMappedProperties(ITypeSymbol typeSymbol, ITypeSymbol sourceTypeSymbol, bool isInheritFromMappedBaseClass) + protected override ImmutableArray GetSourceMappedMembers(ITypeSymbol typeSymbol, ITypeSymbol sourceTypeSymbol, bool isInheritFromMappedBaseClass) { var sourceProperties = sourceTypeSymbol.GetAllMembers().OfType().ToArray(); return typeSymbol.GetMembers() @@ -25,7 +25,7 @@ namespace MapTo .ToImmutableArray()!; } - protected override ImmutableArray GetTypeMappedProperties(ITypeSymbol typeSymbol, ITypeSymbol sourceTypeSymbol, bool isInheritFromMappedBaseClass) + protected override ImmutableArray GetTypeMappedMembers(ITypeSymbol typeSymbol, ITypeSymbol sourceTypeSymbol, bool isInheritFromMappedBaseClass) { var sourceProperties = sourceTypeSymbol.GetAllMembers().OfType().ToArray(); return typeSymbol.GetMembers() diff --git a/src/MapTo/Sources/JsonExtensionAttributeSource.cs b/src/MapTo/Sources/JsonExtensionAttributeSource.cs new file mode 100644 index 0000000..d7ee071 --- /dev/null +++ b/src/MapTo/Sources/JsonExtensionAttributeSource.cs @@ -0,0 +1,40 @@ +using static MapTo.Sources.Constants; + +namespace MapTo.Sources +{ + internal static class JsonExtensionAttributeSource + { + internal const string AttributeName = "JsonExtension"; + internal const string AttributeClassName = AttributeName + "Attribute"; + internal const string FullyQualifiedName = RootNamespace + "." + AttributeClassName; + + internal static SourceCode Generate(SourceGenerationOptions options) + { + using var builder = new SourceBuilder() + .WriteLine(GeneratedFilesHeader) + .WriteLine("using System;") + .WriteLine() + .WriteLine($"namespace {RootNamespace}") + .WriteOpeningBracket(); + + if (options.GenerateXmlDocument) + { + builder + .WriteLine("/// ") + .WriteLine("/// Specifies that the annotated needs ToJson method") + .WriteLine("/// "); + } + + builder + .WriteLine("[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)]") + .WriteLine($"public sealed class {AttributeName}Attribute : Attribute") + .WriteOpeningBracket(); + + builder + .WriteClosingBracket() // class + .WriteClosingBracket(); // namespace + + return new(builder.ToString(), $"{AttributeName}Attribute.g.cs"); + } + } +} \ No newline at end of file diff --git a/src/MapTo/StructMappingContext.cs b/src/MapTo/StructMappingContext.cs index 9347568..24a304b 100644 --- a/src/MapTo/StructMappingContext.cs +++ b/src/MapTo/StructMappingContext.cs @@ -11,25 +11,23 @@ namespace MapTo internal StructMappingContext(Compilation compilation, SourceGenerationOptions sourceGenerationOptions, TypeDeclarationSyntax typeSyntax) : base(compilation, sourceGenerationOptions, typeSyntax) { } - protected override ImmutableArray GetSourceMappedProperties(ITypeSymbol typeSymbol, ITypeSymbol sourceTypeSymbol, bool hasInheritedClass) + protected override ImmutableArray GetSourceMappedMembers(ITypeSymbol typeSymbol, ITypeSymbol sourceTypeSymbol, bool hasInheritedClass) { var sourceProperties = sourceTypeSymbol.GetAllMembers().OfType().ToArray(); return typeSymbol .GetAllMembers() - .OfType() .Where(p => !p.HasAttribute(IgnorePropertyAttributeTypeSymbol)) .Select(property => MapProperty(sourceTypeSymbol, sourceProperties, property)) .Where(mappedProperty => mappedProperty is not null) .ToImmutableArray()!; } - protected override ImmutableArray GetTypeMappedProperties(ITypeSymbol typeSymbol, ITypeSymbol sourceTypeSymbol, bool hasInheritedClass) + protected override ImmutableArray GetTypeMappedMembers(ITypeSymbol typeSymbol, ITypeSymbol sourceTypeSymbol, bool hasInheritedClass) { var sourceProperties = sourceTypeSymbol.GetAllMembers().OfType().ToArray(); return sourceTypeSymbol .GetAllMembers() - .OfType() .Where(p => !p.HasAttribute(IgnorePropertyAttributeTypeSymbol)) .Select(property => MapPropertySimple(typeSymbol, property)) .Where(mappedProperty => mappedProperty is not null) diff --git a/test/TestConsoleApp/Data/User.cs b/test/TestConsoleApp/Data/User.cs index 2a1bbd8..bbb8560 100644 --- a/test/TestConsoleApp/Data/User.cs +++ b/test/TestConsoleApp/Data/User.cs @@ -8,17 +8,17 @@ namespace BlueWest.Data [MapFrom(typeof(UserUpdateDto))] public partial class User { - public int Id { get; } - public string Name { get; set; } - public string Address { get; set; } - - public string BTCAddress { get; set; } - public string LTCAddress { get; set; } + public readonly int Id; + public string Name; + public string Address; - public double BTCAmount { get; set; } - public double LTCAmount { get; set; } + public string BTCAddress; + public string LTCAddress; - public List FinanceTransactions { get; } + public double BTCAmount; + public double LTCAmount; + + public List FinanceTransactions; public User(int id, string name, string address, string btcAddress, string ltcAddress, double btcAmount, double ltcAmount, List financeTransactions) { diff --git a/test/TestConsoleApp/Data/UserUpdateDto.cs b/test/TestConsoleApp/Data/UserUpdateDto.cs index 456193f..a1b18d3 100644 --- a/test/TestConsoleApp/Data/UserUpdateDto.cs +++ b/test/TestConsoleApp/Data/UserUpdateDto.cs @@ -4,16 +4,16 @@ namespace BlueWest.Data { [MapFrom(typeof(User))] - public partial struct UserUpdateDto + public partial class UserUpdateDto { - public string Name { get; set; } - public string Address { get; set; } - - public string BTCAddress { get; set; } - public string LTCAddress { get; set; } + public string Name; + public string Address; - public double BTCAmount { get; set; } - public double LTCAmount { get; set; } + public string BTCAddress; + public string LTCAddress; + + public double BTCAmount; + public double LTCAmount; } } \ No newline at end of file