From 2a671a9dd45d848b636f0aa58bd62d0312506ae6 Mon Sep 17 00:00:00 2001 From: Mohammadreza Taikandi Date: Mon, 21 Dec 2020 16:34:53 +0000 Subject: [PATCH] Cleanup. --- .editorconfig | 72 ++++ LICENSE | 21 + MapTo/CompilerServices/IsExternalInit.cs | 4 +- MapTo/Diagnostics.cs | 14 +- MapTo/Extensions/RoslynExtensions.cs | 44 +- MapTo/Extensions/StringBuilderExtensions.cs | 5 +- MapTo/Extensions/StringExtensions.cs | 5 +- MapTo/MapToGenerator.cs | 50 +-- MapTo/Mapto.csproj | 7 +- MapTo/Models/MapModel.cs | 32 +- MapTo/SourceBuilder.cs | 18 +- MapToTests/CSharpGenerator.cs | 13 +- MapToTests/Tests.cs | 441 ++++++++++---------- README.md | 2 + TestConsoleApp/Program.cs | 8 +- TestConsoleApp/ViewModels/User.cs | 7 +- TestConsoleApp/ViewModels/UserViewModel.cs | 5 +- 17 files changed, 396 insertions(+), 352 deletions(-) create mode 100644 .editorconfig create mode 100644 LICENSE create mode 100644 README.md diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..273fd18 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,72 @@ + +[*.{appxmanifest,asax,ascx,aspx,axaml,build,cg,cginc,compute,cs,cshtml,dtd,fs,fsi,fsscript,fsx,hlsl,hlsli,hlslinc,master,ml,mli,nuspec,paml,razor,resw,resx,skin,usf,ush,vb,xaml,xamlx,xoml,xsd}] +indent_style = space +indent_size = 4 +tab_width = 4 + +[*] + +# Microsoft .NET properties +csharp_new_line_before_members_in_object_initializers = false +csharp_preferred_modifier_order = public, private, protected, internal, new, abstract, virtual, sealed, override, static, readonly, extern, unsafe, volatile, async:suggestion +csharp_space_after_cast = false +csharp_style_var_elsewhere = true:suggestion +csharp_style_var_for_built_in_types = true:suggestion +csharp_style_var_when_type_is_apparent = true:suggestion +dotnet_style_parentheses_in_arithmetic_binary_operators = never_if_unnecessary:none +dotnet_style_parentheses_in_other_binary_operators = never_if_unnecessary:none +dotnet_style_parentheses_in_relational_binary_operators = never_if_unnecessary:none +dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion +dotnet_style_predefined_type_for_member_access = true:suggestion +dotnet_style_qualification_for_event = false:suggestion +dotnet_style_qualification_for_field = false:suggestion +dotnet_style_qualification_for_method = false:suggestion +dotnet_style_qualification_for_property = false:suggestion +dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion + +# ReSharper properties +resharper_autodetect_indent_settings = true +resharper_blank_lines_after_control_transfer_statements = 1 +resharper_blank_lines_after_multiline_statements = 1 +resharper_blank_lines_around_block_case_section = 1 +resharper_blank_lines_around_multiline_case_section = 1 +resharper_blank_lines_around_single_line_auto_property = 1 +resharper_blank_lines_around_single_line_local_method = 1 +resharper_blank_lines_around_single_line_property = 1 +resharper_braces_for_for = required +resharper_braces_for_foreach = required +resharper_braces_for_ifelse = required +resharper_braces_for_while = required +resharper_csharp_blank_lines_around_single_line_invocable = 1 +resharper_csharp_empty_block_style = together_same_line +resharper_csharp_keep_blank_lines_in_code = 1 +resharper_csharp_keep_blank_lines_in_declarations = 1 +resharper_csharp_max_line_length = 180 +resharper_csharp_wrap_lines = false +resharper_local_function_body = expression_body +resharper_method_or_operator_body = expression_body +resharper_place_accessorholder_attribute_on_same_line = false +resharper_place_field_attribute_on_same_line = false +resharper_space_after_cast = false +resharper_space_within_single_line_array_initializer_braces = true +resharper_use_indent_from_vs = false +resharper_xmldoc_indent_text = ZeroIndent + +# ReSharper inspection severities +resharper_arguments_style_literal_highlighting = none +resharper_arguments_style_named_expression_highlighting = none +resharper_arguments_style_other_highlighting = none +resharper_arrange_redundant_parentheses_highlighting = hint +resharper_arrange_this_qualifier_highlighting = hint +resharper_arrange_type_member_modifiers_highlighting = hint +resharper_arrange_type_modifiers_highlighting = hint +resharper_built_in_type_reference_style_for_member_access_highlighting = hint +resharper_built_in_type_reference_style_highlighting = hint +resharper_class_never_instantiated_global_highlighting = none +resharper_redundant_base_qualifier_highlighting = warning +resharper_suggest_var_or_type_built_in_types_highlighting = hint +resharper_suggest_var_or_type_elsewhere_highlighting = hint +resharper_suggest_var_or_type_simple_types_highlighting = hint +resharper_web_config_module_not_resolved_highlighting = warning +resharper_web_config_type_not_resolved_highlighting = warning +resharper_web_config_wrong_module_highlighting = warning diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..97d5740 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 Mohammadreza Taikandi + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/MapTo/CompilerServices/IsExternalInit.cs b/MapTo/CompilerServices/IsExternalInit.cs index 12de4db..e750e2f 100644 --- a/MapTo/CompilerServices/IsExternalInit.cs +++ b/MapTo/CompilerServices/IsExternalInit.cs @@ -12,7 +12,5 @@ namespace System.Runtime.CompilerServices /// This class should not be used by developers in source code. /// [EditorBrowsable(EditorBrowsableState.Never)] - internal static class IsExternalInit - { - } + internal static class IsExternalInit { } } \ No newline at end of file diff --git a/MapTo/Diagnostics.cs b/MapTo/Diagnostics.cs index 1161a84..ae2eb9f 100644 --- a/MapTo/Diagnostics.cs +++ b/MapTo/Diagnostics.cs @@ -6,16 +6,16 @@ namespace MapTo { private const string UsageCategory = "Usage"; - internal static Diagnostic SymbolNotFound(Location location, string syntaxName) => - Diagnostic.Create(CreateDescriptor("MT0001", "Symbol not found.", $"Unable to find any symbols for {syntaxName}"), location); + internal static Diagnostic SymbolNotFound(Location location, string syntaxName) => + Create("MT0001", "Symbol not found.", $"Unable to find any symbols for {syntaxName}", location); - internal static Diagnostic MapFromAttributeNotFound(Location location) => - Diagnostic.Create(CreateDescriptor("MT0002", "Attribute Not Available", $"Unable to find {SourceBuilder.MapFromAttributeName} type."), location); + internal static Diagnostic MapFromAttributeNotFound(Location location) => + Create("MT0002", "Attribute Not Available", $"Unable to find {SourceBuilder.MapFromAttributeName} type.", location); internal static Diagnostic ClassMappingsGenerated(Location location, string typeName) => - Diagnostic.Create(CreateDescriptor("MT1001", "Mapped Type", $"Generated mappings for {typeName}", DiagnosticSeverity.Info), location); + Create("MT1001", "Mapped Type", $"Generated mappings for {typeName}", location, DiagnosticSeverity.Info); - private static DiagnosticDescriptor CreateDescriptor(string id, string title, string message, DiagnosticSeverity severity = DiagnosticSeverity.Error) => - new(id, title, message, UsageCategory, severity, true); + private static Diagnostic Create(string id, string title, string message, Location location, DiagnosticSeverity severity = DiagnosticSeverity.Error) => + Diagnostic.Create(new DiagnosticDescriptor(id, title, message, UsageCategory, severity, true), location); } } \ No newline at end of file diff --git a/MapTo/Extensions/RoslynExtensions.cs b/MapTo/Extensions/RoslynExtensions.cs index 57f01cf..d73b318 100644 --- a/MapTo/Extensions/RoslynExtensions.cs +++ b/MapTo/Extensions/RoslynExtensions.cs @@ -23,35 +23,11 @@ namespace MapTo.Extensions return type.GetBaseTypesAndThis().SelectMany(n => n.GetMembers()); } - public static IEnumerable GetAllMembersOfType(this ITypeSymbol type) where T : ISymbol - { - return type.GetAllMembers().OfType(); - } + public static IEnumerable GetAllMembersOfType(this ITypeSymbol type) where T : ISymbol => type.GetAllMembers().OfType(); - public static CompilationUnitSyntax GetCompilationUnit(this SyntaxNode syntaxNode) - { - return syntaxNode.Ancestors().OfType().Single(); - } + public static CompilationUnitSyntax GetCompilationUnit(this SyntaxNode syntaxNode) => syntaxNode.Ancestors().OfType().Single(); - public static string GetClassName(this ClassDeclarationSyntax classSyntax) - { - return classSyntax.Identifier.Text; - } - - public static string GetClassModifier(this ClassDeclarationSyntax classSyntax) - { - return classSyntax.Modifiers.ToFullString().Trim(); - } - - public static bool HaveAttribute(this ClassDeclarationSyntax classSyntax, string attributeName) - { - return classSyntax.AttributeLists.Count > 0 && - classSyntax.AttributeLists.SelectMany(al => al.Attributes - .Where(a => - (a.Name as IdentifierNameSyntax)?.Identifier.Text == attributeName || - ((a.Name as QualifiedNameSyntax)?.Right as IdentifierNameSyntax)?.Identifier.ValueText == attributeName)) - .Any(); - } + public static string GetClassName(this ClassDeclarationSyntax classSyntax) => classSyntax.Identifier.Text; public static AttributeSyntax? GetAttribute(this ClassDeclarationSyntax classSyntax, string attributeName) { @@ -62,21 +38,11 @@ namespace MapTo.Extensions ((a.Name as QualifiedNameSyntax)?.Right as IdentifierNameSyntax)?.Identifier.ValueText == attributeName); } - public static string? GetNamespace(this CompilationUnitSyntax root) - { - return root.ChildNodes() + public static string? GetNamespace(this CompilationUnitSyntax root) => + root.ChildNodes() .OfType() .FirstOrDefault() ?.Name .ToString(); - } - - public static List GetUsings(this CompilationUnitSyntax root) - { - return root.ChildNodes() - .OfType() - .Select(n => n.Name.ToString()) - .ToList(); - } } } \ No newline at end of file diff --git a/MapTo/Extensions/StringBuilderExtensions.cs b/MapTo/Extensions/StringBuilderExtensions.cs index 33ae966..5d26819 100644 --- a/MapTo/Extensions/StringBuilderExtensions.cs +++ b/MapTo/Extensions/StringBuilderExtensions.cs @@ -15,10 +15,7 @@ namespace MapTo.Extensions return builder; } - internal static StringBuilder AppendOpeningBracket(this StringBuilder builder, int indent = 0) - { - return builder.AppendLine().PadLeft(indent).AppendFormat("{{{0}", Environment.NewLine); - } + internal static StringBuilder AppendOpeningBracket(this StringBuilder builder, int indent = 0) => builder.AppendLine().PadLeft(indent).AppendFormat("{{{0}", Environment.NewLine); internal static StringBuilder AppendClosingBracket(this StringBuilder builder, int indent = 0, bool padNewLine = true) { diff --git a/MapTo/Extensions/StringExtensions.cs b/MapTo/Extensions/StringExtensions.cs index dff6852..ee786cb 100644 --- a/MapTo/Extensions/StringExtensions.cs +++ b/MapTo/Extensions/StringExtensions.cs @@ -2,9 +2,6 @@ { internal static class StringExtensions { - public static string ToCamelCase(this string value) - { - return string.IsNullOrWhiteSpace(value) ? value : $"{char.ToLower(value[0])}{value.Substring(1)}"; - } + public static string ToCamelCase(this string value) => string.IsNullOrWhiteSpace(value) ? value : $"{char.ToLower(value[0])}{value.Substring(1)}"; } } \ No newline at end of file diff --git a/MapTo/MapToGenerator.cs b/MapTo/MapToGenerator.cs index e9ee010..8b70066 100644 --- a/MapTo/MapToGenerator.cs +++ b/MapTo/MapToGenerator.cs @@ -1,6 +1,5 @@ using System.Collections.Generic; using System.Linq; -using MapTo.Extensions; using MapTo.Models; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -19,57 +18,36 @@ namespace MapTo /// public void Execute(GeneratorExecutionContext context) { - context.AddMapToAttribute(); + AddMapFromAttribute(context); - if (!(context.SyntaxReceiver is MapToSyntaxReceiver receiver) || !receiver.CandidateClasses.Any()) + if (context.SyntaxReceiver is MapToSyntaxReceiver receiver && receiver.CandidateClasses.Any()) { - return; + AddGeneratedMappingsClasses(context, receiver.CandidateClasses); } + } - foreach (var classDeclarationSyntax in receiver.CandidateClasses) + private static void AddGeneratedMappingsClasses(GeneratorExecutionContext context, IEnumerable candidateClasses) + { + foreach (var classDeclarationSyntax in candidateClasses) { - var (model, diagnostic) = GetModel(context.Compilation, classDeclarationSyntax); + var (model, diagnostic) = MapModel.Create(context.Compilation, classDeclarationSyntax); if (model is null) { context.ReportDiagnostic(diagnostic!); continue; } - + var (source, hintName) = SourceBuilder.GenerateSource(model); - + context.AddSource(hintName, source); - context.ReportDiagnostic(Diagnostics.ClassMappingsGenerated(classDeclarationSyntax.GetLocation(), model.ClassName)); + context.ReportDiagnostic(Diagnostics.ClassMappingsGenerated(classDeclarationSyntax.GetLocation(), model.ClassName)); } } - private static (MapModel? model, Diagnostic? diagnostic) GetModel(Compilation compilation, ClassDeclarationSyntax classSyntax) + private static void AddMapFromAttribute(GeneratorExecutionContext context) { - var root = classSyntax.GetCompilationUnit(); - var classSemanticModel = compilation.GetSemanticModel(classSyntax.SyntaxTree); - - if (!(classSemanticModel.GetDeclaredSymbol(classSyntax) is INamedTypeSymbol classSymbol)) - { - return (default, Diagnostics.SymbolNotFound(classSyntax.GetLocation(), classSyntax.Identifier.ValueText)); - } - - var sourceTypeSymbol = GetSourceTypeSymbol(classSyntax, classSemanticModel); - if (sourceTypeSymbol is null) - { - return (default, Diagnostics.SymbolNotFound(classSyntax.GetLocation(), classSyntax.Identifier.ValueText)); - } - - return (MapModel.Create(root, classSyntax, classSymbol, sourceTypeSymbol), default); - } - - private static ITypeSymbol? GetSourceTypeSymbol(ClassDeclarationSyntax classSyntax, SemanticModel model) - { - var sourceTypeExpressionSyntax = classSyntax - .GetAttribute(SourceBuilder.MapFromAttributeName) - ?.DescendantNodes() - .OfType() - .SingleOrDefault(); - - return sourceTypeExpressionSyntax is not null ? model.GetTypeInfo(sourceTypeExpressionSyntax.Type).Type : null; + var (source, hintName) = SourceBuilder.GenerateMapFromAttribute(); + context.AddSource(hintName, source); } } } \ No newline at end of file diff --git a/MapTo/Mapto.csproj b/MapTo/Mapto.csproj index 11d8a8f..fae053e 100644 --- a/MapTo/Mapto.csproj +++ b/MapTo/Mapto.csproj @@ -2,11 +2,11 @@ MapTo - Generates mapping code between two types using Roslyn code generator. + An object to object mapping generator using using Roslyn code generator. true true - NU5128 MapTo + LICENSE https://github.com/mrtaikandi/mapto false $(Version) @@ -16,7 +16,7 @@ MapTo netstandard2.0 enable - preview + 9 @@ -32,6 +32,7 @@ + diff --git a/MapTo/Models/MapModel.cs b/MapTo/Models/MapModel.cs index 410e429..c51bcfb 100644 --- a/MapTo/Models/MapModel.cs +++ b/MapTo/Models/MapModel.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Linq; using MapTo.Extensions; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -43,9 +44,23 @@ namespace MapTo.Models public IEnumerable SourceTypeProperties { get; } - internal static MapModel Create(CompilationUnitSyntax root, ClassDeclarationSyntax classSyntax, INamedTypeSymbol classSymbol, ITypeSymbol sourceTypeSymbol) + internal static (MapModel? model, Diagnostic? diagnostic) Create(Compilation compilation, ClassDeclarationSyntax classSyntax) { - return new( + var root = classSyntax.GetCompilationUnit(); + var classSemanticModel = compilation.GetSemanticModel(classSyntax.SyntaxTree); + + if (!(classSemanticModel.GetDeclaredSymbol(classSyntax) is INamedTypeSymbol classSymbol)) + { + return (default, Diagnostics.SymbolNotFound(classSyntax.GetLocation(), classSyntax.Identifier.ValueText)); + } + + var sourceTypeSymbol = GetSourceTypeSymbol(classSyntax, classSemanticModel); + if (sourceTypeSymbol is null) + { + return (default, Diagnostics.SymbolNotFound(classSyntax.GetLocation(), classSyntax.Identifier.ValueText)); + } + + var model = new MapModel( root.GetNamespace(), classSyntax.Modifiers, classSyntax.GetClassName(), @@ -54,6 +69,19 @@ namespace MapTo.Models sourceTypeSymbol.Name, sourceTypeSymbol.ToString(), sourceTypeSymbol.GetAllMembersOfType()); + + return (model, default); + } + + private static ITypeSymbol? GetSourceTypeSymbol(ClassDeclarationSyntax classSyntax, SemanticModel model) + { + var sourceTypeExpressionSyntax = classSyntax + .GetAttribute(SourceBuilder.MapFromAttributeName) + ?.DescendantNodes() + .OfType() + .SingleOrDefault(); + + return sourceTypeExpressionSyntax is not null ? model.GetTypeInfo(sourceTypeExpressionSyntax.Type).Type : null; } } } \ No newline at end of file diff --git a/MapTo/SourceBuilder.cs b/MapTo/SourceBuilder.cs index 1b9634c..d7ebdca 100644 --- a/MapTo/SourceBuilder.cs +++ b/MapTo/SourceBuilder.cs @@ -16,7 +16,7 @@ namespace MapTo private const int Indent2 = Indent1 * 2; // " "; private const int Indent3 = Indent1 * 3; // " "; - internal static void AddMapToAttribute(this GeneratorExecutionContext context) + internal static (string source, string hintName) GenerateMapFromAttribute() { const string source = @" using System; @@ -36,7 +36,7 @@ namespace MapTo } "; - context.AddSource("MapFromAttribute.g.cs", source); + return (source, $"{MapFromAttributeName}Attribute.g.cs"); } internal static (string source, string hintName) GenerateSource(MapModel model) @@ -70,13 +70,13 @@ namespace MapTo .PadLeft(Indent1) .AppendFormat("{0} static partial class {1}Extensions", model.ClassModifiers.FirstOrDefault().ToFullString().Trim(), model.SourceClassName) .AppendOpeningBracket(Indent1) - + // Extension class body .GenerateSourceTypeExtensionMethod(model) - + // End extensions class declaration .AppendClosingBracket(Indent1) - + // End namespace declaration .AppendClosingBracket(); @@ -150,11 +150,9 @@ namespace MapTo .AppendFormat("return {0} == null ? null : new {1}({0});", sourceClassParameterName, model.ClassName) .AppendClosingBracket(Indent2); } - - private static StringBuilder AppendFileHeader(this StringBuilder builder) - { - return builder + + private static StringBuilder AppendFileHeader(this StringBuilder builder) => + builder .AppendLine("// "); - } } } \ No newline at end of file diff --git a/MapToTests/CSharpGenerator.cs b/MapToTests/CSharpGenerator.cs index 22c262a..288595f 100644 --- a/MapToTests/CSharpGenerator.cs +++ b/MapToTests/CSharpGenerator.cs @@ -11,17 +11,6 @@ namespace MapToTests { internal static class CSharpGenerator { - internal static string GetGeneratedOutput(this ITestOutputHelper outputHelper, string source) - { - var (compilation, diagnostics) = GetOutputCompilation(source); - diagnostics.ShouldBeSuccessful(); - - var generatedOutput = compilation.SyntaxTrees.Last().ToString(); - outputHelper.WriteLine(generatedOutput); - - return generatedOutput; - } - internal static void ShouldBeSuccessful(this ImmutableArray diagnostics) { Assert.False(diagnostics.Any(d => d.Severity >= DiagnosticSeverity.Warning), $"Failed: {Environment.NewLine}{string.Join($"{Environment.NewLine}- ", diagnostics.Select(c => c.GetMessage()))}"); @@ -47,7 +36,7 @@ namespace MapToTests ISourceGenerator generator = new MapToGenerator(); var driver = CSharpGeneratorDriver.Create(generator); driver.RunGeneratorsAndUpdateCompilation(compilation, out var outputCompilation, out var generateDiagnostics); - + return (outputCompilation, generateDiagnostics); } } diff --git a/MapToTests/Tests.cs b/MapToTests/Tests.cs index ed82fda..be2e825 100644 --- a/MapToTests/Tests.cs +++ b/MapToTests/Tests.cs @@ -8,7 +8,13 @@ namespace MapToTests { public class Tests { + public Tests(ITestOutputHelper output) + { + _output = output; + } + private readonly ITestOutputHelper _output; + private const string ExpectedAttribute = @" using System; @@ -26,225 +32,6 @@ namespace MapTo } } "; - - public Tests(ITestOutputHelper output) - { - _output = output; - } - - [Fact] - public void VerifyMapToAttribute() - { - // Arrange - const string source = ""; - - // Act - var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source); - - // Assert - diagnostics.ShouldBeSuccessful(); - compilation.SyntaxTrees.ShouldContain(c => c.ToString() == ExpectedAttribute); - } - - [Fact] - public void When_NoMapToAttributeFound_Should_GenerateOnlyTheAttribute() - { - // Arrange - const string source = ""; - - // Act - var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source); - - // Assert - diagnostics.ShouldBeSuccessful(); - compilation.SyntaxTrees.ShouldContain(s => s.ToString() == ExpectedAttribute); - compilation.SyntaxTrees.Select(s => s.ToString()).Where(s => s != string.Empty && s != ExpectedAttribute).ShouldBeEmpty(); - } - - [Fact] - public void When_MapToAttributeFound_Should_GenerateTheClass() - { - // Arrange - const string source = @" -using MapTo; - -namespace Test -{ - [MapFrom(typeof(Baz))] - public partial class Foo - { - - } - - public class Baz - { - public int Prop1 { get; set; } - public int Prop2 { get; } - public int Prop3 { get; set; } - } -} -"; - - const string expectedResult = @" -// -using System; - -namespace Test -{ - public partial class Foo - { - public Foo(Test.Baz baz) - { - if (baz == null) throw new ArgumentNullException(nameof(baz)); - } -"; - - // Act - var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source); - - // Assert - diagnostics.ShouldBeSuccessful(); - compilation.SyntaxTrees.Count().ShouldBe(3); - compilation.SyntaxTrees.Last().ToString().ShouldStartWith(expectedResult.Trim()); - } - - [Fact] - public void When_MapToAttributeWithNamespaceFound_Should_GenerateTheClass() - { - // Arrange - const string source = @" -namespace Test -{ - [MapTo.MapFrom(typeof(Baz))] - public partial class Foo - { - - } - - public class Baz - { - - } -} -"; - - const string expectedResult = @" -// -using System; - -namespace Test -{ - public partial class Foo - { - public Foo(Test.Baz baz) - { - if (baz == null) throw new ArgumentNullException(nameof(baz)); - } -"; - - // Act - var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source); - - // Assert - diagnostics.ShouldBeSuccessful(); - compilation.SyntaxTrees.Count().ShouldBe(3); - compilation.SyntaxTrees.Last().ToString().ShouldStartWith(expectedResult.Trim()); - } - - [Fact] - public void When_SourceTypeHasMatchingProperties_Should_CreateConstructorAndAssignSrcToDest() - { - // Arrange - var source = GetSourceText(); - - const string expectedResult = @" - public partial class Foo - { - public Foo(Test.Models.Baz baz) - { - if (baz == null) throw new ArgumentNullException(nameof(baz)); - Prop1 = baz.Prop1; - Prop2 = baz.Prop2; - Prop3 = baz.Prop3; - } -"; - - // Act - var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source); - - // Assert - diagnostics.ShouldBeSuccessful(); - compilation.SyntaxTrees.Count().ShouldBe(3); - compilation.SyntaxTrees.Last().ToString().ShouldContain(expectedResult.Trim()); - } - - [Fact] - public void When_SourceTypeHasMatchingProperties_Should_CreateFromStaticMethod() - { - // Arrange - var source = GetSourceText(); - - const string expectedResult = @" - public static Foo From(Test.Models.Baz baz) - { - return baz == null ? null : new Foo(baz); - } -"; - - // Act - var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source); - - // Assert - diagnostics.ShouldBeSuccessful(); - compilation.SyntaxTrees.Count().ShouldBe(3); - compilation.SyntaxTrees.Last().ToString().ShouldContain(expectedResult.Trim()); - } - - [Fact] - public void When_SourceTypeHasDifferentNamespace_Should_AddToUsings() - { - // Arrange - var source = GetSourceText(sourceClassNamespace: "Bazaar"); - - const string expectedResult = @" -// -using System; -using Bazaar; -"; - - // Act - var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source); - - // Assert - diagnostics.ShouldBeSuccessful(); - compilation.SyntaxTrees.Count().ShouldBe(3); - compilation.SyntaxTrees.Last().ToString().ShouldStartWith(expectedResult.Trim()); - } - - [Fact] - public void When_SourceTypeHasMatchingProperties_Should_GenerateToExtensionMethodOnSourceType() - { - // Arrange - var source = GetSourceText(); - - const string expectedResult = @" - public static class BazExtensions - { - public static Foo ToFoo(this Test.Models.Baz baz) - { - return baz == null ? null : new Foo(baz); - } - } -"; - - // Act - var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source); - - // Assert - diagnostics.ShouldBeSuccessful(); - compilation.SyntaxTrees.Count().ShouldBe(3); - compilation.SyntaxTrees.Last().ToString().ShouldContain(expectedResult.Trim()); - } private static string GetSourceText(bool includeAttributeNamespace = false, string sourceClassNamespace = "Test.Models") { @@ -253,7 +40,7 @@ using Bazaar; {(includeAttributeNamespace ? string.Empty : "using MapTo;")} namespace Test {{ - {(sourceClassNamespace != "Test" && !includeAttributeNamespace ? $"using {sourceClassNamespace};": string.Empty)} + {(sourceClassNamespace != "Test" && !includeAttributeNamespace ? $"using {sourceClassNamespace};" : string.Empty)} {(includeAttributeNamespace ? "[MapTo.MapFrom(typeof(Baz))]" : "[MapFrom(typeof(Baz))]")} public partial class Foo @@ -279,5 +66,219 @@ namespace {sourceClassNamespace} return builder.ToString(); } + + [Fact] + public void VerifyMapToAttribute() + { + // Arrange + const string source = ""; + + // Act + var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source); + + // Assert + diagnostics.ShouldBeSuccessful(); + compilation.SyntaxTrees.ShouldContain(c => c.ToString() == ExpectedAttribute); + } + + [Fact] + public void When_MapToAttributeFound_Should_GenerateTheClass() + { + // Arrange + const string source = @" +using MapTo; + +namespace Test +{ + [MapFrom(typeof(Baz))] + public partial class Foo + { + + } + + public class Baz + { + public int Prop1 { get; set; } + public int Prop2 { get; } + public int Prop3 { get; set; } } } +"; + + const string expectedResult = @" +// +using System; + +namespace Test +{ + public partial class Foo + { + public Foo(Test.Baz baz) + { + if (baz == null) throw new ArgumentNullException(nameof(baz)); + } +"; + + // Act + var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source); + + // Assert + diagnostics.ShouldBeSuccessful(); + compilation.SyntaxTrees.Count().ShouldBe(3); + compilation.SyntaxTrees.Last().ToString().ShouldStartWith(expectedResult.Trim()); + } + + [Fact] + public void When_MapToAttributeWithNamespaceFound_Should_GenerateTheClass() + { + // Arrange + const string source = @" +namespace Test +{ + [MapTo.MapFrom(typeof(Baz))] + public partial class Foo + { + + } + + public class Baz + { + + } +} +"; + + const string expectedResult = @" +// +using System; + +namespace Test +{ + public partial class Foo + { + public Foo(Test.Baz baz) + { + if (baz == null) throw new ArgumentNullException(nameof(baz)); + } +"; + + // Act + var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source); + + // Assert + diagnostics.ShouldBeSuccessful(); + compilation.SyntaxTrees.Count().ShouldBe(3); + compilation.SyntaxTrees.Last().ToString().ShouldStartWith(expectedResult.Trim()); + } + + [Fact] + public void When_NoMapToAttributeFound_Should_GenerateOnlyTheAttribute() + { + // Arrange + const string source = ""; + + // Act + var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source); + + // Assert + diagnostics.ShouldBeSuccessful(); + compilation.SyntaxTrees.ShouldContain(s => s.ToString() == ExpectedAttribute); + compilation.SyntaxTrees.Select(s => s.ToString()).Where(s => s != string.Empty && s != ExpectedAttribute).ShouldBeEmpty(); + } + + [Fact] + public void When_SourceTypeHasDifferentNamespace_Should_AddToUsings() + { + // Arrange + var source = GetSourceText(sourceClassNamespace: "Bazaar"); + + const string expectedResult = @" +// +using System; +using Bazaar; +"; + + // Act + var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source); + + // Assert + diagnostics.ShouldBeSuccessful(); + compilation.SyntaxTrees.Count().ShouldBe(3); + compilation.SyntaxTrees.Last().ToString().ShouldStartWith(expectedResult.Trim()); + } + + [Fact] + public void When_SourceTypeHasMatchingProperties_Should_CreateConstructorAndAssignSrcToDest() + { + // Arrange + var source = GetSourceText(); + + const string expectedResult = @" + public partial class Foo + { + public Foo(Test.Models.Baz baz) + { + if (baz == null) throw new ArgumentNullException(nameof(baz)); + Prop1 = baz.Prop1; + Prop2 = baz.Prop2; + Prop3 = baz.Prop3; + } +"; + + // Act + var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source); + + // Assert + diagnostics.ShouldBeSuccessful(); + compilation.SyntaxTrees.Count().ShouldBe(3); + compilation.SyntaxTrees.Last().ToString().ShouldContain(expectedResult.Trim()); + } + + [Fact] + public void When_SourceTypeHasMatchingProperties_Should_CreateFromStaticMethod() + { + // Arrange + var source = GetSourceText(); + + const string expectedResult = @" + public static Foo From(Test.Models.Baz baz) + { + return baz == null ? null : new Foo(baz); + } +"; + + // Act + var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source); + + // Assert + diagnostics.ShouldBeSuccessful(); + compilation.SyntaxTrees.Count().ShouldBe(3); + compilation.SyntaxTrees.Last().ToString().ShouldContain(expectedResult.Trim()); + } + + [Fact] + public void When_SourceTypeHasMatchingProperties_Should_GenerateToExtensionMethodOnSourceType() + { + // Arrange + var source = GetSourceText(); + + const string expectedResult = @" + public static partial class BazExtensions + { + public static Foo ToFoo(this Test.Models.Baz baz) + { + return baz == null ? null : new Foo(baz); + } + } +"; + + // Act + var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source); + + // Assert + diagnostics.ShouldBeSuccessful(); + compilation.SyntaxTrees.Count().ShouldBe(3); + compilation.SyntaxTrees.Last().ToString().ShouldContain(expectedResult.Trim()); + } + } +} \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..4c2c323 --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# MapTo +An object to object mapping generator using using Roslyn code generator. \ No newline at end of file diff --git a/TestConsoleApp/Program.cs b/TestConsoleApp/Program.cs index 2542e32..be9106d 100644 --- a/TestConsoleApp/Program.cs +++ b/TestConsoleApp/Program.cs @@ -1,15 +1,13 @@ -using System; -using TestConsoleApp.ViewModels; +using TestConsoleApp.ViewModels; namespace TestConsoleApp { - class Program + internal class Program { - static void Main(string[] args) + private static void Main(string[] args) { var userViewModel = User.From(new Data.Models.User()); var userViewModel2 = UserViewModel.From(new Data.Models.User()); - } } } \ No newline at end of file diff --git a/TestConsoleApp/ViewModels/User.cs b/TestConsoleApp/ViewModels/User.cs index 8415924..475dd76 100644 --- a/TestConsoleApp/ViewModels/User.cs +++ b/TestConsoleApp/ViewModels/User.cs @@ -1,9 +1,10 @@ - -// using MapTo; +// using MapTo; + +using MapTo; namespace TestConsoleApp.ViewModels { - [MapTo.MapFrom(typeof(Data.Models.User))] + [MapFrom(typeof(Data.Models.User))] public partial class User { public string FirstName { get; } diff --git a/TestConsoleApp/ViewModels/UserViewModel.cs b/TestConsoleApp/ViewModels/UserViewModel.cs index 39e9c31..2560b98 100644 --- a/TestConsoleApp/ViewModels/UserViewModel.cs +++ b/TestConsoleApp/ViewModels/UserViewModel.cs @@ -3,8 +3,5 @@ namespace TestConsoleApp.ViewModels { [MapFrom(typeof(Data.Models.User))] - public partial class UserViewModel - { - - } + public partial class UserViewModel { } } \ No newline at end of file