Use SourceBuilder instead of StringBuilder.

This commit is contained in:
Mohammadreza Taikandi 2021-01-30 09:09:58 +00:00
parent 34df045693
commit 8ed331ed95
5 changed files with 167 additions and 185 deletions

View File

@ -27,75 +27,69 @@ namespace MapTo.Tests
const string ns = "Test"; const string ns = "Test";
options ??= new SourceGeneratorOptions(); options ??= new SourceGeneratorOptions();
var hasDifferentSourceNamespace = options.SourceClassNamespace != ns; var hasDifferentSourceNamespace = options.SourceClassNamespace != ns;
var builder = new StringBuilder(); var builder = new SourceBuilder();
builder.AppendLine("//"); builder.WriteLine("//");
builder.AppendLine("// Test source code."); builder.WriteLine("// Test source code.");
builder.AppendLine("//"); builder.WriteLine("//");
builder.AppendLine(); builder.WriteLine();
if (options.UseMapToNamespace) if (options.UseMapToNamespace)
{ {
builder.AppendFormat("using {0};", Constants.RootNamespace).AppendLine(); builder.WriteLine($"using {Constants.RootNamespace};");
} }
builder builder
.AppendFormat("using {0};", options.SourceClassNamespace) .WriteLine($"using {options.SourceClassNamespace};")
.AppendLine() .WriteLine()
.AppendLine(); .WriteLine();
builder builder
.AppendFormat("namespace {0}", ns) .WriteLine($"namespace {ns}")
.AppendOpeningBracket(); .WriteOpeningBracket();
if (hasDifferentSourceNamespace && options.UseMapToNamespace) if (hasDifferentSourceNamespace && options.UseMapToNamespace)
{ {
builder builder
.PadLeft(Indent1) .WriteLine($"using {options.SourceClassNamespace};")
.AppendFormat("using {0};", options.SourceClassNamespace) .WriteLine()
.AppendLine() .WriteLine();
.AppendLine();
} }
builder builder
.PadLeft(Indent1) .WriteLine(options.UseMapToNamespace ? "[MapFrom(typeof(Baz))]" : "[MapTo.MapFrom(typeof(Baz))]")
.AppendLine(options.UseMapToNamespace ? "[MapFrom(typeof(Baz))]" : "[MapTo.MapFrom(typeof(Baz))]") .WriteLine("public partial class Foo")
.PadLeft(Indent1).Append("public partial class Foo") .WriteOpeningBracket();
.AppendOpeningBracket(Indent1);
for (var i = 1; i <= options.ClassPropertiesCount; i++) for (var i = 1; i <= options.ClassPropertiesCount; i++)
{ {
builder builder.WriteLine(i % 2 == 0 ? $"public int Prop{i} {{ get; set; }}" : $"public int Prop{i} {{ get; }}");
.PadLeft(Indent2)
.AppendLine(i % 2 == 0 ? $"public int Prop{i} {{ get; set; }}" : $"public int Prop{i} {{ get; }}");
} }
options.PropertyBuilder?.Invoke(builder); options.PropertyBuilder?.Invoke(builder);
builder builder
.AppendClosingBracket(Indent1, false) .WriteClosingBracket()
.AppendClosingBracket() .WriteClosingBracket()
.AppendLine() .WriteLine()
.AppendLine(); .WriteLine();
builder builder
.AppendFormat("namespace {0}", options.SourceClassNamespace) .WriteLine($"namespace {options.SourceClassNamespace}")
.AppendOpeningBracket() .WriteOpeningBracket()
.PadLeft(Indent1).Append("public class Baz") .WriteLine("public class Baz")
.AppendOpeningBracket(Indent1); .WriteOpeningBracket();
for (var i = 1; i <= options.SourceClassPropertiesCount; i++) for (var i = 1; i <= options.SourceClassPropertiesCount; i++)
{ {
builder builder.WriteLine(i % 2 == 0 ? $"public int Prop{i} {{ get; set; }}" : $"public int Prop{i} {{ get; }}");
.PadLeft(Indent2)
.AppendLine(i % 2 == 0 ? $"public int Prop{i} {{ get; set; }}" : $"public int Prop{i} {{ get; }}");
} }
options.SourcePropertyBuilder?.Invoke(builder); options.SourcePropertyBuilder?.Invoke(builder);
builder builder
.AppendClosingBracket(Indent1, false) .WriteClosingBracket()
.AppendClosingBracket(); .WriteClosingBracket();
return builder.ToString(); return builder.ToString();
} }
@ -125,7 +119,7 @@ namespace MapTo.Tests
string SourceClassNamespace = "Test.Models", string SourceClassNamespace = "Test.Models",
int ClassPropertiesCount = 3, int ClassPropertiesCount = 3,
int SourceClassPropertiesCount = 3, int SourceClassPropertiesCount = 3,
Action<StringBuilder> PropertyBuilder = null, Action<SourceBuilder> PropertyBuilder = null,
Action<StringBuilder> SourcePropertyBuilder = null); Action<SourceBuilder> SourcePropertyBuilder = null);
} }
} }

