diff --git a/MapTo.sln.DotSettings b/MapTo.sln.DotSettings
index ff51a4e..2397ecf 100644
--- a/MapTo.sln.DotSettings
+++ b/MapTo.sln.DotSettings
@@ -1,3 +1,6 @@
True
+ True
+ True
+ True
True
\ No newline at end of file
diff --git a/src/MapTo/Extensions/GeneratorExecutionContextExtensions.cs b/src/MapTo/Extensions/GeneratorExecutionContextExtensions.cs
index ae70b20..7403ccc 100644
--- a/src/MapTo/Extensions/GeneratorExecutionContextExtensions.cs
+++ b/src/MapTo/Extensions/GeneratorExecutionContextExtensions.cs
@@ -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);
}
}
\ No newline at end of file
diff --git a/src/MapTo/MapToGenerator.cs b/src/MapTo/MapToGenerator.cs
index 0e34dcf..e5d77d2 100644
--- a/src/MapTo/MapToGenerator.cs
+++ b/src/MapTo/MapToGenerator.cs
@@ -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 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);
}
}
}
diff --git a/src/MapTo/Sources/IgnorePropertyAttributeSource.cs b/src/MapTo/Sources/IgnorePropertyAttributeSource.cs
index fb2a779..c82ddc2 100644
--- a/src/MapTo/Sources/IgnorePropertyAttributeSource.cs
+++ b/src/MapTo/Sources/IgnorePropertyAttributeSource.cs
@@ -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");
}
}
}
\ No newline at end of file
diff --git a/src/MapTo/Sources/MapClassSource.cs b/src/MapTo/Sources/MapClassSource.cs
index c7dcd7a..268d607 100644
--- a/src/MapTo/Sources/MapClassSource.cs
+++ b/src/MapTo/Sources/MapClassSource.cs
@@ -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)
diff --git a/src/MapTo/Sources/MapFromAttributeSource.cs b/src/MapTo/Sources/MapFromAttributeSource.cs
index cc5de3e..a938e5d 100644
--- a/src/MapTo/Sources/MapFromAttributeSource.cs
+++ b/src/MapTo/Sources/MapFromAttributeSource.cs
@@ -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");
}
}
}
\ No newline at end of file
diff --git a/src/MapTo/Sources/Source.cs b/src/MapTo/Sources/Source.cs
new file mode 100644
index 0000000..db066a0
--- /dev/null
+++ b/src/MapTo/Sources/Source.cs
@@ -0,0 +1,4 @@
+namespace MapTo.Sources
+{
+ internal record Source(string Code, string HintName);
+}
\ No newline at end of file
diff --git a/src/MapTo/Sources/TypeConverterSource.cs b/src/MapTo/Sources/TypeConverterSource.cs
new file mode 100644
index 0000000..40b1b6c
--- /dev/null
+++ b/src/MapTo/Sources/TypeConverterSource.cs
@@ -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("/// ")
+ .WriteLine("/// Converts the value of .")
+ .WriteLine("/// ")
+ .WriteLine("/// The type to convert from.")
+ .WriteLine("/// The type to convert to.");
+ }
+
+ builder
+ .WriteLine($"public interface {InterfaceName}")
+ .WriteOpeningBracket();
+
+ if (options.GenerateXmlDocument)
+ {
+ builder
+ .WriteLine("/// ")
+ .WriteLine("/// Converts the value of object to .")
+ .WriteLine("/// ")
+ .WriteLine("/// The object to convert.")
+ .WriteLine("/// object.");
+ }
+
+ builder
+ .WriteLine("TDestination Convert(TSource source);")
+ .WriteClosingBracket()
+ .WriteClosingBracket();
+
+ return new(builder.ToString(), $"{InterfaceName}.g.cs");
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/MapTo.Tests/Extensions/ShouldlyExtensions.cs b/test/MapTo.Tests/Extensions/ShouldlyExtensions.cs
new file mode 100644
index 0000000..c8f669f
--- /dev/null
+++ b/test/MapTo.Tests/Extensions/ShouldlyExtensions.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, 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);
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/MapTo.Tests/Tests.cs b/test/MapTo.Tests/Tests.cs
index 386beb8..ab4f464 100644
--- a/test/MapTo.Tests/Tests.cs
+++ b/test/MapTo.Tests/Tests.cs
@@ -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
+ {{
+ TDestination Convert(TSource source);
+ }}
+}}
+".Trim();
+
+ // Act
+ var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions);
+
+ // Assert
+ diagnostics.ShouldBeSuccessful();
+ compilation.SyntaxTrees.ShouldContainSource(TypeConverterSource.InterfaceName, expectedInterface);
+ }
}
}
\ No newline at end of file