diff --git a/src/MapTo/MapToGenerator.cs b/src/MapTo/MapToGenerator.cs index 1e5f581..d740394 100644 --- a/src/MapTo/MapToGenerator.cs +++ b/src/MapTo/MapToGenerator.cs @@ -61,6 +61,7 @@ namespace MapTo var (source, hintName) = typeDeclarationSyntax switch { + StructDeclarationSyntax => MapStructSource.Generate(mappingContext.Model), ClassDeclarationSyntax => MapClassSource.Generate(mappingContext.Model), RecordDeclarationSyntax => MapRecordSource.Generate(mappingContext.Model), _ => throw new ArgumentOutOfRangeException() diff --git a/src/MapTo/Sources/MapStructSource.cs b/src/MapTo/Sources/MapStructSource.cs new file mode 100644 index 0000000..6177468 --- /dev/null +++ b/src/MapTo/Sources/MapStructSource.cs @@ -0,0 +1,172 @@ +using MapTo.Extensions; +using static MapTo.Sources.Constants; + +namespace MapTo.Sources +{ + internal static class MapStructSource + { + 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("/// ") + .WriteLine($"/// Initializes a new instance of the class") + .WriteLine($"/// using the property values from the specified .") + .WriteLine("/// ") + .WriteLine($"/// {sourceClassParameterName} is null"); + } + + 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("/// ") + .WriteLine($"/// Creates a new instance of and sets its participating properties") + .WriteLine($"/// using the property values from .") + .WriteLine("/// ") + .WriteLine($"/// The instance of to use as source.") + .WriteLine($"/// A new instance of -or- null if is null."); + } + + private static SourceBuilder GenerateUpdaterMethodsXmlDocs(this SourceBuilder builder, MappingModel model, string sourceClassParameterName) + { + if (!model.Options.GenerateXmlDocument) + { + return builder; + } + + return builder + .WriteLine("/// ") + .WriteLine($"/// Updates and sets its participating properties") + .WriteLine($"/// using the property values from .") + .WriteLine("/// ") + .WriteLine($"/// The instance of to use as source."); + } + + 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(); + } + } +} \ No newline at end of file