Add TypeConverter interface.
This commit is contained in:
parent
8a3bbc095e
commit
e9e44b979a
|
@ -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">
|
<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/=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>
|
<s:Boolean x:Key="/Default/UserDictionary/Words/=Usings/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
|
@ -1,4 +1,5 @@
|
||||||
using System;
|
using System;
|
||||||
|
using MapTo.Sources;
|
||||||
using Microsoft.CodeAnalysis;
|
using Microsoft.CodeAnalysis;
|
||||||
|
|
||||||
namespace MapTo.Extensions
|
namespace MapTo.Extensions
|
||||||
|
@ -33,5 +34,8 @@ namespace MapTo.Extensions
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static string GetBuildPropertyName(string propertyName) => $"build_property.{PropertyNameSuffix}{propertyName}";
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -22,17 +22,15 @@ namespace MapTo
|
||||||
{
|
{
|
||||||
var options = SourceGenerationOptions.From(context);
|
var options = SourceGenerationOptions.From(context);
|
||||||
|
|
||||||
AddAttribute(context, MapFromAttributeSource.Generate(options));
|
context.AddSource(MapFromAttributeSource.Generate(options));
|
||||||
AddAttribute(context, IgnorePropertyAttributeSource.Generate(options));
|
context.AddSource(IgnorePropertyAttributeSource.Generate(options));
|
||||||
|
context.AddSource(TypeConverterSource.Generate(options));
|
||||||
|
|
||||||
if (context.SyntaxReceiver is MapToSyntaxReceiver receiver && receiver.CandidateClasses.Any())
|
if (context.SyntaxReceiver is MapToSyntaxReceiver receiver && receiver.CandidateClasses.Any())
|
||||||
{
|
{
|
||||||
AddGeneratedMappingsClasses(context, receiver.CandidateClasses, options);
|
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)
|
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);
|
var (model, diagnostics) = MapModel.CreateModel(classSemanticModel, classSyntax, options);
|
||||||
|
|
||||||
diagnostics.ForEach(context.ReportDiagnostic);
|
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@ namespace MapTo.Sources
|
||||||
{
|
{
|
||||||
internal const string AttributeName = "IgnoreProperty";
|
internal const string AttributeName = "IgnoreProperty";
|
||||||
|
|
||||||
internal static (string source, string hintName) Generate(SourceGenerationOptions options)
|
internal static Source Generate(SourceGenerationOptions options)
|
||||||
{
|
{
|
||||||
var builder = new SourceBuilder()
|
var builder = new SourceBuilder()
|
||||||
.WriteLine(GeneratedFilesHeader)
|
.WriteLine(GeneratedFilesHeader)
|
||||||
|
@ -29,7 +29,7 @@ namespace MapTo.Sources
|
||||||
.WriteLine($"public sealed class {AttributeName}Attribute : Attribute {{ }}")
|
.WriteLine($"public sealed class {AttributeName}Attribute : Attribute {{ }}")
|
||||||
.WriteClosingBracket();
|
.WriteClosingBracket();
|
||||||
|
|
||||||
return (builder.ToString(), $"{AttributeName}Attribute.g.cs");
|
return new(builder.ToString(), $"{AttributeName}Attribute.g.cs");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -6,7 +6,7 @@ namespace MapTo.Sources
|
||||||
{
|
{
|
||||||
internal static class MapClassSource
|
internal static class MapClassSource
|
||||||
{
|
{
|
||||||
internal static (string source, string hintName) Generate(MapModel model)
|
internal static Source Generate(MapModel model)
|
||||||
{
|
{
|
||||||
using var builder = new SourceBuilder()
|
using var builder = new SourceBuilder()
|
||||||
.WriteLine(GeneratedFilesHeader)
|
.WriteLine(GeneratedFilesHeader)
|
||||||
|
@ -36,7 +36,7 @@ namespace MapTo.Sources
|
||||||
// End namespace declaration
|
// End namespace declaration
|
||||||
.WriteClosingBracket();
|
.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)
|
private static SourceBuilder WriteUsings(this SourceBuilder builder, MapModel model)
|
||||||
|
|
|
@ -7,7 +7,7 @@ namespace MapTo.Sources
|
||||||
{
|
{
|
||||||
internal const string AttributeName = "MapFrom";
|
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()
|
using var builder = new SourceBuilder()
|
||||||
.WriteLine(GeneratedFilesHeader)
|
.WriteLine(GeneratedFilesHeader)
|
||||||
|
@ -57,7 +57,7 @@ namespace MapTo.Sources
|
||||||
.WriteClosingBracket() // class
|
.WriteClosingBracket() // class
|
||||||
.WriteClosingBracket(); // namespace
|
.WriteClosingBracket(); // namespace
|
||||||
|
|
||||||
return (builder.ToString(), $"{AttributeName}Attribute.g.cs");
|
return new(builder.ToString(), $"{AttributeName}Attribute.g.cs");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
namespace MapTo.Sources
|
||||||
|
{
|
||||||
|
internal record Source(string Code, string HintName);
|
||||||
|
}
|
|
@ -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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,6 +5,7 @@ using System.Text;
|
||||||
using MapTo.Extensions;
|
using MapTo.Extensions;
|
||||||
using MapTo.Models;
|
using MapTo.Models;
|
||||||
using MapTo.Sources;
|
using MapTo.Sources;
|
||||||
|
using MapTo.Tests.Extensions;
|
||||||
using MapTo.Tests.Infrastructure;
|
using MapTo.Tests.Infrastructure;
|
||||||
using Microsoft.CodeAnalysis;
|
using Microsoft.CodeAnalysis;
|
||||||
using Shouldly;
|
using Shouldly;
|
||||||
|
@ -143,9 +144,7 @@ namespace MapTo
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
diagnostics.ShouldBeSuccessful();
|
diagnostics.ShouldBeSuccessful();
|
||||||
var attributeSyntax = compilation.SyntaxTrees.Select(s => s.ToString().Trim()).SingleOrDefault(s => s.Contains(IgnorePropertyAttributeSource.AttributeName));
|
compilation.SyntaxTrees.ShouldContainSource(IgnorePropertyAttributeSource.AttributeName, expectedAttribute);
|
||||||
attributeSyntax.ShouldNotBeNullOrWhiteSpace();
|
|
||||||
attributeSyntax.ShouldBe(expectedAttribute);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
@ -159,10 +158,7 @@ namespace MapTo
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
diagnostics.ShouldBeSuccessful();
|
diagnostics.ShouldBeSuccessful();
|
||||||
|
compilation.SyntaxTrees.ShouldContainSource(MapFromAttributeSource.AttributeName, ExpectedAttribute);
|
||||||
var attributeSyntax = compilation.SyntaxTrees.Select(s => s.ToString().Trim()).SingleOrDefault(s => s.Contains(MapFromAttributeSource.AttributeName));
|
|
||||||
attributeSyntax.ShouldNotBeNullOrWhiteSpace();
|
|
||||||
attributeSyntax.ShouldBe(ExpectedAttribute);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
@ -385,6 +381,7 @@ namespace Test
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
const string source = "";
|
const string source = "";
|
||||||
|
var expectedTypes = new[] { IgnorePropertyAttributeSource.AttributeName, MapFromAttributeSource.AttributeName, TypeConverterSource.InterfaceName };
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source);
|
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source);
|
||||||
|
@ -394,7 +391,7 @@ namespace Test
|
||||||
compilation.SyntaxTrees
|
compilation.SyntaxTrees
|
||||||
.Select(s => s.ToString())
|
.Select(s => s.ToString())
|
||||||
.Where(s => !string.IsNullOrWhiteSpace(s.ToString()))
|
.Where(s => !string.IsNullOrWhiteSpace(s.ToString()))
|
||||||
.All(s => s.Contains(": Attribute"))
|
.All(s => expectedTypes.Any(s.Contains))
|
||||||
.ShouldBeTrue();
|
.ShouldBeTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -490,5 +487,30 @@ namespace Test
|
||||||
diagnostics.ShouldBeSuccessful();
|
diagnostics.ShouldBeSuccessful();
|
||||||
compilation.SyntaxTrees.Last().ToString().ShouldContain(expectedResult.Trim());
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
Reference in New Issue