From c1a763a4744a2eebddcfaeb1cdb63e2216ba37e6 Mon Sep 17 00:00:00 2001 From: Mohammadreza Taikandi Date: Wed, 17 Feb 2021 08:32:23 +0000 Subject: [PATCH] Simplified using statements and fix nested mappings. --- src/MapTo/Extensions/RoslynExtensions.cs | 9 +- src/MapTo/MappingContext.cs | 95 +++++++++++-------- src/MapTo/Models.cs | 13 ++- src/MapTo/Sources/MapClassSource.cs | 27 ++++-- test/MapTo.Tests/Common.cs | 70 +++++++++++--- .../IgnorePropertyAttributeTests.cs | 2 +- .../Infrastructure/CSharpGenerator.cs | 2 +- test/MapTo.Tests/MapPropertyTests.cs | 2 +- test/MapTo.Tests/MapToTests.cs | 87 ++++++++++++++--- test/MapTo.Tests/MapTypeConverterTests.cs | 2 +- test/TestConsoleApp/Program.cs | 5 +- .../ViewModels/EmployeeViewModel.cs | 1 + .../ViewModels/ManagerViewModel.cs | 3 +- 13 files changed, 234 insertions(+), 84 deletions(-) diff --git a/src/MapTo/Extensions/RoslynExtensions.cs b/src/MapTo/Extensions/RoslynExtensions.cs index e74f288..6d35e1c 100644 --- a/src/MapTo/Extensions/RoslynExtensions.cs +++ b/src/MapTo/Extensions/RoslynExtensions.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using MapTo.Sources; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -68,7 +67,13 @@ namespace MapTo.Extensions 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."); + + 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; } } \ No newline at end of file diff --git a/src/MapTo/MappingContext.cs b/src/MapTo/MappingContext.cs index 12759f1..445fa05 100644 --- a/src/MapTo/MappingContext.cs +++ b/src/MapTo/MappingContext.cs @@ -12,16 +12,19 @@ namespace MapTo { private readonly ClassDeclarationSyntax _classSyntax; private readonly Compilation _compilation; + private readonly List _diagnostics; private readonly INamedTypeSymbol _ignorePropertyAttributeTypeSymbol; private readonly INamedTypeSymbol _mapFromAttributeTypeSymbol; private readonly INamedTypeSymbol _mapPropertyAttributeTypeSymbol; private readonly INamedTypeSymbol _mapTypeConverterAttributeTypeSymbol; private readonly SourceGenerationOptions _sourceGenerationOptions; private readonly INamedTypeSymbol _typeConverterInterfaceTypeSymbol; + private readonly List _usings; internal MappingContext(Compilation compilation, SourceGenerationOptions sourceGenerationOptions, ClassDeclarationSyntax classSyntax) { - Diagnostics = ImmutableArray.Empty; + _diagnostics = new List(); + _usings = new List { "System" }; _sourceGenerationOptions = sourceGenerationOptions; _classSyntax = classSyntax; _compilation = compilation; @@ -32,26 +35,28 @@ namespace MapTo _mapPropertyAttributeTypeSymbol = compilation.GetTypeByMetadataNameOrThrow(MapPropertyAttributeSource.FullyQualifiedName); _mapFromAttributeTypeSymbol = compilation.GetTypeByMetadataNameOrThrow(MapFromAttributeSource.FullyQualifiedName); + AddUsingIfRequired(sourceGenerationOptions.SupportNullableReferenceTypes, "System.Diagnostics.CodeAnalysis"); + Initialize(); } public MappingModel? Model { get; private set; } - public ImmutableArray Diagnostics { get; private set; } + public IEnumerable Diagnostics => _diagnostics; private void Initialize() { var semanticModel = _compilation.GetSemanticModel(_classSyntax.SyntaxTree); 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; } var sourceTypeSymbol = GetSourceTypeSymbol(_classSyntax, semanticModel); if (sourceTypeSymbol is null) { - ReportDiagnostic(DiagnosticProvider.MapFromAttributeNotFoundError(_classSyntax.GetLocation())); + _diagnostics.Add(DiagnosticProvider.MapFromAttributeNotFoundError(_classSyntax.GetLocation())); return; } @@ -62,10 +67,13 @@ namespace MapTo var mappedProperties = GetMappedProperties(classTypeSymbol, sourceTypeSymbol, isClassInheritFromMappedBaseClass); if (!mappedProperties.Any()) { - ReportDiagnostic(DiagnosticProvider.NoMatchingPropertyFoundError(_classSyntax.GetLocation(), classTypeSymbol, sourceTypeSymbol)); + _diagnostics.Add(DiagnosticProvider.NoMatchingPropertyFoundError(_classSyntax.GetLocation(), classTypeSymbol, sourceTypeSymbol)); return; } + AddUsingIfRequired(sourceTypeSymbol); + AddUsingIfRequired(mappedProperties.Any(p => p.IsEnumerable), "System.Linq"); + Model = new MappingModel( _sourceGenerationOptions, _classSyntax.GetNamespace(), @@ -75,7 +83,8 @@ namespace MapTo sourceClassName, sourceTypeSymbol.ToString(), mappedProperties.ToImmutableArray(), - isClassInheritFromMappedBaseClass); + isClassInheritFromMappedBaseClass, + _usings.ToImmutableArray()); } private bool IsClassInheritFromMappedBaseClass(SemanticModel semanticModel) @@ -103,60 +112,73 @@ namespace MapTo string? converterFullyQualifiedName = null; var converterParameters = ImmutableArray.Empty; - string? mappedSourcePropertyType = null; + ITypeSymbol? mappedSourcePropertyType = null; + ITypeSymbol? enumerableTypeArgumentType = null; if (!_compilation.HasCompatibleTypes(sourceProperty, property)) { if (!TryGetMapTypeConverter(property, sourceProperty, out converterFullyQualifiedName, out converterParameters) && - !TryGetNestedObjectMappings(property, out mappedSourcePropertyType)) + !TryGetNestedObjectMappings(property, out mappedSourcePropertyType, out enumerableTypeArgumentType)) { continue; } } - mappedProperties.Add(new MappedProperty( - property.Name, - property.Type.Name, - converterFullyQualifiedName, - converterParameters.ToImmutableArray(), - sourceProperty.Name, - mappedSourcePropertyType)); + AddUsingIfRequired(property.Type); + AddUsingIfRequired(sourceTypeSymbol); + AddUsingIfRequired(enumerableTypeArgumentType); + AddUsingIfRequired(mappedSourcePropertyType); + + mappedProperties.Add( + new MappedProperty( + property.Name, + property.Type.Name, + converterFullyQualifiedName, + converterParameters.ToImmutableArray(), + sourceProperty.Name, + mappedSourcePropertyType?.Name, + enumerableTypeArgumentType?.Name)); } return mappedProperties.ToImmutableArray(); } - private bool TryGetNestedObjectMappings(IPropertySymbol property, out string? mappedSourcePropertyType) + private bool TryGetNestedObjectMappings(IPropertySymbol property, out ITypeSymbol? mappedSourcePropertyType, out ITypeSymbol? enumerableTypeArgument) { mappedSourcePropertyType = null; + enumerableTypeArgument = null; - if (!Diagnostics.IsEmpty) + if (!_diagnostics.IsEmpty()) { return false; } - var nestedSourceMapFromAttribute = property.Type.GetAttribute(_mapFromAttributeTypeSymbol); - if (nestedSourceMapFromAttribute is null) + var mapFromAttribute = property.Type.GetAttribute(_mapFromAttributeTypeSymbol); + if (mapFromAttribute is null && property.Type is INamedTypeSymbol namedTypeSymbol && _compilation.IsGenericEnumerable(property.Type)) { - ReportDiagnostic(DiagnosticProvider.NoMatchingPropertyTypeFoundError(property)); - return false; + enumerableTypeArgument = namedTypeSymbol.TypeArguments.First(); + 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)); - return false; + _diagnostics.Add(DiagnosticProvider.NoMatchingPropertyTypeFoundError(property)); } - var nestedSourceTypeSymbol = GetSourceTypeSymbol(nestedAttributeSyntax); - if (nestedSourceTypeSymbol is null) - { - ReportDiagnostic(DiagnosticProvider.NoMatchingPropertyTypeFoundError(property)); - return false; - } + return _diagnostics.IsEmpty(); + } - mappedSourcePropertyType = nestedSourceTypeSymbol.Name; - return true; + private void AddUsingIfRequired(ISymbol? namedTypeSymbol) => + 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 converterParameters) @@ -164,7 +186,7 @@ namespace MapTo converterFullyQualifiedName = null; converterParameters = ImmutableArray.Empty; - if (!Diagnostics.IsEmpty) + if (!_diagnostics.IsEmpty()) { return false; } @@ -178,7 +200,7 @@ namespace MapTo var baseInterface = GetTypeConverterBaseInterface(converterTypeSymbol, property, sourceProperty); if (baseInterface is null) { - ReportDiagnostic(DiagnosticProvider.InvalidTypeConverterGenericTypesError(property, sourceProperty)); + _diagnostics.Add(DiagnosticProvider.InvalidTypeConverterGenericTypesError(property, sourceProperty)); return false; } @@ -216,11 +238,6 @@ namespace MapTo : 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) => GetSourceTypeSymbol(classDeclarationSyntax.GetAttribute(MapFromAttributeSource.AttributeName), semanticModel); diff --git a/src/MapTo/Models.cs b/src/MapTo/Models.cs index d30f547..f359673 100644 --- a/src/MapTo/Models.cs +++ b/src/MapTo/Models.cs @@ -8,12 +8,16 @@ namespace MapTo internal record SourceCode(string Text, string HintName); internal record MappedProperty( - string Name, + string Name, string Type, - string? TypeConverter, + string? TypeConverter, ImmutableArray TypeConverterParameters, string SourcePropertyName, - string? MappedSourcePropertyTypeName); + string? MappedSourcePropertyTypeName, + string? EnumerableTypeArgument) + { + public bool IsEnumerable => EnumerableTypeArgument is not null; + } internal record MappingModel ( SourceGenerationOptions Options, @@ -24,7 +28,8 @@ namespace MapTo string SourceClassName, string SourceClassFullName, ImmutableArray MappedProperties, - bool HasMappedBaseClass + bool HasMappedBaseClass, + ImmutableArray Usings ); internal record SourceGenerationOptions( diff --git a/src/MapTo/Sources/MapClassSource.cs b/src/MapTo/Sources/MapClassSource.cs index 72dbb9e..ae97ad7 100644 --- a/src/MapTo/Sources/MapClassSource.cs +++ b/src/MapTo/Sources/MapClassSource.cs @@ -1,4 +1,5 @@ -using MapTo.Extensions; +using System.Linq; +using MapTo.Extensions; using static MapTo.Sources.Constants; namespace MapTo.Sources @@ -42,9 +43,8 @@ namespace MapTo.Sources private static SourceBuilder WriteUsings(this SourceBuilder builder, MappingModel model) { - return builder - .WriteLine("using System;") - .WriteLineIf(model.Options.SupportNullableReferenceTypes, "using System.Diagnostics.CodeAnalysis;"); + model.Usings.Sort().ForEach(u => builder.WriteLine($"using {u};")); + return builder; } private static SourceBuilder GenerateConstructor(this SourceBuilder builder, MappingModel model) @@ -64,7 +64,7 @@ namespace MapTo.Sources var baseConstructor = model.HasMappedBaseClass ? $" : base({sourceClassParameterName})" : string.Empty; builder - .WriteLine($"{model.Options.ConstructorAccessModifier.ToLowercaseString()} {model.ClassName}({model.SourceClassFullName} {sourceClassParameterName}){baseConstructor}") + .WriteLine($"{model.Options.ConstructorAccessModifier.ToLowercaseString()} {model.ClassName}({model.SourceClassName} {sourceClassParameterName}){baseConstructor}") .WriteOpeningBracket() .WriteLine($"if ({sourceClassParameterName} == null) throw new ArgumentNullException(nameof({sourceClassParameterName}));") .WriteLine(); @@ -73,9 +73,16 @@ namespace MapTo.Sources { if (property.TypeConverter is null) { - builder.WriteLine(property.MappedSourcePropertyTypeName is null - ? $"{property.Name} = {sourceClassParameterName}.{property.SourcePropertyName};" - : $"{property.Name} = {sourceClassParameterName}.{property.SourcePropertyName}.To{property.Type}();"); + if (property.IsEnumerable) + { + 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 { @@ -98,7 +105,7 @@ namespace MapTo.Sources return builder .GenerateConvertorMethodsXmlDocs(model, 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() .WriteLine($"return {sourceClassParameterName} == null ? null : new {model.ClassName}({sourceClassParameterName});") .WriteClosingBracket(); @@ -136,7 +143,7 @@ namespace MapTo.Sources return builder .GenerateConvertorMethodsXmlDocs(model, 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() .WriteLine($"return {sourceClassParameterName} == null ? null : new {model.ClassName}({sourceClassParameterName});") .WriteClosingBracket(); diff --git a/test/MapTo.Tests/Common.cs b/test/MapTo.Tests/Common.cs index dba0663..374c489 100644 --- a/test/MapTo.Tests/Common.cs +++ b/test/MapTo.Tests/Common.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Text; using MapTo.Extensions; using MapTo.Sources; using Microsoft.CodeAnalysis; @@ -96,11 +95,19 @@ namespace MapTo.Tests return builder.ToString(); } - internal static string[] GetEmployeeManagerSourceText(Func employeeClassSource = null, Func managerClassSource = null, Func employeeViewModelSource = null, Func managerViewModelSource = null) + internal static string[] GetEmployeeManagerSourceText(Func employeeClassSource = null, Func managerClassSource = null, Func employeeViewModelSource = null, Func managerViewModelSource = null, bool useDifferentViewModelNamespace = false) { 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.Collections.Generic; using System.Text; @@ -115,8 +122,10 @@ namespace Test.Data.Models public Manager Manager { get; set; } } -}".Trim(), - managerClassSource?.Invoke() ?? @"using System; +}".Trim(); + + static string DefaultManagerClassSource() => + @"using System; using System.Collections.Generic; using System.Text; @@ -129,8 +138,28 @@ namespace Test.Data.Models public IEnumerable Employees { get; set; } = Array.Empty(); } } -".Trim(), - employeeViewModelSource?.Invoke() ?? @" +".Trim(); + + 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 Test.Data.Models; @@ -146,8 +175,28 @@ namespace Test.ViewModels public ManagerViewModel Manager { get; set; } } } -".Trim(), - managerViewModelSource?.Invoke() ?? @" +".Trim(); + + 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 Employees { get; set; } = Array.Empty(); + } +} +".Trim() + : @" using System; using System.Collections.Generic; using MapTo; @@ -162,8 +211,7 @@ namespace Test.ViewModels public IEnumerable Employees { get; set; } = Array.Empty(); } -}".Trim() - }; +}".Trim(); } internal static PropertyDeclarationSyntax GetPropertyDeclarationSyntax(SyntaxTree syntaxTree, string targetPropertyName, string targetClass = "Foo") diff --git a/test/MapTo.Tests/IgnorePropertyAttributeTests.cs b/test/MapTo.Tests/IgnorePropertyAttributeTests.cs index 95fa946..f92f56d 100644 --- a/test/MapTo.Tests/IgnorePropertyAttributeTests.cs +++ b/test/MapTo.Tests/IgnorePropertyAttributeTests.cs @@ -52,7 +52,7 @@ namespace MapTo var expectedResult = @" partial class Foo { - public Foo(Test.Models.Baz baz) + public Foo(Baz baz) { if (baz == null) throw new ArgumentNullException(nameof(baz)); diff --git a/test/MapTo.Tests/Infrastructure/CSharpGenerator.cs b/test/MapTo.Tests/Infrastructure/CSharpGenerator.cs index 1786c95..c5c8d67 100644 --- a/test/MapTo.Tests/Infrastructure/CSharpGenerator.cs +++ b/test/MapTo.Tests/Infrastructure/CSharpGenerator.cs @@ -40,7 +40,7 @@ namespace MapTo.Tests.Infrastructure driver.RunGeneratorsAndUpdateCompilation(compilation, out var outputCompilation, out var generateDiagnostics); - generateDiagnostics.ShouldBeSuccessful(); + generateDiagnostics.ShouldBeSuccessful(ignoreDiagnosticsIds: new[] { "MT" }); outputCompilation.GetDiagnostics().ShouldBeSuccessful(outputCompilation); return (outputCompilation, generateDiagnostics); diff --git a/test/MapTo.Tests/MapPropertyTests.cs b/test/MapTo.Tests/MapPropertyTests.cs index 66f238c..bcd70e6 100644 --- a/test/MapTo.Tests/MapPropertyTests.cs +++ b/test/MapTo.Tests/MapPropertyTests.cs @@ -60,7 +60,7 @@ namespace MapTo var expectedResult = @" partial class Foo { - public Foo(Test.Models.Baz baz) + public Foo(Baz baz) { if (baz == null) throw new ArgumentNullException(nameof(baz)); diff --git a/test/MapTo.Tests/MapToTests.cs b/test/MapTo.Tests/MapToTests.cs index 5602ee3..c7817e5 100644 --- a/test/MapTo.Tests/MapToTests.cs +++ b/test/MapTo.Tests/MapToTests.cs @@ -75,14 +75,14 @@ namespace MapTo var expectedExtension = @" 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); } }".Trim(); var expectedFactory = @" - internal static Foo From(Test.Models.Baz baz) + internal static Foo From(Baz baz) { return baz == null ? null : new Foo(baz); }".Trim(); @@ -129,7 +129,7 @@ namespace Test { partial class Foo { - public Foo(Test.Baz baz) + public Foo(Baz baz) { if (baz == null) throw new ArgumentNullException(nameof(baz)); @@ -199,7 +199,7 @@ namespace Test { partial class Foo { - public Foo(Test.Baz baz) + public Foo(Baz baz) { if (baz == null) throw new ArgumentNullException(nameof(baz)); @@ -249,6 +249,7 @@ namespace Test const string expectedResult = @" // +using Bazaar; using System; namespace Test @@ -271,7 +272,7 @@ namespace Test const string expectedResult = @" partial class Foo { - public Foo(Test.Models.Baz baz) + public Foo(Baz baz) { if (baz == null) throw new ArgumentNullException(nameof(baz)); @@ -296,7 +297,7 @@ namespace Test var source = GetSourceText(); const string expectedResult = @" - public static Foo From(Test.Models.Baz baz) + public static Foo From(Baz baz) { return baz == null ? null : new Foo(baz); } @@ -319,7 +320,7 @@ namespace Test const string expectedResult = @" 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); } @@ -356,7 +357,7 @@ namespace Test var expectedResult = @" partial class Foo { - public Foo(Test.Baz baz) + public Foo(Baz baz) { if (baz == null) throw new ArgumentNullException(nameof(baz)); @@ -442,7 +443,7 @@ namespace Test const string expectedResult = @" partial class Foo { - public Foo(Test.Models.Baz baz) + public Foo(Baz baz) { if (baz == null) throw new ArgumentNullException(nameof(baz)); @@ -468,12 +469,76 @@ namespace Test var sources = GetEmployeeManagerSourceText(); 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)); 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 diff --git a/test/MapTo.Tests/MapTypeConverterTests.cs b/test/MapTo.Tests/MapTypeConverterTests.cs index 83bfc8e..d4f3863 100644 --- a/test/MapTo.Tests/MapTypeConverterTests.cs +++ b/test/MapTo.Tests/MapTypeConverterTests.cs @@ -219,7 +219,7 @@ namespace Test var expectedResult = @" partial class Foo { - public Foo(Test.Models.Baz baz) + public Foo(Baz baz) { if (baz == null) throw new ArgumentNullException(nameof(baz)); diff --git a/test/TestConsoleApp/Program.cs b/test/TestConsoleApp/Program.cs index c73e36e..4a70c5f 100644 --- a/test/TestConsoleApp/Program.cs +++ b/test/TestConsoleApp/Program.cs @@ -1,6 +1,7 @@ using System; using TestConsoleApp.Data.Models; using TestConsoleApp.ViewModels; +using TestConsoleApp.ViewModels2; namespace TestConsoleApp { @@ -41,8 +42,8 @@ namespace TestConsoleApp manager1.Employees = new[] { employee1, manager2 }; manager2.Employees = new[] { employee2 }; - var manager1ViewModel = manager1.ToManagerViewModel(); - int a = 0; + manager1.ToManagerViewModel(); + employee1.ToEmployeeViewModel(); } private static void UserTest() diff --git a/test/TestConsoleApp/ViewModels/EmployeeViewModel.cs b/test/TestConsoleApp/ViewModels/EmployeeViewModel.cs index 7baa3ea..06eeccd 100644 --- a/test/TestConsoleApp/ViewModels/EmployeeViewModel.cs +++ b/test/TestConsoleApp/ViewModels/EmployeeViewModel.cs @@ -1,5 +1,6 @@ using MapTo; using TestConsoleApp.Data.Models; +using TestConsoleApp.ViewModels2; namespace TestConsoleApp.ViewModels { diff --git a/test/TestConsoleApp/ViewModels/ManagerViewModel.cs b/test/TestConsoleApp/ViewModels/ManagerViewModel.cs index e2c5518..dc5e660 100644 --- a/test/TestConsoleApp/ViewModels/ManagerViewModel.cs +++ b/test/TestConsoleApp/ViewModels/ManagerViewModel.cs @@ -2,8 +2,9 @@ using System.Collections.Generic; using MapTo; using TestConsoleApp.Data.Models; +using TestConsoleApp.ViewModels; -namespace TestConsoleApp.ViewModels +namespace TestConsoleApp.ViewModels2 { [MapFrom(typeof(Manager))] public partial class ManagerViewModel : EmployeeViewModel