diff --git a/src/MapTo/Diagnostics.cs b/src/MapTo/Diagnostics.cs
index ae2eb9f..f796d1a 100644
--- a/src/MapTo/Diagnostics.cs
+++ b/src/MapTo/Diagnostics.cs
@@ -6,15 +6,18 @@ namespace MapTo
{
private const string UsageCategory = "Usage";
- internal static Diagnostic SymbolNotFound(Location location, string syntaxName) =>
+ internal static Diagnostic SymbolNotFoundError(Location location, string syntaxName) =>
Create("MT0001", "Symbol not found.", $"Unable to find any symbols for {syntaxName}", location);
- internal static Diagnostic MapFromAttributeNotFound(Location location) =>
+ internal static Diagnostic MapFromAttributeNotFoundError(Location location) =>
Create("MT0002", "Attribute Not Available", $"Unable to find {SourceBuilder.MapFromAttributeName} type.", location);
internal static Diagnostic ClassMappingsGenerated(Location location, string typeName) =>
Create("MT1001", "Mapped Type", $"Generated mappings for {typeName}", location, DiagnosticSeverity.Info);
+ internal static Diagnostic NoMatchingPropertyFoundError(Location location, string className, string sourceTypeName) =>
+ Create("MT2001", "Property Not Found", $"No matching properties found between '{className}' and '{sourceTypeName}' types.", location);
+
private static Diagnostic Create(string id, string title, string message, Location location, DiagnosticSeverity severity = DiagnosticSeverity.Error) =>
Diagnostic.Create(new DiagnosticDescriptor(id, title, message, UsageCategory, severity, true), location);
}
diff --git a/src/MapTo/MapTo.csproj b/src/MapTo/MapTo.csproj
index 93b8b26..e842703 100644
--- a/src/MapTo/MapTo.csproj
+++ b/src/MapTo/MapTo.csproj
@@ -20,6 +20,12 @@
MapTo
+
+
+ <_Parameter1>$(AssemblyName).Tests
+
+
+
all
diff --git a/src/MapTo/MapToGenerator.cs b/src/MapTo/MapToGenerator.cs
index e3b28a6..56a2358 100644
--- a/src/MapTo/MapToGenerator.cs
+++ b/src/MapTo/MapToGenerator.cs
@@ -1,4 +1,5 @@
using System.Collections.Generic;
+using System.Collections.Immutable;
using System.Linq;
using MapTo.Extensions;
using MapTo.Models;
@@ -31,19 +32,11 @@ namespace MapTo
{
foreach (var classSyntax in candidateClasses)
{
- var root = classSyntax.GetCompilationUnit();
- var classSemanticModel = context.Compilation.GetSemanticModel(classSyntax.SyntaxTree);
- var classSymbol = classSemanticModel.GetDeclaredSymbol(classSyntax) as INamedTypeSymbol;
- var sourceTypeSymbol = GetSourceTypeSymbol(classSyntax, classSemanticModel);
-
- var (isValid, diagnostics) = Verify(root, classSyntax, classSemanticModel, classSymbol, sourceTypeSymbol);
- if (!isValid)
+ var model = CreateModel(context, classSyntax);
+ if (model is null)
{
- diagnostics.ForEach(context.ReportDiagnostic);
continue;
}
-
- var model = new MapModel(root, classSyntax, classSymbol!, sourceTypeSymbol!);
var (source, hintName) = SourceBuilder.GenerateSource(model);
context.AddSource(hintName, source);
@@ -68,21 +61,51 @@ namespace MapTo
return sourceTypeExpressionSyntax is not null ? model.GetTypeInfo(sourceTypeExpressionSyntax.Type).Type as INamedTypeSymbol : null;
}
- private static (bool isValid, IEnumerable diagnostics) Verify(CompilationUnitSyntax root, ClassDeclarationSyntax classSyntax, SemanticModel classSemanticModel, INamedTypeSymbol? classSymbol, INamedTypeSymbol? sourceTypeSymbol)
+ private static MapModel? CreateModel(GeneratorExecutionContext context, ClassDeclarationSyntax classSyntax)
{
- var diagnostics = new List();
-
- if (classSymbol is null)
+ var root = classSyntax.GetCompilationUnit();
+ var classSemanticModel = context.Compilation.GetSemanticModel(classSyntax.SyntaxTree);
+
+ if (!(classSemanticModel.GetDeclaredSymbol(classSyntax) is INamedTypeSymbol classSymbol))
{
- diagnostics.Add(Diagnostics.SymbolNotFound(classSyntax.GetLocation(), classSyntax.Identifier.ValueText));
+ context.ReportDiagnostic(Diagnostics.SymbolNotFoundError(classSyntax.GetLocation(), classSyntax.Identifier.ValueText));
+ return null;
}
-
+
+ var sourceTypeSymbol = GetSourceTypeSymbol(classSyntax, classSemanticModel);
if (sourceTypeSymbol is null)
{
- diagnostics.Add(Diagnostics.SymbolNotFound(classSyntax.GetLocation(), classSyntax.Identifier.ValueText));
+ context.ReportDiagnostic(Diagnostics.MapFromAttributeNotFoundError(classSyntax.GetLocation()));
+ return null;
}
- return (!diagnostics.Any(), diagnostics);
+ var className = classSyntax.GetClassName();
+ var sourceClassName = sourceTypeSymbol.Name;
+
+ var mappedProperties = GetMappedProperties(classSymbol, sourceTypeSymbol);
+ if (!mappedProperties.Any())
+ {
+ context.ReportDiagnostic(Diagnostics.NoMatchingPropertyFoundError(classSyntax.GetLocation(), className, sourceClassName));
+ return null;
+ }
+
+ return new MapModel(
+ root.GetNamespace(),
+ classSyntax.Modifiers,
+ className,
+ sourceTypeSymbol.ContainingNamespace.ToString(),
+ sourceClassName,
+ sourceTypeSymbol.ToString(),
+ mappedProperties);
+ }
+
+ private static ImmutableArray GetMappedProperties(ITypeSymbol classSymbol, ITypeSymbol sourceTypeSymbol)
+ {
+ return sourceTypeSymbol
+ .GetAllMembersOfType()
+ .Select(p => p.Name)
+ .Intersect(classSymbol.GetAllMembersOfType().Select(p => p.Name))
+ .ToImmutableArray();
}
}
}
\ No newline at end of file
diff --git a/src/MapTo/Models/MapModel.cs b/src/MapTo/Models/MapModel.cs
index 05ca354..610882d 100644
--- a/src/MapTo/Models/MapModel.cs
+++ b/src/MapTo/Models/MapModel.cs
@@ -1,57 +1,40 @@
-using System.Collections.Generic;
-using MapTo.Extensions;
+using System.Collections.Immutable;
using Microsoft.CodeAnalysis;
-using Microsoft.CodeAnalysis.CSharp.Syntax;
namespace MapTo.Models
{
public class MapModel
{
- private MapModel(
+ internal MapModel(
string? ns,
SyntaxTokenList classModifiers,
string className,
- IEnumerable properties,
string sourceNamespace,
string sourceClassName,
string sourceClassFullName,
- IEnumerable sourceTypeProperties)
+ ImmutableArray mappedProperties)
{
Namespace = ns;
ClassModifiers = classModifiers;
ClassName = className;
- Properties = properties;
SourceNamespace = sourceNamespace;
SourceClassName = sourceClassName;
SourceClassFullName = sourceClassFullName;
- SourceTypeProperties = sourceTypeProperties;
+ MappedProperties = mappedProperties;
}
- internal MapModel(CompilationUnitSyntax root, ClassDeclarationSyntax classSyntax, ITypeSymbol classSymbol, ITypeSymbol sourceTypeSymbol)
- : this(
- root.GetNamespace(),
- classSyntax.Modifiers,
- classSyntax.GetClassName(),
- classSymbol.GetAllMembersOfType(),
- sourceTypeSymbol.ContainingNamespace.ToString(),
- sourceTypeSymbol.Name,
- sourceTypeSymbol.ToString(),
- sourceTypeSymbol.GetAllMembersOfType()) { }
-
public string? Namespace { get; }
public SyntaxTokenList ClassModifiers { get; }
public string ClassName { get; }
- public IEnumerable Properties { get; }
-
public string SourceNamespace { get; }
public string SourceClassName { get; }
public string SourceClassFullName { get; }
- public IEnumerable SourceTypeProperties { get; }
+ public ImmutableArray MappedProperties { get; }
}
}
\ No newline at end of file
diff --git a/src/MapTo/SourceBuilder.cs b/src/MapTo/SourceBuilder.cs
index 2b32327..fcefe5a 100644
--- a/src/MapTo/SourceBuilder.cs
+++ b/src/MapTo/SourceBuilder.cs
@@ -57,7 +57,7 @@ namespace MapTo
.AppendOpeningBracket(Indent1)
// Class body
- .GenerateConstructor(model, out var mappedProperties)
+ .GenerateConstructor(model)
.AppendLine()
.GenerateFactoryMethod(model)
@@ -89,7 +89,7 @@ namespace MapTo
return builder.AppendLine();
}
- private static StringBuilder GenerateConstructor(this StringBuilder builder, MapModel model, out List mappedProperties)
+ private static StringBuilder GenerateConstructor(this StringBuilder builder, MapModel model)
{
var sourceClassParameterName = model.SourceClassName.ToCamelCase();
@@ -98,25 +98,15 @@ namespace MapTo
.AppendFormat("public {0}({1} {2})", model.ClassName, model.SourceClassFullName, sourceClassParameterName)
.AppendOpeningBracket(Indent2)
.PadLeft(Indent3)
- .AppendFormat("if ({0} == null) throw new ArgumentNullException(nameof({0}));", sourceClassParameterName)
+ .AppendFormat("if ({0} == null) throw new ArgumentNullException(nameof({0}));", sourceClassParameterName).AppendLine()
.AppendLine();
- mappedProperties = new List();
-
- if (model.SourceTypeProperties.Any())
+ foreach (var property in model.MappedProperties)
{
- builder.AppendLine();
-
- foreach (var propertySymbol in model.SourceTypeProperties)
- {
- if (model.Properties.Any(p => p.Name == propertySymbol.Name))
- {
- mappedProperties.Add(propertySymbol);
- builder
- .PadLeft(Indent3)
- .AppendFormat("{0} = {1}.{2};{3}", propertySymbol.Name, sourceClassParameterName, propertySymbol.Name, Environment.NewLine);
- }
- }
+ builder
+ .PadLeft(Indent3)
+ .AppendFormat("{0} = {1}.{2};", property, sourceClassParameterName, property)
+ .AppendLine();
}
// End constructor declaration
diff --git a/test/MapTo.Tests/Tests.cs b/test/MapTo.Tests/Tests.cs
index be2e825..aa5423e 100644
--- a/test/MapTo.Tests/Tests.cs
+++ b/test/MapTo.Tests/Tests.cs
@@ -1,10 +1,12 @@
using System.Linq;
using System.Text;
+using MapToTests;
+using Microsoft.CodeAnalysis;
using Shouldly;
using Xunit;
using Xunit.Abstractions;
-namespace MapToTests
+namespace MapTo.Tests
{
public class Tests
{
@@ -93,14 +95,12 @@ namespace Test
[MapFrom(typeof(Baz))]
public partial class Foo
{
-
+ public int Prop1 { get; set; }
}
public class Baz
{
public int Prop1 { get; set; }
- public int Prop2 { get; }
- public int Prop3 { get; set; }
}
}
";
@@ -116,6 +116,8 @@ namespace Test
public Foo(Test.Baz baz)
{
if (baz == null) throw new ArgumentNullException(nameof(baz));
+
+ Prop1 = baz.Prop1;
}
";
@@ -127,6 +129,31 @@ namespace Test
compilation.SyntaxTrees.Count().ShouldBe(3);
compilation.SyntaxTrees.Last().ToString().ShouldStartWith(expectedResult.Trim());
}
+
+ [Fact]
+ public void When_MapToAttributeFoundWithoutMatchingProperties_Should_ReportError()
+ {
+ // Arrange
+ var expectedDiagnostic = Diagnostics.NoMatchingPropertyFoundError(Location.None, "Foo", "Baz");
+ const string source = @"
+using MapTo;
+
+namespace Test
+{
+ [MapFrom(typeof(Baz))]
+ public partial class Foo { }
+
+ public class Baz { public int Prop1 { get; set; } }
+}
+";
+
+ // Act
+ var (_, diagnostics) = CSharpGenerator.GetOutputCompilation(source);
+
+ // Assert
+ var error = diagnostics.FirstOrDefault(d => d.Id == expectedDiagnostic.Id);
+ error.ShouldNotBeNull();
+ }
[Fact]
public void When_MapToAttributeWithNamespaceFound_Should_GenerateTheClass()
@@ -136,15 +163,9 @@ namespace Test
namespace Test
{
[MapTo.MapFrom(typeof(Baz))]
- public partial class Foo
- {
-
- }
+ public partial class Foo { public int Prop1 { get; set; } }
- public class Baz
- {
-
- }
+ public class Baz { public int Prop1 { get; set; } }
}
";
@@ -159,6 +180,8 @@ namespace Test
public Foo(Test.Baz baz)
{
if (baz == null) throw new ArgumentNullException(nameof(baz));
+
+ Prop1 = baz.Prop1;
}
";
@@ -187,7 +210,7 @@ namespace Test
}
[Fact]
- public void When_SourceTypeHasDifferentNamespace_Should_AddToUsings()
+ public void When_SourceTypeHasDifferentNamespace_Should_NotAddToUsings()
{
// Arrange
var source = GetSourceText(sourceClassNamespace: "Bazaar");
@@ -195,7 +218,8 @@ namespace Test
const string expectedResult = @"
//
using System;
-using Bazaar;
+
+namespace Test
";
// Act
@@ -219,6 +243,7 @@ using Bazaar;
public Foo(Test.Models.Baz baz)
{
if (baz == null) throw new ArgumentNullException(nameof(baz));
+
Prop1 = baz.Prop1;
Prop2 = baz.Prop2;
Prop3 = baz.Prop3;
@@ -263,7 +288,7 @@ using Bazaar;
var source = GetSourceText();
const string expectedResult = @"
- public static partial class BazExtensions
+ public static partial class BazToFooExtensions
{
public static Foo ToFoo(this Test.Models.Baz baz)
{