From ec21abcf0ee760fbed2fba5e58b2b3a9dd592580 Mon Sep 17 00:00:00 2001 From: Mohammadreza Taikandi Date: Thu, 7 Jan 2021 08:17:36 +0000 Subject: [PATCH] Add MapProperty attribute. --- .../GeneratorExecutionContextExtensions.cs | 4 +- src/MapTo/MapToGenerator.cs | 1 + .../Sources/IgnorePropertyAttributeSource.cs | 2 +- src/MapTo/Sources/MapClassSource.cs | 2 +- src/MapTo/Sources/MapFromAttributeSource.cs | 2 +- .../Sources/MapPropertyAttributeSource.cs | 64 +++++++++++++++++++ src/MapTo/Sources/Source.cs | 4 -- src/MapTo/Sources/SourceCode.cs | 4 ++ src/MapTo/Sources/TypeConverterSource.cs | 2 +- test/MapTo.Tests/Tests.cs | 32 ++++++++++ test/TestConsoleApp/ViewModels/User.cs | 4 ++ .../ViewModels/UserViewModel.cs | 11 +++- 12 files changed, 121 insertions(+), 11 deletions(-) create mode 100644 src/MapTo/Sources/MapPropertyAttributeSource.cs delete mode 100644 src/MapTo/Sources/Source.cs create mode 100644 src/MapTo/Sources/SourceCode.cs diff --git a/src/MapTo/Extensions/GeneratorExecutionContextExtensions.cs b/src/MapTo/Extensions/GeneratorExecutionContextExtensions.cs index 7403ccc..e03c45f 100644 --- a/src/MapTo/Extensions/GeneratorExecutionContextExtensions.cs +++ b/src/MapTo/Extensions/GeneratorExecutionContextExtensions.cs @@ -35,7 +35,7 @@ namespace MapTo.Extensions internal static string GetBuildPropertyName(string propertyName) => $"build_property.{PropertyNameSuffix}{propertyName}"; - internal static void AddSource(this GeneratorExecutionContext context, Source source) => - context.AddSource(source.HintName, source.Code); + internal static void AddSource(this GeneratorExecutionContext context, SourceCode sourceCode) => + context.AddSource(sourceCode.HintName, sourceCode.Text); } } \ No newline at end of file diff --git a/src/MapTo/MapToGenerator.cs b/src/MapTo/MapToGenerator.cs index e5d77d2..e0a18ed 100644 --- a/src/MapTo/MapToGenerator.cs +++ b/src/MapTo/MapToGenerator.cs @@ -25,6 +25,7 @@ namespace MapTo context.AddSource(MapFromAttributeSource.Generate(options)); context.AddSource(IgnorePropertyAttributeSource.Generate(options)); context.AddSource(TypeConverterSource.Generate(options)); + context.AddSource(MapPropertyAttributeSource.Generate(options)); if (context.SyntaxReceiver is MapToSyntaxReceiver receiver && receiver.CandidateClasses.Any()) { diff --git a/src/MapTo/Sources/IgnorePropertyAttributeSource.cs b/src/MapTo/Sources/IgnorePropertyAttributeSource.cs index c82ddc2..bb0a0bf 100644 --- a/src/MapTo/Sources/IgnorePropertyAttributeSource.cs +++ b/src/MapTo/Sources/IgnorePropertyAttributeSource.cs @@ -7,7 +7,7 @@ namespace MapTo.Sources { internal const string AttributeName = "IgnoreProperty"; - internal static Source Generate(SourceGenerationOptions options) + internal static SourceCode Generate(SourceGenerationOptions options) { var builder = new SourceBuilder() .WriteLine(GeneratedFilesHeader) diff --git a/src/MapTo/Sources/MapClassSource.cs b/src/MapTo/Sources/MapClassSource.cs index 268d607..2952117 100644 --- a/src/MapTo/Sources/MapClassSource.cs +++ b/src/MapTo/Sources/MapClassSource.cs @@ -6,7 +6,7 @@ namespace MapTo.Sources { internal static class MapClassSource { - internal static Source Generate(MapModel model) + internal static SourceCode Generate(MapModel model) { using var builder = new SourceBuilder() .WriteLine(GeneratedFilesHeader) diff --git a/src/MapTo/Sources/MapFromAttributeSource.cs b/src/MapTo/Sources/MapFromAttributeSource.cs index a938e5d..8b404fd 100644 --- a/src/MapTo/Sources/MapFromAttributeSource.cs +++ b/src/MapTo/Sources/MapFromAttributeSource.cs @@ -7,7 +7,7 @@ namespace MapTo.Sources { internal const string AttributeName = "MapFrom"; - internal static Source Generate(SourceGenerationOptions options) + internal static SourceCode Generate(SourceGenerationOptions options) { using var builder = new SourceBuilder() .WriteLine(GeneratedFilesHeader) diff --git a/src/MapTo/Sources/MapPropertyAttributeSource.cs b/src/MapTo/Sources/MapPropertyAttributeSource.cs new file mode 100644 index 0000000..7e389ef --- /dev/null +++ b/src/MapTo/Sources/MapPropertyAttributeSource.cs @@ -0,0 +1,64 @@ +using MapTo.Models; +using static MapTo.Sources.Constants; + +namespace MapTo.Sources +{ + internal static class MapPropertyAttributeSource + { + internal const string AttributeName = "MapProperty"; + + 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("/// Maps a property to property of another object.") + .WriteLine("/// "); + } + + builder + .WriteLine("[AttributeUsage(AttributeTargets.Property, AllowMultiple = true)]") + .WriteLine($"public sealed class {AttributeName}Attribute : Attribute") + .WriteOpeningBracket(); + + if (options.GenerateXmlDocument) + { + builder + .WriteLine("/// ") + .WriteLine("/// Initializes a new instance of .") + .WriteLine("/// ") + .WriteLine("/// The to convert the value of the annotated property."); + } + + builder + .WriteLine($"public {AttributeName}Attribute(Type converter = null)") + .WriteOpeningBracket() + .WriteLine("Converter = converter;") + .WriteClosingBracket() + .WriteLine(); + + if (options.GenerateXmlDocument) + { + builder + .WriteLine("/// ") + .WriteLine("/// Gets the to convert the value of the annotated property.") + .WriteLine("/// "); + } + + builder + .WriteLine("public Type Converter { get; }") + .WriteClosingBracket() + .WriteClosingBracket(); + + return new(builder.ToString(), $"{AttributeName}Attribute.g.cs"); + } + } +} \ No newline at end of file diff --git a/src/MapTo/Sources/Source.cs b/src/MapTo/Sources/Source.cs deleted file mode 100644 index db066a0..0000000 --- a/src/MapTo/Sources/Source.cs +++ /dev/null @@ -1,4 +0,0 @@ -namespace MapTo.Sources -{ - internal record Source(string Code, string HintName); -} \ No newline at end of file diff --git a/src/MapTo/Sources/SourceCode.cs b/src/MapTo/Sources/SourceCode.cs new file mode 100644 index 0000000..c9f8f4b --- /dev/null +++ b/src/MapTo/Sources/SourceCode.cs @@ -0,0 +1,4 @@ +namespace MapTo.Sources +{ + internal record SourceCode(string Text, string HintName); +} \ No newline at end of file diff --git a/src/MapTo/Sources/TypeConverterSource.cs b/src/MapTo/Sources/TypeConverterSource.cs index 40b1b6c..ee751de 100644 --- a/src/MapTo/Sources/TypeConverterSource.cs +++ b/src/MapTo/Sources/TypeConverterSource.cs @@ -7,7 +7,7 @@ namespace MapTo.Sources { internal const string InterfaceName = "ITypeConverter"; - internal static Source Generate(SourceGenerationOptions options) + internal static SourceCode Generate(SourceGenerationOptions options) { using var builder = new SourceBuilder() .WriteLine(GeneratedFilesHeader) diff --git a/test/MapTo.Tests/Tests.cs b/test/MapTo.Tests/Tests.cs index ab4f464..2b9cce9 100644 --- a/test/MapTo.Tests/Tests.cs +++ b/test/MapTo.Tests/Tests.cs @@ -512,5 +512,37 @@ namespace MapTo diagnostics.ShouldBeSuccessful(); compilation.SyntaxTrees.ShouldContainSource(TypeConverterSource.InterfaceName, expectedInterface); } + + [Fact] + public void VerifyMapPropertyAttribute() + { + // Arrange + const string source = ""; + var expectedInterface = $@" +{Constants.GeneratedFilesHeader} +using System; + +namespace MapTo +{{ + [AttributeUsage(AttributeTargets.Property, AllowMultiple = true)] + public sealed class MapPropertyAttribute : Attribute + {{ + public MapPropertyAttribute(Type converter = null) + {{ + Converter = converter; + }} + + public Type Converter {{ get; }} + }} +}} +".Trim(); + + // Act + var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions); + + // Assert + diagnostics.ShouldBeSuccessful(); + compilation.SyntaxTrees.ShouldContainSource(MapPropertyAttributeSource.AttributeName, expectedInterface); + } } } \ No newline at end of file diff --git a/test/TestConsoleApp/ViewModels/User.cs b/test/TestConsoleApp/ViewModels/User.cs index 475dd76..27c4c80 100644 --- a/test/TestConsoleApp/ViewModels/User.cs +++ b/test/TestConsoleApp/ViewModels/User.cs @@ -7,6 +7,10 @@ namespace TestConsoleApp.ViewModels [MapFrom(typeof(Data.Models.User))] public partial class User { + public int Id { get; } + public string FirstName { get; } + + public string LastName { get; } } } \ No newline at end of file diff --git a/test/TestConsoleApp/ViewModels/UserViewModel.cs b/test/TestConsoleApp/ViewModels/UserViewModel.cs index 404c759..68d36ac 100644 --- a/test/TestConsoleApp/ViewModels/UserViewModel.cs +++ b/test/TestConsoleApp/ViewModels/UserViewModel.cs @@ -7,7 +7,16 @@ namespace TestConsoleApp.ViewModels { public string FirstName { get; } - // [IgnoreProerty] + [IgnoreProperty] public string LastName { get; } + + [MapProperty(converter: typeof(LastNameConverter))] + public string Key { get; } + + private class LastNameConverter : ITypeConverter + { + /// + public string Convert(int source) => $"{source} :: With Type Converter"; + } } } \ No newline at end of file