Simplified using statements and fix nested mappings.

This commit is contained in:
Mohammadreza Taikandi 2021-02-17 08:32:23 +00:00
parent 6ad7ea83f9
commit c1a763a474
13 changed files with 234 additions and 84 deletions

View File

@ -1,7 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using MapTo.Sources;
using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.CSharp.Syntax;
@ -68,7 +67,13 @@ namespace MapTo.Extensions
targetProperty.NullableAnnotation == NullableAnnotation.Annotated)); targetProperty.NullableAnnotation == NullableAnnotation.Annotated));
} }
public static INamedTypeSymbol GetTypeByMetadataNameOrThrow(this Compilation compilation, string fullyQualifiedMetadataName) => public static INamedTypeSymbol GetTypeByMetadataNameOrThrow(this Compilation compilation, string fullyQualifiedMetadataName) =>
compilation.GetTypeByMetadataName(fullyQualifiedMetadataName) ?? throw new TypeLoadException($"Unable to find '{fullyQualifiedMetadataName}' type."); compilation.GetTypeByMetadataName(fullyQualifiedMetadataName) ?? throw new TypeLoadException($"Unable to find '{fullyQualifiedMetadataName}' type.");
public static bool IsGenericEnumerable(this Compilation compilation, ITypeSymbol typeSymbol) =>
typeSymbol is INamedTypeSymbol { IsGenericType: true } &&
compilation.GetSpecialType(SpecialType.System_Collections_Generic_IEnumerable_T).Equals(typeSymbol.OriginalDefinition, SymbolEqualityComparer.Default);
public static bool IsArray(this Compilation compilation, ITypeSymbol typeSymbol) => typeSymbol is IArrayTypeSymbol;
} }
} }

View File

