From 8a3bbc095e017c936fbd586ac6ac1e78334fefab Mon Sep 17 00:00:00 2001 From: Mohammadreza Taikandi Date: Mon, 4 Jan 2021 10:42:18 +0000 Subject: [PATCH] Put source builders in different files and use IndentedTextWriter instead of StringBuilder. --- src/MapTo/Diagnostics.cs | 5 +- src/MapTo/MapToGenerator.cs | 8 +- src/MapTo/MapToSyntaxReceiver.cs | 7 +- src/MapTo/Models/MapModel.cs | 5 +- src/MapTo/SourceBuilder.cs | 225 ------------------ src/MapTo/Sources/Constants.cs | 8 + .../Sources/IgnorePropertyAttributeSource.cs | 35 +++ src/MapTo/Sources/MapClassSource.cs | 126 ++++++++++ src/MapTo/Sources/MapFromAttributeSource.cs | 63 +++++ src/MapTo/Sources/SourceBuilder.cs | 56 +++++ test/MapTo.Tests/Tests.cs | 16 +- 11 files changed, 313 insertions(+), 241 deletions(-) delete mode 100644 src/MapTo/SourceBuilder.cs create mode 100644 src/MapTo/Sources/Constants.cs create mode 100644 src/MapTo/Sources/IgnorePropertyAttributeSource.cs create mode 100644 src/MapTo/Sources/MapClassSource.cs create mode 100644 src/MapTo/Sources/MapFromAttributeSource.cs create mode 100644 src/MapTo/Sources/SourceBuilder.cs diff --git a/src/MapTo/Diagnostics.cs b/src/MapTo/Diagnostics.cs index 41be782..1f602c5 100644 --- a/src/MapTo/Diagnostics.cs +++ b/src/MapTo/Diagnostics.cs @@ -1,4 +1,5 @@ -using Microsoft.CodeAnalysis; +using MapTo.Sources; +using Microsoft.CodeAnalysis; namespace MapTo { @@ -13,7 +14,7 @@ namespace MapTo Create($"{ErrorId}001", "Symbol not found.", $"Unable to find any symbols for {syntaxName}", location); internal static Diagnostic MapFromAttributeNotFoundError(Location location) => - Create($"{ErrorId}002", "Attribute Not Available", $"Unable to find {SourceBuilder.MapFromAttributeName} type.", location); + Create($"{ErrorId}002", "Attribute Not Available", $"Unable to find {MapFromAttributeSource.AttributeName} type.", location); internal static Diagnostic NoMatchingPropertyFoundError(Location location, string className, string sourceTypeName) => Create($"{ErrorId}003", "Property Not Found", $"No matching properties found between '{className}' and '{sourceTypeName}' types.", location); diff --git a/src/MapTo/MapToGenerator.cs b/src/MapTo/MapToGenerator.cs index e2ce4bc..0e34dcf 100644 --- a/src/MapTo/MapToGenerator.cs +++ b/src/MapTo/MapToGenerator.cs @@ -1,8 +1,8 @@ using System.Collections.Generic; -using System.Collections.Immutable; using System.Linq; using MapTo.Extensions; using MapTo.Models; +using MapTo.Sources; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -22,8 +22,8 @@ namespace MapTo { var options = SourceGenerationOptions.From(context); - AddAttribute(context, SourceBuilder.GenerateMapFromAttribute(options)); - AddAttribute(context, SourceBuilder.GenerateIgnorePropertyAttribute(options)); + AddAttribute(context, MapFromAttributeSource.Generate(options)); + AddAttribute(context, IgnorePropertyAttributeSource.Generate(options)); if (context.SyntaxReceiver is MapToSyntaxReceiver receiver && receiver.CandidateClasses.Any()) { @@ -48,7 +48,7 @@ namespace MapTo continue; } - var (source, hintName) = SourceBuilder.GenerateSource(model); + var (source, hintName) = MapClassSource.Generate(model); context.AddSource(hintName, source); } } diff --git a/src/MapTo/MapToSyntaxReceiver.cs b/src/MapTo/MapToSyntaxReceiver.cs index a59fb65..cb2312c 100644 --- a/src/MapTo/MapToSyntaxReceiver.cs +++ b/src/MapTo/MapToSyntaxReceiver.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Linq; +using MapTo.Sources; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -20,12 +21,12 @@ namespace MapTo var attributeSyntax = attributes .SelectMany(a => a.Attributes) .SingleOrDefault(a => a.Name is - IdentifierNameSyntax { Identifier: { ValueText: SourceBuilder.MapFromAttributeName } } // For: [MapFrom] + IdentifierNameSyntax { Identifier: { ValueText: MapFromAttributeSource.AttributeName } } // For: [MapFrom] or QualifiedNameSyntax // For: [MapTo.MapFrom] { - Left: IdentifierNameSyntax { Identifier: { ValueText: SourceBuilder.NamespaceName } }, - Right: IdentifierNameSyntax { Identifier: { ValueText: SourceBuilder.MapFromAttributeName } } + Left: IdentifierNameSyntax { Identifier: { ValueText: Constants.RootNamespace } }, + Right: IdentifierNameSyntax { Identifier: { ValueText: MapFromAttributeSource.AttributeName } } } ); diff --git a/src/MapTo/Models/MapModel.cs b/src/MapTo/Models/MapModel.cs index 361db26..3520518 100644 --- a/src/MapTo/Models/MapModel.cs +++ b/src/MapTo/Models/MapModel.cs @@ -2,6 +2,7 @@ using System.Collections.Immutable; using System.Linq; using MapTo.Extensions; +using MapTo.Sources; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -65,7 +66,7 @@ namespace MapTo.Models private static INamedTypeSymbol? GetSourceTypeSymbol(ClassDeclarationSyntax classSyntax, SemanticModel model) { var sourceTypeExpressionSyntax = classSyntax - .GetAttribute(SourceBuilder.MapFromAttributeName) + .GetAttribute(MapFromAttributeSource.AttributeName) ?.DescendantNodes() .OfType() .SingleOrDefault(); @@ -80,7 +81,7 @@ namespace MapTo.Models .Select(p => (p.Name, p.Type.ToString())) .Intersect(classSymbol .GetAllMembersOfType() - .Where(p => p.GetAttributes().All(a => a.AttributeClass?.Name != SourceBuilder.IgnorePropertyAttributeName)) + .Where(p => p.GetAttributes().All(a => a.AttributeClass?.Name != IgnorePropertyAttributeSource.AttributeName)) .Select(p => (p.Name, p.Type.ToString()))) .Select(p => p.Name) .ToImmutableArray(); diff --git a/src/MapTo/SourceBuilder.cs b/src/MapTo/SourceBuilder.cs deleted file mode 100644 index 647e581..0000000 --- a/src/MapTo/SourceBuilder.cs +++ /dev/null @@ -1,225 +0,0 @@ -using System.Text; -using MapTo.Extensions; -using MapTo.Models; - -namespace MapTo -{ - internal static class SourceBuilder - { - internal const string NamespaceName = "MapTo"; - internal const string MapFromAttributeName = "MapFrom"; - internal const string IgnorePropertyAttributeName = "IgnoreProperty"; - internal const string GeneratedFilesHeader = "// "; - - private const int Indent1 = 4; - private const int Indent2 = Indent1 * 2; - private const int Indent3 = Indent1 * 3; - - internal static (string source, string hintName) GenerateMapFromAttribute(SourceGenerationOptions options) - { - var builder = new StringBuilder(); - builder - .AppendFileHeader() - .AppendLine("using System;") - .AppendLine() - .AppendFormat("namespace {0}", NamespaceName) - .AppendOpeningBracket(); - - if (options.GenerateXmlDocument) - { - builder - .PadLeft(Indent1).AppendLine("/// ") - .PadLeft(Indent1).AppendLine("/// Specifies that the annotated class can be mapped from the provided .") - .PadLeft(Indent1).AppendLine("/// "); - } - - builder - .PadLeft(Indent1).AppendLine("[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]") - .PadLeft(Indent1).AppendFormat("public sealed class {0}Attribute : Attribute", MapFromAttributeName) - .AppendOpeningBracket(Indent1); - - if (options.GenerateXmlDocument) - { - builder - .PadLeft(Indent2).AppendLine("/// ") - .PadLeft(Indent2).AppendFormat("/// Initializes a new instance of the class with the specified .", MapFromAttributeName).AppendLine() - .PadLeft(Indent2).AppendLine("/// "); - } - - builder - .PadLeft(Indent2).AppendFormat("public {0}Attribute(Type sourceType)", MapFromAttributeName) - .AppendOpeningBracket(Indent2) - .PadLeft(Indent3).AppendLine("SourceType = sourceType;") - .AppendClosingBracket(Indent2, false) - .AppendLine() - .AppendLine(); - - if (options.GenerateXmlDocument) - { - builder - .PadLeft(Indent2).AppendLine("/// ") - .PadLeft(Indent2).AppendLine("/// Gets the type of the class that the annotated class should be able to map from.") - .PadLeft(Indent2).AppendLine("/// "); - } - - builder - .PadLeft(Indent2).AppendLine("public Type SourceType { get; }") - .AppendClosingBracket(Indent1, false) - .AppendClosingBracket(); - - return (builder.ToString(), $"{MapFromAttributeName}Attribute.g.cs"); - } - - internal static (string source, string hintName) GenerateIgnorePropertyAttribute(SourceGenerationOptions options) - { - var builder = new StringBuilder(); - builder - .AppendFileHeader() - .AppendLine("using System;") - .AppendLine() - .AppendFormat("namespace {0}", NamespaceName) - .AppendOpeningBracket(); - - if (options.GenerateXmlDocument) - { - builder - .PadLeft(Indent1).AppendLine("/// ") - .PadLeft(Indent1).AppendLine("/// Specified that the annotated property should not be included in the generated mappings.") - .PadLeft(Indent1).AppendLine("/// "); - } - - builder - .PadLeft(Indent1).AppendLine("[AttributeUsage(AttributeTargets.Property, Inherited = false, AllowMultiple = false)]") - .PadLeft(Indent1).AppendFormat("public sealed class {0}Attribute : Attribute {{ }}", IgnorePropertyAttributeName) - .AppendClosingBracket(); - - return (builder.ToString(), $"{IgnorePropertyAttributeName}Attribute.g.cs"); - } - - internal static (string source, string hintName) GenerateSource(MapModel model) - { - var builder = new StringBuilder(); - - builder - .AppendFileHeader() - .GenerateUsings(model) - - // Namespace declaration - .AppendFormat("namespace {0}", model.Namespace) - .AppendOpeningBracket() - - // Class declaration - .PadLeft(Indent1) - .AppendFormat("partial class {0}", model.ClassName) - .AppendOpeningBracket(Indent1) - - // Class body - .GenerateConstructor(model) - .AppendLine() - .GenerateFactoryMethod(model) - - // End class declaration - .AppendClosingBracket(Indent1) - - // Extensions Class declaration - .AppendLine() - .AppendLine() - .PadLeft(Indent1) - .AppendFormat("{0} static partial class {1}To{2}Extensions", model.Options.GeneratedMethodsAccessModifier.ToLowercaseString(), model.SourceClassName, model.ClassName) - .AppendOpeningBracket(Indent1) - - // Extension class body - .GenerateSourceTypeExtensionMethod(model) - - // End extensions class declaration - .AppendClosingBracket(Indent1) - - // End namespace declaration - .AppendClosingBracket(); - - return (builder.ToString(), $"{model.ClassName}.g.cs"); - } - - private static StringBuilder GenerateUsings(this StringBuilder builder, MapModel model) - { - builder.AppendLine("using System;"); - return builder.AppendLine(); - } - - private static StringBuilder GenerateConstructor(this StringBuilder builder, MapModel model) - { - var sourceClassParameterName = model.SourceClassName.ToCamelCase(); - - if (model.Options.GenerateXmlDocument) - { - builder - .PadLeft(Indent2).AppendLine("/// ") - .PadLeft(Indent2).AppendFormat("/// Initializes a new instance of the class", model.ClassName).AppendLine() - .PadLeft(Indent2).AppendFormat("/// using the property values from the specified .", sourceClassParameterName).AppendLine() - .PadLeft(Indent2).AppendLine("/// ") - .PadLeft(Indent2).AppendFormat("/// {0} is null", sourceClassParameterName).AppendLine(); - } - - builder - .PadLeft(Indent2).AppendFormat("{0} {1}({2} {3})", model.Options.ConstructorAccessModifier.ToLowercaseString(), model.ClassName, model.SourceClassFullName, sourceClassParameterName) - .AppendOpeningBracket(Indent2) - .PadLeft(Indent3).AppendFormat("if ({0} == null) throw new ArgumentNullException(nameof({0}));", sourceClassParameterName).AppendLine() - .AppendLine(); - - foreach (var property in model.MappedProperties) - { - builder - .PadLeft(Indent3) - .AppendFormat("{0} = {1}.{2};", property, sourceClassParameterName, property) - .AppendLine(); - } - - // End constructor declaration - return builder.AppendClosingBracket(Indent2, false); - } - - private static StringBuilder GenerateFactoryMethod(this StringBuilder builder, MapModel model) - { - var sourceClassParameterName = model.SourceClassName.ToCamelCase(); - - return builder - .AppendLine() - .AppendConvertorMethodsXmlDocs(model, sourceClassParameterName) - .PadLeft(Indent2).AppendFormat("{0} static {1} From({2} {3})", model.Options.GeneratedMethodsAccessModifier.ToLowercaseString(), model.ClassName, model.SourceClassFullName, sourceClassParameterName) - .AppendOpeningBracket(Indent2) - .PadLeft(Indent3).AppendFormat("return {0} == null ? null : new {1}({0});", sourceClassParameterName, model.ClassName) - .AppendClosingBracket(Indent2); - } - - private static StringBuilder GenerateSourceTypeExtensionMethod(this StringBuilder builder, MapModel model) - { - var sourceClassParameterName = model.SourceClassName.ToCamelCase(); - - return builder - .AppendConvertorMethodsXmlDocs(model, sourceClassParameterName) - .PadLeft(Indent2).AppendFormat("{0} static {1} To{1}(this {2} {3})", model.Options.GeneratedMethodsAccessModifier.ToLowercaseString(), model.ClassName, model.SourceClassFullName, sourceClassParameterName) - .AppendOpeningBracket(Indent2) - .PadLeft(Indent3).AppendFormat("return {0} == null ? null : new {1}({0});", sourceClassParameterName, model.ClassName) - .AppendClosingBracket(Indent2); - } - - private static StringBuilder AppendFileHeader(this StringBuilder builder) => - builder.AppendLine(GeneratedFilesHeader); - - private static StringBuilder AppendConvertorMethodsXmlDocs(this StringBuilder builder, MapModel model, string sourceClassParameterName) - { - if (!model.Options.GenerateXmlDocument) - { - return builder; - } - - return builder - .PadLeft(Indent2).AppendLine("/// ") - .PadLeft(Indent2).AppendFormat("/// Creates a new instance of and sets its participating properties", model.ClassName).AppendLine() - .PadLeft(Indent2).AppendFormat("/// using the property values from .", sourceClassParameterName).AppendLine() - .PadLeft(Indent2).AppendLine("/// ") - .PadLeft(Indent2).AppendFormat("/// Instance of to use as source.", sourceClassParameterName, model.SourceClassName).AppendLine() - .PadLeft(Indent2).AppendFormat("/// A new instance of -or- null if is null.", model.ClassName, sourceClassParameterName).AppendLine(); - } - } -} \ No newline at end of file diff --git a/src/MapTo/Sources/Constants.cs b/src/MapTo/Sources/Constants.cs new file mode 100644 index 0000000..f25d2ce --- /dev/null +++ b/src/MapTo/Sources/Constants.cs @@ -0,0 +1,8 @@ +namespace MapTo.Sources +{ + internal class Constants + { + internal const string RootNamespace = "MapTo"; + internal const string GeneratedFilesHeader = "// "; + } +} \ No newline at end of file diff --git a/src/MapTo/Sources/IgnorePropertyAttributeSource.cs b/src/MapTo/Sources/IgnorePropertyAttributeSource.cs new file mode 100644 index 0000000..fb2a779 --- /dev/null +++ b/src/MapTo/Sources/IgnorePropertyAttributeSource.cs @@ -0,0 +1,35 @@ +using MapTo.Models; +using static MapTo.Sources.Constants; + +namespace MapTo.Sources +{ + internal static class IgnorePropertyAttributeSource + { + internal const string AttributeName = "IgnoreProperty"; + + internal static (string source, string hintName) Generate(SourceGenerationOptions options) + { + var builder = new SourceBuilder() + .WriteLine(GeneratedFilesHeader) + .WriteLine("using System;") + .WriteLine() + .WriteLine($"namespace {RootNamespace}") + .WriteOpeningBracket(); + + if (options.GenerateXmlDocument) + { + builder + .WriteLine("/// ") + .WriteLine("/// Specified that the annotated property should not be included in the generated mappings.") + .WriteLine("/// "); + } + + builder + .WriteLine("[AttributeUsage(AttributeTargets.Property, Inherited = false, AllowMultiple = false)]") + .WriteLine($"public sealed class {AttributeName}Attribute : Attribute {{ }}") + .WriteClosingBracket(); + + return (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 new file mode 100644 index 0000000..c7dcd7a --- /dev/null +++ b/src/MapTo/Sources/MapClassSource.cs @@ -0,0 +1,126 @@ +using MapTo.Extensions; +using MapTo.Models; +using static MapTo.Sources.Constants; + +namespace MapTo.Sources +{ + internal static class MapClassSource + { + internal static (string source, string hintName) Generate(MapModel model) + { + using var builder = new SourceBuilder() + .WriteLine(GeneratedFilesHeader) + .WriteUsings(model) + .WriteLine() + + // Namespace declaration + .WriteLine($"namespace {model.Namespace}") + .WriteOpeningBracket() + + // Class declaration + .WriteLine($"partial class {model.ClassName}") + .WriteOpeningBracket() + + // Class body + .GenerateConstructor(model) + .WriteLine() + .GenerateFactoryMethod(model) + + // End class declaration + .WriteClosingBracket() + .WriteLine() + + // Extension class declaration + .GenerateSourceTypeExtensionClass(model) + + // End namespace declaration + .WriteClosingBracket(); + + return (builder.ToString(), $"{model.ClassName}.g.cs"); + } + + private static SourceBuilder WriteUsings(this SourceBuilder builder, MapModel model) + { + return builder + .WriteLine("using System;"); + } + + private static SourceBuilder GenerateConstructor(this SourceBuilder builder, MapModel model) + { + var sourceClassParameterName = model.SourceClassName.ToCamelCase(); + + if (model.Options.GenerateXmlDocument) + { + builder + .WriteLine("/// ") + .WriteLine($"/// Initializes a new instance of the class") + .WriteLine($"/// using the property values from the specified .") + .WriteLine("/// ") + .WriteLine($"/// {sourceClassParameterName} is null"); + } + + builder + .WriteLine($"{model.Options.ConstructorAccessModifier.ToLowercaseString()} {model.ClassName}({model.SourceClassFullName} {sourceClassParameterName})") + .WriteOpeningBracket() + .WriteLine($"if ({sourceClassParameterName} == null) throw new ArgumentNullException(nameof({sourceClassParameterName}));") + .WriteLine(); + + foreach (var property in model.MappedProperties) + { + builder.WriteLine($"{property} = {sourceClassParameterName}.{property};"); + } + + // End constructor declaration + return builder.WriteClosingBracket(); + } + + private static SourceBuilder GenerateFactoryMethod(this SourceBuilder builder, MapModel model) + { + var sourceClassParameterName = model.SourceClassName.ToCamelCase(); + + return builder + .GenerateConvertorMethodsXmlDocs(model, sourceClassParameterName) + .WriteLine($"{model.Options.GeneratedMethodsAccessModifier.ToLowercaseString()} static {model.ClassName} From({model.SourceClassFullName} {sourceClassParameterName})") + .WriteOpeningBracket() + .WriteLine($"return {sourceClassParameterName} == null ? null : new {model.ClassName}({sourceClassParameterName});") + .WriteClosingBracket(); + } + + private static SourceBuilder GenerateConvertorMethodsXmlDocs(this SourceBuilder builder, MapModel model, string sourceClassParameterName) + { + if (!model.Options.GenerateXmlDocument) + { + return builder; + } + + return builder + .WriteLine("/// ") + .WriteLine($"/// Creates a new instance of and sets its participating properties") + .WriteLine($"/// using the property values from .") + .WriteLine("/// ") + .WriteLine($"/// Instance of to use as source.") + .WriteLine($"/// A new instance of -or- null if is null."); + } + + private static SourceBuilder GenerateSourceTypeExtensionClass(this SourceBuilder builder, MapModel model) + { + return builder + .WriteLine($"{model.Options.GeneratedMethodsAccessModifier.ToLowercaseString()} static partial class {model.SourceClassName}To{model.ClassName}Extensions") + .WriteOpeningBracket() + .GenerateSourceTypeExtensionMethod(model) + .WriteClosingBracket(); + } + + private static SourceBuilder GenerateSourceTypeExtensionMethod(this SourceBuilder builder, MapModel model) + { + var sourceClassParameterName = model.SourceClassName.ToCamelCase(); + + return builder + .GenerateConvertorMethodsXmlDocs(model, sourceClassParameterName) + .WriteLine($"{model.Options.GeneratedMethodsAccessModifier.ToLowercaseString()} static {model.ClassName} To{model.ClassName}(this {model.SourceClassFullName} {sourceClassParameterName})") + .WriteOpeningBracket() + .WriteLine($"return {sourceClassParameterName} == null ? null : new {model.ClassName}({sourceClassParameterName});") + .WriteClosingBracket(); + } + } +} \ No newline at end of file diff --git a/src/MapTo/Sources/MapFromAttributeSource.cs b/src/MapTo/Sources/MapFromAttributeSource.cs new file mode 100644 index 0000000..cc5de3e --- /dev/null +++ b/src/MapTo/Sources/MapFromAttributeSource.cs @@ -0,0 +1,63 @@ +using MapTo.Models; +using static MapTo.Sources.Constants; + +namespace MapTo.Sources +{ + internal static class MapFromAttributeSource + { + internal const string AttributeName = "MapFrom"; + + internal static (string source, string hintName) Generate(SourceGenerationOptions options) + { + using var builder = new SourceBuilder() + .WriteLine(GeneratedFilesHeader) + .WriteLine("using System;") + .WriteLine() + .WriteLine($"namespace {RootNamespace}") + .WriteOpeningBracket(); + + if (options.GenerateXmlDocument) + { + builder + .WriteLine("/// ") + .WriteLine("/// Specifies that the annotated class can be mapped from the provided .") + .WriteLine("/// "); + } + + builder + .WriteLine("[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]") + .WriteLine($"public sealed class {AttributeName}Attribute : Attribute") + .WriteOpeningBracket(); + + if (options.GenerateXmlDocument) + { + builder + .WriteLine("/// ") + .WriteLine($"/// Initializes a new instance of the class with the specified .") + .WriteLine("/// "); + } + + builder + .WriteLine($"public {AttributeName}Attribute(Type sourceType)") + .WriteOpeningBracket() + .WriteLine("SourceType = sourceType;") + .WriteClosingBracket() + .WriteLine(); + + if (options.GenerateXmlDocument) + { + builder + .WriteLine("/// ") + .WriteLine("/// Gets the type of the class that the annotated class should be able to map from.") + .WriteLine("/// "); + } + + builder + .WriteLine("public Type SourceType { get; }") + .WriteClosingBracket() // class + .WriteClosingBracket(); // namespace + + return (builder.ToString(), $"{AttributeName}Attribute.g.cs"); + } + } +} \ No newline at end of file diff --git a/src/MapTo/Sources/SourceBuilder.cs b/src/MapTo/Sources/SourceBuilder.cs new file mode 100644 index 0000000..2777479 --- /dev/null +++ b/src/MapTo/Sources/SourceBuilder.cs @@ -0,0 +1,56 @@ +using System; +using System.CodeDom.Compiler; +using System.IO; + +namespace MapTo.Sources +{ + public sealed class SourceBuilder : IDisposable + { + private readonly StringWriter _writer; + private readonly IndentedTextWriter _indentedWriter; + + public SourceBuilder() + { + _writer = new StringWriter(); + _indentedWriter = new IndentedTextWriter(_writer, new string(' ', 4)); + } + + /// + public void Dispose() + { + _writer.Dispose(); + _indentedWriter.Dispose(); + } + + public SourceBuilder WriteLine(string value) + { + _indentedWriter.WriteLine(value); + return this; + } + + public SourceBuilder WriteLine() + { + _indentedWriter.WriteLineNoTabs(string.Empty); + return this; + } + + public SourceBuilder WriteOpeningBracket() + { + _indentedWriter.WriteLine("{"); + _indentedWriter.Indent++; + + return this; + } + + public SourceBuilder WriteClosingBracket() + { + _indentedWriter.Indent--; + _indentedWriter.WriteLine("}"); + + return this; + } + + /// + public override string ToString() => _writer.ToString(); + } +} \ No newline at end of file diff --git a/test/MapTo.Tests/Tests.cs b/test/MapTo.Tests/Tests.cs index 004b1bc..386beb8 100644 --- a/test/MapTo.Tests/Tests.cs +++ b/test/MapTo.Tests/Tests.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Text; using MapTo.Extensions; using MapTo.Models; +using MapTo.Sources; using MapTo.Tests.Infrastructure; using Microsoft.CodeAnalysis; using Shouldly; @@ -23,7 +24,7 @@ namespace MapTo.Tests [GetBuildPropertyName(nameof(SourceGenerationOptions.GenerateXmlDocument))] = "false" }; - private static readonly string ExpectedAttribute = $@"{SourceBuilder.GeneratedFilesHeader} + private static readonly string ExpectedAttribute = $@"{Constants.GeneratedFilesHeader} using System; namespace MapTo @@ -57,7 +58,7 @@ namespace MapTo if (options.UseMapToNamespace) { - builder.AppendFormat("using {0};", SourceBuilder.NamespaceName).AppendLine(); + builder.AppendFormat("using {0};", Constants.RootNamespace).AppendLine(); } builder @@ -127,7 +128,7 @@ namespace MapTo // Arrange const string source = ""; var expectedAttribute = $@" -{SourceBuilder.GeneratedFilesHeader} +{Constants.GeneratedFilesHeader} using System; namespace MapTo @@ -142,7 +143,9 @@ namespace MapTo // Assert diagnostics.ShouldBeSuccessful(); - compilation.SyntaxTrees.ShouldContain(c => c.ToString() == expectedAttribute); + var attributeSyntax = compilation.SyntaxTrees.Select(s => s.ToString().Trim()).SingleOrDefault(s => s.Contains(IgnorePropertyAttributeSource.AttributeName)); + attributeSyntax.ShouldNotBeNullOrWhiteSpace(); + attributeSyntax.ShouldBe(expectedAttribute); } [Fact] @@ -156,7 +159,10 @@ namespace MapTo // Assert diagnostics.ShouldBeSuccessful(); - compilation.SyntaxTrees.ShouldContain(c => c.ToString() == ExpectedAttribute); + + var attributeSyntax = compilation.SyntaxTrees.Select(s => s.ToString().Trim()).SingleOrDefault(s => s.Contains(MapFromAttributeSource.AttributeName)); + attributeSyntax.ShouldNotBeNullOrWhiteSpace(); + attributeSyntax.ShouldBe(ExpectedAttribute); } [Fact]