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

View File

@ -44,10 +44,10 @@ namespace MapTo
PropertyBuilder: builder =>
{
builder
.PadLeft(Indent2).AppendLine("[IgnoreProperty]")
.PadLeft(Indent2).AppendLine("public int Prop4 { get; set; }");
.WriteLine("[IgnoreProperty]")
.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 = @"
partial class Foo

View File

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

View File

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

View File

@ -1,69 +1,16 @@
using System.Linq;
using MapTo.Extensions;
using MapTo.Sources;
using MapTo.Tests.Extensions;
using MapTo.Tests.Infrastructure;
using Microsoft.CodeAnalysis;
using Shouldly;
using Xunit;
using static MapTo.Extensions.GeneratorExecutionContextExtensions;
using static MapTo.Tests.Common;
namespace MapTo.Tests
{
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]
public void VerifyMapTypeConverterAttribute()
{
@ -99,7 +46,7 @@ namespace MapTo
diagnostics.ShouldBeSuccessful();
compilation.SyntaxTrees.ShouldContainSource(MapTypeConverterAttributeSource.AttributeName, expectedInterface);
}
[Fact]
public void VerifyMapTypeConverterAttributeWithNullableOptionOn()
{
@ -138,7 +85,58 @@ namespace MapTo
}
[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
var source = GetSourceText(new SourceGeneratorOptions(
@ -146,9 +144,77 @@ namespace MapTo
PropertyBuilder: 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 = @"
partial class Foo
@ -181,12 +247,12 @@ namespace MapTo
PropertyBuilder: builder =>
{
builder
.PadLeft(Indent2).AppendLine("[IgnoreProperty]")
.PadLeft(Indent2).AppendLine("public long IgnoreMe { get; set; }")
.PadLeft(Indent2).AppendLine("[MapTypeConverter(typeof(Prop4Converter))]")
.PadLeft(Indent2).AppendLine("public long Prop4 { get; set; }");
.WriteLine("[IgnoreProperty]")
.WriteLine("public long IgnoreMe { get; set; }")
.WriteLine("[MapTypeConverter(typeof(Prop4Converter))]")
.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 += @"
namespace Test
@ -207,77 +273,5 @@ namespace Test
var expectedError = DiagnosticProvider.InvalidTypeConverterGenericTypesError(GetSourcePropertySymbol("Prop4", compilation), GetSourcePropertySymbol("Prop4", compilation, "Baz"));
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);
}
}
}