@ -12,16 +12,19 @@ namespace MapTo
{ {
private readonly ClassDeclarationSyntax _classSyntax; private readonly ClassDeclarationSyntax _classSyntax;
private readonly Compilation _compilation; private readonly Compilation _compilation;
private readonly List<Diagnostic> _diagnostics;
private readonly INamedTypeSymbol _ignorePropertyAttributeTypeSymbol; private readonly INamedTypeSymbol _ignorePropertyAttributeTypeSymbol;
private readonly INamedTypeSymbol _mapFromAttributeTypeSymbol; private readonly INamedTypeSymbol _mapFromAttributeTypeSymbol;
private readonly INamedTypeSymbol _mapPropertyAttributeTypeSymbol; private readonly INamedTypeSymbol _mapPropertyAttributeTypeSymbol;
private readonly INamedTypeSymbol _mapTypeConverterAttributeTypeSymbol; private readonly INamedTypeSymbol _mapTypeConverterAttributeTypeSymbol;
private readonly SourceGenerationOptions _sourceGenerationOptions; private readonly SourceGenerationOptions _sourceGenerationOptions;
private readonly INamedTypeSymbol _typeConverterInterfaceTypeSymbol; private readonly INamedTypeSymbol _typeConverterInterfaceTypeSymbol;
private readonly List<string> _usings;
internal MappingContext(Compilation compilation, SourceGenerationOptions sourceGenerationOptions, ClassDeclarationSyntax classSyntax) internal MappingContext(Compilation compilation, SourceGenerationOptions sourceGenerationOptions, ClassDeclarationSyntax classSyntax)
{ {
Diagnostics = ImmutableArray<Diagnostic>.Empty; _diagnostics = new List<Diagnostic>();
_usings = new List<string> { "System" };
_sourceGenerationOptions = sourceGenerationOptions; _sourceGenerationOptions = sourceGenerationOptions;
_classSyntax = classSyntax; _classSyntax = classSyntax;
_compilation = compilation; _compilation = compilation;
@ -32,26 +35,28 @@ namespace MapTo
_mapPropertyAttributeTypeSymbol = compilation.GetTypeByMetadataNameOrThrow(MapPropertyAttributeSource.FullyQualifiedName); _mapPropertyAttributeTypeSymbol = compilation.GetTypeByMetadataNameOrThrow(MapPropertyAttributeSource.FullyQualifiedName);
_mapFromAttributeTypeSymbol = compilation.GetTypeByMetadataNameOrThrow(MapFromAttributeSource.FullyQualifiedName); _mapFromAttributeTypeSymbol = compilation.GetTypeByMetadataNameOrThrow(MapFromAttributeSource.FullyQualifiedName);
AddUsingIfRequired(sourceGenerationOptions.SupportNullableReferenceTypes, "System.Diagnostics.CodeAnalysis");
Initialize(); Initialize();
} }
public MappingModel? Model { get; private set; } public MappingModel? Model { get; private set; }
public ImmutableArray<Diagnostic> Diagnostics { get; private set; } public IEnumerable<Diagnostic> Diagnostics => _diagnostics;
private void Initialize() private void Initialize()
{ {
var semanticModel = _compilation.GetSemanticModel(_classSyntax.SyntaxTree); var semanticModel = _compilation.GetSemanticModel(_classSyntax.SyntaxTree);
if (!(semanticModel.GetDeclaredSymbol(_classSyntax) is INamedTypeSymbol classTypeSymbol)) if (!(semanticModel.GetDeclaredSymbol(_classSyntax) is INamedTypeSymbol classTypeSymbol))
{ {
ReportDiagnostic(DiagnosticProvider.TypeNotFoundError(_classSyntax.GetLocation(), _classSyntax.Identifier.ValueText)); _diagnostics.Add(DiagnosticProvider.TypeNotFoundError(_classSyntax.GetLocation(), _classSyntax.Identifier.ValueText));
return; return;
} }
var sourceTypeSymbol = GetSourceTypeSymbol(_classSyntax, semanticModel); var sourceTypeSymbol = GetSourceTypeSymbol(_classSyntax, semanticModel);
if (sourceTypeSymbol is null) if (sourceTypeSymbol is null)
{ {
ReportDiagnostic(DiagnosticProvider.MapFromAttributeNotFoundError(_classSyntax.GetLocation())); _diagnostics.Add(DiagnosticProvider.MapFromAttributeNotFoundError(_classSyntax.GetLocation()));
return; return;
} }
@ -62,10 +67,13 @@ namespace MapTo
var mappedProperties = GetMappedProperties(classTypeSymbol, sourceTypeSymbol, isClassInheritFromMappedBaseClass); var mappedProperties = GetMappedProperties(classTypeSymbol, sourceTypeSymbol, isClassInheritFromMappedBaseClass);
if (!mappedProperties.Any()) if (!mappedProperties.Any())
{ {
ReportDiagnostic(DiagnosticProvider.NoMatchingPropertyFoundError(_classSyntax.GetLocation(), classTypeSymbol, sourceTypeSymbol)); _diagnostics.Add(DiagnosticProvider.NoMatchingPropertyFoundError(_classSyntax.GetLocation(), classTypeSymbol, sourceTypeSymbol));
return; return;
} }
AddUsingIfRequired(sourceTypeSymbol);
AddUsingIfRequired(mappedProperties.Any(p => p.IsEnumerable), "System.Linq");
Model = new MappingModel( Model = new MappingModel(
_sourceGenerationOptions, _sourceGenerationOptions,
_classSyntax.GetNamespace(), _classSyntax.GetNamespace(),
@ -75,7 +83,8 @@ namespace MapTo
sourceClassName, sourceClassName,
sourceTypeSymbol.ToString(), sourceTypeSymbol.ToString(),
mappedProperties.ToImmutableArray(), mappedProperties.ToImmutableArray(),
isClassInheritFromMappedBaseClass); isClassInheritFromMappedBaseClass,
_usings.ToImmutableArray());
} }
private bool IsClassInheritFromMappedBaseClass(SemanticModel semanticModel) private bool IsClassInheritFromMappedBaseClass(SemanticModel semanticModel)
@ -103,60 +112,73 @@ namespace MapTo
string? converterFullyQualifiedName = null; string? converterFullyQualifiedName = null;
var converterParameters = ImmutableArray<string>.Empty; var converterParameters = ImmutableArray<string>.Empty;
string? mappedSourcePropertyType = null; ITypeSymbol? mappedSourcePropertyType = null;
ITypeSymbol? enumerableTypeArgumentType = null;
if (!_compilation.HasCompatibleTypes(sourceProperty, property)) if (!_compilation.HasCompatibleTypes(sourceProperty, property))
{ {
if (!TryGetMapTypeConverter(property, sourceProperty, out converterFullyQualifiedName, out converterParameters) && if (!TryGetMapTypeConverter(property, sourceProperty, out converterFullyQualifiedName, out converterParameters) &&
!TryGetNestedObjectMappings(property, out mappedSourcePropertyType)) !TryGetNestedObjectMappings(property, out mappedSourcePropertyType, out enumerableTypeArgumentType))
{ {
continue; continue;
} }
} }
mappedProperties.Add(new MappedProperty( AddUsingIfRequired(property.Type);
property.Name, AddUsingIfRequired(sourceTypeSymbol);
property.Type.Name, AddUsingIfRequired(enumerableTypeArgumentType);
converterFullyQualifiedName, AddUsingIfRequired(mappedSourcePropertyType);
converterParameters.ToImmutableArray(),
sourceProperty.Name, mappedProperties.Add(
mappedSourcePropertyType)); new MappedProperty(
property.Name,
property.Type.Name,
converterFullyQualifiedName,
converterParameters.ToImmutableArray(),
sourceProperty.Name,
mappedSourcePropertyType?.Name,
enumerableTypeArgumentType?.Name));
} }
return mappedProperties.ToImmutableArray(); return mappedProperties.ToImmutableArray();
} }
private bool TryGetNestedObjectMappings(IPropertySymbol property, out string? mappedSourcePropertyType) private bool TryGetNestedObjectMappings(IPropertySymbol property, out ITypeSymbol? mappedSourcePropertyType, out ITypeSymbol? enumerableTypeArgument)
{ {
mappedSourcePropertyType = null; mappedSourcePropertyType = null;
enumerableTypeArgument = null;
if (!Diagnostics.IsEmpty) if (!_diagnostics.IsEmpty())
{ {
return false; return false;
} }
var nestedSourceMapFromAttribute = property.Type.GetAttribute(_mapFromAttributeTypeSymbol); var mapFromAttribute = property.Type.GetAttribute(_mapFromAttributeTypeSymbol);
if (nestedSourceMapFromAttribute is null) if (mapFromAttribute is null && property.Type is INamedTypeSymbol namedTypeSymbol && _compilation.IsGenericEnumerable(property.Type))
{ {
ReportDiagnostic(DiagnosticProvider.NoMatchingPropertyTypeFoundError(property)); enumerableTypeArgument = namedTypeSymbol.TypeArguments.First();
return false; mapFromAttribute = enumerableTypeArgument.GetAttribute(_mapFromAttributeTypeSymbol);
} }
if (!(nestedSourceMapFromAttribute.ApplicationSyntaxReference?.GetSyntax() is AttributeSyntax nestedAttributeSyntax)) mappedSourcePropertyType = mapFromAttribute?.ConstructorArguments.First().Value as INamedTypeSymbol;
if (mappedSourcePropertyType is null && enumerableTypeArgument is null)
{ {
ReportDiagnostic(DiagnosticProvider.NoMatchingPropertyTypeFoundError(property)); _diagnostics.Add(DiagnosticProvider.NoMatchingPropertyTypeFoundError(property));
return false;
} }
var nestedSourceTypeSymbol = GetSourceTypeSymbol(nestedAttributeSyntax); return _diagnostics.IsEmpty();
if (nestedSourceTypeSymbol is null) }
{
ReportDiagnostic(DiagnosticProvider.NoMatchingPropertyTypeFoundError(property));
return false;
}
mappedSourcePropertyType = nestedSourceTypeSymbol.Name; private void AddUsingIfRequired(ISymbol? namedTypeSymbol) =>
return true; AddUsingIfRequired(namedTypeSymbol?.ContainingNamespace.IsGlobalNamespace == false, namedTypeSymbol?.ContainingNamespace.ToDisplayString());
private void AddUsingIfRequired(bool condition, string? ns)
{
if (condition && ns is not null && ns != _classSyntax.GetNamespace() && !_usings.Contains(ns))
{
_usings.Add(ns);
}
} }
private bool TryGetMapTypeConverter(IPropertySymbol property, IPropertySymbol sourceProperty, out string? converterFullyQualifiedName, out ImmutableArray<string> converterParameters) private bool TryGetMapTypeConverter(IPropertySymbol property, IPropertySymbol sourceProperty, out string? converterFullyQualifiedName, out ImmutableArray<string> converterParameters)
@ -164,7 +186,7 @@ namespace MapTo
converterFullyQualifiedName = null; converterFullyQualifiedName = null;
converterParameters = ImmutableArray<string>.Empty; converterParameters = ImmutableArray<string>.Empty;
if (!Diagnostics.IsEmpty) if (!_diagnostics.IsEmpty())
{ {
return false; return false;
} }
@ -178,7 +200,7 @@ namespace MapTo
var baseInterface = GetTypeConverterBaseInterface(converterTypeSymbol, property, sourceProperty); var baseInterface = GetTypeConverterBaseInterface(converterTypeSymbol, property, sourceProperty);
if (baseInterface is null) if (baseInterface is null)
{ {
ReportDiagnostic(DiagnosticProvider.InvalidTypeConverterGenericTypesError(property, sourceProperty)); _diagnostics.Add(DiagnosticProvider.InvalidTypeConverterGenericTypesError(property, sourceProperty));
return false; return false;
} }
@ -216,11 +238,6 @@ namespace MapTo
: converterParameter.Values.Where(v => v.Value is not null).Select(v => v.Value!.ToSourceCodeString()).ToImmutableArray(); : converterParameter.Values.Where(v => v.Value is not null).Select(v => v.Value!.ToSourceCodeString()).ToImmutableArray();
} }
private void ReportDiagnostic(Diagnostic diagnostic)
{
Diagnostics = Diagnostics.Add(diagnostic);
}
private INamedTypeSymbol? GetSourceTypeSymbol(ClassDeclarationSyntax classDeclarationSyntax, SemanticModel? semanticModel = null) => private INamedTypeSymbol? GetSourceTypeSymbol(ClassDeclarationSyntax classDeclarationSyntax, SemanticModel? semanticModel = null) =>
GetSourceTypeSymbol(classDeclarationSyntax.GetAttribute(MapFromAttributeSource.AttributeName), semanticModel); GetSourceTypeSymbol(classDeclarationSyntax.GetAttribute(MapFromAttributeSource.AttributeName), semanticModel);

