From e9e44b979a49cf6dd6f40aa5975311fa06b4ec92 Mon Sep 17 00:00:00 2001 From: Mohammadreza Taikandi Date: Wed, 6 Jan 2021 08:52:50 +0000 Subject: [PATCH] Add TypeConverter interface. --- MapTo.sln.DotSettings | 3 ++ .../GeneratorExecutionContextExtensions.cs | 4 ++ src/MapTo/MapToGenerator.cs | 17 +++---- .../Sources/IgnorePropertyAttributeSource.cs | 4 +- src/MapTo/Sources/MapClassSource.cs | 4 +- src/MapTo/Sources/MapFromAttributeSource.cs | 4 +- src/MapTo/Sources/Source.cs | 4 ++ src/MapTo/Sources/TypeConverterSource.cs | 50 +++++++++++++++++++ .../Extensions/ShouldlyExtensions.cs | 20 ++++++++ test/MapTo.Tests/Tests.cs | 38 +++++++++++--- 10 files changed, 123 insertions(+), 25 deletions(-) create mode 100644 src/MapTo/Sources/Source.cs create mode 100644 src/MapTo/Sources/TypeConverterSource.cs create mode 100644 test/MapTo.Tests/Extensions/ShouldlyExtensions.cs diff --git a/MapTo.sln.DotSettings b/MapTo.sln.DotSettings index ff51a4e..2397ecf 100644 --- a/MapTo.sln.DotSettings +++ b/MapTo.sln.DotSettings @@ -1,3 +1,6 @@  True + True + True + True True \ No newline at end of file diff --git a/src/MapTo/Extensions/GeneratorExecutionContextExtensions.cs b/src/MapTo/Extensions/GeneratorExecutionContextExtensions.cs index ae70b20..7403ccc 100644 --- a/src/MapTo/Extensions/GeneratorExecutionContextExtensions.cs +++ b/src/MapTo/Extensions/GeneratorExecutionContextExtensions.cs @@ -1,4 +1,5 @@ using System; +using MapTo.Sources; using Microsoft.CodeAnalysis; namespace MapTo.Extensions @@ -33,5 +34,8 @@ 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); } } \ No newline at end of file diff --git a/src/MapTo/MapToGenerator.cs b/src/MapTo/MapToGenerator.cs index 0e34dcf..e5d77d2 100644 --- a/src/MapTo/MapToGenerator.cs +++ b/src/MapTo/MapToGenerator.cs @@ -22,17 +22,15 @@ namespace MapTo { var options = SourceGenerationOptions.From(context); - AddAttribute(context, MapFromAttributeSource.Generate(options)); - AddAttribute(context, IgnorePropertyAttributeSource.Generate(options)); + context.AddSource(MapFromAttributeSource.Generate(options)); + context.AddSource(IgnorePropertyAttributeSource.Generate(options)); + context.AddSource(TypeConverterSource.Generate(options)); if (context.SyntaxReceiver is MapToSyntaxReceiver receiver && receiver.CandidateClasses.Any()) { AddGeneratedMappingsClasses(context, receiver.CandidateClasses, options); } } - - private static void AddAttribute(GeneratorExecutionContext context, (string source, string hintName) attribute) - => context.AddSource(attribute.hintName, attribute.source); private static void AddGeneratedMappingsClasses(GeneratorExecutionContext context, IEnumerable candidateClasses, SourceGenerationOptions options) { @@ -42,14 +40,11 @@ namespace MapTo var (model, diagnostics) = MapModel.CreateModel(classSemanticModel, classSyntax, options); diagnostics.ForEach(context.ReportDiagnostic); - - if (model is null) + + if (model is not null) { - continue; + context.AddSource(MapClassSource.Generate(model)); } - - var (source, hintName) = MapClassSource.Generate(model); - context.AddSource(hintName, source); } } } diff --git a/src/MapTo/Sources/IgnorePropertyAttributeSource.cs b/src/MapTo/Sources/IgnorePropertyAttributeSource.cs index fb2a779..c82ddc2 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 (string source, string hintName) Generate(SourceGenerationOptions options) + internal static Source Generate(SourceGenerationOptions options) { var builder = new SourceBuilder() .WriteLine(GeneratedFilesHeader) @@ -29,7 +29,7 @@ namespace MapTo.Sources .WriteLine($"public sealed class {AttributeName}Attribute : Attribute {{ }}") .WriteClosingBracket(); - return (builder.ToString(), $"{AttributeName}Attribute.g.cs"); + return new(builder.ToString(), $"{AttributeName}Attribute.g.cs"); } } } \ No newline at end of file diff --git a/src/MapTo/Sources/MapClassSource.cs b/src/MapTo/Sources/MapClassSource.cs index c7dcd7a..268d607 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 (string source, string hintName) Generate(MapModel model) + internal static Source Generate(MapModel model) { using var builder = new SourceBuilder() .WriteLine(GeneratedFilesHeader) @@ -36,7 +36,7 @@ namespace MapTo.Sources // End namespace declaration .WriteClosingBracket(); - return (builder.ToString(), $"{model.ClassName}.g.cs"); + return new(builder.ToString(), $"{model.ClassName}.g.cs"); } private static SourceBuilder WriteUsings(this SourceBuilder builder, MapModel model) diff --git a/src/MapTo/Sources/MapFromAttributeSource.cs b/src/MapTo/Sources/MapFromAttributeSource.cs index cc5de3e..a938e5d 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 (string source, string hintName) Generate(SourceGenerationOptions options) + internal static Source Generate(SourceGenerationOptions options) { using var builder = new SourceBuilder() .WriteLine(GeneratedFilesHeader) @@ -57,7 +57,7 @@ namespace MapTo.Sources .WriteClosingBracket() // class .WriteClosingBracket(); // namespace - return (builder.ToString(), $"{AttributeName}Attribute.g.cs"); + 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 new file mode 100644 index 0000000..db066a0 --- /dev/null +++ b/src/MapTo/Sources/Source.cs @@ -0,0 +1,4 @@ +namespace MapTo.Sources +{ + internal record Source(string Code, string HintName); +} \ No newline at end of file diff --git a/src/MapTo/Sources/TypeConverterSource.cs b/src/MapTo/Sources/TypeConverterSource.cs new file mode 100644 index 0000000..40b1b6c --- /dev/null +++ b/src/MapTo/Sources/TypeConverterSource.cs @@ -0,0 +1,50 @@ +using MapTo.Models; +using static MapTo.Sources.Constants; + +namespace MapTo.Sources +{ + internal class TypeConverterSource + { + internal const string InterfaceName = "ITypeConverter"; + + internal static Source Generate(SourceGenerationOptions options) + { + using var builder = new SourceBuilder() + .WriteLine(GeneratedFilesHeader) + .WriteLine() + .WriteLine($"namespace {RootNamespace}") + .WriteOpeningBracket(); + + if (options.GenerateXmlDocument) + { + builder + .WriteLine("/// ") + .WriteLine("/// Converts the value of .") + .WriteLine("/// ") + .WriteLine("/// The type to convert from.") + .WriteLine("/// The type to convert to."); + } + + builder + .WriteLine($"public interface {InterfaceName}") + .WriteOpeningBracket(); + + if (options.GenerateXmlDocument) + { + builder + .WriteLine("/// ") + .WriteLine("/// Converts the value of object to .") + .WriteLine("/// ") + .WriteLine("/// The object to convert.") + .WriteLine("/// object."); + } + + builder + .WriteLine("TDestination Convert(TSource source);") + .WriteClosingBracket() + .WriteClosingBracket(); + + return new(builder.ToString(), $"{InterfaceName}.g.cs"); + } + } +} \ No newline at end of file diff --git a/test/MapTo.Tests/Extensions/ShouldlyExtensions.cs b/test/MapTo.Tests/Extensions/ShouldlyExtensions.cs new file mode 100644 index 0000000..c8f669f --- /dev/null +++ b/test/MapTo.Tests/Extensions/ShouldlyExtensions.cs @@ -0,0 +1,20 @@ +using System.Collections.Generic; +using System.Linq; +using Microsoft.CodeAnalysis; +using Shouldly; + +namespace MapTo.Tests.Extensions +{ + internal static class ShouldlyExtensions + { + internal static void ShouldContainSource(this IEnumerable syntaxTree, string typeName, string expectedSource, string customMessage = null) + { + var syntax = syntaxTree + .Select(s => s.ToString().Trim()) + .SingleOrDefault(s => s.Contains(typeName)); + + syntax.ShouldNotBeNullOrWhiteSpace(); + syntax.ShouldBe(expectedSource, customMessage); + } + } +} \ No newline at end of file diff --git a/test/MapTo.Tests/Tests.cs b/test/MapTo.Tests/Tests.cs index 386beb8..ab4f464 100644 --- a/test/MapTo.Tests/Tests.cs +++ b/test/MapTo.Tests/Tests.cs @@ -5,6 +5,7 @@ using System.Text; using MapTo.Extensions; using MapTo.Models; using MapTo.Sources; +using MapTo.Tests.Extensions; using MapTo.Tests.Infrastructure; using Microsoft.CodeAnalysis; using Shouldly; @@ -143,9 +144,7 @@ namespace MapTo // Assert diagnostics.ShouldBeSuccessful(); - var attributeSyntax = compilation.SyntaxTrees.Select(s => s.ToString().Trim()).SingleOrDefault(s => s.Contains(IgnorePropertyAttributeSource.AttributeName)); - attributeSyntax.ShouldNotBeNullOrWhiteSpace(); - attributeSyntax.ShouldBe(expectedAttribute); + compilation.SyntaxTrees.ShouldContainSource(IgnorePropertyAttributeSource.AttributeName, expectedAttribute); } [Fact] @@ -159,10 +158,7 @@ namespace MapTo // Assert diagnostics.ShouldBeSuccessful(); - - var attributeSyntax = compilation.SyntaxTrees.Select(s => s.ToString().Trim()).SingleOrDefault(s => s.Contains(MapFromAttributeSource.AttributeName)); - attributeSyntax.ShouldNotBeNullOrWhiteSpace(); - attributeSyntax.ShouldBe(ExpectedAttribute); + compilation.SyntaxTrees.ShouldContainSource(MapFromAttributeSource.AttributeName, ExpectedAttribute); } [Fact] @@ -385,6 +381,7 @@ namespace Test { // Arrange const string source = ""; + var expectedTypes = new[] { IgnorePropertyAttributeSource.AttributeName, MapFromAttributeSource.AttributeName, TypeConverterSource.InterfaceName }; // Act var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source); @@ -394,7 +391,7 @@ namespace Test compilation.SyntaxTrees .Select(s => s.ToString()) .Where(s => !string.IsNullOrWhiteSpace(s.ToString())) - .All(s => s.Contains(": Attribute")) + .All(s => expectedTypes.Any(s.Contains)) .ShouldBeTrue(); } @@ -490,5 +487,30 @@ namespace Test diagnostics.ShouldBeSuccessful(); compilation.SyntaxTrees.Last().ToString().ShouldContain(expectedResult.Trim()); } + + [Fact] + public void VerifyTypeConverterInterface() + { + // Arrange + const string source = ""; + var expectedInterface = $@" +{Constants.GeneratedFilesHeader} + +namespace MapTo +{{ + public interface ITypeConverter + {{ + TDestination Convert(TSource source); + }} +}} +".Trim(); + + // Act + var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions); + + // Assert + diagnostics.ShouldBeSuccessful(); + compilation.SyntaxTrees.ShouldContainSource(TypeConverterSource.InterfaceName, expectedInterface); + } } } \ No newline at end of file