Add MappedSourceType to the list

This commit is contained in:
CodeLiturgy 2022-08-18 16:01:39 +01:00
parent 2cd2a3868c
commit 3a2d3a1b41
6 changed files with 111 additions and 75 deletions

View File

@ -5,7 +5,7 @@ using MapTo;
namespace BlueWest.Data
{
[MapFrom(typeof(CountryUpdate))]
[MapFrom(new [] {typeof(CountryUpdate), typeof(CountryCreate)})]
public partial class Country
{
// ISO 3166-1 numeric code

View File

@ -30,6 +30,7 @@ namespace MapTo.Extensions
internal static SourceBuilder WriteModelInfo(this SourceBuilder builder, MappingModel model)
{
var targetSourceType = model.MappedSourceTypes[0];
return builder
.WriteLine()
.WriteComment($" IsTypeUpdatable {model.IsTypeUpdatable}")
@ -38,9 +39,9 @@ namespace MapTo.Extensions
.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}");
.WriteComment($" SourceNamespace {targetSourceType.SourceNamespace}")
.WriteComment($" SourceTypeFullName {targetSourceType.SourceTypeFullName}")
.WriteComment($" SourceTypeIdentifierName {targetSourceType.SourceTypeIdentifierName}");
}

View File

@ -15,6 +15,8 @@ namespace MapTo.Extensions
{
const bool writeDebugInfo = true;
var targetSourceType = model.MappedSourceTypes[0];
using var builder = new SourceBuilder()
.WriteLine(GeneratedFilesHeader)
.WriteNullableContextOptionIf(model.Options.SupportNullableReferenceTypes)
@ -31,16 +33,16 @@ namespace MapTo.Extensions
.WriteLine()
.WriteComment("Type properties")
.WriteComment()
.WriteMappedProperties(model.TypeProperties)
.WriteMappedProperties(targetSourceType.TypeProperties)
.WriteLine()
.WriteComment("Source properties")
.WriteLine()
.WriteComment("Type fields")
.WriteComment()
.WriteMappedProperties(model.TypeFields)
.WriteMappedProperties(targetSourceType.TypeFields)
.WriteLine()
.WriteComment("Source fields")
.WriteMappedProperties(model.SourceFields)
.WriteMappedProperties(targetSourceType.SourceFields)
.WriteLine();
builder
@ -52,8 +54,8 @@ namespace MapTo.Extensions
.GeneratePublicConstructor(model);
if (model.IsJsonExtension) builder.WriteToJsonMethod(model);
if (model.IsTypeUpdatable && model.TypeProperties.GetWritableMappedProperties().Length > 0) builder.GenerateUpdateMethod(model);
if (model.IsTypeUpdatable && model.TypeFields.GetWritableMappedProperties().Length > 0) builder.GenerateUpdateMethod(model);
if (model.IsTypeUpdatable && targetSourceType.TypeProperties.GetWritableMappedProperties().Length > 0) builder.GenerateUpdateMethod(model);
if (model.IsTypeUpdatable && targetSourceType.TypeFields.GetWritableMappedProperties().Length > 0) builder.GenerateUpdateMethod(model);
builder
.WriteLine()
@ -68,7 +70,8 @@ namespace MapTo.Extensions
private static SourceBuilder GeneratePublicConstructor(this SourceBuilder builder, MappingModel model)
{
var sourceClassParameterName = model.SourceTypeIdentifierName.ToCamelCase();
var targetSourceType = model.MappedSourceTypes[0];
var sourceClassParameterName = targetSourceType.SourceTypeIdentifierName.ToCamelCase();
const string mappingContextParameterName = "context";
var baseConstructor = /*model.HasMappedBaseClass ? $" : base({mappingContextParameterName}, {sourceClassParameterName})" :*/ string.Empty;
@ -77,9 +80,9 @@ namespace MapTo.Extensions
var otherProperties = new List<MappedMember>();
foreach (var property in model.TypeProperties)
foreach (var property in targetSourceType.TypeProperties)
{
if (!model.SourceProperties.IsMappedProperty(property))
if (!targetSourceType.SourceProperties.IsMappedProperty(property))
{
stringBuilder.Append(", ");
stringBuilder.Append($"{property.FullyQualifiedType} {property.SourcePropertyName.ToCamelCase()}");
@ -87,9 +90,9 @@ namespace MapTo.Extensions
}
}
foreach (var property in model.TypeFields)
foreach (var property in targetSourceType.TypeFields)
{
if (!model.SourceFields.IsMappedProperty(property))
if (!targetSourceType.SourceFields.IsMappedProperty(property))
{
stringBuilder.Append(", ");
stringBuilder.Append($"{property.FullyQualifiedType} {property.SourcePropertyName.ToCamelCase()}");
@ -101,7 +104,7 @@ namespace MapTo.Extensions
var readOnlyPropertiesArguments = stringBuilder.ToString();
builder
.WriteLine($"public {model.TypeIdentifierName}({model.SourceType} {sourceClassParameterName}{readOnlyPropertiesArguments}){baseConstructor}")
.WriteLine($"public {model.TypeIdentifierName}({targetSourceType.SourceType} {sourceClassParameterName}{readOnlyPropertiesArguments}){baseConstructor}")
.WriteOpeningBracket()
.WriteAssignmentMethod(model, otherProperties.ToArray().ToImmutableArray(), sourceClassParameterName, mappingContextParameterName, false);
@ -128,7 +131,8 @@ namespace MapTo.Extensions
.WriteLine("var stringBuilder = new System.Text.StringBuilder();")
.WriteLine(GetStringBuilderAppendNoInterpolation("{"));
foreach (var property in model.TypeProperties)
var targetSourceType = model.MappedSourceTypes[0];
foreach (var property in targetSourceType.TypeProperties)
{
if (!property.isEnumerable)
HandlePropertyEnumerable(builder, property);
@ -137,7 +141,7 @@ namespace MapTo.Extensions
builder = WriteJsonField(builder, property);
}
}
foreach (var property in model.TypeFields)
foreach (var property in targetSourceType.TypeFields)
{
if (!property.isEnumerable)
HandleFieldEnumerable(builder, property);
@ -227,8 +231,9 @@ namespace MapTo.Extensions
private static SourceBuilder WriteAssignmentMethod(this SourceBuilder builder, MappingModel model, System.Collections.Immutable.ImmutableArray<MappedMember>? otherProperties,
string? sourceClassParameterName, string mappingContextParameterName, bool fromUpdate)
{
var targetSourceType = model.MappedSourceTypes[0];
foreach (var property in model.SourceProperties)
foreach (var property in targetSourceType.SourceProperties)
{
if (property.isReadOnly && fromUpdate) continue;
@ -236,7 +241,7 @@ namespace MapTo.Extensions
}
foreach (var property in model.SourceFields)
foreach (var property in targetSourceType.SourceFields)
{
if (property.isReadOnly && fromUpdate) continue;
@ -261,11 +266,12 @@ namespace MapTo.Extensions
private static SourceBuilder GenerateUpdateMethod(this SourceBuilder builder, MappingModel model)
{
var sourceClassParameterName = model.SourceTypeIdentifierName.ToCamelCase();
var targetSourceType = model.MappedSourceTypes[0];
var sourceClassParameterName = targetSourceType.SourceTypeIdentifierName.ToCamelCase();
builder
.GenerateUpdaterMethodsXmlDocs(model, sourceClassParameterName)
.WriteLine($"public void Update({model.SourceType} {sourceClassParameterName})")
.WriteLine($"public void Update({targetSourceType.SourceType} {sourceClassParameterName})")
.WriteOpeningBracket()
.WriteAssignmentMethod(model, null, sourceClassParameterName, "context", true)
.WriteClosingBracket();
@ -280,21 +286,25 @@ namespace MapTo.Extensions
return builder;
}
var targetSourceType = model.MappedSourceTypes[0];
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>");
.WriteLine($"/// <param name=\"{sourceClassParameterName}\">The instance of <see cref=\"{targetSourceType.SourceType}\"/> to use as source.</param>");
}
private static SourceBuilder GenerateEnumerableJsonSourceTypeExtensionMethod(this SourceBuilder builder, MappingModel model)
{
var sourceClassParameterName = model.SourceTypeIdentifierName.ToCamelCase();
var targetSourceType = model.MappedSourceTypes[0];
var sourceClassParameterName = targetSourceType.SourceTypeIdentifierName.ToCamelCase();
return builder
.WriteLineIf(model.Options.SupportNullableStaticAnalysis, $"[return: NotNullIfNotNull(\"{sourceClassParameterName}\")]")
.WriteLine($"{model.Options.GeneratedMethodsAccessModifier.ToLowercaseString()} static string ToJson(this IEnumerable<{model.SourceType}{model.Options.NullableReferenceSyntax}> {sourceClassParameterName}List)")
.WriteLine($"{model.Options.GeneratedMethodsAccessModifier.ToLowercaseString()} static string ToJson(this IEnumerable<{targetSourceType.SourceType}{model.Options.NullableReferenceSyntax}> {sourceClassParameterName}List)")
.WriteOpeningBracket()
.WriteLine($"return {sourceClassParameterName} == null ? null : new {model.TypeIdentifierName}({sourceClassParameterName});")
.WriteClosingBracket();

View File

@ -504,21 +504,38 @@ namespace MapTo
return null;
}
var sourceTypeSymbol = sourceTypeSymbols[0];
// Pick first one to avoid errors. TODO: Make possible to use different source types
_ignoredNamespaces.Add(sourceTypeSymbol.ContainingNamespace.ToDisplayParts().First());
var typeIdentifierName = TypeSyntax.GetIdentifierName();
var sourceTypeIdentifierName = sourceTypeSymbol.Name;
var isTypeInheritFromMappedBaseClass = IsTypeInheritFromMappedBaseClass(semanticModel);
var isTypeUpdatable = false; //IsTypeUpdatable();
var hasJsonExtension = false; // HasJsonExtension();
var shouldGenerateSecondaryConstructor = ShouldGenerateSecondaryConstructor(semanticModel, sourceTypeSymbol);
var mappedProperties = GetSourceMappedProperties(typeSymbol, sourceTypeSymbol, isTypeInheritFromMappedBaseClass);
var mappedFields = GetSourceMappedFields(typeSymbol, sourceTypeSymbol, isTypeInheritFromMappedBaseClass);
List<MappedSourceType> mappedSourceTypes = new List<MappedSourceType>();
foreach (var sourceTypeSymbol in sourceTypeSymbols)
{
_ignoredNamespaces.Add(sourceTypeSymbol.ContainingNamespace.ToDisplayParts().First());
var sourceTypeIdentifierName = sourceTypeSymbol.Name;
var shouldGenerateSecondaryConstructor = ShouldGenerateSecondaryConstructor(semanticModel, sourceTypeSymbol);
var mappedProperties = GetSourceMappedProperties(typeSymbol, sourceTypeSymbol, isTypeInheritFromMappedBaseClass);
var mappedFields = GetSourceMappedFields(typeSymbol, sourceTypeSymbol, isTypeInheritFromMappedBaseClass);
AddUsingIfRequired(mappedProperties.Any(p => p.IsEnumerable), "System.Linq");
var allProperties = GetTypeMappedProperties(sourceTypeSymbol, typeSymbol , isTypeInheritFromMappedBaseClass);
var allFields = GetTypeMappedFields(sourceTypeSymbol, typeSymbol, isTypeInheritFromMappedBaseClass);
mappedSourceTypes.Add(new MappedSourceType(
sourceTypeSymbol.ContainingNamespace.ToDisplayString(),
sourceTypeIdentifierName,
sourceTypeSymbol.ToDisplayString(),
mappedProperties, mappedFields, allProperties, allFields, shouldGenerateSecondaryConstructor));
}
//var sourceTypeSymbol = sourceTypeSymbols[0];
// Pick first one to avoid errors. TODO: Make possible to use different source types
/*if (!mappedProperties.Any())
{
@ -526,29 +543,17 @@ namespace MapTo
return null;
}*/
AddUsingIfRequired(mappedProperties.Any(p => p.IsEnumerable), "System.Linq");
var allProperties = GetTypeMappedProperties(sourceTypeSymbol, typeSymbol , isTypeInheritFromMappedBaseClass);
var allFields = GetTypeMappedFields(sourceTypeSymbol, typeSymbol, isTypeInheritFromMappedBaseClass);
return new MappingModel(
SourceGenerationOptions,
TypeSyntax.GetNamespace(),
TypeSyntax.Modifiers,
TypeSyntax.Keyword.Text,
typeIdentifierName,
sourceTypeSymbol.ContainingNamespace.ToDisplayString(),
sourceTypeIdentifierName,
sourceTypeSymbol.ToDisplayString(),
isTypeUpdatable,
hasJsonExtension,
mappedProperties,
allProperties,
mappedFields,
allFields,
mappedSourceTypes.ToImmutableArray(),
isTypeInheritFromMappedBaseClass,
Usings,
shouldGenerateSecondaryConstructor);
Usings);
}

View File

@ -39,29 +39,35 @@ namespace MapTo
public bool IsEnumerable => EnumerableTypeArgument is not null;
}
internal record MappingModel (
SourceGenerationOptions Options,
string? Namespace,
SyntaxTokenList Modifiers,
string Type,
string TypeIdentifierName,
internal record MappedSourceType
(
string SourceNamespace,
string SourceTypeIdentifierName,
string SourceTypeFullName,
bool IsTypeUpdatable,
bool IsJsonExtension,
ImmutableArray<MappedMember> SourceProperties,
ImmutableArray<MappedMember> TypeProperties,
ImmutableArray<MappedMember> SourceFields,
ImmutableArray<MappedMember> TypeProperties,
ImmutableArray<MappedMember> TypeFields,
bool HasMappedBaseClass,
ImmutableArray<string> Usings,
bool GenerateSecondaryConstructor
)
{
public string SourceType => SourceTypeFullName;
}
internal record MappingModel(
SourceGenerationOptions Options,
string? Namespace,
SyntaxTokenList Modifiers,
string Type,
string TypeIdentifierName,
bool IsTypeUpdatable,
bool IsJsonExtension,
ImmutableArray<MappedSourceType> MappedSourceTypes,
bool HasMappedBaseClass,
ImmutableArray<string> Usings
);
internal record SourceGenerationOptions(
AccessModifier ConstructorAccessModifier,
AccessModifier GeneratedMethodsAccessModifier,

View File

@ -22,8 +22,10 @@ namespace MapTo.Sources
.WriteLine($"partial record {model.TypeIdentifierName}")
.WriteOpeningBracket();
var targetSourceType = model.MappedSourceTypes[0];
// Class body
if (model.GenerateSecondaryConstructor)
if (targetSourceType.GenerateSecondaryConstructor)
{
builder
.GenerateSecondaryConstructor(model)
@ -52,7 +54,9 @@ namespace MapTo.Sources
private static SourceBuilder GenerateSecondaryConstructor(this SourceBuilder builder, MappingModel model)
{
var sourceClassParameterName = model.SourceTypeIdentifierName.ToCamelCase();
// grab first data from array
var targetSourceType = model.MappedSourceTypes[0];
var sourceClassParameterName = targetSourceType.SourceTypeIdentifierName.ToCamelCase();
if (model.Options.GenerateXmlDocument)
{
@ -65,17 +69,19 @@ namespace MapTo.Sources
}
return builder
.WriteLine($"{model.Options.ConstructorAccessModifier.ToLowercaseString()} {model.TypeIdentifierName}({model.SourceType} {sourceClassParameterName})")
.WriteLine($"{model.Options.ConstructorAccessModifier.ToLowercaseString()} {model.TypeIdentifierName}({targetSourceType.SourceType} {sourceClassParameterName})")
.WriteLine($" : this(new {MappingContextSource.ClassName}(), {sourceClassParameterName}) {{ }}");
}
private static SourceBuilder GeneratePrivateConstructor(this SourceBuilder builder, MappingModel model)
{
var sourceClassParameterName = model.SourceTypeIdentifierName.ToCamelCase();
var targetSourceType = model.MappedSourceTypes[0];
var sourceClassParameterName = targetSourceType.SourceTypeIdentifierName.ToCamelCase();
const string mappingContextParameterName = "context";
builder
.WriteLine($"private protected {model.TypeIdentifierName}({MappingContextSource.ClassName} {mappingContextParameterName}, {model.SourceType} {sourceClassParameterName})")
.WriteLine($"private protected {model.TypeIdentifierName}({MappingContextSource.ClassName} {mappingContextParameterName}, {targetSourceType.SourceType} {sourceClassParameterName})")
.Indent()
.Write(": this(").
@ -96,9 +102,11 @@ namespace MapTo.Sources
private static SourceBuilder WriteProperties(this SourceBuilder builder, MappingModel model, string sourceClassParameterName,
string mappingContextParameterName)
{
for (var i = 0; i < model.SourceProperties.Length; i++)
var targetSourceType = model.MappedSourceTypes[0];
for (var i = 0; i < targetSourceType.SourceProperties.Length; i++)
{
var property = model.SourceProperties[i];
var property = targetSourceType.SourceProperties[i];
if (property.TypeConverter is null)
{
if (property.IsEnumerable)
@ -123,7 +131,7 @@ namespace MapTo.Sources
$"{property.Name}: new {property.TypeConverter}().Convert({sourceClassParameterName}.{property.SourcePropertyName}, {parameters})");
}
if (i < model.SourceProperties.Length - 1)
if (i < targetSourceType.SourceProperties.Length - 1)
{
builder.Write(", ");
}
@ -134,16 +142,17 @@ namespace MapTo.Sources
private static SourceBuilder GenerateFactoryMethod(this SourceBuilder builder, MappingModel model)
{
var sourceClassParameterName = model.SourceTypeIdentifierName.ToCamelCase();
var targetSourceType = model.MappedSourceTypes[0];
var sourceClassParameterName = targetSourceType.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})")
$"{model.Options.GeneratedMethodsAccessModifier.ToLowercaseString()} static {model.TypeIdentifierName}{model.Options.NullableReferenceSyntax} From({targetSourceType.SourceType}{model.Options.NullableReferenceSyntax} {sourceClassParameterName})")
.WriteOpeningBracket()
.WriteLine(
$"return {sourceClassParameterName} == null ? null : {MappingContextSource.ClassName}.{MappingContextSource.FactoryMethodName}<{model.SourceType}, {model.TypeIdentifierName}>({sourceClassParameterName});")
$"return {sourceClassParameterName} == null ? null : {MappingContextSource.ClassName}.{MappingContextSource.FactoryMethodName}<{targetSourceType.SourceType}, {model.TypeIdentifierName}>({sourceClassParameterName});")
.WriteClosingBracket();
}
@ -153,22 +162,25 @@ namespace MapTo.Sources
{
return builder;
}
var targetSourceType = model.MappedSourceTypes[0];
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($"/// <param name=\"{sourceClassParameterName}\">The instance of <see cref=\"{targetSourceType.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 GenerateSourceTypeExtensionClass(this SourceBuilder builder, MappingModel model)
{
var targetSourceType = model.MappedSourceTypes[0];
return builder
.WriteLine(
$"{model.Options.GeneratedMethodsAccessModifier.ToLowercaseString()} static partial class {model.SourceTypeIdentifierName}To{model.TypeIdentifierName}Extensions")
$"{model.Options.GeneratedMethodsAccessModifier.ToLowercaseString()} static partial class {targetSourceType.SourceTypeIdentifierName}To{model.TypeIdentifierName}Extensions")
.WriteOpeningBracket()
.GenerateSourceTypeExtensionMethod(model)
.WriteClosingBracket();
@ -176,13 +188,15 @@ namespace MapTo.Sources
private static SourceBuilder GenerateSourceTypeExtensionMethod(this SourceBuilder builder, MappingModel model)
{
var sourceClassParameterName = model.SourceTypeIdentifierName.ToCamelCase();
var targetSourceType = model.MappedSourceTypes[0];
var sourceClassParameterName = targetSourceType.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})")
$"{model.Options.GeneratedMethodsAccessModifier.ToLowercaseString()} static {model.TypeIdentifierName}{model.Options.NullableReferenceSyntax} To{model.TypeIdentifierName}(this {targetSourceType.SourceType}{model.Options.NullableReferenceSyntax} {sourceClassParameterName})")
.WriteOpeningBracket()
.WriteLine($"return {sourceClassParameterName} == null ? null : new {model.TypeIdentifierName}({sourceClassParameterName});")
.WriteClosingBracket();