View File

@ -8,12 +8,16 @@ namespace MapTo
internal record SourceCode(string Text, string HintName); internal record SourceCode(string Text, string HintName);
internal record MappedProperty( internal record MappedProperty(
string Name, string Name,
string Type, string Type,
string? TypeConverter, string? TypeConverter,
ImmutableArray<string> TypeConverterParameters, ImmutableArray<string> TypeConverterParameters,
string SourcePropertyName, string SourcePropertyName,
string? MappedSourcePropertyTypeName); string? MappedSourcePropertyTypeName,
string? EnumerableTypeArgument)
{
public bool IsEnumerable => EnumerableTypeArgument is not null;
}
internal record MappingModel ( internal record MappingModel (
SourceGenerationOptions Options, SourceGenerationOptions Options,
@ -24,7 +28,8 @@ namespace MapTo
string SourceClassName, string SourceClassName,
string SourceClassFullName, string SourceClassFullName,
ImmutableArray<MappedProperty> MappedProperties, ImmutableArray<MappedProperty> MappedProperties,
bool HasMappedBaseClass bool HasMappedBaseClass,
ImmutableArray<string> Usings
); );
internal record SourceGenerationOptions( internal record SourceGenerationOptions(

View File

@ -1,4 +1,5 @@
using MapTo.Extensions; using System.Linq;
using MapTo.Extensions;
using static MapTo.Sources.Constants; using static MapTo.Sources.Constants;
namespace MapTo.Sources namespace MapTo.Sources
@ -42,9 +43,8 @@ namespace MapTo.Sources
private static SourceBuilder WriteUsings(this SourceBuilder builder, MappingModel model) private static SourceBuilder WriteUsings(this SourceBuilder builder, MappingModel model)
{ {
return builder model.Usings.Sort().ForEach(u => builder.WriteLine($"using {u};"));
.WriteLine("using System;") return builder;
.WriteLineIf(model.Options.SupportNullableReferenceTypes, "using System.Diagnostics.CodeAnalysis;");
} }
private static SourceBuilder GenerateConstructor(this SourceBuilder builder, MappingModel model) private static SourceBuilder GenerateConstructor(this SourceBuilder builder, MappingModel model)
@ -64,7 +64,7 @@ namespace MapTo.Sources
var baseConstructor = model.HasMappedBaseClass ? $" : base({sourceClassParameterName})" : string.Empty; var baseConstructor = model.HasMappedBaseClass ? $" : base({sourceClassParameterName})" : string.Empty;
builder builder
.WriteLine($"{model.Options.ConstructorAccessModifier.ToLowercaseString()} {model.ClassName}({model.SourceClassFullName} {sourceClassParameterName}){baseConstructor}") .WriteLine($"{model.Options.ConstructorAccessModifier.ToLowercaseString()} {model.ClassName}({model.SourceClassName} {sourceClassParameterName}){baseConstructor}")
.WriteOpeningBracket() .WriteOpeningBracket()
.WriteLine($"if ({sourceClassParameterName} == null) throw new ArgumentNullException(nameof({sourceClassParameterName}));") .WriteLine($"if ({sourceClassParameterName} == null) throw new ArgumentNullException(nameof({sourceClassParameterName}));")
.WriteLine(); .WriteLine();
@ -73,9 +73,16 @@ namespace MapTo.Sources
{ {
if (property.TypeConverter is null) if (property.TypeConverter is null)
{ {
builder.WriteLine(property.MappedSourcePropertyTypeName is null if (property.IsEnumerable)
? $"{property.Name} = {sourceClassParameterName}.{property.SourcePropertyName};" {
: $"{property.Name} = {sourceClassParameterName}.{property.SourcePropertyName}.To{property.Type}();"); builder.WriteLine($"{property.Name} = {sourceClassParameterName}.{property.SourcePropertyName}.Select({property.MappedSourcePropertyTypeName}To{property.EnumerableTypeArgument}Extensions.To{property.EnumerableTypeArgument}).ToList();");
}
else
{
builder.WriteLine(property.MappedSourcePropertyTypeName is null
? $"{property.Name} = {sourceClassParameterName}.{property.SourcePropertyName};"
: $"{property.Name} = {sourceClassParameterName}.{property.SourcePropertyName}.To{property.Type}();");
}
} }
else else
{ {
@ -98,7 +105,7 @@ namespace MapTo.Sources
return builder return builder
.GenerateConvertorMethodsXmlDocs(model, sourceClassParameterName) .GenerateConvertorMethodsXmlDocs(model, sourceClassParameterName)
.WriteLineIf(model.Options.SupportNullableReferenceTypes, $"[return: NotNullIfNotNull(\"{sourceClassParameterName}\")]") .WriteLineIf(model.Options.SupportNullableReferenceTypes, $"[return: NotNullIfNotNull(\"{sourceClassParameterName}\")]")
.WriteLine($"{model.Options.GeneratedMethodsAccessModifier.ToLowercaseString()} static {model.ClassName}{model.Options.NullableReferenceSyntax} From({model.SourceClassFullName}{model.Options.NullableReferenceSyntax} {sourceClassParameterName})") .WriteLine($"{model.Options.GeneratedMethodsAccessModifier.ToLowercaseString()} static {model.ClassName}{model.Options.NullableReferenceSyntax} From({model.SourceClassName}{model.Options.NullableReferenceSyntax} {sourceClassParameterName})")
.WriteOpeningBracket() .WriteOpeningBracket()
.WriteLine($"return {sourceClassParameterName} == null ? null : new {model.ClassName}({sourceClassParameterName});") .WriteLine($"return {sourceClassParameterName} == null ? null : new {model.ClassName}({sourceClassParameterName});")
.WriteClosingBracket(); .WriteClosingBracket();
@ -136,7 +143,7 @@ namespace MapTo.Sources
return builder return builder
.GenerateConvertorMethodsXmlDocs(model, sourceClassParameterName) .GenerateConvertorMethodsXmlDocs(model, sourceClassParameterName)
.WriteLineIf(model.Options.SupportNullableReferenceTypes, $"[return: NotNullIfNotNull(\"{sourceClassParameterName}\")]") .WriteLineIf(model.Options.SupportNullableReferenceTypes, $"[return: NotNullIfNotNull(\"{sourceClassParameterName}\")]")
.WriteLine($"{model.Options.GeneratedMethodsAccessModifier.ToLowercaseString()} static {model.ClassName}{model.Options.NullableReferenceSyntax} To{model.ClassName}(this {model.SourceClassFullName}{model.Options.NullableReferenceSyntax} {sourceClassParameterName})") .WriteLine($"{model.Options.GeneratedMethodsAccessModifier.ToLowercaseString()} static {model.ClassName}{model.Options.NullableReferenceSyntax} To{model.ClassName}(this {model.SourceClassName}{model.Options.NullableReferenceSyntax} {sourceClassParameterName})")
.WriteOpeningBracket() .WriteOpeningBracket()
.WriteLine($"return {sourceClassParameterName} == null ? null : new {model.ClassName}({sourceClassParameterName});") .WriteLine($"return {sourceClassParameterName} == null ? null : new {model.ClassName}({sourceClassParameterName});")
.WriteClosingBracket(); .WriteClosingBracket();

View File

@ -1,7 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text;
using MapTo.Extensions; using MapTo.Extensions;
using MapTo.Sources; using MapTo.Sources;
using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis;
@ -96,11 +95,19 @@ namespace MapTo.Tests
return builder.ToString(); return builder.ToString();
} }
internal static string[] GetEmployeeManagerSourceText(Func<string> employeeClassSource = null, Func<string> managerClassSource = null, Func<string> employeeViewModelSource = null, Func<string> managerViewModelSource = null) internal static string[] GetEmployeeManagerSourceText(Func<string> employeeClassSource = null, Func<string> managerClassSource = null, Func<string> employeeViewModelSource = null, Func<string> managerViewModelSource = null, bool useDifferentViewModelNamespace = false)
{ {
return new[] return new[]
{ {
employeeClassSource?.Invoke() ?? @" employeeClassSource?.Invoke() ?? DefaultEmployeeClassSource(),
managerClassSource?.Invoke() ?? DefaultManagerClassSource(),
employeeViewModelSource?.Invoke() ??
DefaultEmployeeViewModelSource(useDifferentViewModelNamespace),
managerViewModelSource?.Invoke() ?? DefaultManagerViewModelSource(useDifferentViewModelNamespace)
};
static string DefaultEmployeeClassSource() =>
@"
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text; using System.Text;
@ -115,8 +122,10 @@ namespace Test.Data.Models
public Manager Manager { get; set; } public Manager Manager { get; set; }
} }
}".Trim(), }".Trim();
managerClassSource?.Invoke() ?? @"using System;
static string DefaultManagerClassSource() =>
@"using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text; using System.Text;
@ -129,8 +138,28 @@ namespace Test.Data.Models
public IEnumerable<Employee> Employees { get; set; } = Array.Empty<Employee>(); public IEnumerable<Employee> Employees { get; set; } = Array.Empty<Employee>();
} }
} }
".Trim(), ".Trim();
employeeViewModelSource?.Invoke() ?? @"
static string DefaultEmployeeViewModelSource(bool useDifferentNamespace) => useDifferentNamespace
? @"
using MapTo;
using Test.Data.Models;
using Test.ViewModels2;
namespace Test.ViewModels
{
[MapFrom(typeof(Employee))]
public partial class EmployeeViewModel
{
public int Id { get; set; }
public string EmployeeCode { get; set; }
public ManagerViewModel Manager { get; set; }
}
}
".Trim()
: @"
using MapTo; using MapTo;
using Test.Data.Models; using Test.Data.Models;
@ -146,8 +175,28 @@ namespace Test.ViewModels
public ManagerViewModel Manager { get; set; } public ManagerViewModel Manager { get; set; }
} }
} }
".Trim(), ".Trim();
managerViewModelSource?.Invoke() ?? @"
static string DefaultManagerViewModelSource(bool useDifferentNamespace) => useDifferentNamespace
? @"
using System;
using System.Collections.Generic;
using MapTo;
using Test.Data.Models;
using Test.ViewModels;
namespace Test.ViewModels2
{
[MapFrom(typeof(Manager))]
public partial class ManagerViewModel : EmployeeViewModel
{
public int Level { get; set; }
public IEnumerable<EmployeeViewModel> Employees { get; set; } = Array.Empty<EmployeeViewModel>();
}
}
".Trim()
: @"
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using MapTo; using MapTo;
@ -162,8 +211,7 @@ namespace Test.ViewModels
public IEnumerable<EmployeeViewModel> Employees { get; set; } = Array.Empty<EmployeeViewModel>(); public IEnumerable<EmployeeViewModel> Employees { get; set; } = Array.Empty<EmployeeViewModel>();
} }
}".Trim() }".Trim();
};
} }
internal static PropertyDeclarationSyntax GetPropertyDeclarationSyntax(SyntaxTree syntaxTree, string targetPropertyName, string targetClass = "Foo") internal static PropertyDeclarationSyntax GetPropertyDeclarationSyntax(SyntaxTree syntaxTree, string targetPropertyName, string targetClass = "Foo")