View File

@ -44,10 +44,10 @@ namespace MapTo
PropertyBuilder: builder => PropertyBuilder: builder =>
{ {
builder builder
.PadLeft(Indent2).AppendLine("[IgnoreProperty]") .WriteLine("[IgnoreProperty]")
.PadLeft(Indent2).AppendLine("public int Prop4 { get; set; }"); .WriteLine("public int Prop4 { get; set; }");
}, },
SourcePropertyBuilder: builder => builder.PadLeft(Indent2).AppendLine("public int Prop4 { get; set; }"))); SourcePropertyBuilder: builder => builder.WriteLine("public int Prop4 { get; set; }")));
var expectedResult = @" var expectedResult = @"
partial class Foo partial class Foo

View File

@ -1,6 +1,5 @@
using System; using System;
using System.Linq; using System.Linq;
using MapTo.Extensions;
using MapTo.Sources; using MapTo.Sources;
using MapTo.Tests.Extensions; using MapTo.Tests.Extensions;
using MapTo.Tests.Infrastructure; using MapTo.Tests.Infrastructure;
@ -53,10 +52,10 @@ namespace MapTo
PropertyBuilder: builder => PropertyBuilder: builder =>
{ {
builder builder
.PadLeft(Indent2).AppendLine("[MapProperty(SourcePropertyName = nameof(Baz.Prop3))]") .WriteLine("[MapProperty(SourcePropertyName = nameof(Baz.Prop3))]")
.PadLeft(Indent2).AppendLine("public int Prop4 { get; set; }"); .WriteLine("public int Prop4 { get; set; }");
}, },
SourcePropertyBuilder: builder => builder.PadLeft(Indent2).AppendLine("public int Prop4 { get; set; }"))); SourcePropertyBuilder: builder => builder.WriteLine("public int Prop4 { get; set; }")));
var expectedResult = @" var expectedResult = @"
partial class Foo partial class Foo

View File

@ -1,6 +1,5 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using MapTo.Extensions;
using MapTo.Sources; using MapTo.Sources;
using MapTo.Tests.Extensions; using MapTo.Tests.Extensions;
using MapTo.Tests.Infrastructure; using MapTo.Tests.Infrastructure;
@ -50,12 +49,8 @@ namespace MapTo
// Arrange // Arrange
var source = GetSourceText(new SourceGeneratorOptions( var source = GetSourceText(new SourceGeneratorOptions(
true, true,
PropertyBuilder: builder => PropertyBuilder: builder => { builder.WriteLine("public string Prop4 { get; set; }"); },
{ SourcePropertyBuilder: builder => builder.WriteLine("public int Prop4 { get; set; }")));
builder
.PadLeft(Indent2).AppendLine("public string Prop4 { get; set; }");
},
SourcePropertyBuilder: builder => builder.PadLeft(Indent2).AppendLine("public int Prop4 { get; set; }")));
// Act // Act
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions); var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions);

View File

