Put source builders in different files and use IndentedTextWriter instead of StringBuilder.
This commit is contained in:
parent
f7ff6e4793
commit
8a3bbc095e
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 } }
|
||||
}
|
||||
);
|
||||
|
||||
|
|
|
@ -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<TypeOfExpressionSyntax>()
|
||||
.SingleOrDefault();
|
||||
|
@ -80,7 +81,7 @@ namespace MapTo.Models
|
|||
.Select(p => (p.Name, p.Type.ToString()))
|
||||
.Intersect(classSymbol
|
||||
.GetAllMembersOfType<IPropertySymbol>()
|
||||
.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();
|
||||
|
|
|
@ -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 = "// <auto-generated />";
|
||||
|
||||
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("/// <summary>")
|
||||
.PadLeft(Indent1).AppendLine("/// Specifies that the annotated class can be mapped from the provided <see cref=\"SourceType\"/>.")
|
||||
.PadLeft(Indent1).AppendLine("/// </summary>");
|
||||
}
|
||||
|
||||
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("/// <summary>")
|
||||
.PadLeft(Indent2).AppendFormat("/// Initializes a new instance of the <see cref=\"{0}Attribute\"/> class with the specified <paramref name=\"sourceType\"/>.", MapFromAttributeName).AppendLine()
|
||||
.PadLeft(Indent2).AppendLine("/// </summary>");
|
||||
}
|
||||
|
||||
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("/// <summary>")
|
||||
.PadLeft(Indent2).AppendLine("/// Gets the type of the class that the annotated class should be able to map from.")
|
||||
.PadLeft(Indent2).AppendLine("/// </summary>");
|
||||
}
|
||||
|
||||
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("/// <summary>")
|
||||
.PadLeft(Indent1).AppendLine("/// Specified that the annotated property should not be included in the generated mappings.")
|
||||
.PadLeft(Indent1).AppendLine("/// </summary>");
|
||||
}
|
||||
|
||||
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("/// <summary>")
|
||||
.PadLeft(Indent2).AppendFormat("/// Initializes a new instance of the <see cref=\"{0}\"/> class", model.ClassName).AppendLine()
|
||||
.PadLeft(Indent2).AppendFormat("/// using the property values from the specified <paramref name=\"{0}\"/>.", sourceClassParameterName).AppendLine()
|
||||
.PadLeft(Indent2).AppendLine("/// </summary>")
|
||||
.PadLeft(Indent2).AppendFormat("/// <exception cref=\"ArgumentNullException\">{0} is null</exception>", 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("/// <summary>")
|
||||
.PadLeft(Indent2).AppendFormat("/// Creates a new instance of <see cref=\"{0}\"/> and sets its participating properties", model.ClassName).AppendLine()
|
||||
.PadLeft(Indent2).AppendFormat("/// using the property values from <paramref name=\"{0}\"/>.", sourceClassParameterName).AppendLine()
|
||||
.PadLeft(Indent2).AppendLine("/// </summary>")
|
||||
.PadLeft(Indent2).AppendFormat("/// <param name=\"{0}\">Instance of <see cref=\"{1}\"/> to use as source.</param>", sourceClassParameterName, model.SourceClassName).AppendLine()
|
||||
.PadLeft(Indent2).AppendFormat("/// <returns>A new instance of <see cred=\"{0}\"/> -or- <c>null</c> if <paramref name=\"{1}\"/> is <c>null</c>.</returns>", model.ClassName, sourceClassParameterName).AppendLine();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
namespace MapTo.Sources
|
||||
{
|
||||
internal class Constants
|
||||
{
|
||||
internal const string RootNamespace = "MapTo";
|
||||
internal const string GeneratedFilesHeader = "// <auto-generated />";
|
||||
}
|
||||
}
|
|
@ -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("/// <summary>")
|
||||
.WriteLine("/// Specified that the annotated property should not be included in the generated mappings.")
|
||||
.WriteLine("/// </summary>");
|
||||
}
|
||||
|
||||
builder
|
||||
.WriteLine("[AttributeUsage(AttributeTargets.Property, Inherited = false, AllowMultiple = false)]")
|
||||
.WriteLine($"public sealed class {AttributeName}Attribute : Attribute {{ }}")
|
||||
.WriteClosingBracket();
|
||||
|
||||
return (builder.ToString(), $"{AttributeName}Attribute.g.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("/// <summary>")
|
||||
.WriteLine($"/// Initializes a new instance of the <see cref=\"{model.ClassName}\"/> class")
|
||||
.WriteLine($"/// using the property values from the specified <paramref name=\"{sourceClassParameterName}\"/>.")
|
||||
.WriteLine("/// </summary>")
|
||||
.WriteLine($"/// <exception cref=\"ArgumentNullException\">{sourceClassParameterName} is null</exception>");
|
||||
}
|
||||
|
||||
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("/// <summary>")
|
||||
.WriteLine($"/// Creates a new instance of <see cref=\"{model.ClassName}\"/> and sets its participating properties")
|
||||
.WriteLine($"/// using the property values from <paramref name=\"{sourceClassParameterName}\"/>.")
|
||||
.WriteLine("/// </summary>")
|
||||
.WriteLine($"/// <param name=\"{sourceClassParameterName}\">Instance of <see cref=\"{model.SourceClassName}\"/> to use as source.</param>")
|
||||
.WriteLine($"/// <returns>A new instance of <see cred=\"{model.ClassName}\"/> -or- <c>null</c> if <paramref name=\"{sourceClassParameterName}\"/> is <c>null</c>.</returns>");
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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("/// <summary>")
|
||||
.WriteLine("/// Specifies that the annotated class can be mapped from the provided <see cref=\"SourceType\"/>.")
|
||||
.WriteLine("/// </summary>");
|
||||
}
|
||||
|
||||
builder
|
||||
.WriteLine("[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]")
|
||||
.WriteLine($"public sealed class {AttributeName}Attribute : Attribute")
|
||||
.WriteOpeningBracket();
|
||||
|
||||
if (options.GenerateXmlDocument)
|
||||
{
|
||||
builder
|
||||
.WriteLine("/// <summary>")
|
||||
.WriteLine($"/// Initializes a new instance of the <see cref=\"{AttributeName}Attribute\"/> class with the specified <paramref name=\"sourceType\"/>.")
|
||||
.WriteLine("/// </summary>");
|
||||
}
|
||||
|
||||
builder
|
||||
.WriteLine($"public {AttributeName}Attribute(Type sourceType)")
|
||||
.WriteOpeningBracket()
|
||||
.WriteLine("SourceType = sourceType;")
|
||||
.WriteClosingBracket()
|
||||
.WriteLine();
|
||||
|
||||
if (options.GenerateXmlDocument)
|
||||
{
|
||||
builder
|
||||
.WriteLine("/// <summary>")
|
||||
.WriteLine("/// Gets the type of the class that the annotated class should be able to map from.")
|
||||
.WriteLine("/// </summary>");
|
||||
}
|
||||
|
||||
builder
|
||||
.WriteLine("public Type SourceType { get; }")
|
||||
.WriteClosingBracket() // class
|
||||
.WriteClosingBracket(); // namespace
|
||||
|
||||
return (builder.ToString(), $"{AttributeName}Attribute.g.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));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
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;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString() => _writer.ToString();
|
||||
}
|
||||
}
|
|
@ -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]
|
||||
|
|
Loading…
Reference in New Issue