Add TypeConverter interface.

This commit is contained in:
Mohammadreza Taikandi 2021-01-06 08:52:50 +00:00
parent 8a3bbc095e
commit e9e44b979a
10 changed files with 123 additions and 25 deletions

View File

@ -1,3 +1,6 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:Boolean x:Key="/Default/UserDictionary/Words/=Mapto/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=paramref/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Shouldly/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=typeparam/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Usings/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

View File

@ -1,4 +1,5 @@
using System;
using MapTo.Sources;
using Microsoft.CodeAnalysis;
namespace MapTo.Extensions
@ -33,5 +34,8 @@ namespace MapTo.Extensions
}
internal static string GetBuildPropertyName(string propertyName) => $"build_property.{PropertyNameSuffix}{propertyName}";
internal static void AddSource(this GeneratorExecutionContext context, Source source) =>
context.AddSource(source.HintName, source.Code);
}
}

View File

@ -22,17 +22,15 @@ namespace MapTo
{
var options = SourceGenerationOptions.From(context);
AddAttribute(context, MapFromAttributeSource.Generate(options));
AddAttribute(context, IgnorePropertyAttributeSource.Generate(options));
context.AddSource(MapFromAttributeSource.Generate(options));
context.AddSource(IgnorePropertyAttributeSource.Generate(options));
context.AddSource(TypeConverterSource.Generate(options));
if (context.SyntaxReceiver is MapToSyntaxReceiver receiver && receiver.CandidateClasses.Any())
{
AddGeneratedMappingsClasses(context, receiver.CandidateClasses, options);
}
}
private static void AddAttribute(GeneratorExecutionContext context, (string source, string hintName) attribute)
=> context.AddSource(attribute.hintName, attribute.source);
private static void AddGeneratedMappingsClasses(GeneratorExecutionContext context, IEnumerable<ClassDeclarationSyntax> candidateClasses, SourceGenerationOptions options)
{
@ -42,14 +40,11 @@ namespace MapTo
var (model, diagnostics) = MapModel.CreateModel(classSemanticModel, classSyntax, options);
diagnostics.ForEach(context.ReportDiagnostic);
if (model is null)
if (model is not null)
{
continue;
context.AddSource(MapClassSource.Generate(model));
}
var (source, hintName) = MapClassSource.Generate(model);
context.AddSource(hintName, source);
}
}
}

View File

@ -7,7 +7,7 @@ namespace MapTo.Sources
{
internal const string AttributeName = "IgnoreProperty";
internal static (string source, string hintName) Generate(SourceGenerationOptions options)
internal static Source Generate(SourceGenerationOptions options)
{
var builder = new SourceBuilder()
.WriteLine(GeneratedFilesHeader)
@ -29,7 +29,7 @@ namespace MapTo.Sources
.WriteLine($"public sealed class {AttributeName}Attribute : Attribute {{ }}")
.WriteClosingBracket();
return (builder.ToString(), $"{AttributeName}Attribute.g.cs");
return new(builder.ToString(), $"{AttributeName}Attribute.g.cs");
}
}
}

View File

