Handle structs and updates
This commit is contained in:
parent
302a15b30f
commit
4077dfd692
|
@ -11,7 +11,7 @@ namespace MapTo
|
|||
internal ClassMappingContext(Compilation compilation, SourceGenerationOptions sourceGenerationOptions, TypeDeclarationSyntax typeSyntax)
|
||||
: base(compilation, sourceGenerationOptions, typeSyntax) { }
|
||||
|
||||
protected override ImmutableArray<MappedProperty> GetMappedProperties(ITypeSymbol typeSymbol, ITypeSymbol sourceTypeSymbol, bool isInheritFromMappedBaseClass)
|
||||
protected override ImmutableArray<MappedProperty> GetSourceMappedProperties(ITypeSymbol typeSymbol, ITypeSymbol sourceTypeSymbol, bool isInheritFromMappedBaseClass)
|
||||
{
|
||||
var sourceProperties = sourceTypeSymbol.GetAllMembers().OfType<IPropertySymbol>().ToArray();
|
||||
|
||||
|
@ -23,5 +23,18 @@ namespace MapTo
|
|||
.Where(mappedProperty => mappedProperty is not null)
|
||||
.ToImmutableArray()!;
|
||||
}
|
||||
|
||||
protected override ImmutableArray<MappedProperty> GetTypeMappedProperties(ITypeSymbol typeSymbol, ITypeSymbol sourceTypeSymbol, bool isInheritFromMappedBaseClass)
|
||||
{
|
||||
var sourceProperties = sourceTypeSymbol.GetAllMembers().OfType<IPropertySymbol>().ToArray();
|
||||
|
||||
return typeSymbol
|
||||
.GetAllMembers()
|
||||
.OfType<IPropertySymbol>()
|
||||
.Where(p => !p.HasAttribute(IgnorePropertyAttributeTypeSymbol))
|
||||
.Select(property => MapProperty(typeSymbol, sourceProperties, property))
|
||||
.Where(mappedProperty => mappedProperty is not null)
|
||||
.ToImmutableArray()!;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
using MapTo.Sources;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace MapTo.Extensions
|
||||
{
|
||||
internal static class CommonExtensions
|
||||
{
|
||||
internal static SourceBuilder WriteComment(this SourceBuilder builder, string comment = "")
|
||||
{
|
||||
return builder.WriteLine($"// {comment}");
|
||||
}
|
||||
|
||||
internal static SourceBuilder WriteModelInfo(this SourceBuilder builder, MappingModel model)
|
||||
{
|
||||
return builder
|
||||
.WriteLine()
|
||||
.WriteComment($" IsTypeUpdatable {model.IsTypeUpdatable}")
|
||||
.WriteComment($" HasMappedBaseClass {model.HasMappedBaseClass.ToString()}")
|
||||
.WriteComment($" Namespace {model.Namespace}")
|
||||
.WriteComment($" Options {model.Options.ToString()}")
|
||||
.WriteComment($" Type {model.Type}")
|
||||
.WriteComment($" TypeIdentifierName {model.TypeIdentifierName}")
|
||||
.WriteComment($" SourceNamespace {model.SourceNamespace}")
|
||||
.WriteComment($" SourceTypeFullName {model.SourceTypeFullName}")
|
||||
.WriteComment($" SourceTypeIdentifierName {model.SourceTypeIdentifierName}");
|
||||
|
||||
}
|
||||
|
||||
internal static SourceBuilder WriteMappedProperties(this SourceBuilder builder, System.Collections.Immutable.ImmutableArray<MappedProperty> mappedProperties)
|
||||
{
|
||||
foreach (var item in mappedProperties)
|
||||
{
|
||||
builder .WriteComment($" Name {item.Name}")
|
||||
.WriteComment($" Type {item.Type}")
|
||||
.WriteComment($" MappedSourcePropertyTypeName {item.MappedSourcePropertyTypeName}")
|
||||
.WriteComment($" IsEnumerable {item.IsEnumerable}")
|
||||
.WriteComment($" SourcePropertyName {item.SourcePropertyName}")
|
||||
.WriteComment($" TypeSymbol {item.FullyQualifiedType.ToString()}")
|
||||
.WriteComment($" TypeSymbol {item.FullyQualifiedType.ToString()}")
|
||||
.WriteLine();
|
||||
}
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,177 @@
|
|||
using MapTo.Sources;
|
||||
using static MapTo.Sources.Constants;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace MapTo.Extensions
|
||||
{
|
||||
internal static class CommonSource
|
||||
{
|
||||
internal static SourceCode GenerateStructOrClass(this MappingModel model, string structOrClass)
|
||||
{
|
||||
const bool writeDebugInfo = false;
|
||||
|
||||
using var builder = new SourceBuilder()
|
||||
.WriteLine(GeneratedFilesHeader)
|
||||
.WriteNullableContextOptionIf(model.Options.SupportNullableReferenceTypes)
|
||||
.WriteUsings(model.Usings)
|
||||
.WriteLine()
|
||||
|
||||
// Namespace declaration
|
||||
.WriteLine($"namespace {model.Namespace}")
|
||||
.WriteOpeningBracket();
|
||||
|
||||
if(writeDebugInfo)
|
||||
builder
|
||||
.WriteModelInfo(model)
|
||||
.WriteLine()
|
||||
.WriteComment("Type properties")
|
||||
.WriteComment()
|
||||
.WriteMappedProperties(model.TypeProperties)
|
||||
.WriteLine()
|
||||
.WriteComment("Source properties")
|
||||
.WriteMappedProperties(model.SourceProperties)
|
||||
.WriteLine();
|
||||
|
||||
builder
|
||||
// Class declaration
|
||||
.WriteLine($"partial {structOrClass} {model.TypeIdentifierName}")
|
||||
.WriteOpeningBracket()
|
||||
.WriteLine()
|
||||
// Class body
|
||||
.GeneratePublicConstructor(model);
|
||||
|
||||
if (model.IsTypeUpdatable && model.TypeProperties.GetWritableMappedProperties().Length > 0) builder.GenerateUpdateMethod(model);
|
||||
|
||||
builder
|
||||
.WriteLine()
|
||||
// End class declaration
|
||||
.WriteClosingBracket()
|
||||
.WriteLine()
|
||||
// End namespace declaration
|
||||
.WriteClosingBracket();
|
||||
|
||||
return new(builder.ToString(), $"{model.Namespace}.{model.TypeIdentifierName}.g.cs");
|
||||
}
|
||||
|
||||
private static SourceBuilder GeneratePublicConstructor(this SourceBuilder builder, MappingModel model)
|
||||
{
|
||||
var sourceClassParameterName = model.SourceTypeIdentifierName.ToCamelCase();
|
||||
const string mappingContextParameterName = "context";
|
||||
|
||||
var baseConstructor = /*model.HasMappedBaseClass ? $" : base({mappingContextParameterName}, {sourceClassParameterName})" :*/ string.Empty;
|
||||
|
||||
var readOnlyProperties = model.TypeProperties.GetReadOnlyMappedProperties();
|
||||
|
||||
var stringBuilder = new StringBuilder();
|
||||
|
||||
for (int i = 0; i < readOnlyProperties.Length; i++)
|
||||
{
|
||||
var property = readOnlyProperties[i];
|
||||
if(!model.SourceProperties.IsMappedProperty(property))
|
||||
{
|
||||
stringBuilder.Append(", ");
|
||||
stringBuilder.Append($"{property.FullyQualifiedType} {property.SourcePropertyName.ToCamelCase()}");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
var readOnlyPropertiesArguments = stringBuilder.ToString();
|
||||
|
||||
builder
|
||||
.WriteLine($"public {model.TypeIdentifierName}({model.SourceType} {sourceClassParameterName}{readOnlyPropertiesArguments}){baseConstructor}")
|
||||
.WriteOpeningBracket()
|
||||
.TryWriteProperties(model.SourceProperties, readOnlyProperties, sourceClassParameterName, mappingContextParameterName, false);
|
||||
|
||||
// End constructor declaration
|
||||
return builder.WriteClosingBracket();
|
||||
}
|
||||
|
||||
private static bool IsMappedProperty(this System.Collections.Immutable.ImmutableArray<MappedProperty> properties, MappedProperty property) => properties.Contains(property);
|
||||
|
||||
private static SourceBuilder TryWriteProperties(this SourceBuilder builder, System.Collections.Immutable.ImmutableArray<MappedProperty> properties, System.Collections.Immutable.ImmutableArray<MappedProperty>? otherProperties,
|
||||
string? sourceClassParameterName, string mappingContextParameterName, bool fromUpdate)
|
||||
{
|
||||
if (fromUpdate)
|
||||
{
|
||||
properties = properties.GetWritableMappedProperties();
|
||||
}
|
||||
|
||||
foreach (var property in properties)
|
||||
{
|
||||
if (property.isReadOnly && fromUpdate) continue;
|
||||
|
||||
if (property.TypeConverter is null)
|
||||
{
|
||||
if (property.IsEnumerable)
|
||||
{
|
||||
builder.WriteLine(
|
||||
$"{property.Name} = {sourceClassParameterName}.{property.SourcePropertyName}.Select({mappingContextParameterName}.{MappingContextSource.MapMethodName}<{property.MappedSourcePropertyTypeName}, {property.EnumerableTypeArgument}>).ToList();");
|
||||
}
|
||||
else
|
||||
{
|
||||
builder.WriteLine(property.MappedSourcePropertyTypeName is null
|
||||
? $"{property.Name} = {sourceClassParameterName}.{property.SourcePropertyName};"
|
||||
: "");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var parameters = property.TypeConverterParameters.IsEmpty
|
||||
? "null"
|
||||
: $"new object[] {{ {string.Join(", ", property.TypeConverterParameters)} }}";
|
||||
|
||||
builder.WriteLine(
|
||||
$"{property.Name} = new {property.TypeConverter}().Convert({sourceClassParameterName}.{property.SourcePropertyName}, {parameters});");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
if (otherProperties == null) return builder;
|
||||
|
||||
foreach (var property in otherProperties)
|
||||
{
|
||||
if(!properties.IsMappedProperty(property))
|
||||
builder.WriteLine(property.MappedSourcePropertyTypeName is null
|
||||
? $"{property.Name} = {property.SourcePropertyName.ToCamelCase()};"
|
||||
: "");
|
||||
|
||||
}
|
||||
|
||||
return builder;
|
||||
|
||||
}
|
||||
|
||||
|
||||
private static SourceBuilder GenerateUpdateMethod(this SourceBuilder builder, MappingModel model)
|
||||
{
|
||||
var sourceClassParameterName = model.SourceTypeIdentifierName.ToCamelCase();
|
||||
|
||||
builder
|
||||
.GenerateUpdaterMethodsXmlDocs(model, sourceClassParameterName)
|
||||
.WriteLine($"public void Update({model.SourceType} {sourceClassParameterName})")
|
||||
.WriteOpeningBracket()
|
||||
.TryWriteProperties(model.SourceProperties, null, sourceClassParameterName, "context", true)
|
||||
.WriteClosingBracket();
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
private static SourceBuilder GenerateUpdaterMethodsXmlDocs(this SourceBuilder builder, MappingModel model, string sourceClassParameterName)
|
||||
{
|
||||
if (!model.Options.GenerateXmlDocument)
|
||||
{
|
||||
return builder;
|
||||
}
|
||||
|
||||
return builder
|
||||
.WriteLine("/// <summary>")
|
||||
.WriteLine($"/// Updates <see cref=\"{model.TypeIdentifierName}\"/> and sets its participating properties")
|
||||
.WriteLine($"/// using the property values from <paramref name=\"{sourceClassParameterName}\"/>.")
|
||||
.WriteLine("/// </summary>")
|
||||
.WriteLine($"/// <param name=\"{sourceClassParameterName}\">The instance of <see cref=\"{model.SourceType}\"/> to use as source.</param>");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -28,6 +28,7 @@ namespace MapTo
|
|||
var options = SourceGenerationOptions.From(context);
|
||||
|
||||
var compilation = context.Compilation
|
||||
.AddSource(ref context, UseUpdateAttributeSource.Generate(options))
|
||||
.AddSource(ref context, MapFromAttributeSource.Generate(options))
|
||||
.AddSource(ref context, IgnorePropertyAttributeSource.Generate(options))
|
||||
.AddSource(ref context, ITypeConverterSource.Generate(options))
|
||||
|
|
|
@ -9,6 +9,12 @@ using Microsoft.CodeAnalysis.CSharp.Syntax;
|
|||
|
||||
namespace MapTo
|
||||
{
|
||||
internal static class MappingContextExtensions
|
||||
{
|
||||
internal static ImmutableArray<MappedProperty> GetReadOnlyMappedProperties(this ImmutableArray<MappedProperty> mappedProperties) => mappedProperties.Where(p => p.isReadOnly).ToImmutableArray()!;
|
||||
internal static ImmutableArray<MappedProperty> GetWritableMappedProperties(this ImmutableArray<MappedProperty> mappedProperties) => mappedProperties.Where(p => !p.isReadOnly).ToImmutableArray()!;
|
||||
}
|
||||
|
||||
internal abstract class MappingContext
|
||||
{
|
||||
private readonly List<SymbolDisplayPart> _ignoredNamespaces;
|
||||
|
@ -27,6 +33,7 @@ namespace MapTo
|
|||
TypeConverterInterfaceTypeSymbol = compilation.GetTypeByMetadataNameOrThrow(ITypeConverterSource.FullyQualifiedName);
|
||||
MapPropertyAttributeTypeSymbol = compilation.GetTypeByMetadataNameOrThrow(MapPropertyAttributeSource.FullyQualifiedName);
|
||||
MapFromAttributeTypeSymbol = compilation.GetTypeByMetadataNameOrThrow(MapFromAttributeSource.FullyQualifiedName);
|
||||
UseUpdateAttributeTypeSymbol = compilation.GetTypeByMetadataNameOrThrow(UseUpdateAttributeSource.FullyQualifiedName);
|
||||
MappingContextTypeSymbol = compilation.GetTypeByMetadataNameOrThrow(MappingContextSource.FullyQualifiedName);
|
||||
|
||||
AddUsingIfRequired(sourceGenerationOptions.SupportNullableStaticAnalysis, "System.Diagnostics.CodeAnalysis");
|
||||
|
@ -41,6 +48,8 @@ namespace MapTo
|
|||
protected INamedTypeSymbol IgnorePropertyAttributeTypeSymbol { get; }
|
||||
|
||||
protected INamedTypeSymbol MapFromAttributeTypeSymbol { get; }
|
||||
|
||||
protected INamedTypeSymbol UseUpdateAttributeTypeSymbol { get; }
|
||||
|
||||
protected INamedTypeSymbol MappingContextTypeSymbol { get; }
|
||||
|
||||
|
@ -60,6 +69,7 @@ namespace MapTo
|
|||
{
|
||||
MappingContext context = typeSyntax switch
|
||||
{
|
||||
StructDeclarationSyntax => new StructMappingContext(compilation, sourceGenerationOptions, typeSyntax),
|
||||
ClassDeclarationSyntax => new ClassMappingContext(compilation, sourceGenerationOptions, typeSyntax),
|
||||
RecordDeclarationSyntax => new RecordMappingContext(compilation, sourceGenerationOptions, typeSyntax),
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
|
@ -100,7 +110,8 @@ namespace MapTo
|
|||
return sourceProperties.SingleOrDefault(p => p.Name == propertyName);
|
||||
}
|
||||
|
||||
protected abstract ImmutableArray<MappedProperty> GetMappedProperties(ITypeSymbol typeSymbol, ITypeSymbol sourceTypeSymbol, bool isInheritFromMappedBaseClass);
|
||||
protected abstract ImmutableArray<MappedProperty> GetSourceMappedProperties(ITypeSymbol typeSymbol, ITypeSymbol sourceTypeSymbol, bool isInheritFromMappedBaseClass);
|
||||
protected abstract ImmutableArray<MappedProperty> GetTypeMappedProperties(ITypeSymbol typeSymbol, ITypeSymbol sourceTypeSymbol, bool isInheritFromMappedBaseClass);
|
||||
|
||||
protected INamedTypeSymbol? GetSourceTypeSymbol(TypeDeclarationSyntax typeDeclarationSyntax, SemanticModel? semanticModel = null) =>
|
||||
GetSourceTypeSymbol(typeDeclarationSyntax.GetAttribute(MapFromAttributeSource.AttributeName), semanticModel);
|
||||
|
@ -128,6 +139,11 @@ namespace MapTo
|
|||
.Any(t => t?.GetAttribute(MapFromAttributeTypeSymbol) != null);
|
||||
}
|
||||
|
||||
protected bool IsTypeUpdatable()
|
||||
{
|
||||
return TypeSyntax.GetAttribute("UseUpdate") != null;
|
||||
}
|
||||
|
||||
protected virtual MappedProperty? MapProperty(ISymbol sourceTypeSymbol, IReadOnlyCollection<IPropertySymbol> sourceProperties, ISymbol property)
|
||||
{
|
||||
var sourceProperty = FindSourceProperty(sourceProperties, property);
|
||||
|
@ -136,6 +152,7 @@ namespace MapTo
|
|||
return null;
|
||||
}
|
||||
|
||||
|
||||
string? converterFullyQualifiedName = null;
|
||||
var converterParameters = ImmutableArray<string>.Empty;
|
||||
ITypeSymbol? mappedSourcePropertyType = null;
|
||||
|
@ -154,16 +171,50 @@ namespace MapTo
|
|||
AddUsingIfRequired(enumerableTypeArgumentType);
|
||||
AddUsingIfRequired(mappedSourcePropertyType);
|
||||
|
||||
|
||||
return new MappedProperty(
|
||||
property.Name,
|
||||
property.GetTypeSymbol().ToString(),
|
||||
ToQualifiedDisplayName(propertyType) ?? propertyType.Name,
|
||||
converterFullyQualifiedName,
|
||||
converterParameters.ToImmutableArray(),
|
||||
sourceProperty.Name,
|
||||
ToQualifiedDisplayName(mappedSourcePropertyType),
|
||||
ToQualifiedDisplayName(enumerableTypeArgumentType));
|
||||
ToQualifiedDisplayName(enumerableTypeArgumentType),
|
||||
(property as IPropertySymbol).IsReadOnly);
|
||||
;
|
||||
}
|
||||
protected virtual MappedProperty? MapPropertySimple(ISymbol sourceTypeSymbol, ISymbol property)
|
||||
{
|
||||
if (!property.TryGetTypeSymbol(out var propertyType))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
string? converterFullyQualifiedName = null;
|
||||
var converterParameters = ImmutableArray<string>.Empty;
|
||||
ITypeSymbol? mappedSourcePropertyType = null;
|
||||
ITypeSymbol? enumerableTypeArgumentType = null;
|
||||
|
||||
|
||||
AddUsingIfRequired(propertyType);
|
||||
AddUsingIfRequired(enumerableTypeArgumentType);
|
||||
AddUsingIfRequired(mappedSourcePropertyType);
|
||||
|
||||
|
||||
return new MappedProperty(
|
||||
property.Name,
|
||||
property.GetTypeSymbol().ToString(),
|
||||
ToQualifiedDisplayName(propertyType) ?? propertyType.Name,
|
||||
converterFullyQualifiedName,
|
||||
converterParameters.ToImmutableArray(),
|
||||
property.Name,
|
||||
ToQualifiedDisplayName(mappedSourcePropertyType),
|
||||
ToQualifiedDisplayName(enumerableTypeArgumentType),
|
||||
(property as IPropertySymbol).IsReadOnly);
|
||||
;
|
||||
}
|
||||
protected bool TryGetMapTypeConverter(ISymbol property, IPropertySymbol sourceProperty, out string? converterFullyQualifiedName,
|
||||
out ImmutableArray<string> converterParameters)
|
||||
{
|
||||
|
@ -258,9 +309,10 @@ namespace MapTo
|
|||
var typeIdentifierName = TypeSyntax.GetIdentifierName();
|
||||
var sourceTypeIdentifierName = sourceTypeSymbol.Name;
|
||||
var isTypeInheritFromMappedBaseClass = IsTypeInheritFromMappedBaseClass(semanticModel);
|
||||
var isTypeUpdatable = IsTypeUpdatable();
|
||||
var shouldGenerateSecondaryConstructor = ShouldGenerateSecondaryConstructor(semanticModel, sourceTypeSymbol);
|
||||
|
||||
var mappedProperties = GetMappedProperties(typeSymbol, sourceTypeSymbol, isTypeInheritFromMappedBaseClass);
|
||||
var mappedProperties = GetSourceMappedProperties(typeSymbol, sourceTypeSymbol, isTypeInheritFromMappedBaseClass);
|
||||
if (!mappedProperties.Any())
|
||||
{
|
||||
AddDiagnostic(DiagnosticsFactory.NoMatchingPropertyFoundError(TypeSyntax.GetLocation(), typeSymbol, sourceTypeSymbol));
|
||||
|
@ -269,6 +321,8 @@ namespace MapTo
|
|||
|
||||
AddUsingIfRequired(mappedProperties.Any(p => p.IsEnumerable), "System.Linq");
|
||||
|
||||
var allProperties = GetTypeMappedProperties(sourceTypeSymbol, typeSymbol , isTypeInheritFromMappedBaseClass);
|
||||
|
||||
return new MappingModel(
|
||||
SourceGenerationOptions,
|
||||
TypeSyntax.GetNamespace(),
|
||||
|
@ -278,12 +332,16 @@ namespace MapTo
|
|||
sourceTypeSymbol.ContainingNamespace.ToDisplayString(),
|
||||
sourceTypeIdentifierName,
|
||||
sourceTypeSymbol.ToDisplayString(),
|
||||
isTypeUpdatable,
|
||||
mappedProperties,
|
||||
allProperties,
|
||||
isTypeInheritFromMappedBaseClass,
|
||||
Usings,
|
||||
shouldGenerateSecondaryConstructor);
|
||||
}
|
||||
|
||||
|
||||
|
||||
private INamedTypeSymbol? GetTypeConverterBaseInterface(ITypeSymbol converterTypeSymbol, ISymbol property, IPropertySymbol sourceProperty)
|
||||
{
|
||||
if (!property.TryGetTypeSymbol(out var propertyType))
|
||||
|
|
|
@ -24,12 +24,14 @@ namespace MapTo
|
|||
|
||||
internal record MappedProperty(
|
||||
string Name,
|
||||
string FullyQualifiedType,
|
||||
string Type,
|
||||
string? TypeConverter,
|
||||
ImmutableArray<string> TypeConverterParameters,
|
||||
string SourcePropertyName,
|
||||
string? MappedSourcePropertyTypeName,
|
||||
string? EnumerableTypeArgument)
|
||||
string? EnumerableTypeArgument,
|
||||
bool isReadOnly)
|
||||
{
|
||||
public bool IsEnumerable => EnumerableTypeArgument is not null;
|
||||
}
|
||||
|
@ -43,7 +45,9 @@ namespace MapTo
|
|||
string SourceNamespace,
|
||||
string SourceTypeIdentifierName,
|
||||
string SourceTypeFullName,
|
||||
ImmutableArray<MappedProperty> MappedProperties,
|
||||
bool IsTypeUpdatable,
|
||||
ImmutableArray<MappedProperty> SourceProperties,
|
||||
ImmutableArray<MappedProperty> TypeProperties,
|
||||
bool HasMappedBaseClass,
|
||||
ImmutableArray<string> Usings,
|
||||
bool GenerateSecondaryConstructor
|
||||
|
|
|
@ -11,7 +11,7 @@ namespace MapTo
|
|||
internal RecordMappingContext(Compilation compilation, SourceGenerationOptions sourceGenerationOptions, TypeDeclarationSyntax typeSyntax)
|
||||
: base(compilation, sourceGenerationOptions, typeSyntax) { }
|
||||
|
||||
protected override ImmutableArray<MappedProperty> GetMappedProperties(ITypeSymbol typeSymbol, ITypeSymbol sourceTypeSymbol, bool isInheritFromMappedBaseClass)
|
||||
protected override ImmutableArray<MappedProperty> GetSourceMappedProperties(ITypeSymbol typeSymbol, ITypeSymbol sourceTypeSymbol, bool isInheritFromMappedBaseClass)
|
||||
{
|
||||
var sourceProperties = sourceTypeSymbol.GetAllMembers().OfType<IPropertySymbol>().ToArray();
|
||||
return typeSymbol.GetMembers()
|
||||
|
@ -24,5 +24,19 @@ namespace MapTo
|
|||
.Where(mappedProperty => mappedProperty is not null)
|
||||
.ToImmutableArray()!;
|
||||
}
|
||||
|
||||
protected override ImmutableArray<MappedProperty> GetTypeMappedProperties(ITypeSymbol typeSymbol, ITypeSymbol sourceTypeSymbol, bool isInheritFromMappedBaseClass)
|
||||
{
|
||||
var sourceProperties = sourceTypeSymbol.GetAllMembers().OfType<IPropertySymbol>().ToArray();
|
||||
return typeSymbol.GetMembers()
|
||||
.OfType<IMethodSymbol>()
|
||||
.OrderByDescending(s => s.Parameters.Length)
|
||||
.First(s => s.Name == ".ctor")
|
||||
.Parameters
|
||||
.Where(p => !p.HasAttribute(IgnorePropertyAttributeTypeSymbol))
|
||||
.Select(property => MapProperty(typeSymbol, sourceProperties, property))
|
||||
.Where(mappedProperty => mappedProperty is not null)
|
||||
.ToImmutableArray()!;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
using MapTo.Extensions;
|
||||
using System.Text;
|
||||
using static MapTo.Sources.Constants;
|
||||
|
||||
namespace MapTo.Sources
|
||||
|
@ -7,200 +8,7 @@ namespace MapTo.Sources
|
|||
{
|
||||
internal static SourceCode Generate(MappingModel model)
|
||||
{
|
||||
using var builder = new SourceBuilder()
|
||||
.WriteLine(GeneratedFilesHeader)
|
||||
.WriteNullableContextOptionIf(model.Options.SupportNullableReferenceTypes)
|
||||
.WriteUsings(model.Usings)
|
||||
.WriteLine()
|
||||
|
||||
// Namespace declaration
|
||||
.WriteLine($"namespace {model.Namespace}")
|
||||
.WriteOpeningBracket()
|
||||
|
||||
// Class declaration
|
||||
.WriteLine($"partial class {model.TypeIdentifierName}")
|
||||
.WriteOpeningBracket();
|
||||
|
||||
// Class body
|
||||
if (model.GenerateSecondaryConstructor)
|
||||
{
|
||||
builder
|
||||
.GenerateSecondaryConstructor(model)
|
||||
.WriteLine();
|
||||
}
|
||||
|
||||
builder
|
||||
.GeneratePrivateConstructor(model)
|
||||
.WriteLine()
|
||||
.GenerateFactoryMethod(model)
|
||||
.GenerateUpdateMethod(model)
|
||||
|
||||
// End class declaration
|
||||
.WriteClosingBracket()
|
||||
.WriteLine()
|
||||
|
||||
// Extension class declaration
|
||||
.GenerateSourceTypeExtensionClass(model)
|
||||
|
||||
// End namespace declaration
|
||||
.WriteClosingBracket();
|
||||
|
||||
return new(builder.ToString(), $"{model.Namespace}.{model.TypeIdentifierName}.g.cs");
|
||||
}
|
||||
|
||||
private static SourceBuilder GenerateSecondaryConstructor(this SourceBuilder builder, MappingModel model)
|
||||
{
|
||||
var sourceClassParameterName = model.SourceTypeIdentifierName.ToCamelCase();
|
||||
|
||||
if (model.Options.GenerateXmlDocument)
|
||||
{
|
||||
builder
|
||||
.WriteLine("/// <summary>")
|
||||
.WriteLine($"/// Initializes a new instance of the <see cref=\"{model.TypeIdentifierName}\"/> class")
|
||||
.WriteLine($"/// using the property values from the specified <paramref name=\"{sourceClassParameterName}\"/>.")
|
||||
.WriteLine("/// </summary>")
|
||||
.WriteLine($"/// <exception cref=\"ArgumentNullException\">{sourceClassParameterName} is null</exception>");
|
||||
}
|
||||
|
||||
return builder
|
||||
.WriteLine($"{model.Options.ConstructorAccessModifier.ToLowercaseString()} {model.TypeIdentifierName}({model.SourceType} {sourceClassParameterName})")
|
||||
.WriteLine($" : this(new {MappingContextSource.ClassName}(), {sourceClassParameterName}) {{ }}");
|
||||
}
|
||||
|
||||
private static SourceBuilder GeneratePrivateConstructor(this SourceBuilder builder, MappingModel model)
|
||||
{
|
||||
var sourceClassParameterName = model.SourceTypeIdentifierName.ToCamelCase();
|
||||
const string mappingContextParameterName = "context";
|
||||
|
||||
var baseConstructor = model.HasMappedBaseClass ? $" : base({mappingContextParameterName}, {sourceClassParameterName})" : string.Empty;
|
||||
|
||||
builder
|
||||
.WriteLine($"private protected {model.TypeIdentifierName}({MappingContextSource.ClassName} {mappingContextParameterName}, {model.SourceType} {sourceClassParameterName}){baseConstructor}")
|
||||
.WriteOpeningBracket()
|
||||
.WriteLine($"if ({mappingContextParameterName} == null) throw new ArgumentNullException(nameof({mappingContextParameterName}));")
|
||||
.WriteLine($"if ({sourceClassParameterName} == null) throw new ArgumentNullException(nameof({sourceClassParameterName}));")
|
||||
.WriteLine()
|
||||
.WriteLine($"{mappingContextParameterName}.{MappingContextSource.RegisterMethodName}({sourceClassParameterName}, this);")
|
||||
.WriteLine().
|
||||
|
||||
WriteProperties( model, sourceClassParameterName, mappingContextParameterName);
|
||||
|
||||
// End constructor declaration
|
||||
return builder.WriteClosingBracket();
|
||||
}
|
||||
|
||||
private static SourceBuilder WriteProperties(this SourceBuilder builder, MappingModel model,
|
||||
string? sourceClassParameterName, string mappingContextParameterName)
|
||||
{
|
||||
foreach (var property in model.MappedProperties)
|
||||
{
|
||||
if (property.TypeConverter is null)
|
||||
{
|
||||
if (property.IsEnumerable)
|
||||
{
|
||||
builder.WriteLine(
|
||||
$"{property.Name} = {sourceClassParameterName}.{property.SourcePropertyName}.Select({mappingContextParameterName}.{MappingContextSource.MapMethodName}<{property.MappedSourcePropertyTypeName}, {property.EnumerableTypeArgument}>).ToList();");
|
||||
}
|
||||
else
|
||||
{
|
||||
builder.WriteLine(property.MappedSourcePropertyTypeName is null
|
||||
? $"{property.Name} = {sourceClassParameterName}.{property.SourcePropertyName};"
|
||||
: $"{property.Name} = {mappingContextParameterName}.{MappingContextSource.MapMethodName}<{property.MappedSourcePropertyTypeName}, {property.Type}>({sourceClassParameterName}.{property.SourcePropertyName});");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var parameters = property.TypeConverterParameters.IsEmpty
|
||||
? "null"
|
||||
: $"new object[] {{ {string.Join(", ", property.TypeConverterParameters)} }}";
|
||||
|
||||
builder.WriteLine(
|
||||
$"{property.Name} = new {property.TypeConverter}().Convert({sourceClassParameterName}.{property.SourcePropertyName}, {parameters});");
|
||||
}
|
||||
|
||||
}
|
||||
return builder;
|
||||
|
||||
}
|
||||
|
||||
private static SourceBuilder GenerateFactoryMethod(this SourceBuilder builder, MappingModel model)
|
||||
{
|
||||
var sourceClassParameterName = model.SourceTypeIdentifierName.ToCamelCase();
|
||||
|
||||
return builder
|
||||
.GenerateConvertorMethodsXmlDocs(model, sourceClassParameterName)
|
||||
.WriteLineIf(model.Options.SupportNullableStaticAnalysis, $"[return: NotNullIfNotNull(\"{sourceClassParameterName}\")]")
|
||||
.WriteLine($"{model.Options.GeneratedMethodsAccessModifier.ToLowercaseString()} static {model.TypeIdentifierName}{model.Options.NullableReferenceSyntax} From({model.SourceType}{model.Options.NullableReferenceSyntax} {sourceClassParameterName})")
|
||||
.WriteOpeningBracket()
|
||||
.WriteLine($"return {sourceClassParameterName} == null ? null : {MappingContextSource.ClassName}.{MappingContextSource.FactoryMethodName}<{model.SourceType}, {model.TypeIdentifierName}>({sourceClassParameterName});")
|
||||
.WriteClosingBracket();
|
||||
}
|
||||
|
||||
private static SourceBuilder GenerateUpdateMethod(this SourceBuilder builder, MappingModel model)
|
||||
{
|
||||
var sourceClassParameterName = model.SourceTypeIdentifierName.ToCamelCase();
|
||||
|
||||
builder
|
||||
.GenerateUpdaterMethodsXmlDocs(model, sourceClassParameterName)
|
||||
.WriteLine($"public void {model.Options.NullableReferenceSyntax}Update({model.SourceType}{model.Options.NullableReferenceSyntax} {sourceClassParameterName})")
|
||||
.WriteOpeningBracket()
|
||||
.WriteProperties( model, sourceClassParameterName,"context" )
|
||||
.WriteClosingBracket();
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
private static SourceBuilder GenerateConvertorMethodsXmlDocs(this SourceBuilder builder, MappingModel model, string sourceClassParameterName)
|
||||
{
|
||||
if (!model.Options.GenerateXmlDocument)
|
||||
{
|
||||
return builder;
|
||||
}
|
||||
|
||||
return builder
|
||||
.WriteLine("/// <summary>")
|
||||
.WriteLine($"/// Creates a new instance of <see cref=\"{model.TypeIdentifierName}\"/> and sets its participating properties")
|
||||
.WriteLine($"/// using the property values from <paramref name=\"{sourceClassParameterName}\"/>.")
|
||||
.WriteLine("/// </summary>")
|
||||
.WriteLine($"/// <param name=\"{sourceClassParameterName}\">The instance of <see cref=\"{model.SourceType}\"/> to use as source.</param>")
|
||||
.WriteLine($"/// <returns>A new instance of <see cred=\"{model.TypeIdentifierName}\"/> -or- <c>null</c> if <paramref name=\"{sourceClassParameterName}\"/> is <c>null</c>.</returns>");
|
||||
}
|
||||
|
||||
private static SourceBuilder GenerateUpdaterMethodsXmlDocs(this SourceBuilder builder, MappingModel model, string sourceClassParameterName)
|
||||
{
|
||||
if (!model.Options.GenerateXmlDocument)
|
||||
{
|
||||
return builder;
|
||||
}
|
||||
|
||||
return builder
|
||||
.WriteLine("/// <summary>")
|
||||
.WriteLine($"/// Updates <see cref=\"{model.TypeIdentifierName}\"/> and sets its participating properties")
|
||||
.WriteLine($"/// using the property values from <paramref name=\"{sourceClassParameterName}\"/>.")
|
||||
.WriteLine("/// </summary>")
|
||||
.WriteLine($"/// <param name=\"{sourceClassParameterName}\">The instance of <see cref=\"{model.SourceType}\"/> to use as source.</param>");
|
||||
}
|
||||
|
||||
private static SourceBuilder GenerateSourceTypeExtensionClass(this SourceBuilder builder, MappingModel model)
|
||||
{
|
||||
return builder
|
||||
.WriteLine($"{model.Options.GeneratedMethodsAccessModifier.ToLowercaseString()} static partial class {model.SourceTypeIdentifierName}To{model.TypeIdentifierName}Extensions")
|
||||
.WriteOpeningBracket()
|
||||
.GenerateSourceTypeExtensionMethod(model)
|
||||
.WriteClosingBracket();
|
||||
}
|
||||
|
||||
private static SourceBuilder GenerateSourceTypeExtensionMethod(this SourceBuilder builder, MappingModel model)
|
||||
{
|
||||
var sourceClassParameterName = model.SourceTypeIdentifierName.ToCamelCase();
|
||||
|
||||
return builder
|
||||
.GenerateConvertorMethodsXmlDocs(model, sourceClassParameterName)
|
||||
.WriteLineIf(model.Options.SupportNullableStaticAnalysis, $"[return: NotNullIfNotNull(\"{sourceClassParameterName}\")]")
|
||||
.WriteLine($"{model.Options.GeneratedMethodsAccessModifier.ToLowercaseString()} static {model.TypeIdentifierName}{model.Options.NullableReferenceSyntax} To{model.TypeIdentifierName}(this {model.SourceType}{model.Options.NullableReferenceSyntax} {sourceClassParameterName})")
|
||||
.WriteOpeningBracket()
|
||||
.WriteLine($"return {sourceClassParameterName} == null ? null : new {model.TypeIdentifierName}({sourceClassParameterName});")
|
||||
.WriteClosingBracket();
|
||||
return model.GenerateStructOrClass("class");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -26,7 +26,7 @@ namespace MapTo.Sources
|
|||
}
|
||||
|
||||
builder
|
||||
.WriteLine("[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]")
|
||||
.WriteLine("[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)]")
|
||||
.WriteLine($"public sealed class {AttributeName}Attribute : Attribute")
|
||||
.WriteOpeningBracket();
|
||||
|
||||
|
|
|
@ -96,9 +96,9 @@ namespace MapTo.Sources
|
|||
private static SourceBuilder WriteProperties(this SourceBuilder builder, MappingModel model, string sourceClassParameterName,
|
||||
string mappingContextParameterName)
|
||||
{
|
||||
for (var i = 0; i < model.MappedProperties.Length; i++)
|
||||
for (var i = 0; i < model.SourceProperties.Length; i++)
|
||||
{
|
||||
var property = model.MappedProperties[i];
|
||||
var property = model.SourceProperties[i];
|
||||
if (property.TypeConverter is null)
|
||||
{
|
||||
if (property.IsEnumerable)
|
||||
|
@ -123,7 +123,7 @@ namespace MapTo.Sources
|
|||
$"{property.Name}: new {property.TypeConverter}().Convert({sourceClassParameterName}.{property.SourcePropertyName}, {parameters})");
|
||||
}
|
||||
|
||||
if (i < model.MappedProperties.Length - 1)
|
||||
if (i < model.SourceProperties.Length - 1)
|
||||
{
|
||||
builder.Write(", ");
|
||||
}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
using MapTo.Extensions;
|
||||
using static MapTo.Sources.Constants;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace MapTo.Sources
|
||||
{
|
||||
|
@ -7,166 +9,7 @@ namespace MapTo.Sources
|
|||
{
|
||||
internal static SourceCode Generate(MappingModel model)
|
||||
{
|
||||
using var builder = new SourceBuilder()
|
||||
.WriteLine(GeneratedFilesHeader)
|
||||
.WriteNullableContextOptionIf(model.Options.SupportNullableReferenceTypes)
|
||||
.WriteUsings(model.Usings)
|
||||
.WriteLine()
|
||||
|
||||
// Namespace declaration
|
||||
.WriteLine($"namespace {model.Namespace}")
|
||||
.WriteOpeningBracket()
|
||||
|
||||
// Class declaration
|
||||
.WriteLine($"partial class {model.TypeIdentifierName}")
|
||||
.WriteOpeningBracket();
|
||||
|
||||
// Class body
|
||||
if (model.GenerateSecondaryConstructor)
|
||||
{
|
||||
builder
|
||||
.GenerateSecondaryConstructor(model)
|
||||
.WriteLine();
|
||||
}
|
||||
|
||||
builder
|
||||
.GeneratePrivateConstructor(model)
|
||||
.WriteLine()
|
||||
// End class declaration
|
||||
.WriteClosingBracket()
|
||||
.WriteLine()
|
||||
|
||||
// End namespace declaration
|
||||
.WriteClosingBracket();
|
||||
|
||||
return new(builder.ToString(), $"{model.Namespace}.{model.TypeIdentifierName}.g.cs");
|
||||
}
|
||||
|
||||
private static SourceBuilder GenerateSecondaryConstructor(this SourceBuilder builder, MappingModel model)
|
||||
{
|
||||
var sourceClassParameterName = model.SourceTypeIdentifierName.ToCamelCase();
|
||||
|
||||
if (model.Options.GenerateXmlDocument)
|
||||
{
|
||||
builder
|
||||
.WriteLine("/// <summary>")
|
||||
.WriteLine($"/// Initializes a new instance of the <see cref=\"{model.TypeIdentifierName}\"/> class")
|
||||
.WriteLine($"/// using the property values from the specified <paramref name=\"{sourceClassParameterName}\"/>.")
|
||||
.WriteLine("/// </summary>")
|
||||
.WriteLine($"/// <exception cref=\"ArgumentNullException\">{sourceClassParameterName} is null</exception>");
|
||||
}
|
||||
|
||||
return builder
|
||||
.WriteLine($"{model.Options.ConstructorAccessModifier.ToLowercaseString()} {model.TypeIdentifierName}({model.SourceType} {sourceClassParameterName})")
|
||||
.WriteLine($" : this(new {MappingContextSource.ClassName}(), {sourceClassParameterName}) {{ }}");
|
||||
}
|
||||
|
||||
private static SourceBuilder GeneratePrivateConstructor(this SourceBuilder builder, MappingModel model)
|
||||
{
|
||||
var sourceClassParameterName = model.SourceTypeIdentifierName.ToCamelCase();
|
||||
const string mappingContextParameterName = "context";
|
||||
|
||||
var baseConstructor = model.HasMappedBaseClass ? $" : base({mappingContextParameterName}, {sourceClassParameterName})" : string.Empty;
|
||||
|
||||
builder
|
||||
.WriteLine($"private protected {model.TypeIdentifierName}({MappingContextSource.ClassName} {mappingContextParameterName}, {model.SourceType} {sourceClassParameterName}){baseConstructor}")
|
||||
.WriteOpeningBracket()
|
||||
.WriteLine()
|
||||
.WriteLine($"{mappingContextParameterName}.{MappingContextSource.RegisterMethodName}({sourceClassParameterName}, this);")
|
||||
.WriteLine().
|
||||
|
||||
WriteProperties( model, sourceClassParameterName, mappingContextParameterName);
|
||||
|
||||
// End constructor declaration
|
||||
return builder.WriteClosingBracket();
|
||||
}
|
||||
|
||||
private static SourceBuilder WriteProperties(this SourceBuilder builder, MappingModel model,
|
||||
string? sourceClassParameterName, string mappingContextParameterName)
|
||||
{
|
||||
foreach (var property in model.MappedProperties)
|
||||
{
|
||||
if (property.TypeConverter is null)
|
||||
{
|
||||
if (property.IsEnumerable)
|
||||
{
|
||||
builder.WriteLine(
|
||||
$"{property.Name} = {sourceClassParameterName}.{property.SourcePropertyName}.Select({mappingContextParameterName}.{MappingContextSource.MapMethodName}<{property.MappedSourcePropertyTypeName}, {property.EnumerableTypeArgument}>).ToList();");
|
||||
}
|
||||
else
|
||||
{
|
||||
builder.WriteLine(property.MappedSourcePropertyTypeName is null
|
||||
? $"{property.Name} = {sourceClassParameterName}.{property.SourcePropertyName};"
|
||||
: $"{property.Name} = {mappingContextParameterName}.{MappingContextSource.MapMethodName}<{property.MappedSourcePropertyTypeName}, {property.Type}>({sourceClassParameterName}.{property.SourcePropertyName});");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var parameters = property.TypeConverterParameters.IsEmpty
|
||||
? "null"
|
||||
: $"new object[] {{ {string.Join(", ", property.TypeConverterParameters)} }}";
|
||||
|
||||
builder.WriteLine(
|
||||
$"{property.Name} = new {property.TypeConverter}().Convert({sourceClassParameterName}.{property.SourcePropertyName}, {parameters});");
|
||||
}
|
||||
|
||||
}
|
||||
return builder;
|
||||
|
||||
}
|
||||
|
||||
|
||||
private static SourceBuilder GenerateConvertorMethodsXmlDocs(this SourceBuilder builder, MappingModel model, string sourceClassParameterName)
|
||||
{
|
||||
if (!model.Options.GenerateXmlDocument)
|
||||
{
|
||||
return builder;
|
||||
}
|
||||
|
||||
return builder
|
||||
.WriteLine("/// <summary>")
|
||||
.WriteLine($"/// Creates a new instance of <see cref=\"{model.TypeIdentifierName}\"/> and sets its participating properties")
|
||||
.WriteLine($"/// using the property values from <paramref name=\"{sourceClassParameterName}\"/>.")
|
||||
.WriteLine("/// </summary>")
|
||||
.WriteLine($"/// <param name=\"{sourceClassParameterName}\">The instance of <see cref=\"{model.SourceType}\"/> to use as source.</param>")
|
||||
.WriteLine($"/// <returns>A new instance of <see cred=\"{model.TypeIdentifierName}\"/> -or- <c>null</c> if <paramref name=\"{sourceClassParameterName}\"/> is <c>null</c>.</returns>");
|
||||
}
|
||||
|
||||
private static SourceBuilder GenerateUpdaterMethodsXmlDocs(this SourceBuilder builder, MappingModel model, string sourceClassParameterName)
|
||||
{
|
||||
if (!model.Options.GenerateXmlDocument)
|
||||
{
|
||||
return builder;
|
||||
}
|
||||
|
||||
return builder
|
||||
.WriteLine("/// <summary>")
|
||||
.WriteLine($"/// Updates <see cref=\"{model.TypeIdentifierName}\"/> and sets its participating properties")
|
||||
.WriteLine($"/// using the property values from <paramref name=\"{sourceClassParameterName}\"/>.")
|
||||
.WriteLine("/// </summary>")
|
||||
.WriteLine($"/// <param name=\"{sourceClassParameterName}\">The instance of <see cref=\"{model.SourceType}\"/> to use as source.</param>");
|
||||
}
|
||||
|
||||
private static SourceBuilder GenerateSourceTypeExtensionClass(this SourceBuilder builder, MappingModel model)
|
||||
{
|
||||
return builder
|
||||
.WriteLine($"{model.Options.GeneratedMethodsAccessModifier.ToLowercaseString()} static partial class {model.SourceTypeIdentifierName}To{model.TypeIdentifierName}Extensions")
|
||||
.WriteOpeningBracket()
|
||||
.GenerateSourceTypeExtensionMethod(model)
|
||||
.WriteClosingBracket();
|
||||
}
|
||||
|
||||
private static SourceBuilder GenerateSourceTypeExtensionMethod(this SourceBuilder builder, MappingModel model)
|
||||
{
|
||||
var sourceClassParameterName = model.SourceTypeIdentifierName.ToCamelCase();
|
||||
|
||||
return builder
|
||||
.GenerateConvertorMethodsXmlDocs(model, sourceClassParameterName)
|
||||
.WriteLineIf(model.Options.SupportNullableStaticAnalysis, $"[return: NotNullIfNotNull(\"{sourceClassParameterName}\")]")
|
||||
.WriteLine($"{model.Options.GeneratedMethodsAccessModifier.ToLowercaseString()} static {model.TypeIdentifierName}{model.Options.NullableReferenceSyntax} To{model.TypeIdentifierName}(this {model.SourceType}{model.Options.NullableReferenceSyntax} {sourceClassParameterName})")
|
||||
.WriteOpeningBracket()
|
||||
.WriteLine($"return {sourceClassParameterName} == null ? null : new {model.TypeIdentifierName}({sourceClassParameterName});")
|
||||
.WriteClosingBracket();
|
||||
return model.GenerateStructOrClass("struct");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
using static MapTo.Sources.Constants;
|
||||
|
||||
namespace MapTo.Sources
|
||||
{
|
||||
internal static class ReadOnlyPropertyAttributeSource
|
||||
{
|
||||
internal const string AttributeName = "ReadOnlyProperty";
|
||||
internal const string AttributeClassName = AttributeName + "Attribute";
|
||||
internal const string FullyQualifiedName = RootNamespace + "." + AttributeClassName;
|
||||
|
||||
internal static SourceCode Generate(SourceGenerationOptions options)
|
||||
{
|
||||
var builder = new SourceBuilder()
|
||||
.WriteLine(GeneratedFilesHeader)
|
||||
.WriteLine("using System;")
|
||||
.WriteLine()
|
||||
.WriteLine($"namespace {RootNamespace}")
|
||||
.WriteOpeningBracket();
|
||||
|
||||
if (options.GenerateXmlDocument)
|
||||
{
|
||||
builder
|
||||
.WriteLine("/// <summary>")
|
||||
.WriteLine("/// Specifies that the annotated property should be excluded.")
|
||||
.WriteLine("/// </summary>");
|
||||
}
|
||||
|
||||
builder
|
||||
.WriteLine("[AttributeUsage(AttributeTargets.Property, Inherited = false, AllowMultiple = false)]")
|
||||
.WriteLine($"public sealed class {AttributeClassName} : Attribute {{ }}")
|
||||
.WriteClosingBracket();
|
||||
|
||||
return new(builder.ToString(), $"{AttributeClassName}.g.cs");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
using static MapTo.Sources.Constants;
|
||||
|
||||
namespace MapTo.Sources
|
||||
{
|
||||
internal static class UseUpdateAttributeSource
|
||||
{
|
||||
internal const string AttributeName = "UseUpdate";
|
||||
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("/// <summary>")
|
||||
.WriteLine("/// Specifies that the annotated class can be mapped from the provided <see cref=\"SourceType\"/>.")
|
||||
.WriteLine("/// </summary>");
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using MapTo.Extensions;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
|
||||
namespace MapTo
|
||||
{
|
||||
internal class StructMappingContext : MappingContext
|
||||
{
|
||||
internal StructMappingContext(Compilation compilation, SourceGenerationOptions sourceGenerationOptions, TypeDeclarationSyntax typeSyntax)
|
||||
: base(compilation, sourceGenerationOptions, typeSyntax) { }
|
||||
|
||||
protected override ImmutableArray<MappedProperty> GetSourceMappedProperties(ITypeSymbol typeSymbol, ITypeSymbol sourceTypeSymbol, bool hasInheritedClass)
|
||||
{
|
||||
var sourceProperties = sourceTypeSymbol.GetAllMembers().OfType<IPropertySymbol>().ToArray();
|
||||
|
||||
return typeSymbol
|
||||
.GetAllMembers()
|
||||
.OfType<IPropertySymbol>()
|
||||
.Where(p => !p.HasAttribute(IgnorePropertyAttributeTypeSymbol))
|
||||
.Select(property => MapProperty(sourceTypeSymbol, sourceProperties, property))
|
||||
.Where(mappedProperty => mappedProperty is not null)
|
||||
.ToImmutableArray()!;
|
||||
}
|
||||
protected override ImmutableArray<MappedProperty> GetTypeMappedProperties(ITypeSymbol typeSymbol, ITypeSymbol sourceTypeSymbol, bool hasInheritedClass)
|
||||
{
|
||||
var sourceProperties = sourceTypeSymbol.GetAllMembers().OfType<IPropertySymbol>().ToArray();
|
||||
|
||||
return sourceTypeSymbol
|
||||
.GetAllMembers()
|
||||
.OfType<IPropertySymbol>()
|
||||
.Where(p => !p.HasAttribute(IgnorePropertyAttributeTypeSymbol))
|
||||
.Select(property => MapPropertySimple(typeSymbol, property))
|
||||
.Where(mappedProperty => mappedProperty is not null)
|
||||
.ToImmutableArray()!;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using MapTo;
|
||||
using TestConsoleApp.ViewModels;
|
||||
|
||||
namespace TestConsoleApp.Data.Models
|
||||
{
|
||||
[MapFrom(typeof(CarReadDto))]
|
||||
[UseUpdate]
|
||||
partial class Car
|
||||
{
|
||||
public int Size { get; }
|
||||
public int Id { get; }
|
||||
|
||||
public string Brand { get; }
|
||||
|
||||
public Car(int size, int id, string brand)
|
||||
{
|
||||
Size = size;
|
||||
Id = id;
|
||||
Brand = brand;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -4,12 +4,18 @@ using System.Text;
|
|||
|
||||
namespace TestConsoleApp.Data.Models
|
||||
{
|
||||
|
||||
public class Employee
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public int Id { get; }
|
||||
|
||||
public string EmployeeCode { get; set; }
|
||||
public string EmployeeCode { get; }
|
||||
|
||||
public Employee(int id, string employeeCode)
|
||||
{
|
||||
Id = id;
|
||||
EmployeeCode = employeeCode;
|
||||
}
|
||||
|
||||
public Manager Manager { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace TestConsoleApp.Data.Models
|
||||
{
|
||||
public class Manager: Employee
|
||||
{
|
||||
public int Level { get; set; }
|
||||
|
||||
public IEnumerable<Employee> Employees { get; set; } = Array.Empty<Employee>();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using TestConsoleApp.ViewModels;
|
||||
using MapTo;
|
||||
|
||||
namespace TestConsoleApp.Data.Models
|
||||
{
|
||||
[MapFrom(typeof(MyStructViewModel))]
|
||||
[UseUpdate]
|
||||
public partial struct MyStruct
|
||||
{
|
||||
public int SomeInt { get; set; }
|
||||
|
||||
public string ReadOnlyString { get; }
|
||||
|
||||
public MyStruct(int someInt, string readOnlyString)
|
||||
{
|
||||
SomeInt = someInt;
|
||||
ReadOnlyString = readOnlyString;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
namespace TestConsoleApp.Data.Models
|
||||
{
|
||||
public class Profile
|
||||
{
|
||||
public string FirstName { get; set; }
|
||||
|
||||
public string LastName { get; set; }
|
||||
|
||||
public string FullName => $"{FirstName} {LastName}";
|
||||
}
|
||||
}
|
|
@ -1,13 +1,29 @@
|
|||
using System;
|
||||
using TestConsoleApp.ViewModels;
|
||||
using MapTo;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace TestConsoleApp.Data.Models
|
||||
{
|
||||
public class User
|
||||
[MapFrom(typeof(UserViewModel))]
|
||||
public partial struct User
|
||||
{
|
||||
public int Id { get; set; }
|
||||
|
||||
public List<List<string>> ListOfListOfString {get; }
|
||||
|
||||
public List<string> StringList { get; }
|
||||
public DateTimeOffset RegisteredAt { get; set; }
|
||||
|
||||
public Profile Profile { get; set; }
|
||||
public User( int id, List<List<string>> listOfListOfString, List<string> stringList, DateTimeOffset registeredAt)
|
||||
{
|
||||
this.StringList = stringList;
|
||||
this.Id = id;
|
||||
ListOfListOfString = listOfListOfString;
|
||||
RegisteredAt = registeredAt;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
|
@ -2,7 +2,6 @@
|
|||
using MapTo;
|
||||
using TestConsoleApp.Data.Models;
|
||||
using TestConsoleApp.ViewModels;
|
||||
using TestConsoleApp.ViewModels2;
|
||||
|
||||
namespace TestConsoleApp
|
||||
{
|
||||
|
@ -11,7 +10,6 @@ namespace TestConsoleApp
|
|||
private static void Main(string[] args)
|
||||
{
|
||||
//UserTest();
|
||||
CyclicReferenceTest();
|
||||
|
||||
// EmployeeManagerTest();
|
||||
Console.WriteLine("done");
|
||||
|
@ -19,74 +17,16 @@ namespace TestConsoleApp
|
|||
|
||||
private static void EmployeeManagerTest()
|
||||
{
|
||||
var manager1 = new Manager
|
||||
{
|
||||
Id = 1,
|
||||
EmployeeCode = "M001",
|
||||
Level = 100
|
||||
};
|
||||
|
||||
var manager2 = new Manager
|
||||
{
|
||||
Id = 2,
|
||||
EmployeeCode = "M002",
|
||||
Level = 100,
|
||||
Manager = manager1
|
||||
};
|
||||
|
||||
var employee1 = new Employee
|
||||
{
|
||||
Id = 101,
|
||||
EmployeeCode = "E101",
|
||||
Manager = manager1
|
||||
};
|
||||
var employee = new Employee(1, "hello");
|
||||
|
||||
|
||||
var employee2 = new Employee
|
||||
{
|
||||
Id = 102,
|
||||
EmployeeCode = "E102",
|
||||
Manager = manager2
|
||||
};
|
||||
|
||||
manager1.Employees = new[] { employee1, manager2 };
|
||||
manager2.Employees = new[] { employee2 };
|
||||
|
||||
manager1.ToManagerViewModel();
|
||||
employee1.ToEmployeeViewModel();
|
||||
}
|
||||
|
||||
private static ManagerViewModel CyclicReferenceTest()
|
||||
{
|
||||
var manager1 = new Manager
|
||||
{
|
||||
Id = 1,
|
||||
EmployeeCode = "M001",
|
||||
Level = 100
|
||||
};
|
||||
|
||||
manager1.Manager = manager1;
|
||||
return manager1.ToManagerViewModel();
|
||||
}
|
||||
|
||||
private static void UserTest()
|
||||
{
|
||||
var user = new User
|
||||
{
|
||||
Id = 1234,
|
||||
RegisteredAt = DateTimeOffset.Now,
|
||||
Profile = new Profile
|
||||
{
|
||||
FirstName = "John",
|
||||
LastName = "Doe"
|
||||
}
|
||||
};
|
||||
|
||||
var vm = user.ToUserViewModel();
|
||||
|
||||
Console.WriteLine("Key: {0}", vm.Key);
|
||||
Console.WriteLine("RegisteredAt: {0}", vm.RegisteredAt);
|
||||
Console.WriteLine("FirstName: {0}", vm.Profile.FirstName);
|
||||
Console.WriteLine("LastName: {0}", vm.Profile.LastName);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using MapTo;
|
||||
using TestConsoleApp.Data.Models;
|
||||
|
||||
namespace TestConsoleApp.ViewModels
|
||||
{
|
||||
[MapFrom(typeof(Car))]
|
||||
partial class CarReadDto
|
||||
{
|
||||
public int Size { get; }
|
||||
public string Brand { get; }
|
||||
|
||||
public CarReadDto(int size, string brand)
|
||||
{
|
||||
Size = size;
|
||||
Brand = brand;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,16 +1,13 @@
|
|||
using MapTo;
|
||||
using TestConsoleApp.Data.Models;
|
||||
using TestConsoleApp.ViewModels2;
|
||||
|
||||
namespace TestConsoleApp.ViewModels
|
||||
{
|
||||
[MapFrom(typeof(Employee))]
|
||||
public partial class EmployeeViewModel
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public int Id { get; }
|
||||
|
||||
public string EmployeeCode { get; set; }
|
||||
|
||||
public ManagerViewModel Manager { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,16 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using MapTo;
|
||||
using TestConsoleApp.Data.Models;
|
||||
using TestConsoleApp.ViewModels;
|
||||
|
||||
namespace TestConsoleApp.ViewModels2
|
||||
{
|
||||
[MapFrom(typeof(Manager))]
|
||||
public partial class ManagerViewModel : EmployeeViewModel
|
||||
{
|
||||
public int Level { get; set; }
|
||||
|
||||
public IEnumerable<EmployeeViewModel> Employees { get; set; } = Array.Empty<EmployeeViewModel>();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using TestConsoleApp.Data.Models;
|
||||
using MapTo;
|
||||
|
||||
namespace TestConsoleApp.ViewModels
|
||||
{
|
||||
[MapFrom(typeof(MyStruct))]
|
||||
|
||||
public partial struct MyStructViewModel
|
||||
{
|
||||
public int SomeInt { get; set; }
|
||||
|
||||
public MyStructViewModel(int someInt)
|
||||
{
|
||||
SomeInt = someInt;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
using MapTo;
|
||||
using TestConsoleApp.Data.Models;
|
||||
|
||||
namespace TestConsoleApp.ViewModels
|
||||
{
|
||||
[MapFrom(typeof(Profile))]
|
||||
public partial class ProfileViewModel
|
||||
{
|
||||
public string FirstName { get; }
|
||||
|
||||
public string LastName { get; }
|
||||
}
|
||||
}
|
|
@ -5,20 +5,10 @@ using TestConsoleApp.Data.Models;
|
|||
namespace TestConsoleApp.ViewModels
|
||||
{
|
||||
[MapFrom(typeof(User))]
|
||||
public partial class UserViewModel
|
||||
public partial struct UserViewModel
|
||||
{
|
||||
[MapProperty(SourcePropertyName = nameof(User.Id))]
|
||||
[MapTypeConverter(typeof(IdConverter))]
|
||||
public string Key { get; }
|
||||
public int Id { get; set; }
|
||||
|
||||
public DateTimeOffset RegisteredAt { get; set; }
|
||||
|
||||
// [IgnoreProperty]
|
||||
public ProfileViewModel Profile { get; set; }
|
||||
|
||||
private class IdConverter : ITypeConverter<int, string>
|
||||
{
|
||||
public string Convert(int source, object[]? converterParameters) => $"{source:X}";
|
||||
}
|
||||
public DateTimeOffset RegisteredAt { get; }
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue