From a6f511665605041c327c6578d2b9789a3c9351bd Mon Sep 17 00:00:00 2001 From: Wvader <34067397+wvader@users.noreply.github.com> Date: Sun, 21 Aug 2022 22:15:00 +0100 Subject: [PATCH] Supporting 'friendly constructors' --- src/BlueWest.MapTo/Extensions/CommonSource.cs | 60 +++++++---------- src/BlueWest.MapTo/MapToGenerator.cs | 1 + .../Sources/AddDataAttributeSource.cs | 66 +++++++++++++++++++ .../Sources/UpdateDataAttributeSource.cs | 66 +++++++++++++++++++ 4 files changed, 158 insertions(+), 35 deletions(-) create mode 100644 src/BlueWest.MapTo/Sources/AddDataAttributeSource.cs create mode 100644 src/BlueWest.MapTo/Sources/UpdateDataAttributeSource.cs diff --git a/src/BlueWest.MapTo/Extensions/CommonSource.cs b/src/BlueWest.MapTo/Extensions/CommonSource.cs index b804eed..92434e8 100644 --- a/src/BlueWest.MapTo/Extensions/CommonSource.cs +++ b/src/BlueWest.MapTo/Extensions/CommonSource.cs @@ -14,42 +14,17 @@ namespace MapTo.Extensions internal static SourceCode GenerateStructOrClass(this MappingModel model, string structOrClass) { const bool writeDebugInfo = false; - + + List constructorHeaders = new List(); using var builder = new SourceBuilder() .WriteLine(GeneratedFilesHeader) .WriteNullableContextOptionIf(model.Options.SupportNullableReferenceTypes) .WriteUsings(model.Usings) .WriteLine() - // Namespace declaration .WriteLine($"namespace {model.Namespace}") .WriteOpeningBracket(); - - - - foreach (var targetSourceType in model.MappedSourceTypes) - { - if (writeDebugInfo) - builder - .WriteModelInfo(model) - .WriteLine() - .WriteComment("Type properties") - .WriteComment() - .WriteMappedProperties(targetSourceType.TypeProperties) - .WriteLine() - .WriteComment("Source properties") - .WriteLine() - .WriteComment("Type fields") - .WriteComment() - .WriteMappedProperties(targetSourceType.TypeFields) - .WriteLine() - .WriteComment("Source fields") - .WriteMappedProperties(targetSourceType.SourceFields) - .WriteLine(); - - } - builder // Class declaration @@ -57,10 +32,10 @@ namespace MapTo.Extensions .WriteOpeningBracket() .WriteLine() // Class body - .GeneratePublicConstructor(model); + .GeneratePublicConstructor(model, ref constructorHeaders) + .GeneratePublicConstructor(model, ref constructorHeaders, true); + - var addedMembers = new List(); - if (model.IsTypeUpdatable) builder.GenerateUpdateMethod(model); if (model.IsJsonExtension) builder.WriteToJsonMethod(model); @@ -76,7 +51,7 @@ namespace MapTo.Extensions return new(builder.ToString(), $"{model.Namespace}.{model.TypeIdentifierName}.g.cs"); } - private static SourceBuilder GeneratePublicConstructor(this SourceBuilder builder, MappingModel model) + private static SourceBuilder GeneratePublicConstructor(this SourceBuilder builder, MappingModel model, ref List constructorHeaders, bool filterNonMapped = false) { const string mappingContextParameterName = "context"; @@ -89,7 +64,7 @@ namespace MapTo.Extensions foreach (var property in targetSourceType.TypeProperties) { - if (!targetSourceType.SourceProperties.IsMappedProperty(property)) + if (!targetSourceType.SourceProperties.IsMappedProperty(property) && !filterNonMapped) { stringBuilder.Append(", "); stringBuilder.Append($"{property.FullyQualifiedType} {property.SourcePropertyName.ToCamelCase()}"); @@ -99,7 +74,7 @@ namespace MapTo.Extensions } foreach (var property in targetSourceType.TypeFields) { - if (!targetSourceType.SourceFields.IsMappedProperty(property)) + if (!targetSourceType.SourceFields.IsMappedProperty(property) && !filterNonMapped) { stringBuilder.Append(", "); stringBuilder.Append($"{property.FullyQualifiedType} {property.SourcePropertyName.ToCamelCase()}"); @@ -109,10 +84,25 @@ namespace MapTo.Extensions var readOnlyPropertiesArguments = stringBuilder.ToString(); + var constructorHeader = + $"public {model.TypeIdentifierName}({targetSourceType.SourceType} {sourceClassParameterName}{readOnlyPropertiesArguments}){baseConstructor}"; + + + bool hasAlreadyConstructor = false; + + foreach (var header in constructorHeaders) + { + if(constructorHeader.Contains(header)) hasAlreadyConstructor = true; + } + + if (hasAlreadyConstructor) continue; + + constructorHeaders.Add(constructorHeader); + builder - .WriteLine($"public {model.TypeIdentifierName}({targetSourceType.SourceType} {sourceClassParameterName}{readOnlyPropertiesArguments}){baseConstructor}") + .WriteLine(constructorHeader) .WriteOpeningBracket() - .WriteAssignmentMethod(model, otherProperties.ToArray().ToImmutableArray(), sourceClassParameterName, mappingContextParameterName, false); + .WriteAssignmentMethod(model, filterNonMapped ? null : otherProperties.ToArray().ToImmutableArray(), sourceClassParameterName, mappingContextParameterName, filterNonMapped); builder.WriteClosingBracket() .WriteLine(); diff --git a/src/BlueWest.MapTo/MapToGenerator.cs b/src/BlueWest.MapTo/MapToGenerator.cs index c9b12eb..50b9771 100644 --- a/src/BlueWest.MapTo/MapToGenerator.cs +++ b/src/BlueWest.MapTo/MapToGenerator.cs @@ -29,6 +29,7 @@ namespace MapTo var compilation = context.Compilation .AddSource(ref context, UseUpdateAttributeSource.Generate(options)) + .AddSource(ref context, AddDataAttributeSource.Generate(options)) .AddSource(ref context, JsonExtensionAttributeSource.Generate(options)) .AddSource(ref context, MapFromAttributeSource.Generate(options)) .AddSource(ref context, IgnoreMemberAttributeSource.Generate(options)) diff --git a/src/BlueWest.MapTo/Sources/AddDataAttributeSource.cs b/src/BlueWest.MapTo/Sources/AddDataAttributeSource.cs new file mode 100644 index 0000000..b56ce64 --- /dev/null +++ b/src/BlueWest.MapTo/Sources/AddDataAttributeSource.cs @@ -0,0 +1,66 @@ +using static MapTo.Sources.Constants; + +namespace MapTo.Sources +{ + public class AddDataAttributeSource + { + internal const string AttributeName = "AddDataGenerator"; + internal const string AttributeClassName = AttributeName + "Attribute"; + internal const string FullyQualifiedName = RootNamespace + "." + AttributeClassName; + + internal static SourceCode Generate(SourceGenerationOptions options) + { + using var builder = new SourceBuilder() + .WriteLine(GeneratedFilesHeader) + .WriteLine("using System;") + .WriteLine() + .WriteLine($"namespace {RootNamespace}") + .WriteOpeningBracket(); + + if (options.GenerateXmlDocument) + { + builder + .WriteLine("/// ") + .WriteLine("/// Generates CRUD functions to be used with the specified database context and entity.") + .WriteLine("/// "); + } + + builder + .WriteLine("[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]") + .WriteLine($"public sealed class {AttributeName}Attribute : Attribute") + .WriteOpeningBracket(); + + if (options.GenerateXmlDocument) + { + builder + .WriteLine("/// ") + .WriteLine($"/// Initializes a new instance of the class with the specified .") + .WriteLine("/// ") + .WriteLine("/// The type of to map from."); + } + + builder + .WriteLine($"public {AttributeName}Attribute(Type databaseContextType)") + .WriteOpeningBracket() + .WriteLine("DatabaseContextType = databaseContextType;") + .WriteClosingBracket() + .WriteLine(); + + + if (options.GenerateXmlDocument) + { + builder + .WriteLine("/// ") + .WriteLine("/// Gets the type to map from.") + .WriteLine("/// "); + } + + builder + .WriteLine("public Type DatabaseContextType { get; }") + .WriteClosingBracket() // class + .WriteClosingBracket(); // namespace + + return new(builder.ToString(), $"{AttributeName}Attribute.g.cs"); + } + } +} \ No newline at end of file diff --git a/src/BlueWest.MapTo/Sources/UpdateDataAttributeSource.cs b/src/BlueWest.MapTo/Sources/UpdateDataAttributeSource.cs new file mode 100644 index 0000000..1bf082c --- /dev/null +++ b/src/BlueWest.MapTo/Sources/UpdateDataAttributeSource.cs @@ -0,0 +1,66 @@ +using static MapTo.Sources.Constants; + +namespace MapTo.Sources +{ + public class UpdateDataAttributeSource + { + internal const string AttributeName = "UpdateData"; + internal const string AttributeClassName = AttributeName + "Attribute"; + internal const string FullyQualifiedName = RootNamespace + "." + AttributeClassName; + + internal static SourceCode Generate(SourceGenerationOptions options) + { + using var builder = new SourceBuilder() + .WriteLine(GeneratedFilesHeader) + .WriteLine("using System;") + .WriteLine() + .WriteLine($"namespace {RootNamespace}") + .WriteOpeningBracket(); + + if (options.GenerateXmlDocument) + { + builder + .WriteLine("/// ") + .WriteLine("/// Generates CRUD functions to be used with the specified database context and entity.") + .WriteLine("/// "); + } + + builder + .WriteLine("[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]") + .WriteLine($"public sealed class {AttributeName}Attribute : Attribute") + .WriteOpeningBracket(); + + if (options.GenerateXmlDocument) + { + builder + .WriteLine("/// ") + .WriteLine($"/// Initializes a new instance of the class with the specified .") + .WriteLine("/// ") + .WriteLine("/// The type of to map from."); + } + + builder + .WriteLine($"public {AttributeName}Attribute(Type databaseContextType)") + .WriteOpeningBracket() + .WriteLine("DatabaseContextType = databaseContextType;") + .WriteClosingBracket() + .WriteLine(); + + + if (options.GenerateXmlDocument) + { + builder + .WriteLine("/// ") + .WriteLine("/// Gets the type to map from.") + .WriteLine("/// "); + } + + builder + .WriteLine("public Type DatabaseContextType { get; }") + .WriteClosingBracket() // class + .WriteClosingBracket(); // namespace + + return new(builder.ToString(), $"{AttributeName}Attribute.g.cs"); + } + } +} \ No newline at end of file