@ -6,7 +6,7 @@ namespace MapTo.Sources
{
internal static class MapClassSource
{
internal static (string source, string hintName) Generate(MapModel model)
internal static Source Generate(MapModel model)
{
using var builder = new SourceBuilder()
.WriteLine(GeneratedFilesHeader)
@ -36,7 +36,7 @@ namespace MapTo.Sources
// End namespace declaration
.WriteClosingBracket();
return (builder.ToString(), $"{model.ClassName}.g.cs");
return new(builder.ToString(), $"{model.ClassName}.g.cs");
}
private static SourceBuilder WriteUsings(this SourceBuilder builder, MapModel model)

View File

@ -7,7 +7,7 @@ namespace MapTo.Sources
{
internal const string AttributeName = "MapFrom";
internal static (string source, string hintName) Generate(SourceGenerationOptions options)
internal static Source Generate(SourceGenerationOptions options)
{
using var builder = new SourceBuilder()
.WriteLine(GeneratedFilesHeader)
@ -57,7 +57,7 @@ namespace MapTo.Sources
.WriteClosingBracket() // class
.WriteClosingBracket(); // namespace
return (builder.ToString(), $"{AttributeName}Attribute.g.cs");
return new(builder.ToString(), $"{AttributeName}Attribute.g.cs");
}
}
}

View File

@ -0,0 +1,4 @@
namespace MapTo.Sources
{
internal record Source(string Code, string HintName);
}

View File

@ -0,0 +1,50 @@
using MapTo.Models;
using static MapTo.Sources.Constants;
namespace MapTo.Sources
{
internal class TypeConverterSource
{
internal const string InterfaceName = "ITypeConverter";
internal static Source Generate(SourceGenerationOptions options)
{
using var builder = new SourceBuilder()
.WriteLine(GeneratedFilesHeader)
.WriteLine()
.WriteLine($"namespace {RootNamespace}")
.WriteOpeningBracket();
if (options.GenerateXmlDocument)
{
builder
.WriteLine("/// <summary>")
.WriteLine("/// Converts the value of <typeparamref name=\"TSource\"/> <typeparamref name=\"TDestination\"/>.")
.WriteLine("/// </summary>")
.WriteLine("/// <typeparam name=\"TSource\">The type to convert from.</typeparam>")
.WriteLine("/// <typeparam name=\"TDestination\">The type to convert to.</typeparam>");
}
builder
.WriteLine($"public interface {InterfaceName}<in TSource, out TDestination>")
.WriteOpeningBracket();
if (options.GenerateXmlDocument)
{
builder
.WriteLine("/// <summary>")
.WriteLine("/// Converts the value of <paramref name=\"value\"/> object to <typeparamref name=\"TDestination\"/>.")
.WriteLine("/// </summary>")
.WriteLine("/// <param name=\"value\">The object to convert.</param>")
.WriteLine("/// <returns><typeparamref name=\"TDestination\"/> object.</returns>");
}
builder
.WriteLine("TDestination Convert(TSource source);")
.WriteClosingBracket()
.WriteClosingBracket();
return new(builder.ToString(), $"{InterfaceName}.g.cs");
}
}
}

View File

@ -0,0 +1,20 @@
using System.Collections.Generic;
using System.Linq;
using Microsoft.CodeAnalysis;
using Shouldly;
namespace MapTo.Tests.Extensions
{
internal static class ShouldlyExtensions
{
internal static void ShouldContainSource(this IEnumerable<SyntaxTree> syntaxTree, string typeName, string expectedSource, string customMessage = null)
{
var syntax = syntaxTree
.Select(s => s.ToString().Trim())
.SingleOrDefault(s => s.Contains(typeName));
syntax.ShouldNotBeNullOrWhiteSpace();
syntax.ShouldBe(expectedSource, customMessage);
}
}
}

View File

@ -5,6 +5,7 @@ using System.Text;
using MapTo.Extensions;
using MapTo.Models;
using MapTo.Sources;
using MapTo.Tests.Extensions;
using MapTo.Tests.Infrastructure;
using Microsoft.CodeAnalysis;
using Shouldly;
@ -143,9 +144,7 @@ namespace MapTo
// Assert
diagnostics.ShouldBeSuccessful();
var attributeSyntax = compilation.SyntaxTrees.Select(s => s.ToString().Trim()).SingleOrDefault(s => s.Contains(IgnorePropertyAttributeSource.AttributeName));
attributeSyntax.ShouldNotBeNullOrWhiteSpace();
attributeSyntax.ShouldBe(expectedAttribute);
compilation.SyntaxTrees.ShouldContainSource(IgnorePropertyAttributeSource.AttributeName, expectedAttribute);
}
[Fact]
@ -159,10 +158,7 @@ namespace MapTo
// Assert
diagnostics.ShouldBeSuccessful();
var attributeSyntax = compilation.SyntaxTrees.Select(s => s.ToString().Trim()).SingleOrDefault(s => s.Contains(MapFromAttributeSource.AttributeName));
attributeSyntax.ShouldNotBeNullOrWhiteSpace();
attributeSyntax.ShouldBe(ExpectedAttribute);
compilation.SyntaxTrees.ShouldContainSource(MapFromAttributeSource.AttributeName, ExpectedAttribute);
}
[Fact]
@ -385,6 +381,7 @@ namespace Test
{
// Arrange
const string source = "";
var expectedTypes = new[] { IgnorePropertyAttributeSource.AttributeName, MapFromAttributeSource.AttributeName, TypeConverterSource.InterfaceName };
// Act
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source);
@ -394,7 +391,7 @@ namespace Test
compilation.SyntaxTrees
.Select(s => s.ToString())
.Where(s => !string.IsNullOrWhiteSpace(s.ToString()))
.All(s => s.Contains(": Attribute"))
.All(s => expectedTypes.Any(s.Contains))
.ShouldBeTrue();
}
@ -490,5 +487,30 @@ namespace Test
diagnostics.ShouldBeSuccessful();
compilation.SyntaxTrees.Last().ToString().ShouldContain(expectedResult.Trim());
}
[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);
}}
}}
".Trim();
// Act
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions);
// Assert
diagnostics.ShouldBeSuccessful();
compilation.SyntaxTrees.ShouldContainSource(TypeConverterSource.InterfaceName, expectedInterface);
}
}
}