diff --git a/Compentio.SourceMapper.Generator/AnalyzerReleases.Unshipped.md b/Compentio.SourceMapper.Generator/AnalyzerReleases.Unshipped.md index e880452..26ab822 100644 --- a/Compentio.SourceMapper.Generator/AnalyzerReleases.Unshipped.md +++ b/Compentio.SourceMapper.Generator/AnalyzerReleases.Unshipped.md @@ -5,6 +5,7 @@ Rule ID | Category | Severity | Notes --------|----------|----------|------- SMAP0004 | Design | Warning | SourceMapperDescriptors, [Documentation](https://github.com/alekshura/SourceMapper/wiki/Diagnostics##smap0004) +SMAP0005 | Design | Warning | SourceMapperDescriptors, [Documentation](https://github.com/alekshura/SourceMapper/wiki/Diagnostics##smap0005) ### Changed Rules Rule ID | New Category | New Severity | Old Category | Old Severity | Notes diff --git a/Compentio.SourceMapper.Generator/Diagnostics/SourceMapperDescriptors.cs b/Compentio.SourceMapper.Generator/Diagnostics/SourceMapperDescriptors.cs index e8528e0..9e9e66a 100644 --- a/Compentio.SourceMapper.Generator/Diagnostics/SourceMapperDescriptors.cs +++ b/Compentio.SourceMapper.Generator/Diagnostics/SourceMapperDescriptors.cs @@ -28,5 +28,9 @@ public static class SourceMapperDescriptors public static readonly DiagnosticDescriptor DependencyInjectionNotUsed = new("SMAP0004", "Dependency Injection not used", "No Dependency Injection mechanism used in project. '{0}'.", "Design", DiagnosticSeverity.Warning, true, "SourceMapper based on Dependency Injection, but no used in project.", $"{DiagnosticsDescriptorsUri}#smap0004"); + + public static readonly DiagnosticDescriptor FieldIsNotMapped = + new("SMAP0005", "Field is not mapped", "The field '{0}' is not mapped", "Design", DiagnosticSeverity.Warning, true, + "Source field for defined target field not found, thus field does not mapped.", $"{DiagnosticsDescriptorsUri}#smap0005"); } } diff --git a/Compentio.SourceMapper.Generator/Matchers/AttributesMatchers.cs b/Compentio.SourceMapper.Generator/Matchers/AttributesMatchers.cs index 8ddeb28..747f644 100644 --- a/Compentio.SourceMapper.Generator/Matchers/AttributesMatchers.cs +++ b/Compentio.SourceMapper.Generator/Matchers/AttributesMatchers.cs @@ -13,7 +13,7 @@ internal static class AttributesMatchers /// Attributes collection /// Target property to match /// Matched attribute - internal static MappingAttribute MatchTargetAttribute(this IEnumerable attributes, IPropertyMetadata targetProperty) + internal static MappingAttribute MatchTargetAttribute(this IEnumerable attributes, IMetadata targetProperty) { return attributes.FirstOrDefault(attribute => attribute?.Target == targetProperty?.Name); } @@ -26,7 +26,7 @@ internal static MappingAttribute MatchTargetAttribute(this IEnumerable /// /// - internal static MappingAttribute MatchExpressionAttribute(this IEnumerable attributes, IPropertyMetadata targetProperty, IPropertyMetadata sourceProperty) + internal static MappingAttribute MatchExpressionAttribute(this IEnumerable attributes, IMetadata targetProperty, IMetadata sourceProperty) { var matchedExpressionAttribute = attributes.FirstOrDefault(attribute => attribute?.Target == targetProperty?.Name && attribute?.Source == sourceProperty?.Name diff --git a/Compentio.SourceMapper.Generator/Matchers/MembersMatchers.cs b/Compentio.SourceMapper.Generator/Matchers/MembersMatchers.cs index 76e7f9f..9da3c60 100644 --- a/Compentio.SourceMapper.Generator/Matchers/MembersMatchers.cs +++ b/Compentio.SourceMapper.Generator/Matchers/MembersMatchers.cs @@ -8,13 +8,13 @@ namespace Compentio.SourceMapper.Matchers internal static class MembersMatchers { /// - /// Method searches for source member that matches value. This also should match the target property. + /// Method searches for source member that matches value. This also should match the target member. /// - /// Collection of source properties + /// Collection of source members /// Collection of all defined mapping attributes /// Target member /// Matched source member - internal static IPropertyMetadata MatchSourceMember(this IEnumerable sourceMembers, IEnumerable mappingAttributes, IPropertyMetadata targetMember) + internal static IMemberMetadata MatchSourceMember(this IEnumerable sourceMembers, IEnumerable mappingAttributes, IMemberMetadata targetMember) { var matchedAttribute = mappingAttributes.MatchTargetAttribute(targetMember); var matchedSourceMember = sourceMembers.FirstOrDefault(member => member?.Name == matchedAttribute?.Source); @@ -27,21 +27,21 @@ internal static IPropertyMetadata MatchSourceMember(this IEnumerable /// Method searches for source member that name matches target member. /// - /// Collection of source properties + /// Collection of source members /// Target member /// Matched source member - internal static IPropertyMetadata MatchSourceMember(this IEnumerable members, IPropertyMetadata targetMember) + internal static IMemberMetadata MatchSourceMember(this IEnumerable members, IMemberMetadata targetMember) { return members.FirstOrDefault(member => member?.Name == targetMember?.Name); } /// - /// Method searches for target member that matches value. This also should match the target property. + /// Method searches for target member that matches value. This also should match the target member. /// - /// Collection of target properties + /// Collection of target members /// Collection of all defined mapping attributes /// Target member /// Matched target member - internal static IPropertyMetadata MatchTargetMember(this IEnumerable targetMembers, IEnumerable mappingAttributes, IPropertyMetadata targetMember) + internal static IMemberMetadata MatchTargetMember(this IEnumerable targetMembers, IEnumerable mappingAttributes, IMemberMetadata targetMember) { var matchedAttribute = mappingAttributes.MatchTargetAttribute(targetMember); var matchedTargetMember = targetMembers.FirstOrDefault(member => member?.Name == matchedAttribute?.Target); diff --git a/Compentio.SourceMapper.Generator/Matchers/MethodsMatchers.cs b/Compentio.SourceMapper.Generator/Matchers/MethodsMatchers.cs index 0fcd198..e85aed4 100644 --- a/Compentio.SourceMapper.Generator/Matchers/MethodsMatchers.cs +++ b/Compentio.SourceMapper.Generator/Matchers/MethodsMatchers.cs @@ -12,10 +12,10 @@ internal static class MethodsMatchers /// Returns method data that defined in mapping corresponds to source and target types. /// This method can be used in mappings of nested types. /// - /// Source property metadata - /// Target property metadata + /// Source member metadata + /// Target member metadata /// Matched method - internal static IMethodMetadata MatchDefinedMethod(this IMapperMetadata mapperMetadata, IPropertyMetadata source, IPropertyMetadata target) + internal static IMethodMetadata MatchDefinedMethod(this IMapperMetadata mapperMetadata, IMemberMetadata source, IMemberMetadata target) { var method = mapperMetadata.MethodsMetadata.FirstOrDefault(m => m.ReturnType.FullName == target.FullName && m.Parameters.FirstOrDefault().FullName == source.FullName); diff --git a/Compentio.SourceMapper.Generator/Metadata/MemberMetadata.cs b/Compentio.SourceMapper.Generator/Metadata/MemberMetadata.cs new file mode 100644 index 0000000..528178d --- /dev/null +++ b/Compentio.SourceMapper.Generator/Metadata/MemberMetadata.cs @@ -0,0 +1,120 @@ +using Compentio.SourceMapper.Attributes; +using Microsoft.CodeAnalysis; +using System.Collections.Generic; +using System.Linq; + +namespace Compentio.SourceMapper.Metadata +{ + /// + /// Encapsulates a member that is mapped + /// + internal interface IMemberMetadata : IMetadata + { + /// + /// Indicate that member is field or property (or unknown) + /// Based od and + /// + MemberType MemberType { get; } + + /// + /// Full name with namespace of the member + /// + string FullName { get; } + + /// + /// Indicates whether the member is user defined class or no + /// + bool IsClass { get; } + + /// + /// Indicates whether the member is static + /// + bool IsStatic { get; } + + /// + /// Indicate that field should be ignored during mapping generating, due to + /// + bool IgnoreInMapping { get; } + + /// + /// List of properties. It is empty in a case when the property is not user defined class: IsClass == false or is a field: IsField == true + /// + /// + /// + IEnumerable Properties { get; } + } + + internal class MemberMetadata : IMemberMetadata + { + private readonly ISymbol _symbol; + + internal MemberMetadata(ISymbol symbol) + { + _symbol = symbol; + } + + public MemberType MemberType + { + get + { + if (_symbol is IFieldSymbol) return MemberType.Field; + if (_symbol is IPropertySymbol) return MemberType.Property; + + return MemberType.Unknown; + } + } + + public string FullName => TypeMetadata.FullName; + + public bool IsClass => Type.SpecialType == SpecialType.None && Type.TypeKind == TypeKind.Class; + + public bool IsStatic => _symbol.IsStatic; + + public bool IgnoreInMapping + { + get + { + var attribute = _symbol.GetAttributes().FirstOrDefault(attribute => attribute is not null && attribute.AttributeClass?.Name == nameof(IgnoreMappingAttribute)); + return attribute != null; + } + } + + public IEnumerable Properties + { + get + { + if (!IsClass || MemberType != MemberType.Property) + return Enumerable.Empty(); + + return Type.GetMembers() + .Where(member => member.Kind == SymbolKind.Property && !member.IsStatic) + .Select(member => new MemberMetadata(member as IPropertySymbol)); + } + } + + public Location? Location => _symbol.Locations.FirstOrDefault(); + + public string Name => _symbol.Name; + + private ITypeSymbol Type + { + get + { + if (MemberType == MemberType.Field) return ((IFieldSymbol)_symbol).Type; + else return ((IPropertySymbol)_symbol).Type; + } + } + + private ITypeMetadata TypeMetadata => new TypeMetadata(Type); + } + + /// + /// Types of member + /// + internal enum MemberType + { + Field, + Property, + Unknown + } +} \ No newline at end of file diff --git a/Compentio.SourceMapper.Generator/Metadata/PropertyMetadata.cs b/Compentio.SourceMapper.Generator/Metadata/PropertyMetadata.cs deleted file mode 100644 index bcd0f61..0000000 --- a/Compentio.SourceMapper.Generator/Metadata/PropertyMetadata.cs +++ /dev/null @@ -1,80 +0,0 @@ -using Compentio.SourceMapper.Attributes; -using Microsoft.CodeAnalysis; -using System.Collections.Generic; -using System.Linq; - -namespace Compentio.SourceMapper.Metadata -{ - /// - /// Encapsulates a property that is mapped - /// - internal interface IPropertyMetadata : IMetadata - { - /// - /// Full name with namespace of the property - /// - string FullName { get; } - - /// - /// Indicates whether the property is user defined class or no - /// - /// - bool IsClass { get; } - - /// - /// Indicate that property should be ignored during mapping generating, due to - /// - bool IgnoreInMapping { get; } - - /// - /// List of properties. It is empty in a case when the property is not user defined class: IsClass == false - /// - /// - IEnumerable Properties { get; } - } - - internal class PropertyMetadata : IPropertyMetadata - { - private readonly IPropertySymbol _propertySymbol; - - internal PropertyMetadata(IPropertySymbol propertySymbol) - { - _propertySymbol = propertySymbol; - } - - public string Name => _propertySymbol.Name; - - public string FullName => Type.FullName; - - public bool IsClass => _propertySymbol.Type.SpecialType == SpecialType.None && _propertySymbol.Type.TypeKind == TypeKind.Class; - - private ITypeMetadata Type => new TypeMetadata(_propertySymbol.Type); - - public IEnumerable Properties - { - get - { - if (!IsClass) - return Enumerable.Empty(); - - return _propertySymbol.Type.GetMembers() - .Where(member => member.Kind == SymbolKind.Property && !member.IsStatic) - .Select(member => new PropertyMetadata(member as IPropertySymbol)); - } - } - - public Location? Location => _propertySymbol.Locations.FirstOrDefault(); - - /// - /// Indicate that property should be ignored during mapping generating, due to - /// - public bool IgnoreInMapping - { - get - { - var attribute = _propertySymbol.GetAttributes().FirstOrDefault(attribute => attribute is not null && attribute.AttributeClass?.Name == nameof(IgnoreMappingAttribute)); - return attribute != null; - } - } - } -} \ No newline at end of file diff --git a/Compentio.SourceMapper.Generator/Metadata/TypeMetadata.cs b/Compentio.SourceMapper.Generator/Metadata/TypeMetadata.cs index 6a40b8c..d5d0a34 100644 --- a/Compentio.SourceMapper.Generator/Metadata/TypeMetadata.cs +++ b/Compentio.SourceMapper.Generator/Metadata/TypeMetadata.cs @@ -15,16 +15,21 @@ internal interface ITypeMetadata : IMetadata string FullName { get; } /// - /// Object lis of properties + /// Object list of properties /// - IEnumerable Properties { get; } + IEnumerable Properties { get; } + + /// + /// Object list of fields + /// + IEnumerable Fields { get; } /// /// Recurrent method that return flatten list of properties tree for the object /// /// List of properties /// - IEnumerable FlattenProperties(IEnumerable propertyMetadata); + IEnumerable FlattenProperties(IEnumerable propertyMetadata); } internal class ParameterTypeMetadata : ITypeMetadata @@ -39,13 +44,17 @@ internal ParameterTypeMetadata(IParameterSymbol parameterSymbol) public string Name => _parameterSymbol.Name; public string FullName => _parameterSymbol.ToDisplayString(); - public IEnumerable Properties => _parameterSymbol.Type.GetMembers() + public IEnumerable Properties => _parameterSymbol.Type.GetMembers() .Where(member => member as IPropertySymbol is not null) - .Select(member => new PropertyMetadata(member as IPropertySymbol)); + .Select(member => new MemberMetadata(member as IPropertySymbol)); + + public IEnumerable Fields => _parameterSymbol.Type.GetMembers() + .Where(member => member is IFieldSymbol && member.CanBeReferencedByName && !((IFieldSymbol)member).IsConst) + .Select(member => new MemberMetadata(member as IFieldSymbol)); public Location? Location => _parameterSymbol.Locations.FirstOrDefault(); - public IEnumerable FlattenProperties(IEnumerable propertyMetadata) => + public IEnumerable FlattenProperties(IEnumerable propertyMetadata) => propertyMetadata.SelectMany(c => FlattenProperties(c.Properties)).Concat(propertyMetadata); } @@ -63,13 +72,17 @@ internal TypeMetadata(ITypeSymbol typeSymbol) public ITypeSymbol Type => _typeSymbol; - public IEnumerable Properties => _typeSymbol.GetMembers() + public IEnumerable Properties => _typeSymbol.GetMembers() .Where(member => member.Kind == SymbolKind.Property && !member.IsStatic) - .Select(member => new PropertyMetadata(member as IPropertySymbol)); + .Select(member => new MemberMetadata(member as IPropertySymbol)); + + public IEnumerable Fields => _typeSymbol.GetMembers() + .Where(member => member.Kind == SymbolKind.Field && member.CanBeReferencedByName && !((IFieldSymbol)member).IsConst) + .Select(member => new MemberMetadata(member as IFieldSymbol)); public Location? Location => _typeSymbol.Locations.FirstOrDefault(); - public IEnumerable FlattenProperties(IEnumerable propertyMetadata) => + public IEnumerable FlattenProperties(IEnumerable propertyMetadata) => propertyMetadata.SelectMany(c => FlattenProperties(c.Properties)).Concat(propertyMetadata); } } \ No newline at end of file diff --git a/Compentio.SourceMapper.Generator/Processors/ClassProcessorStrategy.cs b/Compentio.SourceMapper.Generator/Processors/ClassProcessorStrategy.cs index 2108f08..4091888 100644 --- a/Compentio.SourceMapper.Generator/Processors/ClassProcessorStrategy.cs +++ b/Compentio.SourceMapper.Generator/Processors/ClassProcessorStrategy.cs @@ -1,5 +1,4 @@ -using Compentio.SourceMapper.Attributes; -using Compentio.SourceMapper.Matchers; +using Compentio.SourceMapper.Matchers; using Compentio.SourceMapper.Metadata; using System.Linq; using System.Text; @@ -13,6 +12,8 @@ internal class ClassProcessorStrategy : AbstractProcessorStrategy { protected override string Modifier => "override"; + protected override ProcessorType ProcessorType => ProcessorType.Class; + protected override string GenerateMapperCode(IMapperMetadata mapperMetadata) { var result = @$"// @@ -66,54 +67,5 @@ private string GeneratePartialClassMethod(IMethodMetadata methodMetadata) { return $"public abstract {methodMetadata.InverseMethodFullName};"; } - - protected override string GenerateMappings(IMapperMetadata sourceMetadata, IMethodMetadata methodMetadata, bool inverseMapping = false) - { - var mappingsStringBuilder = new StringBuilder(); - var sourceMembers = methodMetadata.Parameters.First().Properties; - var targetMemebers = methodMetadata.ReturnType.Properties; - - foreach (var targetMember in targetMemebers) - { - var matchedSourceMember = sourceMembers.MatchSourceMember(methodMetadata.MappingAttributes, targetMember); - var matchedTargetMember = targetMemebers.MatchTargetMember(methodMetadata.MappingAttributes, targetMember); - - if (IgnorePropertyMapping(matchedSourceMember, matchedTargetMember)) continue; - - var expressionAttribute = methodMetadata.MappingAttributes.MatchExpressionAttribute(targetMember, matchedSourceMember); - var expressionMapping = MapExpression(expressionAttribute, methodMetadata.Parameters.First(), matchedSourceMember, matchedTargetMember); - - if (!string.IsNullOrEmpty(expressionMapping)) - { - if (inverseMapping) continue; - - mappingsStringBuilder.Append(expressionMapping); - continue; - } - - mappingsStringBuilder.Append(GenerateMapping(sourceMetadata, methodMetadata.Parameters.First(), matchedSourceMember, matchedTargetMember, inverseMapping)); - } - - return mappingsStringBuilder.ToString(); - } - - private string MapExpression(MappingAttribute expressionAttribute, ITypeMetadata parameter, IPropertyMetadata matchedSourceMember, IPropertyMetadata matchedTargetMember) - { - var mapping = new StringBuilder(); - - if (expressionAttribute is not null && matchedSourceMember is not null) - { - mapping.AppendLine($"target.{expressionAttribute?.Target} = {expressionAttribute?.Expression}({parameter.Name}.{matchedSourceMember.Name});"); - return mapping.ToString(); - } - - if (expressionAttribute is not null && matchedTargetMember is not null && matchedSourceMember is null) - { - mapping.AppendLine($"target.{expressionAttribute?.Target} = {expressionAttribute?.Expression}({parameter.Name});"); - return mapping.ToString(); - } - - return mapping.ToString(); - } } } \ No newline at end of file diff --git a/Compentio.SourceMapper.Generator/Processors/IProcessorStrategy.cs b/Compentio.SourceMapper.Generator/Processors/IProcessorStrategy.cs index e373b39..733592c 100644 --- a/Compentio.SourceMapper.Generator/Processors/IProcessorStrategy.cs +++ b/Compentio.SourceMapper.Generator/Processors/IProcessorStrategy.cs @@ -1,4 +1,5 @@ -using Compentio.SourceMapper.Diagnostics; +using Compentio.SourceMapper.Attributes; +using Compentio.SourceMapper.Diagnostics; using Compentio.SourceMapper.Helpers; using Compentio.SourceMapper.Matchers; using Compentio.SourceMapper.Metadata; @@ -55,9 +56,12 @@ public IResult GenerateCode(IMapperMetadata mapperMetadata) /// protected abstract string Modifier { get; } - protected abstract string GenerateMapperCode(IMapperMetadata mapperMetadata); + /// + /// Determine processor strategy type + /// + protected abstract ProcessorType ProcessorType { get; } - protected abstract string GenerateMappings(IMapperMetadata sourceMetadata, IMethodMetadata methodMetadata, bool inverseMapping = false); + protected abstract string GenerateMapperCode(IMapperMetadata mapperMetadata); protected string GenerateMethods(IMapperMetadata sourceMetadata) { @@ -85,7 +89,8 @@ protected string GenerateMethod(IMapperMetadata sourceMetadata, IMethodMetadata var target = new {methodMetadata.ReturnType.FullName}(); - {GenerateMappings(sourceMetadata, methodMetadata)} + {GenerateMappings(sourceMetadata, methodMetadata, MemberType.Field)} + {GenerateMappings(sourceMetadata, methodMetadata, MemberType.Property)} return target; }}"; @@ -100,47 +105,88 @@ protected string GenerateInverseMethod(IMapperMetadata sourceMetadata, IMethodMe var target = new {methodMetadata.Parameters.First().FullName}(); - {GenerateMappings(sourceMetadata, methodMetadata, true)} + {GenerateMappings(sourceMetadata, methodMetadata, MemberType.Field, true)} + {GenerateMappings(sourceMetadata, methodMetadata, MemberType.Property, true)} return target; }}"; } - protected string GenerateMapping(IMapperMetadata sourceMetadata, ITypeMetadata parameter, IPropertyMetadata matchedSourceMember, IPropertyMetadata matchedTargetMember, bool inverseMapping = false) + protected string GenerateMappings(IMapperMetadata sourceMetadata, IMethodMetadata methodMetadata, MemberType memberType, bool inverseMapping = false) + { + var mappingsStringBuilder = new StringBuilder(); + var sourceMembers = GetSourceMembers(methodMetadata, memberType); + var targetMemebers = GetTargetMembers(methodMetadata, memberType); + + foreach (var targetMember in targetMemebers) + { + var matchedSourceMember = sourceMembers.MatchSourceMember(methodMetadata.MappingAttributes, targetMember); + var matchedTargetMember = targetMemebers.MatchTargetMember(methodMetadata.MappingAttributes, targetMember); + + if (IgnoreMapping(matchedSourceMember, matchedTargetMember)) continue; + + if (ProcessorType == ProcessorType.Class && memberType == MemberType.Property) + { + var expressionAttribute = methodMetadata.MappingAttributes.MatchExpressionAttribute(targetMember, matchedSourceMember); + var expressionMapping = MapExpression(expressionAttribute, methodMetadata.Parameters.First(), matchedSourceMember, matchedTargetMember); + + if (!string.IsNullOrEmpty(expressionMapping)) + { + if (inverseMapping) continue; + + mappingsStringBuilder.Append(expressionMapping); + continue; + } + } + + mappingsStringBuilder.Append(GenerateMapping(sourceMetadata, methodMetadata, matchedSourceMember, matchedTargetMember, memberType, inverseMapping)); + } + + return mappingsStringBuilder.ToString(); + } + + protected string GenerateMapping(IMapperMetadata sourceMetadata, IMethodMetadata methodMetadata, IMemberMetadata matchedSourceMember, IMemberMetadata matchedTargetMember, MemberType memberType, bool inverseMapping = false) { if (inverseMapping) ObjectHelper.Swap(ref matchedSourceMember, ref matchedTargetMember); if (matchedTargetMember is null && matchedSourceMember is not null) { - PropertyMappingWarning(matchedSourceMember); + MemberMappingWarning(matchedSourceMember); } if (matchedSourceMember is null || matchedTargetMember is null) { - PropertyMappingWarning(matchedSourceMember ?? matchedTargetMember); + MemberMappingWarning(matchedSourceMember ?? matchedTargetMember); return string.Empty; } var mapping = new StringBuilder(); + if (matchedSourceMember.IsStatic && matchedTargetMember.IsStatic && memberType == MemberType.Field) + { + mapping.AppendLine(MapStaticProperty(matchedSourceMember, matchedTargetMember, methodMetadata, inverseMapping)); + return mapping.ToString(); + } + if (!matchedSourceMember.IsClass && !matchedTargetMember.IsClass) { - mapping.AppendLine(MapProperty(matchedSourceMember, matchedTargetMember, parameter)); + mapping.AppendLine(MapProperty(matchedSourceMember, matchedTargetMember, methodMetadata.Parameters.First())); } if (matchedSourceMember.IsClass && matchedTargetMember.IsClass) { - mapping.AppendLine(MapClass(sourceMetadata, matchedSourceMember, matchedTargetMember, parameter, inverseMapping)); + mapping.AppendLine(MapClass(sourceMetadata, matchedSourceMember, matchedTargetMember, methodMetadata.Parameters.First(), inverseMapping)); } + return mapping.ToString(); } - protected string MapProperty(IPropertyMetadata matchedSourceMember, IPropertyMetadata matchedTargetMember, ITypeMetadata parameter) + protected string MapProperty(IMetadata matchedSourceMember, IMetadata matchedTargetMember, ITypeMetadata parameter) { return $"target.{matchedTargetMember?.Name} = {parameter.Name}.{matchedSourceMember?.Name};"; } - protected string MapClass(IMapperMetadata sourceMetadata, IPropertyMetadata matchedSourceMember, IPropertyMetadata matchedTargetMember, ITypeMetadata parameter, bool inverseMapping) + protected string MapClass(IMapperMetadata sourceMetadata, IMemberMetadata matchedSourceMember, IMemberMetadata matchedTargetMember, ITypeMetadata parameter, bool inverseMapping) { var method = GetDefinedMethod(sourceMetadata, matchedSourceMember, matchedTargetMember, inverseMapping); @@ -153,13 +199,40 @@ protected string MapClass(IMapperMetadata sourceMetadata, IPropertyMetadata matc } else { - PropertyMappingWarning(matchedTargetMember); + MemberMappingWarning(matchedTargetMember); } return string.Empty; } - protected IMethodMetadata? GetDefinedMethod(IMapperMetadata sourceMetadata, IPropertyMetadata matchedSourceMember, IPropertyMetadata matchedTargetMember, bool inverseMapping) + protected string MapStaticProperty(IMemberMetadata matchedSourceMember, IMemberMetadata matchedTargetMember, IMethodMetadata methodMetadata, bool inverseMapping) + { + if (inverseMapping) + return $"{methodMetadata.Parameters.First().FullName}.{matchedTargetMember?.Name} = {methodMetadata.ReturnType.FullName}.{matchedSourceMember?.Name};"; + else + return $"{methodMetadata.ReturnType.FullName}.{matchedTargetMember?.Name} = {methodMetadata.Parameters.First().FullName}.{matchedSourceMember?.Name};"; + } + + protected string MapExpression(MappingAttribute expressionAttribute, ITypeMetadata parameter, IMemberMetadata matchedSourceMember, IMemberMetadata matchedTargetMember) + { + var mapping = new StringBuilder(); + + if (expressionAttribute is not null && matchedSourceMember is not null) + { + mapping.AppendLine($"target.{expressionAttribute?.Target} = {expressionAttribute?.Expression}({parameter.Name}.{matchedSourceMember.Name});"); + return mapping.ToString(); + } + + if (expressionAttribute is not null && matchedTargetMember is not null && matchedSourceMember is null) + { + mapping.AppendLine($"target.{expressionAttribute?.Target} = {expressionAttribute?.Expression}({parameter.Name});"); + return mapping.ToString(); + } + + return mapping.ToString(); + } + + protected IMethodMetadata? GetDefinedMethod(IMapperMetadata sourceMetadata, IMemberMetadata matchedSourceMember, IMemberMetadata matchedTargetMember, bool inverseMapping) { if (inverseMapping) { @@ -172,23 +245,66 @@ protected string MapClass(IMapperMetadata sourceMetadata, IPropertyMetadata matc } /// - /// Metchod checks that property metadata should be ignored during mapping due to + /// Metchod checks that member metadata should be ignored during mapping due to /// - /// - /// + /// + /// /// - protected bool IgnorePropertyMapping(IPropertyMetadata? sourcePropertyMetadata, IPropertyMetadata? targetPropertyMetadata) + protected bool IgnoreMapping(IMemberMetadata? sourceFieldMetadata, IMemberMetadata? targetFieldMetadata) { - return (sourcePropertyMetadata?.IgnoreInMapping is true || targetPropertyMetadata?.IgnoreInMapping is true); + return (sourceFieldMetadata?.IgnoreInMapping is true || targetFieldMetadata?.IgnoreInMapping is true); } - protected void PropertyMappingWarning(IPropertyMetadata metadata) + protected void MemberMappingWarning(IMemberMetadata metadata) { _diagnostics.Add(new DiagnosticsInfo { - DiagnosticDescriptor = SourceMapperDescriptors.PropertyIsNotMapped, + DiagnosticDescriptor = metadata.MemberType == MemberType.Field ? SourceMapperDescriptors.FieldIsNotMapped : SourceMapperDescriptors.PropertyIsNotMapped, Metadata = metadata }); } + + protected IEnumerable GetSourceMembers(IMethodMetadata methodMetadata, MemberType memberType) + { + var typeMetadata = methodMetadata.Parameters.First(); + + switch (memberType) + { + case MemberType.Field: + return typeMetadata.Fields; + + case MemberType.Property: + return typeMetadata.Properties; + + default: + return Enumerable.Empty(); + } + } + + protected IEnumerable GetTargetMembers(IMethodMetadata methodMetadata, MemberType memberType) + { + var typeMetadata = methodMetadata.ReturnType; + + switch (memberType) + { + case MemberType.Field: + return typeMetadata.Fields; + + case MemberType.Property: + return typeMetadata.Properties; + + default: + return Enumerable.Empty(); + } + } + } + + /// + /// Encapsulate processor strategy types + /// + internal enum ProcessorType + { + Class, + Interface } } \ No newline at end of file diff --git a/Compentio.SourceMapper.Generator/Processors/InterfaceProcessorStrategy.cs b/Compentio.SourceMapper.Generator/Processors/InterfaceProcessorStrategy.cs index 0d3487e..a17abf3 100644 --- a/Compentio.SourceMapper.Generator/Processors/InterfaceProcessorStrategy.cs +++ b/Compentio.SourceMapper.Generator/Processors/InterfaceProcessorStrategy.cs @@ -12,6 +12,8 @@ internal class InterfaceProcessorStrategy : AbstractProcessorStrategy { protected override string Modifier => "virtual"; + protected override ProcessorType ProcessorType => ProcessorType.Interface; + protected override string GenerateMapperCode(IMapperMetadata mapperMetadata) { var result = @$"// @@ -67,24 +69,5 @@ private string GenerateInterfaceMethod(IMethodMetadata methodMetadata) { return $"{methodMetadata.InverseMethodFullName};"; } - - protected override string GenerateMappings(IMapperMetadata sourceMetadata, IMethodMetadata methodMetadata, bool inverseMapping = false) - { - var mappingsStringBuilder = new StringBuilder(); - var sourceMembers = methodMetadata.Parameters.First().Properties; - var targetMemebers = methodMetadata.ReturnType.Properties; - - foreach (var targetMember in targetMemebers) - { - var matchedSourceMember = sourceMembers.MatchSourceMember(methodMetadata.MappingAttributes, targetMember); - var matchedTargetMember = targetMemebers.MatchTargetMember(methodMetadata.MappingAttributes, targetMember); - - if (IgnorePropertyMapping(matchedSourceMember, matchedTargetMember)) continue; - - mappingsStringBuilder.Append(GenerateMapping(sourceMetadata, methodMetadata.Parameters.First(), matchedSourceMember, matchedTargetMember, inverseMapping)); - } - - return mappingsStringBuilder.ToString(); - } } } \ No newline at end of file diff --git a/Compentio.SourceMapper.Tests/Entities/UserDao.cs b/Compentio.SourceMapper.Tests/Entities/UserDao.cs index fd98776..31f639b 100644 --- a/Compentio.SourceMapper.Tests/Entities/UserDao.cs +++ b/Compentio.SourceMapper.Tests/Entities/UserDao.cs @@ -6,6 +6,11 @@ namespace Compentio.SourceMapper.Tests.Entities { public class UserDao { + public static string UserCodeStatic = "UserCodeStaticDao"; + public string UserCode = "UserCodeDao"; + [IgnoreMapping] + public int IgnoredField; + public long UserId { get; set; } public string FirstName { get; set; } public string LastName { get; set; } @@ -20,6 +25,8 @@ public class UserDao public class UserDataDao { + public AddressDao AddressField; + public long UserId { get; set; } public string FirstName { get; set; } public string LastName { get; set; } diff --git a/Compentio.SourceMapper.Tests/Entities/UserInfo.cs b/Compentio.SourceMapper.Tests/Entities/UserInfo.cs index 33988b0..40b6455 100644 --- a/Compentio.SourceMapper.Tests/Entities/UserInfo.cs +++ b/Compentio.SourceMapper.Tests/Entities/UserInfo.cs @@ -6,6 +6,9 @@ namespace Compentio.SourceMapper.Tests.Entities { public class UserInfo { + public static string UserCodeStatic = "UserCodeStaticInfo"; + public string UserCode = "UserCodeInfo"; + public Address AddressField; public int Id { get; set; } public string Name { get; set; } public Sex Sex { get; set; } diff --git a/Compentio.SourceMapper.Tests/Matchers/AttributesMatchersTests.cs b/Compentio.SourceMapper.Tests/Matchers/AttributesMatchersTests.cs index 8ebc159..2a9936c 100644 --- a/Compentio.SourceMapper.Tests/Matchers/AttributesMatchersTests.cs +++ b/Compentio.SourceMapper.Tests/Matchers/AttributesMatchersTests.cs @@ -15,8 +15,8 @@ public class AttributesMatchersTests private readonly IFixture _fixture; private readonly Mock _mockMethodMetadata; private readonly Mock _mockMappingAttribute; - private readonly Mock _mockTargetPropertyMetadata; - private readonly Mock _mockSourcePropertyMetadata; + private readonly Mock _mockTargetMemberMetadata; + private readonly Mock _mockSourceMemberMetadata; public AttributesMatchersTests() { @@ -25,8 +25,8 @@ public AttributesMatchersTests() .Customize(new SupportMutableValueTypesCustomization()); _mockMethodMetadata = _fixture.Create>(); _mockMappingAttribute = _fixture.Create>(); - _mockTargetPropertyMetadata = _fixture.Create>(); - _mockSourcePropertyMetadata = _fixture.Create>(); + _mockTargetMemberMetadata = _fixture.Create>(); + _mockSourceMemberMetadata = _fixture.Create>(); } [Fact] @@ -67,10 +67,10 @@ public void MatchTargetAttribute_Match() { // Arrange IEnumerable mappingAttributes = new List { _mockMappingAttribute.Object }; - _mockMappingAttribute.Setup(m => m.Target).Returns(_mockTargetPropertyMetadata.Object.Name); + _mockMappingAttribute.Setup(m => m.Target).Returns(_mockTargetMemberMetadata.Object.Name); // Act - var result = mappingAttributes.MatchTargetAttribute(_mockTargetPropertyMetadata.Object); + var result = mappingAttributes.MatchTargetAttribute(_mockTargetMemberMetadata.Object); // Assert result.Should().NotBeNull(); @@ -84,7 +84,7 @@ public void MatchTargetAttribute_NotMatch() _mockMappingAttribute.Setup(m => m.Target).Returns(string.Empty); // Act - var result = mappingAttributes.MatchTargetAttribute(_mockTargetPropertyMetadata.Object); + var result = mappingAttributes.MatchTargetAttribute(_mockTargetMemberMetadata.Object); // Assert result.Should().BeNull(); @@ -95,11 +95,11 @@ public void MatchExpressionAttribute_MatchSourceAndTarget() { // Arrange IEnumerable mappingAttributes = new List { _mockMappingAttribute.Object }; - _mockMappingAttribute.Setup(m => m.Target).Returns(_mockTargetPropertyMetadata.Object.Name); - _mockMappingAttribute.Setup(m => m.Source).Returns(_mockSourcePropertyMetadata.Object.Name); + _mockMappingAttribute.Setup(m => m.Target).Returns(_mockTargetMemberMetadata.Object.Name); + _mockMappingAttribute.Setup(m => m.Source).Returns(_mockSourceMemberMetadata.Object.Name); // Act - var result = mappingAttributes.MatchExpressionAttribute(_mockTargetPropertyMetadata.Object, _mockSourcePropertyMetadata.Object); + var result = mappingAttributes.MatchExpressionAttribute(_mockTargetMemberMetadata.Object, _mockSourceMemberMetadata.Object); // Assert result.Should().NotBeNull(); @@ -110,10 +110,10 @@ public void MatchExpressionAttribute_MatchTarget() { // Arrange IEnumerable mappingAttributes = new List { _mockMappingAttribute.Object }; - _mockMappingAttribute.Setup(m => m.Target).Returns(_mockTargetPropertyMetadata.Object.Name); + _mockMappingAttribute.Setup(m => m.Target).Returns(_mockTargetMemberMetadata.Object.Name); // Act - var result = mappingAttributes.MatchExpressionAttribute(_mockTargetPropertyMetadata.Object, _mockSourcePropertyMetadata.Object); + var result = mappingAttributes.MatchExpressionAttribute(_mockTargetMemberMetadata.Object, _mockSourceMemberMetadata.Object); // Assert result.Should().NotBeNull(); @@ -128,7 +128,7 @@ public void MatchExpressionAttribute_NotMatchSourceAndTarget() _mockMappingAttribute.Setup(m => m.Source).Returns(string.Empty); // Act - var result = mappingAttributes.MatchExpressionAttribute(_mockTargetPropertyMetadata.Object, _mockSourcePropertyMetadata.Object); + var result = mappingAttributes.MatchExpressionAttribute(_mockTargetMemberMetadata.Object, _mockSourceMemberMetadata.Object); // Assert result.Should().BeNull(); @@ -140,10 +140,10 @@ public void MatchExpressionAttribute_NotMatchTarget() // Arrange IEnumerable mappingAttributes = new List { _mockMappingAttribute.Object }; _mockMappingAttribute.Setup(m => m.Target).Returns(string.Empty); - _mockMappingAttribute.Setup(m => m.Source).Returns(_mockSourcePropertyMetadata.Object.Name); + _mockMappingAttribute.Setup(m => m.Source).Returns(_mockSourceMemberMetadata.Object.Name); // Act - var result = mappingAttributes.MatchExpressionAttribute(_mockTargetPropertyMetadata.Object, _mockSourcePropertyMetadata.Object); + var result = mappingAttributes.MatchExpressionAttribute(_mockTargetMemberMetadata.Object, _mockSourceMemberMetadata.Object); // Assert result.Should().BeNull(); diff --git a/Compentio.SourceMapper.Tests/Metadata/PropertyMetadataTestBase.cs b/Compentio.SourceMapper.Tests/Metadata/MemberMetadataTestBase.cs similarity index 90% rename from Compentio.SourceMapper.Tests/Metadata/PropertyMetadataTestBase.cs rename to Compentio.SourceMapper.Tests/Metadata/MemberMetadataTestBase.cs index 3c2581f..01daf7d 100644 --- a/Compentio.SourceMapper.Tests/Metadata/PropertyMetadataTestBase.cs +++ b/Compentio.SourceMapper.Tests/Metadata/MemberMetadataTestBase.cs @@ -6,7 +6,7 @@ namespace Compentio.SourceMapper.Tests.Metadata { - public abstract class PropertyMetadataTestBase + public abstract class MemberMetadataTestBase { protected abstract string MockNamespace { get; } protected abstract string MockClassName { get; } @@ -27,7 +27,7 @@ protected ImmutableArray GetAttributeDataMock(string sourceCode, protected Compilation GetCompilationMock(string sourceCode) { - return CSharpCompilation.Create("PropertyMetadataTestBase", + return CSharpCompilation.Create("MemberMetadataTestBase", new[] { CSharpSyntaxTree.ParseText(sourceCode) }, new[] { MetadataReference.CreateFromFile(typeof(Binder).GetTypeInfo().Assembly.Location) }, new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)); @@ -41,13 +41,15 @@ namespace {MockNamespace} public abstract class {MockClassName} {{ - public abstract MockTypeDto {MockMethodName}(MockTypeDao fake); + public abstract MockTypeDto {MockMethodName}(MockTypeDao mock); }} public class MockTypeDao {{ [IgnoreMapping] public virtual string PropertyToIgnore {{ get; set; }} + public static string StaticFieldProperty; + public string FieldProperty; }} {IgnoreMappingAttributeSourceCode} diff --git a/Compentio.SourceMapper.Tests/Metadata/MemberMetadataTests.cs b/Compentio.SourceMapper.Tests/Metadata/MemberMetadataTests.cs new file mode 100644 index 0000000..1b15ea8 --- /dev/null +++ b/Compentio.SourceMapper.Tests/Metadata/MemberMetadataTests.cs @@ -0,0 +1,255 @@ +using AutoFixture; +using AutoFixture.AutoMoq; +using Compentio.SourceMapper.Metadata; +using FluentAssertions; +using Microsoft.CodeAnalysis; +using Moq; +using System.Collections.Immutable; +using Xunit; + +namespace Compentio.SourceMapper.Tests.Metadata +{ + public class MemberMetadataTests : MemberMetadataTestBase + { + private readonly IFixture _fixture; + private readonly Mock _mockPropertySymbol; + private readonly Mock _mockFieldSymbol; + private readonly Mock _mockLocation; + private readonly Mock _mockSymbol; + + private const string MockName = "MockName"; + private const string MockFullName = "MockFullName"; + + protected override string MockNamespace => "MockNamespace"; + + protected override string MockClassName => "MockClassName"; + + protected override string MockMethodName => "MockMethodName"; + + public MemberMetadataTests() + { + _fixture = new Fixture() + .Customize(new AutoMoqCustomization { ConfigureMembers = true }) + .Customize(new SupportMutableValueTypesCustomization()); + _mockPropertySymbol = _fixture.Create>(); + _mockFieldSymbol = _fixture.Create>(); + _mockLocation = _fixture.Create>(); + _mockSymbol = _fixture.Create>(); + } + + [Fact] + public void InstanceForProperty_ValidNameField() + { + // Arrange + _mockPropertySymbol.Setup(p => p.Name).Returns(MockName); + + // Act + var memberMetadata = new MemberMetadata(_mockPropertySymbol.Object); + + // Assert + memberMetadata.Name.Should().Be(MockName); + } + + [Fact] + public void InstanceForField_ValidNameField() + { + // Arrange + _mockFieldSymbol.Setup(p => p.Name).Returns(MockName); + + // Act + var memberMetadata = new MemberMetadata(_mockFieldSymbol.Object); + + // Assert + memberMetadata.Name.Should().Be(MockName); + } + + [Fact] + public void InstanceForProperty_ValidFullNameField() + { + // Arrange + _mockPropertySymbol.Setup(p => p.Type.ToDisplayString(It.IsAny())).Returns(MockFullName); + + // Act + var memberMetadata = new MemberMetadata(_mockPropertySymbol.Object); + + // Assert + memberMetadata.FullName.Should().Be(MockFullName); + } + + [Fact] + public void InstanceForField_ValidFullNameField() + { + // Arrange + _mockFieldSymbol.Setup(p => p.Type.ToDisplayString(It.IsAny())).Returns(MockFullName); + + // Act + var memberMetadata = new MemberMetadata(_mockFieldSymbol.Object); + + // Assert + memberMetadata.FullName.Should().Be(MockFullName); + } + + [Fact] + public void InstanceForProperty_ValidIsClassField() + { + // Arrange + _mockPropertySymbol.Setup(p => p.Type.TypeKind).Returns(TypeKind.Class); + _mockPropertySymbol.Setup(p => p.Type.SpecialType).Returns(SpecialType.None); + + // Act + var memberMetadata = new MemberMetadata(_mockPropertySymbol.Object); + + // Assert + memberMetadata.IsClass.Should().BeTrue(); + } + + [Fact] + public void InstanceForField_ValidIsClassField() + { + // Arrange + _mockFieldSymbol.Setup(p => p.Type.TypeKind).Returns(TypeKind.Class); + _mockFieldSymbol.Setup(p => p.Type.SpecialType).Returns(SpecialType.None); + + // Act + var memberMetadata = new MemberMetadata(_mockFieldSymbol.Object); + + // Assert + memberMetadata.IsClass.Should().BeTrue(); + } + + [Fact] + public void InstanceForProperty_ValidLocationField() + { + // Arrange + _mockPropertySymbol.Setup(p => p.Locations).Returns(ImmutableArray.Create(_mockLocation.Object)); + + // Act + var memberMetadata = new MemberMetadata(_mockPropertySymbol.Object); + + // Assert + memberMetadata.Location.Should().NotBeNull(); + } + + [Fact] + public void InstanceForField_ValidLocationField() + { + // Arrange + _mockFieldSymbol.Setup(p => p.Locations).Returns(ImmutableArray.Create(_mockLocation.Object)); + + // Act + var memberMetadata = new MemberMetadata(_mockFieldSymbol.Object); + + // Assert + memberMetadata.Location.Should().NotBeNull(); + } + + [Fact] + public void InstanceForProperty_NotEmptyProperties() + { + // Arrange + _mockPropertySymbol.Setup(p => p.Type.TypeKind).Returns(TypeKind.Class); + _mockPropertySymbol.Setup(p => p.Type.SpecialType).Returns(SpecialType.None); + _mockPropertySymbol.Setup(p => p.Type.GetMembers()).Returns(ImmutableArray.Create(_mockSymbol.Object)); + _mockSymbol.Setup(s => s.Kind).Returns(SymbolKind.Property); + _mockSymbol.Setup(s => s.IsStatic).Returns(false); + + // Act + var memberMetadata = new MemberMetadata(_mockPropertySymbol.Object); + + // Assert + memberMetadata.Properties.Should().NotBeEmpty(); + } + + [Fact] + public void InstanceForProperty_EmptyProperties() + { + // Arrange + _mockPropertySymbol.Setup(p => p.Type.TypeKind).Returns(TypeKind.Interface); + + // Act + var memberMetadata = new MemberMetadata(_mockPropertySymbol.Object); + + // Assert + memberMetadata.Properties.Should().BeEmpty(); + } + + [Fact] + public void InstanceForField_EmptyProperties() + { + // Act + var memberMetadata = new MemberMetadata(_mockFieldSymbol.Object); + + // Assert + memberMetadata.Properties.Should().BeEmpty(); + } + + [Fact] + public void InstanceForProperty_ValidIgnoreInMapping() + { + // Arrange + _mockPropertySymbol.Setup(p => p.GetAttributes()).Returns(GetAttributeDataMock(MockSourceCode, MockMethodName)); + + // Act + var memberMetadata = new MemberMetadata(_mockPropertySymbol.Object); + + // Assert + memberMetadata.IgnoreInMapping.Should().BeTrue(); + } + + [Fact] + public void InstanceForField_ValidIgnoreInMapping() + { + // Arrange + _mockFieldSymbol.Setup(p => p.GetAttributes()).Returns(GetAttributeDataMock(MockSourceCode, MockMethodName)); + + // Act + var memberMetadata = new MemberMetadata(_mockFieldSymbol.Object); + + // Assert + memberMetadata.IgnoreInMapping.Should().BeTrue(); + } + + [Fact] + public void InstanceForField_ValidIsStatic() + { + // Arrange + _mockFieldSymbol.Setup(p => p.IsStatic).Returns(true); + + // Act + var memberMetadata = new MemberMetadata(_mockFieldSymbol.Object); + + // Assert + memberMetadata.IsStatic.Should().BeTrue(); + } + + [Fact] + public void InstanceForProperty_IsPropertyMember() + { + // Act + var memberMetadata = new MemberMetadata(_mockPropertySymbol.Object); + + // Assert + memberMetadata.MemberType.Should().Be(MemberType.Property); + } + + [Fact] + public void InstanceForField_IsFieldMember() + { + // Act + var memberMetadata = new MemberMetadata(_mockFieldSymbol.Object); + + // Assert + memberMetadata.MemberType.Should().Be(MemberType.Field); + } + + [Fact] + public void Instance_MemberUnknown() + { + // Act + var memberMetadata = new MemberMetadata(_mockSymbol.Object); + + // Assert + memberMetadata.MemberType.Should().Be(MemberType.Unknown); + } + } +} \ No newline at end of file diff --git a/Compentio.SourceMapper.Tests/Metadata/ParameterTypeMetadataTests.cs b/Compentio.SourceMapper.Tests/Metadata/ParameterTypeMetadataTests.cs index be908c8..ae3e203 100644 --- a/Compentio.SourceMapper.Tests/Metadata/ParameterTypeMetadataTests.cs +++ b/Compentio.SourceMapper.Tests/Metadata/ParameterTypeMetadataTests.cs @@ -16,7 +16,7 @@ public class ParameterTypeMetadataTests private readonly Mock _mockParameterSymbol; private readonly Mock _mockLocation; private readonly Mock _mockSymbol; - private readonly Mock _mockPropertyMetadata; + private readonly Mock _mockMemberMetadata; public ParameterTypeMetadataTests() { @@ -26,7 +26,7 @@ public ParameterTypeMetadataTests() _mockParameterSymbol = _fixture.Create>(); _mockLocation = _fixture.Create>(); _mockSymbol = _fixture.Create>(); - _mockPropertyMetadata = _fixture.Create>(); + _mockMemberMetadata = _fixture.Create>(); } [Fact] @@ -84,15 +84,29 @@ public void Instance_EmptyProperties() parameterTypeMetadata.Properties.Should().BeEmpty(); } + [Fact] + public void Instance_EmptyFields() + { + // Arrange + _mockSymbol.Setup(s => s.CanBeReferencedByName).Returns(false); + _mockParameterSymbol.Setup(t => t.Type.GetMembers()).Returns(ImmutableArray.Create(_mockSymbol.Object)); + + // Act + var parameterTypeMetadata = new ParameterTypeMetadata(_mockParameterSymbol.Object); + + // Assert + parameterTypeMetadata.Fields.Should().BeEmpty(); + } + [Fact] public void FlattenProperties_ValidFlatten() { // Arrange - var limitedPropertyMetadata = _fixture.Create>(); - limitedPropertyMetadata.Setup(l => l.Properties).Returns(new List()); + var limitedPropertyMetadata = _fixture.Create>(); + limitedPropertyMetadata.Setup(l => l.Properties).Returns(new List()); - _mockPropertyMetadata.Setup(p => p.Properties).Returns( - new List + _mockMemberMetadata.Setup(p => p.Properties).Returns( + new List { limitedPropertyMetadata.Object, limitedPropertyMetadata.Object, @@ -103,7 +117,7 @@ public void FlattenProperties_ValidFlatten() // Act var parameterTypeMetadata = new ParameterTypeMetadata(_mockParameterSymbol.Object); - var result = parameterTypeMetadata.FlattenProperties(new List { _mockPropertyMetadata.Object }); + var result = parameterTypeMetadata.FlattenProperties(new List { _mockMemberMetadata.Object }); // Assert result.Should().NotBeEmpty(); diff --git a/Compentio.SourceMapper.Tests/Metadata/PropertyMetadataTests.cs b/Compentio.SourceMapper.Tests/Metadata/PropertyMetadataTests.cs deleted file mode 100644 index acb3098..0000000 --- a/Compentio.SourceMapper.Tests/Metadata/PropertyMetadataTests.cs +++ /dev/null @@ -1,131 +0,0 @@ -using AutoFixture; -using AutoFixture.AutoMoq; -using Compentio.SourceMapper.Metadata; -using FluentAssertions; -using Microsoft.CodeAnalysis; -using Moq; -using System.Collections.Immutable; -using Xunit; - -namespace Compentio.SourceMapper.Tests.Metadata -{ - public class PropertyMetadataTests : PropertyMetadataTestBase - { - private readonly IFixture _fixture; - private readonly Mock _mockPropertySymbol; - private readonly Mock _mockLocation; - private readonly Mock _mockSymbol; - - protected override string MockNamespace => "MockNamespace"; - - protected override string MockClassName => "MockClassName"; - - protected override string MockMethodName => "MockMethodName"; - - public PropertyMetadataTests() - { - _fixture = new Fixture() - .Customize(new AutoMoqCustomization { ConfigureMembers = true }) - .Customize(new SupportMutableValueTypesCustomization()); - _mockPropertySymbol = _fixture.Create>(); - _mockLocation = _fixture.Create>(); - _mockSymbol = _fixture.Create>(); - } - - [Fact] - public void InstanceForClass_ValidNameField() - { - // Arrange - _mockPropertySymbol.Setup(p => p.Name).Returns("Name"); - - // Act - var propertyMetadata = new PropertyMetadata(_mockPropertySymbol.Object); - - // Assert - propertyMetadata.Name.Should().Be("Name"); - } - - [Fact] - public void InstanceForClass_ValidFullNameField() - { - // Arrange - _mockPropertySymbol.Setup(p => p.Type.ToDisplayString(It.IsAny())).Returns("FullName"); - - // Act - var propertyMetadata = new PropertyMetadata(_mockPropertySymbol.Object); - - // Assert - propertyMetadata.FullName.Should().Be("FullName"); - } - - [Fact] - public void InstanceForClass_ValidIsClassField() - { - // Arrange - _mockPropertySymbol.Setup(p => p.Type.TypeKind).Returns(TypeKind.Class); - _mockPropertySymbol.Setup(p => p.Type.SpecialType).Returns(SpecialType.None); - - // Act - var propertyMetadata = new PropertyMetadata(_mockPropertySymbol.Object); - - // Assert - propertyMetadata.IsClass.Should().BeTrue(); - } - - [Fact] - public void InstanceForClass_ValidLocationField() - { - // Arrange - _mockPropertySymbol.Setup(p => p.Locations).Returns(ImmutableArray.Create(_mockLocation.Object)); - - // Act - var propertyMetadata = new PropertyMetadata(_mockPropertySymbol.Object); - - // Assert - propertyMetadata.Location.Should().NotBeNull(); - } - - [Fact] - public void InstanceForClass_NotEmptyProperties() - { - // Arrange - _mockPropertySymbol.Setup(p => p.Type.TypeKind).Returns(TypeKind.Class); - _mockPropertySymbol.Setup(p => p.Type.SpecialType).Returns(SpecialType.None); - _mockPropertySymbol.Setup(p => p.Type.GetMembers()).Returns(ImmutableArray.Create(_mockSymbol.Object)); - _mockSymbol.Setup(s => s.Kind).Returns(SymbolKind.Property); - _mockSymbol.Setup(s => s.IsStatic).Returns(false); - - // Act - var propertyMetadata = new PropertyMetadata(_mockPropertySymbol.Object); - - // Assert - propertyMetadata.Properties.Should().NotBeEmpty(); - } - - [Fact] - public void InstanceForInterface_EmptyProperties() - { - // Arrange - _mockPropertySymbol.Setup(p => p.Type.TypeKind).Returns(TypeKind.Interface); - - // Act - var propertyMetadata = new PropertyMetadata(_mockPropertySymbol.Object); - - // Assert - propertyMetadata.Properties.Should().BeEmpty(); - } - - [Fact] - public void InstanceForClass_ValidIgnoreInMapping() - { - // Arrange - _mockPropertySymbol.Setup(p => p.GetAttributes()).Returns(GetAttributeDataMock(MockSourceCode, MockMethodName)); - - // Act - var propertyMetadata = new PropertyMetadata(_mockPropertySymbol.Object); - - // Assert - propertyMetadata.IgnoreInMapping.Should().BeTrue(); - } - } -} \ No newline at end of file diff --git a/Compentio.SourceMapper.Tests/Metadata/TypeMetadataTests.cs b/Compentio.SourceMapper.Tests/Metadata/TypeMetadataTests.cs index 056e7e0..dc2e0a5 100644 --- a/Compentio.SourceMapper.Tests/Metadata/TypeMetadataTests.cs +++ b/Compentio.SourceMapper.Tests/Metadata/TypeMetadataTests.cs @@ -16,7 +16,7 @@ public class TypeMetadataTests private readonly Mock _mockTypeSymbol; private readonly Mock _mockLocation; private readonly Mock _mockSymbol; - private readonly Mock _mockPropertyMetadata; + private readonly Mock _mockMemberMetadata; public TypeMetadataTests() { @@ -26,7 +26,7 @@ public TypeMetadataTests() _mockTypeSymbol = _fixture.Create>(); _mockLocation = _fixture.Create>(); _mockSymbol = _fixture.Create>(); - _mockPropertyMetadata = _fixture.Create>(); + _mockMemberMetadata = _fixture.Create>(); } [Fact] @@ -95,6 +95,23 @@ public void Instanc_NotEmptyProperties() typeMetadata.Properties.Should().NotBeEmpty(); } + [Fact] + public void Instanc_NotEmptyFields() + { + // Arrange + var mockFieldSymbol = _fixture.Create>(); + mockFieldSymbol.Setup(s => s.Kind).Returns(SymbolKind.Field); + mockFieldSymbol.Setup(s => s.CanBeReferencedByName).Returns(true); + mockFieldSymbol.Setup(s => s.IsConst).Returns(false); + _mockTypeSymbol.Setup(t => t.GetMembers()).Returns(ImmutableArray.Create(mockFieldSymbol.Object as ISymbol)); + + // Act + var typeMetadata = new TypeMetadata(_mockTypeSymbol.Object); + + // Assert + typeMetadata.Fields.Should().NotBeEmpty(); + } + [Fact] public void Instanc_EmptyProperties() { @@ -110,15 +127,29 @@ public void Instanc_EmptyProperties() typeMetadata.Properties.Should().BeEmpty(); } + [Fact] + public void Instanc_EmptyFields() + { + // Arrange + _mockTypeSymbol.Setup(t => t.GetMembers()).Returns(ImmutableArray.Create(_mockSymbol.Object)); + _mockSymbol.Setup(s => s.Kind).Returns(SymbolKind.ErrorType); + + // Act + var typeMetadata = new TypeMetadata(_mockTypeSymbol.Object); + + // Assert + typeMetadata.Fields.Should().BeEmpty(); + } + [Fact] public void FlattenProperties_ValidFlatten() { // Arrange - var limitedPropertyMetadata = _fixture.Create>(); - limitedPropertyMetadata.Setup(l => l.Properties).Returns(new List()); + var limitedPropertyMetadata = _fixture.Create>(); + limitedPropertyMetadata.Setup(l => l.Properties).Returns(new List()); - _mockPropertyMetadata.Setup(p => p.Properties).Returns( - new List + _mockMemberMetadata.Setup(p => p.Properties).Returns( + new List { limitedPropertyMetadata.Object, limitedPropertyMetadata.Object, @@ -129,7 +160,7 @@ public void FlattenProperties_ValidFlatten() // Act var typeMetadata = new TypeMetadata(_mockTypeSymbol.Object); - var result = typeMetadata.FlattenProperties(new List { _mockPropertyMetadata.Object }); + var result = typeMetadata.FlattenProperties(new List { _mockMemberMetadata.Object }); // Assert result.Should().NotBeEmpty(); diff --git a/Compentio.SourceMapper.Tests/Processors/ClassProcessorStrategyTests.cs b/Compentio.SourceMapper.Tests/Processors/ClassProcessorStrategyTests.cs index e5c116d..0c4d203 100644 --- a/Compentio.SourceMapper.Tests/Processors/ClassProcessorStrategyTests.cs +++ b/Compentio.SourceMapper.Tests/Processors/ClassProcessorStrategyTests.cs @@ -93,7 +93,7 @@ public void GenerateCode_UnmatchedData_ReportNotMapped() // Arrange var methodMetadata = GetValidMethodWithAttributes(_mockMappingAttribute); // Create unmatched mock source and target metadata - methodMetadata.Setup(m => m.ReturnType.Properties).Returns(_fixture.Create>()); + methodMetadata.Setup(m => m.ReturnType.Properties).Returns(_fixture.Create>()); methodMetadata.Setup(m => m.Parameters).Returns(_fixture.Create>()); _sourceMetadataMock.Setup(sourceMetadata => sourceMetadata.MethodsMetadata).Returns(new List { methodMetadata.Object }); @@ -101,7 +101,8 @@ public void GenerateCode_UnmatchedData_ReportNotMapped() var result = _processorStrategy.GenerateCode(_sourceMetadataMock.Object); // Assert - result.Diagnostics.Should().Contain(d => d.DiagnosticDescriptor == SourceMapperDescriptors.PropertyIsNotMapped); + result.Diagnostics.Should().Contain(d => d.DiagnosticDescriptor == SourceMapperDescriptors.PropertyIsNotMapped || + d.DiagnosticDescriptor == SourceMapperDescriptors.FieldIsNotMapped); } [Fact] @@ -138,13 +139,13 @@ private Mock GetValidMethodWithAttributes(Mock>(); // Create new method property - var mockProperty = _fixture.Create>(); + var mockProperty = _fixture.Create>(); mockProperty.Setup(p => p.IsClass).Returns(false); mockProperty.Setup(p => p.IgnoreInMapping).Returns(ignoreInMapping); // Inject property var mockTypeMetadata = _fixture.Create>(); - mockTypeMetadata.Setup(t => t.Properties).Returns(new List { mockProperty.Object }); + mockTypeMetadata.Setup(t => t.Properties).Returns(new List { mockProperty.Object }); var sourceParameters = mockTypeMetadata.Object; var mockParameters = GetValidMethodParameters(sourceParameters); @@ -164,7 +165,7 @@ private Mock GetValidMethodParameters(ITypeMetadata sourceTypeMet mockParameters.Setup(p => p.Name).Returns(sourceTypeMetadata.Name); mockParameters.Setup(p => p.FullName).Returns(sourceTypeMetadata.FullName); mockParameters.Setup(p => p.Location).Returns(sourceTypeMetadata.Location); - mockParameters.Setup(p => p.Properties).Returns(new List { sourceTypeMetadata.Properties.First() }); + mockParameters.Setup(p => p.Properties).Returns(new List { sourceTypeMetadata.Properties.First() }); return mockParameters; } diff --git a/Compentio.SourceMapper.Tests/Processors/ClassUserMapperTests.cs b/Compentio.SourceMapper.Tests/Processors/ClassUserMapperTests.cs index 5bc8448..a71e0a9 100644 --- a/Compentio.SourceMapper.Tests/Processors/ClassUserMapperTests.cs +++ b/Compentio.SourceMapper.Tests/Processors/ClassUserMapperTests.cs @@ -36,7 +36,9 @@ public void Mapper_User_Dao_Match_Converters() mappingResult.Name.Should().Be($"{userDao.FirstName} {userDao.LastName}"); mappingResult.BirthDate.Should().Be(userDao.BirthDate); mappingResult.Id.Should().Be((int)userDao.UserId); - mappingResult.Sex.Should().Be(Sex.W); + mappingResult.Sex.Should().Be(Sex.W); + mappingResult.UserCode.Should().Be(userDao.UserCode); + UserInfo.UserCodeStatic.Should().Be(UserDao.UserCodeStatic); } [Fact] @@ -54,6 +56,8 @@ public void Mapper_User_Info_Match_Converters() // Assert mappingResult.BirthDate.Should().Be(userInfo.BirthDate); mappingResult.UserGender.Should().Be(UserGender.Female); + mappingResult.UserCode.Should().Be(userInfo.UserCode); + UserDao.UserCodeStatic.Should().Be(UserInfo.UserCodeStatic); // Not mapped mappingResult.UserId.Should().NotBe(userInfo.Id); diff --git a/Compentio.SourceMapper.Tests/Processors/InterfaceProcessorStrategyTests.cs b/Compentio.SourceMapper.Tests/Processors/InterfaceProcessorStrategyTests.cs index c7750a4..a18a37f 100644 --- a/Compentio.SourceMapper.Tests/Processors/InterfaceProcessorStrategyTests.cs +++ b/Compentio.SourceMapper.Tests/Processors/InterfaceProcessorStrategyTests.cs @@ -90,7 +90,7 @@ public void GenerateCode_UnmatchedData_ReportNotMapped() { // Arrange var methodMetadata = GetValidMethodWithAttributes(_mockMappingAttribute); - methodMetadata.Setup(m => m.ReturnType.Properties).Returns(_fixture.Create>()); + methodMetadata.Setup(m => m.ReturnType.Properties).Returns(_fixture.Create>()); methodMetadata.Setup(m => m.Parameters).Returns(_fixture.Create>()); _sourceMetadataMock.Setup(sourceMetadata => sourceMetadata.MethodsMetadata).Returns(new List { methodMetadata.Object }); @@ -98,7 +98,8 @@ public void GenerateCode_UnmatchedData_ReportNotMapped() var result = _processorStrategy.GenerateCode(_sourceMetadataMock.Object); // Assert - result.Diagnostics.Should().Contain(d => d.DiagnosticDescriptor == SourceMapperDescriptors.PropertyIsNotMapped); + result.Diagnostics.Should().Contain(d => d.DiagnosticDescriptor == SourceMapperDescriptors.PropertyIsNotMapped || + d.DiagnosticDescriptor == SourceMapperDescriptors.FieldIsNotMapped); } [Fact] @@ -135,13 +136,13 @@ private Mock GetValidMethodWithAttributes(Mock>(); // Create new method property - var mockProperty = _fixture.Create>(); + var mockProperty = _fixture.Create>(); mockProperty.Setup(p => p.IsClass).Returns(false); mockProperty.Setup(p => p.IgnoreInMapping).Returns(ignoreInMapping); // Inject property var mockTypeMetadata = _fixture.Create>(); - mockTypeMetadata.Setup(t => t.Properties).Returns(new List { mockProperty.Object }); + mockTypeMetadata.Setup(t => t.Properties).Returns(new List { mockProperty.Object }); var sourceParameters = mockTypeMetadata.Object; var mockParameters = GetValidMethodParameters(sourceParameters); @@ -161,7 +162,7 @@ private Mock GetValidMethodParameters(ITypeMetadata sourceTypeMet mockParameters.Setup(p => p.Name).Returns(sourceTypeMetadata.Name); mockParameters.Setup(p => p.FullName).Returns(sourceTypeMetadata.FullName); mockParameters.Setup(p => p.Location).Returns(sourceTypeMetadata.Location); - mockParameters.Setup(p => p.Properties).Returns(new List { sourceTypeMetadata.Properties.First() }); + mockParameters.Setup(p => p.Properties).Returns(new List { sourceTypeMetadata.Properties.First() }); return mockParameters; } diff --git a/Compentio.SourceMapper.Tests/Processors/InterfaceUserMapperTests.cs b/Compentio.SourceMapper.Tests/Processors/InterfaceUserMapperTests.cs index 6db63e8..d0d523e 100644 --- a/Compentio.SourceMapper.Tests/Processors/InterfaceUserMapperTests.cs +++ b/Compentio.SourceMapper.Tests/Processors/InterfaceUserMapperTests.cs @@ -31,6 +31,8 @@ public void Mapper_User_Dao_Match_Properties_And_Attributes() // Assert mappingResult.Name.Should().Be(userDao.FirstName); mappingResult.BirthDate.Should().Be(userDao.BirthDate); + mappingResult.UserCode.Should().Be(userDao.UserCode); + UserInfo.UserCodeStatic.Should().Be(UserDao.UserCodeStatic); // Not mapped mappingResult.Id.Should().NotBe((int)userDao.UserId); mappingResult.Address.Should().BeNull(); @@ -49,6 +51,8 @@ public void Mapper_User_Info_Match_Properties_And_Attributes() // Assert mappingResult.FirstName.Should().Be(userInfo.Name); mappingResult.BirthDate.Should().Be(userInfo.BirthDate); + mappingResult.UserCode.Should().Be(userInfo.UserCode); + UserDao.UserCodeStatic.Should().Be(UserInfo.UserCodeStatic); //// Not mapped mappingResult.UserId.Should().NotBe(userInfo.Id); mappingResult.City.Should().BeNull(); @@ -87,7 +91,7 @@ public void Mapper_User_Data_Dao_Match_Properties_And_Attributes() mappingResult.Name.Should().Be(userDataDao.FirstName); mappingResult.BirthDate.Should().Be(userDataDao.BirthDate); - + // Property mappingResult.Address.Should().NotBeNull(); mappingResult.Address.City.Should().Be(userDataDao.UserAddress.City); mappingResult.Address.House.Should().Be(userDataDao.UserAddress.House); @@ -96,6 +100,15 @@ public void Mapper_User_Data_Dao_Match_Properties_And_Attributes() mappingResult.Address.Region.Should().NotBeNull(); mappingResult.Address.Region.State.Should().Be(userDataDao.UserAddress.Region.State); mappingResult.Address.Region.District.Should().Be(userDataDao.UserAddress.Region.District); + // Field + mappingResult.AddressField.Should().NotBeNull(); + mappingResult.AddressField.City.Should().Be(userDataDao.AddressField.City); + mappingResult.AddressField.House.Should().Be(userDataDao.AddressField.House); + mappingResult.AddressField.Street.Should().Be(userDataDao.AddressField.Street); + + mappingResult.AddressField.Region.Should().NotBeNull(); + mappingResult.AddressField.Region.State.Should().Be(userDataDao.AddressField.Region.State); + mappingResult.AddressField.Region.District.Should().Be(userDataDao.AddressField.Region.District); } [Fact] @@ -113,7 +126,7 @@ public void Mapper_User_Data_Info_Match_Properties_And_Attributes() mappingResult.FirstName.Should().Be(userInfo.Name); mappingResult.BirthDate.Should().Be(userInfo.BirthDate); - + // Property mappingResult.UserAddress.Should().NotBeNull(); mappingResult.UserAddress.City.Should().Be(userInfo.Address.City); mappingResult.UserAddress.House.Should().Be(userInfo.Address.House); @@ -122,6 +135,15 @@ public void Mapper_User_Data_Info_Match_Properties_And_Attributes() mappingResult.UserAddress.Region.Should().NotBeNull(); mappingResult.UserAddress.Region.State.Should().Be(userInfo.Address.Region.State); mappingResult.UserAddress.Region.District.Should().Be(userInfo.Address.Region.District); + // Field + mappingResult.AddressField.Should().NotBeNull(); + mappingResult.AddressField.City.Should().Be(userInfo.AddressField.City); + mappingResult.AddressField.House.Should().Be(userInfo.AddressField.House); + mappingResult.AddressField.Street.Should().Be(userInfo.AddressField.Street); + + mappingResult.AddressField.Region.Should().NotBeNull(); + mappingResult.AddressField.Region.State.Should().Be(userInfo.AddressField.Region.State); + mappingResult.AddressField.Region.District.Should().Be(userInfo.AddressField.Region.District); } [Fact] diff --git a/README.md b/README.md index be02bd0..471aecd 100644 --- a/README.md +++ b/README.md @@ -42,11 +42,11 @@ public interface INotesMapper NoteDto MapToDto(NoteDao source); } ``` -This will generate mapping class with default class name `NotesMapper` for properties that names are the same for `NoteDto` and `NoteDao` classes. +This will generate mapping class with default class name `NotesMapper` for properties and fields that names are the same for `NoteDto` and `NoteDao` classes. The generated class is in the same namespace as its base abstract class of interface. It can be found in project in Visual Studio: > Dependencies -> Analyzers -> Compentio.SourceMapper.Generators.MainSourceGenerator. -When the names are different than we can use `Source` and `Target` names of the properties: +When the names are different than we can use `Source` and `Target` names of the properties or fields: ```csharp [Mapper(ClassName = "InterfaceUserMapper")] @@ -149,13 +149,13 @@ public abstract class NotesClassMapper ``` -`Expression` - it is a name of mapping function, that can be used for additional properties mapping. +`Expression` - it is a name of mapping function, that can be used for additional properties/fields mapping. > It must be `public` or `protected`, since it is used in generated mapper class that implements abstract mapping class. ## Ignore mapping -If for any reason part of the class/interface properties should not be mapped, `IgnoreMapping` attribute should be used for that. -Added `IgnoreMapping` causes that both source and target property during mapping generation will be omitted, not generating any linked map and not reporting any warning in diagnostics. +If for any reason part of the class/interface properties or fields should not be mapped, `IgnoreMapping` attribute should be used for that. +Added `IgnoreMapping` causes that both source and target property/field during mapping generation will be omitted, not generating any linked map and not reporting any warning in diagnostics. If we have two classes `NoteDao` and `NoteDto` ```csharp