@ -1,69 +1,16 @@
using System.Linq; using System.Linq;
using MapTo.Extensions;
using MapTo.Sources; using MapTo.Sources;
using MapTo.Tests.Extensions; using MapTo.Tests.Extensions;
using MapTo.Tests.Infrastructure; using MapTo.Tests.Infrastructure;
using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis;
using Shouldly; using Shouldly;
using Xunit; using Xunit;
using static MapTo.Extensions.GeneratorExecutionContextExtensions;
using static MapTo.Tests.Common; using static MapTo.Tests.Common;
namespace MapTo.Tests namespace MapTo.Tests
{ {
public class MapTypeConverterTests public class MapTypeConverterTests
{ {
[Fact]
public void VerifyTypeConverterInterface()
{
// Arrange
const string source = "";
var expectedInterface = $@"
{Constants.GeneratedFilesHeader}
namespace MapTo
{{
public interface ITypeConverter<in TSource, out TDestination>
{{
TDestination Convert(TSource source, object[] converterParameters);
}}
}}
".Trim();
// Act
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions);
// Assert
diagnostics.ShouldBeSuccessful();
compilation.SyntaxTrees.ShouldContainSource(ITypeConverterSource.InterfaceName, expectedInterface);
}
[Fact]
public void VerifyTypeConverterInterfaceWithNullableOptionOn()
{
// Arrange
const string source = "";
var expectedInterface = $@"
{Constants.GeneratedFilesHeader}
#nullable enable
namespace MapTo
{{
public interface ITypeConverter<in TSource, out TDestination>
{{
TDestination Convert(TSource source, object[]? converterParameters);
}}
}}
".Trim();
// Act
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions, nullableContextOptions: NullableContextOptions.Enable);
// Assert
diagnostics.ShouldBeSuccessful();
compilation.SyntaxTrees.ShouldContainSource(ITypeConverterSource.InterfaceName, expectedInterface);
}
[Fact] [Fact]
public void VerifyMapTypeConverterAttribute() public void VerifyMapTypeConverterAttribute()
{ {
@ -99,7 +46,7 @@ namespace MapTo
diagnostics.ShouldBeSuccessful(); diagnostics.ShouldBeSuccessful();
compilation.SyntaxTrees.ShouldContainSource(MapTypeConverterAttributeSource.AttributeName, expectedInterface); compilation.SyntaxTrees.ShouldContainSource(MapTypeConverterAttributeSource.AttributeName, expectedInterface);
} }
[Fact] [Fact]
public void VerifyMapTypeConverterAttributeWithNullableOptionOn() public void VerifyMapTypeConverterAttributeWithNullableOptionOn()
{ {
@ -138,7 +85,58 @@ namespace MapTo
} }
[Fact] [Fact]
public void When_FoundMatchingPropertyNameWithDifferentImplicitlyConvertibleType_Should_GenerateTheProperty() public void VerifyTypeConverterInterface()
{
// Arrange
const string source = "";
var expectedInterface = $@"
{Constants.GeneratedFilesHeader}
namespace MapTo
{{
public interface ITypeConverter<in TSource, out TDestination>
{{
TDestination Convert(TSource source, object[] converterParameters);
}}
}}
".Trim();
// Act
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions);
// Assert
diagnostics.ShouldBeSuccessful();
compilation.SyntaxTrees.ShouldContainSource(ITypeConverterSource.InterfaceName, expectedInterface);
}
[Fact]
public void VerifyTypeConverterInterfaceWithNullableOptionOn()
{
// Arrange
const string source = "";
var expectedInterface = $@"
{Constants.GeneratedFilesHeader}
#nullable enable
namespace MapTo
{{
public interface ITypeConverter<in TSource, out TDestination>
{{
TDestination Convert(TSource source, object[]? converterParameters);
}}
}}
".Trim();
// Act
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions, nullableContextOptions: NullableContextOptions.Enable);
// Assert
diagnostics.ShouldBeSuccessful();
compilation.SyntaxTrees.ShouldContainSource(ITypeConverterSource.InterfaceName, expectedInterface);
}
[Fact]
public void When_FoundMatchingPropertyNameWithConverterType_ShouldUseTheConverterAndItsParametersToAssignProperties()
{ {
// Arrange // Arrange
var source = GetSourceText(new SourceGeneratorOptions( var source = GetSourceText(new SourceGeneratorOptions(
@ -146,9 +144,77 @@ namespace MapTo
PropertyBuilder: builder => PropertyBuilder: builder =>
{ {
builder builder
.PadLeft(Indent2).AppendLine("public long Prop4 { get; set; }"); .WriteLine("[MapTypeConverter(typeof(Prop4Converter), new object[]{\"G\", 'C', 10})]")
.WriteLine("public string Prop4 { get; set; }");
}, },
SourcePropertyBuilder: builder => builder.PadLeft(Indent2).AppendLine("public int Prop4 { get; set; }"))); SourcePropertyBuilder: builder => builder.WriteLine("public long Prop4 { get; set; }")));
source += @"
namespace Test
{
using MapTo;
public class Prop4Converter: ITypeConverter<long, string>
{
public string Convert(long source, object[] converterParameters) => source.ToString(converterParameters[0] as string);
}
}
";
const string expectedSyntax = "Prop4 = new Test.Prop4Converter().Convert(baz.Prop4, new object[] { \"G\", 'C', 10 });";
// Act
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions);
// Assert
diagnostics.ShouldBeSuccessful();
compilation.SyntaxTrees.Last().ToString().ShouldContain(expectedSyntax);
}
[Fact]
public void When_FoundMatchingPropertyNameWithConverterType_ShouldUseTheConverterToAssignProperties()
{
// Arrange
var source = GetSourceText(new SourceGeneratorOptions(
true,
PropertyBuilder: builder =>
{
builder
.WriteLine("[MapTypeConverter(typeof(Prop4Converter))]")
.WriteLine("public long Prop4 { get; set; }");
},
SourcePropertyBuilder: builder => builder.WriteLine("public string Prop4 { get; set; }")));
source += @"
namespace Test
{
using MapTo;
public class Prop4Converter: ITypeConverter<string, long>
{
public long Convert(string source, object[] converterParameters) => long.Parse(source);
}
}
";
const string expectedSyntax = "Prop4 = new Test.Prop4Converter().Convert(baz.Prop4, null);";
// Act
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions);
// Assert
diagnostics.ShouldBeSuccessful();
compilation.SyntaxTrees.Last().ToString().ShouldContain(expectedSyntax);
}
[Fact]
public void When_FoundMatchingPropertyNameWithDifferentImplicitlyConvertibleType_Should_GenerateTheProperty()
{
// Arrange
var source = GetSourceText(new SourceGeneratorOptions(
true,
PropertyBuilder: builder => { builder.WriteLine("public long Prop4 { get; set; }"); },
SourcePropertyBuilder: builder => builder.WriteLine("public int Prop4 { get; set; }")));
var expectedResult = @" var expectedResult = @"
partial class Foo partial class Foo
@ -181,12 +247,12 @@ namespace MapTo
PropertyBuilder: builder => PropertyBuilder: builder =>
{ {
builder builder
.PadLeft(Indent2).AppendLine("[IgnoreProperty]") .WriteLine("[IgnoreProperty]")
.PadLeft(Indent2).AppendLine("public long IgnoreMe { get; set; }") .WriteLine("public long IgnoreMe { get; set; }")
.PadLeft(Indent2).AppendLine("[MapTypeConverter(typeof(Prop4Converter))]") .WriteLine("[MapTypeConverter(typeof(Prop4Converter))]")
.PadLeft(Indent2).AppendLine("public long Prop4 { get; set; }"); .WriteLine("public long Prop4 { get; set; }");
}, },
SourcePropertyBuilder: builder => builder.PadLeft(Indent2).AppendLine("public string Prop4 { get; set; }"))); SourcePropertyBuilder: builder => builder.WriteLine("public string Prop4 { get; set; }")));
source += @" source += @"
namespace Test namespace Test
@ -207,77 +273,5 @@ namespace Test
var expectedError = DiagnosticProvider.InvalidTypeConverterGenericTypesError(GetSourcePropertySymbol("Prop4", compilation), GetSourcePropertySymbol("Prop4", compilation, "Baz")); var expectedError = DiagnosticProvider.InvalidTypeConverterGenericTypesError(GetSourcePropertySymbol("Prop4", compilation), GetSourcePropertySymbol("Prop4", compilation, "Baz"));
diagnostics.ShouldBeUnsuccessful(expectedError); diagnostics.ShouldBeUnsuccessful(expectedError);
} }
[Fact]
public void When_FoundMatchingPropertyNameWithConverterType_ShouldUseTheConverterToAssignProperties()
{
// Arrange
var source = GetSourceText(new SourceGeneratorOptions(
true,
PropertyBuilder: builder =>
{
builder
.PadLeft(Indent2).AppendLine("[MapTypeConverter(typeof(Prop4Converter))]")
.PadLeft(Indent2).AppendLine("public long Prop4 { get; set; }");
},
SourcePropertyBuilder: builder => builder.PadLeft(Indent2).AppendLine("public string Prop4 { get; set; }")));
source += @"
namespace Test
{
using MapTo;
public class Prop4Converter: ITypeConverter<string, long>
{
public long Convert(string source, object[] converterParameters) => long.Parse(source);
}
}
";
const string expectedSyntax = "Prop4 = new Test.Prop4Converter().Convert(baz.Prop4, null);";
// Act
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions);
// Assert
diagnostics.ShouldBeSuccessful();
compilation.SyntaxTrees.Last().ToString().ShouldContain(expectedSyntax);
}
[Fact]
public void When_FoundMatchingPropertyNameWithConverterType_ShouldUseTheConverterAndItsParametersToAssignProperties()
{
// Arrange
var source = GetSourceText(new SourceGeneratorOptions(
true,
PropertyBuilder: builder =>
{
builder
.PadLeft(Indent2).AppendLine("[MapTypeConverter(typeof(Prop4Converter), new object[]{\"G\", 'C', 10})]")
.PadLeft(Indent2).AppendLine("public string Prop4 { get; set; }");
},
SourcePropertyBuilder: builder => builder.PadLeft(Indent2).AppendLine("public long Prop4 { get; set; }")));
source += @"
namespace Test
{
using MapTo;
public class Prop4Converter: ITypeConverter<long, string>
{
public string Convert(long source, object[] converterParameters) => source.ToString(converterParameters[0] as string);
}
}
";
const string expectedSyntax = "Prop4 = new Test.Prop4Converter().Convert(baz.Prop4, new object[] { \"G\", 'C', 10 });";
// Act
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions);
// Assert
diagnostics.ShouldBeSuccessful();
compilation.SyntaxTrees.Last().ToString().ShouldContain(expectedSyntax);
}
} }
} }