diff --git a/MapTo/Extensions/StringBuilderExtensions.cs b/MapTo/Extensions/StringBuilderExtensions.cs new file mode 100644 index 0000000..33ae966 --- /dev/null +++ b/MapTo/Extensions/StringBuilderExtensions.cs @@ -0,0 +1,33 @@ +using System; +using System.Text; + +namespace MapTo.Extensions +{ + internal static class StringBuilderExtensions + { + public static StringBuilder PadLeft(this StringBuilder builder, int width) + { + for (var i = 0; i < width; i++) + { + builder.Append(" "); + } + + return builder; + } + + internal static StringBuilder AppendOpeningBracket(this StringBuilder builder, int indent = 0) + { + return builder.AppendLine().PadLeft(indent).AppendFormat("{{{0}", Environment.NewLine); + } + + internal static StringBuilder AppendClosingBracket(this StringBuilder builder, int indent = 0, bool padNewLine = true) + { + if (padNewLine) + { + builder.AppendLine(); + } + + return builder.PadLeft(indent).Append("}"); + } + } +} \ No newline at end of file diff --git a/MapTo/SourceProvider.cs b/MapTo/SourceProvider.cs index 1cd4c51..5670a1d 100644 --- a/MapTo/SourceProvider.cs +++ b/MapTo/SourceProvider.cs @@ -45,9 +45,12 @@ namespace MapTo { var builder = new StringBuilder(); - // Namespace declaration builder .AppendFileHeader() + .GenerateUsings(); + + // Namespace declaration + builder .AppendFormat("namespace {0}", model.Namespace) .AppendOpeningBracket(); @@ -57,8 +60,11 @@ namespace MapTo .AppendFormat("{0} class {1}", model.ClassModifiers, model.ClassName) .AppendOpeningBracket(Indent1); - // Constructor declaration - builder.GenerateConstructor(model, out var mappedProperties); + // Class body + builder + .GenerateConstructor(model, out var mappedProperties) + .AppendLine() + .GenerateFactoryMethod(model); // End class declaration builder.AppendClosingBracket(Indent1); @@ -69,6 +75,13 @@ namespace MapTo return (builder.ToString(), $"{model.ClassName}.cs"); } + private static StringBuilder GenerateUsings(this StringBuilder builder) + { + return builder + .AppendLine("using System;") + .AppendLine(); + } + private static StringBuilder GenerateConstructor(this StringBuilder builder, MapModel model, out List mappedProperties) { var destinationClassParameterName = model.DestinationClassName.ToCamelCase(); @@ -76,7 +89,10 @@ namespace MapTo builder .PadLeft(Indent2) .AppendFormat("public {0}({1} {2})", model.ClassName, model.DestinationClassName, destinationClassParameterName) - .AppendOpeningBracket(Indent2); + .AppendOpeningBracket(Indent2) + .PadLeft(Indent3) + .AppendFormat("if ({0} == null) throw new ArgumentNullException(nameof({0}));", destinationClassParameterName) + .AppendLine(); mappedProperties = new List(); foreach (var propertySymbol in model.DestinationTypeProperties) @@ -94,41 +110,24 @@ namespace MapTo return builder.AppendClosingBracket(Indent2, padNewLine: false); } - private static StringBuilder AppendOpeningBracket(this StringBuilder builder, int indent = 0) + private static StringBuilder GenerateFactoryMethod(this StringBuilder builder, MapModel model) { - return builder.AppendLine().PadLeft(indent).AppendFormat("{{{0}", Environment.NewLine); - } + var destinationClassParameterName = model.DestinationClassName.ToCamelCase(); - private static StringBuilder AppendClosingBracket(this StringBuilder builder, int indent = 0, bool padNewLine = true) - { - if (padNewLine) - { - builder.AppendLine(); - } - - return builder.PadLeft(indent).Append("}"); - } - - private static StringBuilder PadLeft(this StringBuilder builder, int width) - { - for (var i = 0; i < width; i++) - { - builder.Append(" "); - } - - return builder; + return builder + .AppendLine() + .PadLeft(Indent2) + .AppendFormat("public static {0} From({1} {2})", model.ClassName, model.DestinationClassName, destinationClassParameterName) + .AppendOpeningBracket(Indent2) + .PadLeft(Indent3) + .AppendFormat("return {0} == null ? null : new {1}({0});", destinationClassParameterName, model.ClassName ) + .AppendClosingBracket(Indent2); } private static StringBuilder AppendFileHeader(this StringBuilder builder) { return builder - .AppendLine("// ") - .AppendLine(); - } - - private static string GetValueText(this NameSyntax? nameSyntax) - { - return (nameSyntax as IdentifierNameSyntax)?.Identifier.ValueText ?? string.Empty; + .AppendLine("// "); } } } \ No newline at end of file diff --git a/MapToTests/Tests.cs b/MapToTests/Tests.cs index f26d81a..f223308 100644 --- a/MapToTests/Tests.cs +++ b/MapToTests/Tests.cs @@ -84,6 +84,7 @@ namespace Test const string expectedResult = @" // +using System; namespace Test { @@ -91,9 +92,8 @@ namespace Test { public Foo(Baz baz) { + if (baz == null) throw new ArgumentNullException(nameof(baz)); } - } -} "; // Act @@ -102,7 +102,7 @@ namespace Test // Assert diagnostics.ShouldBeSuccessful(); compilation.SyntaxTrees.Count().ShouldBe(3); - compilation.SyntaxTrees.Last().ToString().ShouldBe(expectedResult.Trim()); + compilation.SyntaxTrees.Last().ToString().ShouldStartWith(expectedResult.Trim()); } [Fact] @@ -127,6 +127,7 @@ namespace Test const string expectedResult = @" // +using System; namespace Test { @@ -134,9 +135,8 @@ namespace Test { public Foo(Baz baz) { + if (baz == null) throw new ArgumentNullException(nameof(baz)); } - } -} "; // Act @@ -145,7 +145,7 @@ namespace Test // Assert diagnostics.ShouldBeSuccessful(); compilation.SyntaxTrees.Count().ShouldBe(3); - compilation.SyntaxTrees.Last().ToString().ShouldBe(expectedResult.Trim()); + compilation.SyntaxTrees.Last().ToString().ShouldStartWith(expectedResult.Trim()); } [Fact] @@ -159,6 +159,7 @@ namespace Test { public Foo(Baz baz) { + if (baz == null) throw new ArgumentNullException(nameof(baz)); Prop1 = baz.Prop1; Prop2 = baz.Prop2; Prop3 = baz.Prop3; @@ -173,6 +174,28 @@ namespace Test 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(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) {