View File

@ -52,7 +52,7 @@ namespace MapTo
var expectedResult = @" var expectedResult = @"
partial class Foo partial class Foo
{ {
public Foo(Test.Models.Baz baz) public Foo(Baz baz)
{ {
if (baz == null) throw new ArgumentNullException(nameof(baz)); if (baz == null) throw new ArgumentNullException(nameof(baz));

View File

@ -40,7 +40,7 @@ namespace MapTo.Tests.Infrastructure
driver.RunGeneratorsAndUpdateCompilation(compilation, out var outputCompilation, out var generateDiagnostics); driver.RunGeneratorsAndUpdateCompilation(compilation, out var outputCompilation, out var generateDiagnostics);
generateDiagnostics.ShouldBeSuccessful(); generateDiagnostics.ShouldBeSuccessful(ignoreDiagnosticsIds: new[] { "MT" });
outputCompilation.GetDiagnostics().ShouldBeSuccessful(outputCompilation); outputCompilation.GetDiagnostics().ShouldBeSuccessful(outputCompilation);
return (outputCompilation, generateDiagnostics); return (outputCompilation, generateDiagnostics);

View File

@ -60,7 +60,7 @@ namespace MapTo
var expectedResult = @" var expectedResult = @"
partial class Foo partial class Foo
{ {
public Foo(Test.Models.Baz baz) public Foo(Baz baz)
{ {
if (baz == null) throw new ArgumentNullException(nameof(baz)); if (baz == null) throw new ArgumentNullException(nameof(baz));

View File

@ -75,14 +75,14 @@ namespace MapTo
var expectedExtension = @" var expectedExtension = @"
internal static partial class BazToFooExtensions internal static partial class BazToFooExtensions
{ {
internal static Foo ToFoo(this Test.Models.Baz baz) internal static Foo ToFoo(this Baz baz)
{ {
return baz == null ? null : new Foo(baz); return baz == null ? null : new Foo(baz);
} }
}".Trim(); }".Trim();
var expectedFactory = @" var expectedFactory = @"
internal static Foo From(Test.Models.Baz baz) internal static Foo From(Baz baz)
{ {
return baz == null ? null : new Foo(baz); return baz == null ? null : new Foo(baz);
}".Trim(); }".Trim();
@ -129,7 +129,7 @@ namespace Test
{ {
partial class Foo partial class Foo
{ {
public Foo(Test.Baz baz) public Foo(Baz baz)
{ {
if (baz == null) throw new ArgumentNullException(nameof(baz)); if (baz == null) throw new ArgumentNullException(nameof(baz));
@ -199,7 +199,7 @@ namespace Test
{ {
partial class Foo partial class Foo
{ {
public Foo(Test.Baz baz) public Foo(Baz baz)
{ {
if (baz == null) throw new ArgumentNullException(nameof(baz)); if (baz == null) throw new ArgumentNullException(nameof(baz));
@ -249,6 +249,7 @@ namespace Test
const string expectedResult = @" const string expectedResult = @"
// <auto-generated /> // <auto-generated />
using Bazaar;
using System; using System;
namespace Test namespace Test
@ -271,7 +272,7 @@ namespace Test
const string expectedResult = @" const string expectedResult = @"
partial class Foo partial class Foo
{ {
public Foo(Test.Models.Baz baz) public Foo(Baz baz)
{ {
if (baz == null) throw new ArgumentNullException(nameof(baz)); if (baz == null) throw new ArgumentNullException(nameof(baz));
@ -296,7 +297,7 @@ namespace Test
var source = GetSourceText(); var source = GetSourceText();
const string expectedResult = @" const string expectedResult = @"
public static Foo From(Test.Models.Baz baz) public static Foo From(Baz baz)
{ {
return baz == null ? null : new Foo(baz); return baz == null ? null : new Foo(baz);
} }
@ -319,7 +320,7 @@ namespace Test
const string expectedResult = @" const string expectedResult = @"
public static partial class BazToFooExtensions public static partial class BazToFooExtensions
{ {
public static Foo ToFoo(this Test.Models.Baz baz) public static Foo ToFoo(this Baz baz)
{ {
return baz == null ? null : new Foo(baz); return baz == null ? null : new Foo(baz);
} }
@ -356,7 +357,7 @@ namespace Test
var expectedResult = @" var expectedResult = @"
partial class Foo partial class Foo
{ {
public Foo(Test.Baz baz) public Foo(Baz baz)
{ {
if (baz == null) throw new ArgumentNullException(nameof(baz)); if (baz == null) throw new ArgumentNullException(nameof(baz));
@ -442,7 +443,7 @@ namespace Test
const string expectedResult = @" const string expectedResult = @"
partial class Foo partial class Foo
{ {
public Foo(Test.Models.Baz baz) public Foo(Baz baz)
{ {
if (baz == null) throw new ArgumentNullException(nameof(baz)); if (baz == null) throw new ArgumentNullException(nameof(baz));
@ -468,12 +469,76 @@ namespace Test
var sources = GetEmployeeManagerSourceText(); var sources = GetEmployeeManagerSourceText();
const string expectedResult = @" const string expectedResult = @"
public ManagerViewModel(Test.Data.Models.Manager manager) : base(manager) public ManagerViewModel(Manager manager) : base(manager)
{ {
if (manager == null) throw new ArgumentNullException(nameof(manager)); if (manager == null) throw new ArgumentNullException(nameof(manager));
Level = manager.Level; Level = manager.Level;
} ";
// Act
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(sources, analyzerConfigOptions: DefaultAnalyzerOptions);
// Assert
diagnostics.ShouldBeSuccessful();
compilation.SyntaxTrees.Last().ShouldContainPartialSource(expectedResult);
}
[Fact]
public void When_SourceTypeHasEnumerablePropertiesWithMapFromAttribute_Should_CreateANewEnumerableWithMappedObjects()
{
// Arrange
var sources = GetEmployeeManagerSourceText();
const string expectedResult = @"
using System;
using System.Collections.Generic;
using System.Linq;
using Test.Data.Models;
namespace Test.ViewModels
{
partial class ManagerViewModel
{
public ManagerViewModel(Manager manager) : base(manager)
{
if (manager == null) throw new ArgumentNullException(nameof(manager));
Level = manager.Level;
Employees = manager.Employees.Select(EmployeeToEmployeeViewModelExtensions.ToEmployeeViewModel).ToList();
";
// Act
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(sources, analyzerConfigOptions: DefaultAnalyzerOptions);
// Assert
diagnostics.ShouldBeSuccessful();
compilation.SyntaxTrees.Last().ShouldContainPartialSource(expectedResult);
}
[Fact]
public void When_SourceTypeHasEnumerablePropertiesWithMapFromAttributeInDifferentNamespaces_Should_CreateANewEnumerableWithMappedObjectsAndImportNamespace()
{
// Arrange
var sources = GetEmployeeManagerSourceText(useDifferentViewModelNamespace: true);
const string expectedResult = @"
using System;
using System.Collections.Generic;
using System.Linq;
using Test.Data.Models;
using Test.ViewModels;
namespace Test.ViewModels2
{
partial class ManagerViewModel
{
public ManagerViewModel(Manager manager) : base(manager)
{
if (manager == null) throw new ArgumentNullException(nameof(manager));
Level = manager.Level;
Employees = manager.Employees.Select(EmployeeToEmployeeViewModelExtensions.ToEmployeeViewModel).ToList();
"; ";
// Act // Act

View File

@ -219,7 +219,7 @@ namespace Test
var expectedResult = @" var expectedResult = @"
partial class Foo partial class Foo
{ {
public Foo(Test.Models.Baz baz) public Foo(Baz baz)
{ {
if (baz == null) throw new ArgumentNullException(nameof(baz)); if (baz == null) throw new ArgumentNullException(nameof(baz));

View File

@ -1,6 +1,7 @@
using System; using System;
using TestConsoleApp.Data.Models; using TestConsoleApp.Data.Models;
using TestConsoleApp.ViewModels; using TestConsoleApp.ViewModels;
using TestConsoleApp.ViewModels2;
namespace TestConsoleApp namespace TestConsoleApp
{ {
@ -41,8 +42,8 @@ namespace TestConsoleApp
manager1.Employees = new[] { employee1, manager2 }; manager1.Employees = new[] { employee1, manager2 };
manager2.Employees = new[] { employee2 }; manager2.Employees = new[] { employee2 };
var manager1ViewModel = manager1.ToManagerViewModel(); manager1.ToManagerViewModel();
int a = 0; employee1.ToEmployeeViewModel();
} }
private static void UserTest() private static void UserTest()

View File

@ -1,5 +1,6 @@
using MapTo; using MapTo;
using TestConsoleApp.Data.Models; using TestConsoleApp.Data.Models;
using TestConsoleApp.ViewModels2;
namespace TestConsoleApp.ViewModels namespace TestConsoleApp.ViewModels
{ {

View File

@ -2,8 +2,9 @@
using System.Collections.Generic; using System.Collections.Generic;
using MapTo; using MapTo;
using TestConsoleApp.Data.Models; using TestConsoleApp.Data.Models;
using TestConsoleApp.ViewModels;
namespace TestConsoleApp.ViewModels namespace TestConsoleApp.ViewModels2
{ {
[MapFrom(typeof(Manager))] [MapFrom(typeof(Manager))]
public partial class ManagerViewModel : EmployeeViewModel public partial class ManagerViewModel : EmployeeViewModel