bump mapto
This commit is contained in:
parent
c9b2b5a652
commit
c1dd14a38d
|
@ -2,12 +2,6 @@
|
||||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MapTo", "src\MapTo\MapTo.csproj", "{4DB371AC-48D0-4F01-8EF3-7707D06EF0A7}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MapTo", "src\MapTo\MapTo.csproj", "{4DB371AC-48D0-4F01-8EF3-7707D06EF0A7}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MapToTests", "test\MapTo.Tests\MapTo.Tests.csproj", "{797DA57B-AC7E-468B-8799-44C5A574C0E3}"
|
|
||||||
EndProject
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestConsoleApp", "test\TestConsoleApp\TestConsoleApp.csproj", "{5BE2551A-9EF9-42FA-B6D1-5B5E6A90CC85}"
|
|
||||||
EndProject
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MapTo.Integration.Tests", "test\MapTo.Integration.Tests\MapTo.Integration.Tests.csproj", "{23B46FDF-6A1E-4287-88C9-C8C5D7EECB8C}"
|
|
||||||
EndProject
|
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
|
|
@ -4,7 +4,6 @@
|
||||||
<TargetFramework>netstandard2.0</TargetFramework>
|
<TargetFramework>netstandard2.0</TargetFramework>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<LangVersion>9</LangVersion>
|
<LangVersion>9</LangVersion>
|
||||||
|
|
||||||
<AssemblyName>MapTo</AssemblyName>
|
<AssemblyName>MapTo</AssemblyName>
|
||||||
<Description>An object to object mapping generator using Roslyn source generator.</Description>
|
<Description>An object to object mapping generator using Roslyn source generator.</Description>
|
||||||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||||
|
@ -36,6 +35,9 @@
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="3.8.0" />
|
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="3.8.0" />
|
||||||
|
<PackageReference Update="Nerdbank.GitVersioning">
|
||||||
|
<Version>3.6.65-alpha</Version>
|
||||||
|
</PackageReference>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<configuration>
|
||||||
|
<packageSources>
|
||||||
|
<add key="BlueWest.EfGenerator" value="https://git.codeliturgy.com/api/packages/P0/nuget/index.json" />
|
||||||
|
</packageSources>
|
||||||
|
</configuration>
|
|
@ -1,85 +0,0 @@
|
||||||
using System.Linq;
|
|
||||||
using MapTo.Integration.Tests.Data.Models;
|
|
||||||
using MapTo.Integration.Tests.Data.ViewModels;
|
|
||||||
using Shouldly;
|
|
||||||
using Xunit;
|
|
||||||
|
|
||||||
namespace MapTo.Integration.Tests
|
|
||||||
{
|
|
||||||
public class CyclicReferenceTests
|
|
||||||
{
|
|
||||||
[Fact]
|
|
||||||
public void VerifySelfReference()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var manager = new Manager { Id = 1, EmployeeCode = "M001", Level = 100 };
|
|
||||||
manager.Manager = manager;
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var result = manager.ToManagerViewModel();
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
result.Id.ShouldBe(manager.Id);
|
|
||||||
result.EmployeeCode.ShouldBe(manager.EmployeeCode);
|
|
||||||
result.Level.ShouldBe(manager.Level);
|
|
||||||
result.Manager.ShouldBeSameAs(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void VerifyNestedReference()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var manager1 = new Manager { Id = 100, EmployeeCode = "M001", Level = 100 };
|
|
||||||
var manager2 = new Manager { Id = 102, EmployeeCode = "M002", Level = 100 };
|
|
||||||
|
|
||||||
var employee1 = new Employee { Id = 200, EmployeeCode = "E001"};
|
|
||||||
var employee2 = new Employee { Id = 201, EmployeeCode = "E002"};
|
|
||||||
|
|
||||||
employee1.Manager = manager1;
|
|
||||||
employee2.Manager = manager2;
|
|
||||||
|
|
||||||
manager2.Manager = manager1;
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var manager1ViewModel = manager1.ToManagerViewModel();
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
manager1ViewModel.Id.ShouldBe(manager1.Id);
|
|
||||||
manager1ViewModel.Manager.ShouldBeNull();
|
|
||||||
manager1ViewModel.Employees.Count.ShouldBe(2);
|
|
||||||
manager1ViewModel.Employees[0].Id.ShouldBe(employee1.Id);
|
|
||||||
manager1ViewModel.Employees[0].Manager.ShouldBeSameAs(manager1ViewModel);
|
|
||||||
manager1ViewModel.Employees[1].Id.ShouldBe(manager2.Id);
|
|
||||||
manager1ViewModel.Employees[1].Manager.ShouldBeSameAs(manager1ViewModel);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void VerifyNestedSelfReference()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var manager1 = new Manager { Id = 100, EmployeeCode = "M001", Level = 100 };
|
|
||||||
var manager3 = new Manager { Id = 101, EmployeeCode = "M003", Level = 100 };
|
|
||||||
var manager2 = new Manager { Id = 102, EmployeeCode = "M002", Level = 100 };
|
|
||||||
|
|
||||||
var employee1 = new Employee { Id = 200, EmployeeCode = "E001"};
|
|
||||||
var employee2 = new Employee { Id = 201, EmployeeCode = "E002"};
|
|
||||||
var employee3 = new Employee { Id = 202, EmployeeCode = "E003"};
|
|
||||||
|
|
||||||
employee1.Manager = manager1;
|
|
||||||
employee2.Manager = manager2;
|
|
||||||
employee3.Manager = manager3;
|
|
||||||
|
|
||||||
manager2.Manager = manager1;
|
|
||||||
manager3.Manager = manager2;
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var manager3ViewModel = manager3.ToManagerViewModel();
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
manager3ViewModel.Manager.ShouldNotBeNull();
|
|
||||||
manager3ViewModel.Manager.Id.ShouldBe(manager2.Id);
|
|
||||||
manager3ViewModel.Manager.Manager.Id.ShouldBe(manager1.Id);
|
|
||||||
manager3ViewModel.Employees.All(e => ReferenceEquals(e.Manager, manager3ViewModel)).ShouldBeTrue();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,29 +0,0 @@
|
||||||
namespace MapTo.Integration.Tests.Data.Models
|
|
||||||
{
|
|
||||||
public class Employee
|
|
||||||
{
|
|
||||||
private Manager _manager;
|
|
||||||
|
|
||||||
public int Id { get; set; }
|
|
||||||
|
|
||||||
public string EmployeeCode { get; set; }
|
|
||||||
|
|
||||||
public Manager Manager
|
|
||||||
{
|
|
||||||
get => _manager;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (value == null)
|
|
||||||
{
|
|
||||||
_manager.Employees.Remove(this);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
value.Employees.Add(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
_manager = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,11 +0,0 @@
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace MapTo.Integration.Tests.Data.Models
|
|
||||||
{
|
|
||||||
public class Manager : Employee
|
|
||||||
{
|
|
||||||
public int Level { get; set; }
|
|
||||||
|
|
||||||
public List<Employee> Employees { get; set; } = new();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,14 +0,0 @@
|
||||||
using MapTo.Integration.Tests.Data.Models;
|
|
||||||
|
|
||||||
namespace MapTo.Integration.Tests.Data.ViewModels
|
|
||||||
{
|
|
||||||
[MapFrom(typeof(Employee))]
|
|
||||||
public partial class EmployeeViewModel
|
|
||||||
{
|
|
||||||
public int Id { get; set; }
|
|
||||||
|
|
||||||
public string EmployeeCode { get; set; }
|
|
||||||
|
|
||||||
public ManagerViewModel Manager { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,13 +0,0 @@
|
||||||
using System.Collections.Generic;
|
|
||||||
using MapTo.Integration.Tests.Data.Models;
|
|
||||||
|
|
||||||
namespace MapTo.Integration.Tests.Data.ViewModels
|
|
||||||
{
|
|
||||||
[MapFrom(typeof(Manager))]
|
|
||||||
public partial class ManagerViewModel : EmployeeViewModel
|
|
||||||
{
|
|
||||||
public int Level { get; set; }
|
|
||||||
|
|
||||||
public List<EmployeeViewModel> Employees { get; set; } = new();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,26 +0,0 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<TargetFramework>net5.0</TargetFramework>
|
|
||||||
<IsPackable>false</IsPackable>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<ProjectReference Include="..\..\src\MapTo\MapTo.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.10.0" />
|
|
||||||
<PackageReference Include="Shouldly" Version="4.0.3" />
|
|
||||||
<PackageReference Include="xunit" Version="2.4.1" />
|
|
||||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
|
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
|
||||||
<PrivateAssets>all</PrivateAssets>
|
|
||||||
</PackageReference>
|
|
||||||
<PackageReference Include="coverlet.collector" Version="3.0.3">
|
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
|
||||||
<PrivateAssets>all</PrivateAssets>
|
|
||||||
</PackageReference>
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Project>
|
|
|
@ -1,252 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using MapTo.Extensions;
|
|
||||||
using MapTo.Sources;
|
|
||||||
using Microsoft.CodeAnalysis;
|
|
||||||
using Microsoft.CodeAnalysis.CSharp;
|
|
||||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
|
||||||
using Shouldly;
|
|
||||||
|
|
||||||
namespace MapTo.Tests
|
|
||||||
{
|
|
||||||
internal static class Common
|
|
||||||
{
|
|
||||||
internal const int Indent1 = 4;
|
|
||||||
internal const int Indent2 = Indent1 * 2;
|
|
||||||
internal const int Indent3 = Indent1 * 3;
|
|
||||||
internal static readonly Location IgnoreLocation = Location.None;
|
|
||||||
|
|
||||||
internal static readonly Dictionary<string, string> DefaultAnalyzerOptions = new()
|
|
||||||
{
|
|
||||||
[GeneratorExecutionContextExtensions.GetBuildPropertyName(nameof(SourceGenerationOptions.GenerateXmlDocument))] = "false"
|
|
||||||
};
|
|
||||||
|
|
||||||
internal static string GetSourceText(SourceGeneratorOptions? options = null)
|
|
||||||
{
|
|
||||||
const string ns = "Test";
|
|
||||||
options ??= new SourceGeneratorOptions();
|
|
||||||
var hasDifferentSourceNamespace = options.SourceClassNamespace != ns;
|
|
||||||
var builder = new SourceBuilder();
|
|
||||||
|
|
||||||
builder.WriteLine("//");
|
|
||||||
builder.WriteLine("// Test source code.");
|
|
||||||
builder.WriteLine("//");
|
|
||||||
builder.WriteLine();
|
|
||||||
|
|
||||||
options.Usings?.ForEach(s => builder.WriteLine($"using {s};"));
|
|
||||||
|
|
||||||
if (options.UseMapToNamespace)
|
|
||||||
{
|
|
||||||
builder.WriteLine($"using {Constants.RootNamespace};");
|
|
||||||
}
|
|
||||||
|
|
||||||
builder
|
|
||||||
.WriteLine($"using {options.SourceClassNamespace};")
|
|
||||||
.WriteLine()
|
|
||||||
.WriteLine();
|
|
||||||
|
|
||||||
builder
|
|
||||||
.WriteLine($"namespace {ns}")
|
|
||||||
.WriteOpeningBracket();
|
|
||||||
|
|
||||||
if (hasDifferentSourceNamespace && options.UseMapToNamespace)
|
|
||||||
{
|
|
||||||
builder
|
|
||||||
.WriteLine($"using {options.SourceClassNamespace};")
|
|
||||||
.WriteLine()
|
|
||||||
.WriteLine();
|
|
||||||
}
|
|
||||||
|
|
||||||
builder
|
|
||||||
.WriteLine(options.UseMapToNamespace ? "[MapFrom(typeof(Baz))]" : "[MapTo.MapFrom(typeof(Baz))]")
|
|
||||||
.WriteLine("public partial class Foo")
|
|
||||||
.WriteOpeningBracket();
|
|
||||||
|
|
||||||
for (var i = 1; i <= options.ClassPropertiesCount; i++)
|
|
||||||
{
|
|
||||||
builder.WriteLine(i % 2 == 0 ? $"public int Prop{i} {{ get; set; }}" : $"public int Prop{i} {{ get; }}");
|
|
||||||
}
|
|
||||||
|
|
||||||
options.PropertyBuilder?.Invoke(builder);
|
|
||||||
|
|
||||||
builder
|
|
||||||
.WriteClosingBracket()
|
|
||||||
.WriteClosingBracket()
|
|
||||||
.WriteLine()
|
|
||||||
.WriteLine();
|
|
||||||
|
|
||||||
builder
|
|
||||||
.WriteLine($"namespace {options.SourceClassNamespace}")
|
|
||||||
.WriteOpeningBracket()
|
|
||||||
.WriteLine("public class Baz")
|
|
||||||
.WriteOpeningBracket();
|
|
||||||
|
|
||||||
for (var i = 1; i <= options.SourceClassPropertiesCount; i++)
|
|
||||||
{
|
|
||||||
builder.WriteLine(i % 2 == 0 ? $"public int Prop{i} {{ get; set; }}" : $"public int Prop{i} {{ get; }}");
|
|
||||||
}
|
|
||||||
|
|
||||||
options.SourcePropertyBuilder?.Invoke(builder);
|
|
||||||
|
|
||||||
builder
|
|
||||||
.WriteClosingBracket()
|
|
||||||
.WriteClosingBracket();
|
|
||||||
|
|
||||||
return builder.ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
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[]
|
|
||||||
{
|
|
||||||
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;
|
|
||||||
|
|
||||||
namespace Test.Data.Models
|
|
||||||
{
|
|
||||||
public class Employee
|
|
||||||
{
|
|
||||||
public int Id { get; set; }
|
|
||||||
|
|
||||||
public string EmployeeCode { get; set; }
|
|
||||||
|
|
||||||
public Manager Manager { get; set; }
|
|
||||||
}
|
|
||||||
}".Trim();
|
|
||||||
|
|
||||||
static string DefaultManagerClassSource() =>
|
|
||||||
@"using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace Test.Data.Models
|
|
||||||
{
|
|
||||||
public class Manager: Employee
|
|
||||||
{
|
|
||||||
public int Level { get; set; }
|
|
||||||
|
|
||||||
public IEnumerable<Employee> Employees { get; set; } = Array.Empty<Employee>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
".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;
|
|
||||||
|
|
||||||
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();
|
|
||||||
|
|
||||||
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.Collections.Generic;
|
|
||||||
using MapTo;
|
|
||||||
using Test.Data.Models;
|
|
||||||
|
|
||||||
namespace Test.ViewModels
|
|
||||||
{
|
|
||||||
[MapFrom(typeof(Manager))]
|
|
||||||
public partial class ManagerViewModel : EmployeeViewModel
|
|
||||||
{
|
|
||||||
public int Level { get; set; }
|
|
||||||
|
|
||||||
public IEnumerable<EmployeeViewModel> Employees { get; set; } = Array.Empty<EmployeeViewModel>();
|
|
||||||
}
|
|
||||||
}".Trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static PropertyDeclarationSyntax GetPropertyDeclarationSyntax(SyntaxTree syntaxTree, string targetPropertyName, string targetClass = "Foo")
|
|
||||||
{
|
|
||||||
return syntaxTree.GetRoot()
|
|
||||||
.DescendantNodes()
|
|
||||||
.OfType<ClassDeclarationSyntax>()
|
|
||||||
.Single(c => c.Identifier.ValueText == targetClass)
|
|
||||||
.DescendantNodes()
|
|
||||||
.OfType<PropertyDeclarationSyntax>()
|
|
||||||
.Single(p => p.Identifier.ValueText == targetPropertyName);
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static IPropertySymbol GetSourcePropertySymbol(string propertyName, Compilation compilation, string targetClass = "Foo")
|
|
||||||
{
|
|
||||||
var syntaxTree = compilation.SyntaxTrees.First();
|
|
||||||
var propSyntax = GetPropertyDeclarationSyntax(syntaxTree, propertyName, targetClass);
|
|
||||||
|
|
||||||
var semanticModel = compilation.GetSemanticModel(syntaxTree);
|
|
||||||
return semanticModel.GetDeclaredSymbol(propSyntax).ShouldNotBeNull();
|
|
||||||
}
|
|
||||||
|
|
||||||
internal record SourceGeneratorOptions(
|
|
||||||
bool UseMapToNamespace = false,
|
|
||||||
string SourceClassNamespace = "Test.Models",
|
|
||||||
int ClassPropertiesCount = 3,
|
|
||||||
int SourceClassPropertiesCount = 3,
|
|
||||||
Action<SourceBuilder>? PropertyBuilder = null,
|
|
||||||
Action<SourceBuilder>? SourcePropertyBuilder = null,
|
|
||||||
IEnumerable<string>? Usings = null);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,16 +0,0 @@
|
||||||
// ReSharper disable UnusedType.Global
|
|
||||||
// ReSharper disable CheckNamespace
|
|
||||||
// Licensed to the .NET Foundation under one or more agreements.
|
|
||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
|
||||||
|
|
||||||
using System.ComponentModel;
|
|
||||||
|
|
||||||
namespace System.Runtime.CompilerServices
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Reserved to be used by the compiler for tracking metadata.
|
|
||||||
/// This class should not be used by developers in source code.
|
|
||||||
/// </summary>
|
|
||||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
|
||||||
internal static class IsExternalInit { }
|
|
||||||
}
|
|
|
@ -1,42 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using Microsoft.CodeAnalysis;
|
|
||||||
|
|
||||||
namespace MapTo.Tests.Extensions
|
|
||||||
{
|
|
||||||
internal static class RoslynExtensions
|
|
||||||
{
|
|
||||||
internal static SyntaxTree? GetGeneratedSyntaxTree(this Compilation compilation, string className) =>
|
|
||||||
compilation.SyntaxTrees.SingleOrDefault(s => s.FilePath.EndsWith($"{className}.g.cs"));
|
|
||||||
|
|
||||||
internal static string PrintSyntaxTree(this Compilation compilation)
|
|
||||||
{
|
|
||||||
var builder = new StringBuilder();
|
|
||||||
|
|
||||||
return string.Join(
|
|
||||||
Environment.NewLine,
|
|
||||||
compilation.SyntaxTrees
|
|
||||||
.Reverse()
|
|
||||||
.Select((s, i) =>
|
|
||||||
{
|
|
||||||
builder
|
|
||||||
.Clear()
|
|
||||||
.AppendLine("----------------------------------------")
|
|
||||||
.AppendFormat("File Path: \"{0}\"", s.FilePath).AppendLine()
|
|
||||||
.AppendFormat("Index: \"{0}\"", i).AppendLine()
|
|
||||||
.AppendLine();
|
|
||||||
|
|
||||||
var lines = s.ToString().Split(Environment.NewLine);
|
|
||||||
var lineNumber = 0;
|
|
||||||
foreach (var line in lines)
|
|
||||||
{
|
|
||||||
builder.AppendFormat("{0:00}: {1}", lineNumber, line).AppendLine();
|
|
||||||
lineNumber++;
|
|
||||||
}
|
|
||||||
|
|
||||||
return builder.ToString();
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,88 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Collections.Immutable;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using Microsoft.CodeAnalysis;
|
|
||||||
using Shouldly;
|
|
||||||
using Xunit;
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static void ShouldContainPartialSource(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.ShouldContainWithoutWhitespace(expectedSource, customMessage);
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static void ShouldContainPartialSource(this SyntaxTree syntaxTree, string expectedSource, string? customMessage = null)
|
|
||||||
{
|
|
||||||
var syntax = syntaxTree.ToString();
|
|
||||||
syntax.ShouldNotBeNullOrWhiteSpace();
|
|
||||||
syntax.ShouldContainWithoutWhitespace(expectedSource, customMessage);
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static void ShouldBeSuccessful(this IEnumerable<Diagnostic> diagnostics, Compilation? compilation = null, IEnumerable<string>? ignoreDiagnosticsIds = null)
|
|
||||||
{
|
|
||||||
var actual = diagnostics
|
|
||||||
.Where(d => (ignoreDiagnosticsIds is null || ignoreDiagnosticsIds.All(i => !d.Id.StartsWith(i) )) && (d.Severity is DiagnosticSeverity.Warning or DiagnosticSeverity.Error))
|
|
||||||
.Select(c => $"{c.Severity}: {c.Location.GetLineSpan()} - {c.GetMessage()}").ToArray();
|
|
||||||
|
|
||||||
if (!actual.Any())
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var builder = new StringBuilder();
|
|
||||||
builder.AppendLine("Failed");
|
|
||||||
|
|
||||||
foreach (var d in actual)
|
|
||||||
{
|
|
||||||
builder.AppendFormat("- {0}", d).AppendLine();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (compilation is not null)
|
|
||||||
{
|
|
||||||
builder.AppendLine("Generated Sources:");
|
|
||||||
builder.AppendLine(compilation.PrintSyntaxTree());
|
|
||||||
}
|
|
||||||
|
|
||||||
Assert.False(true, builder.ToString());
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static void ShouldNotBeSuccessful(this ImmutableArray<Diagnostic> diagnostics, Diagnostic expectedError)
|
|
||||||
{
|
|
||||||
var actualDiagnostics = diagnostics.SingleOrDefault(d => d.Id == expectedError.Id);
|
|
||||||
var compilationDiagnostics = actualDiagnostics == null ? diagnostics : diagnostics.Except(new[] { actualDiagnostics });
|
|
||||||
|
|
||||||
compilationDiagnostics.ShouldBeSuccessful();
|
|
||||||
|
|
||||||
Assert.NotNull(actualDiagnostics);
|
|
||||||
Assert.Equal(expectedError.Id, actualDiagnostics?.Id);
|
|
||||||
Assert.Equal(expectedError.Descriptor.Id, actualDiagnostics?.Descriptor.Id);
|
|
||||||
Assert.Equal(expectedError.Descriptor.Description, actualDiagnostics?.Descriptor.Description);
|
|
||||||
Assert.Equal(expectedError.Descriptor.Title, actualDiagnostics?.Descriptor.Title);
|
|
||||||
|
|
||||||
if (expectedError.Location != Location.None)
|
|
||||||
{
|
|
||||||
Assert.Equal(expectedError.Location, actualDiagnostics?.Location);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,79 +0,0 @@
|
||||||
using System.Linq;
|
|
||||||
using MapTo.Extensions;
|
|
||||||
using MapTo.Sources;
|
|
||||||
using MapTo.Tests.Extensions;
|
|
||||||
using MapTo.Tests.Infrastructure;
|
|
||||||
using Shouldly;
|
|
||||||
using Xunit;
|
|
||||||
using static MapTo.Tests.Common;
|
|
||||||
|
|
||||||
namespace MapTo.Tests
|
|
||||||
{
|
|
||||||
public class IgnorePropertyAttributeTests
|
|
||||||
{
|
|
||||||
[Fact]
|
|
||||||
public void VerifyIgnorePropertyAttribute()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
const string source = "";
|
|
||||||
var expectedAttribute = $@"
|
|
||||||
{Constants.GeneratedFilesHeader}
|
|
||||||
using System;
|
|
||||||
|
|
||||||
namespace MapTo
|
|
||||||
{{
|
|
||||||
[AttributeUsage(AttributeTargets.Property, Inherited = false, AllowMultiple = false)]
|
|
||||||
public sealed class IgnorePropertyAttribute : Attribute {{ }}
|
|
||||||
}}
|
|
||||||
".Trim();
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
diagnostics.ShouldBeSuccessful();
|
|
||||||
compilation.SyntaxTrees.ShouldContainSource(IgnorePropertyAttributeSource.AttributeName, expectedAttribute);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void When_IgnorePropertyAttributeIsSpecified_Should_NotGenerateMappingsForThatProperty()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var source = GetSourceText(new SourceGeneratorOptions(
|
|
||||||
true,
|
|
||||||
PropertyBuilder: builder =>
|
|
||||||
{
|
|
||||||
builder
|
|
||||||
.WriteLine("[IgnoreProperty]")
|
|
||||||
.WriteLine("public int Prop4 { get; set; }");
|
|
||||||
},
|
|
||||||
SourcePropertyBuilder: builder => builder.WriteLine("public int Prop4 { get; set; }")));
|
|
||||||
|
|
||||||
var expectedResult = @"
|
|
||||||
partial class Foo
|
|
||||||
{
|
|
||||||
public Foo(Test.Models.Baz baz)
|
|
||||||
: this(new MappingContext(), baz) { }
|
|
||||||
|
|
||||||
private protected Foo(MappingContext context, Test.Models.Baz baz)
|
|
||||||
{
|
|
||||||
if (context == null) throw new ArgumentNullException(nameof(context));
|
|
||||||
if (baz == null) throw new ArgumentNullException(nameof(baz));
|
|
||||||
|
|
||||||
context.Register(baz, this);
|
|
||||||
|
|
||||||
Prop1 = baz.Prop1;
|
|
||||||
Prop2 = baz.Prop2;
|
|
||||||
Prop3 = baz.Prop3;
|
|
||||||
}
|
|
||||||
".Trim();
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
diagnostics.ShouldBeSuccessful();
|
|
||||||
compilation.SyntaxTrees.Last().ShouldContainPartialSource(expectedResult);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,64 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Collections.Immutable;
|
|
||||||
using System.Linq;
|
|
||||||
using MapTo.Tests.Extensions;
|
|
||||||
using Microsoft.CodeAnalysis;
|
|
||||||
using Microsoft.CodeAnalysis.CSharp;
|
|
||||||
|
|
||||||
namespace MapTo.Tests.Infrastructure
|
|
||||||
{
|
|
||||||
internal static class CSharpGenerator
|
|
||||||
{
|
|
||||||
internal static (Compilation compilation, ImmutableArray<Diagnostic> diagnostics) GetOutputCompilation(
|
|
||||||
string source,
|
|
||||||
bool assertCompilation = false,
|
|
||||||
IDictionary<string, string>? analyzerConfigOptions = null,
|
|
||||||
NullableContextOptions nullableContextOptions = NullableContextOptions.Disable,
|
|
||||||
LanguageVersion languageVersion = LanguageVersion.CSharp7_3) =>
|
|
||||||
GetOutputCompilation(
|
|
||||||
new[] { source },
|
|
||||||
assertCompilation,
|
|
||||||
analyzerConfigOptions,
|
|
||||||
nullableContextOptions,
|
|
||||||
languageVersion);
|
|
||||||
|
|
||||||
internal static (Compilation compilation, ImmutableArray<Diagnostic> diagnostics) GetOutputCompilation(
|
|
||||||
IEnumerable<string> sources,
|
|
||||||
bool assertCompilation = false,
|
|
||||||
IDictionary<string, string>? analyzerConfigOptions = null,
|
|
||||||
NullableContextOptions nullableContextOptions = NullableContextOptions.Disable,
|
|
||||||
LanguageVersion languageVersion = LanguageVersion.CSharp7_3)
|
|
||||||
{
|
|
||||||
var references = AppDomain.CurrentDomain.GetAssemblies()
|
|
||||||
.Where(a => !a.IsDynamic && !string.IsNullOrWhiteSpace(a.Location))
|
|
||||||
.Select(a => MetadataReference.CreateFromFile(a.Location))
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
var compilation = CSharpCompilation.Create(
|
|
||||||
$"{typeof(CSharpGenerator).Assembly.GetName().Name}.Dynamic",
|
|
||||||
sources.Select((source, index) => CSharpSyntaxTree.ParseText(source, path: $"Test{index:00}.g.cs", options: new CSharpParseOptions(languageVersion))),
|
|
||||||
references,
|
|
||||||
new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary, nullableContextOptions: nullableContextOptions));
|
|
||||||
|
|
||||||
if (assertCompilation)
|
|
||||||
{
|
|
||||||
// NB: fail tests when the injected program isn't valid _before_ running generators
|
|
||||||
compilation.GetDiagnostics().ShouldBeSuccessful();
|
|
||||||
}
|
|
||||||
|
|
||||||
var driver = CSharpGeneratorDriver.Create(
|
|
||||||
new[] { new MapToGenerator() },
|
|
||||||
optionsProvider: new TestAnalyzerConfigOptionsProvider(analyzerConfigOptions),
|
|
||||||
parseOptions: new CSharpParseOptions(languageVersion)
|
|
||||||
);
|
|
||||||
|
|
||||||
driver.RunGeneratorsAndUpdateCompilation(compilation, out var outputCompilation, out var generateDiagnostics);
|
|
||||||
|
|
||||||
generateDiagnostics.ShouldBeSuccessful(ignoreDiagnosticsIds: new[] { "MT" });
|
|
||||||
outputCompilation.GetDiagnostics().ShouldBeSuccessful(outputCompilation);
|
|
||||||
|
|
||||||
return (outputCompilation, generateDiagnostics);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,19 +0,0 @@
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Collections.Immutable;
|
|
||||||
using System.Diagnostics.CodeAnalysis;
|
|
||||||
using Microsoft.CodeAnalysis.Diagnostics;
|
|
||||||
|
|
||||||
namespace MapTo.Tests.Infrastructure
|
|
||||||
{
|
|
||||||
internal sealed class TestAnalyzerConfigOptions : AnalyzerConfigOptions
|
|
||||||
{
|
|
||||||
private readonly ImmutableDictionary<string, string> _backing;
|
|
||||||
|
|
||||||
public TestAnalyzerConfigOptions(IDictionary<string, string>? properties)
|
|
||||||
{
|
|
||||||
_backing = properties?.ToImmutableDictionary(KeyComparer) ?? ImmutableDictionary.Create<string, string>(KeyComparer);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool TryGetValue(string key, out string? value) => _backing.TryGetValue(key, out value);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,24 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using Microsoft.CodeAnalysis;
|
|
||||||
using Microsoft.CodeAnalysis.Diagnostics;
|
|
||||||
|
|
||||||
namespace MapTo.Tests.Infrastructure
|
|
||||||
{
|
|
||||||
internal sealed class TestAnalyzerConfigOptionsProvider : AnalyzerConfigOptionsProvider
|
|
||||||
{
|
|
||||||
public TestAnalyzerConfigOptionsProvider(IDictionary<string, string>? options)
|
|
||||||
{
|
|
||||||
GlobalOptions = new TestAnalyzerConfigOptions(options);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override AnalyzerConfigOptions GlobalOptions { get; }
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override AnalyzerConfigOptions GetOptions(SyntaxTree tree) => throw new NotImplementedException();
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override AnalyzerConfigOptions GetOptions(AdditionalText textFile) => throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,202 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using MapTo.Sources;
|
|
||||||
using MapTo.Tests.Extensions;
|
|
||||||
using MapTo.Tests.Infrastructure;
|
|
||||||
using Microsoft.CodeAnalysis;
|
|
||||||
using Microsoft.CodeAnalysis.CSharp;
|
|
||||||
using Xunit;
|
|
||||||
using static MapTo.Tests.Common;
|
|
||||||
|
|
||||||
namespace MapTo.Tests
|
|
||||||
{
|
|
||||||
public class MapPropertyTests
|
|
||||||
{
|
|
||||||
[Theory]
|
|
||||||
[InlineData(NullableContextOptions.Disable)]
|
|
||||||
[InlineData(NullableContextOptions.Enable)]
|
|
||||||
public void VerifyMapPropertyAttribute(NullableContextOptions nullableContextOptions)
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
const string source = "";
|
|
||||||
var nullableSyntax = nullableContextOptions == NullableContextOptions.Enable ? "?" : string.Empty;
|
|
||||||
var languageVersion = nullableContextOptions == NullableContextOptions.Enable ? LanguageVersion.CSharp8 : LanguageVersion.CSharp7_3;
|
|
||||||
var expectedInterface = $@"
|
|
||||||
{Constants.GeneratedFilesHeader}
|
|
||||||
{(nullableContextOptions == NullableContextOptions.Enable ? $"#nullable enable{Environment.NewLine}" : string.Empty)}
|
|
||||||
using System;
|
|
||||||
|
|
||||||
namespace MapTo
|
|
||||||
{{
|
|
||||||
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Parameter, AllowMultiple = true)]
|
|
||||||
public sealed class MapPropertyAttribute : Attribute
|
|
||||||
{{
|
|
||||||
public string{nullableSyntax} SourcePropertyName {{ get; set; }}
|
|
||||||
}}
|
|
||||||
}}
|
|
||||||
".Trim();
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions, nullableContextOptions: nullableContextOptions, languageVersion: languageVersion);
|
|
||||||
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
diagnostics.ShouldBeSuccessful();
|
|
||||||
compilation.SyntaxTrees.ShouldContainSource(MapPropertyAttributeSource.AttributeName, expectedInterface);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void When_MapPropertyFound_Should_UseItToMapToSourceProperty()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var source = GetSourceText(new SourceGeneratorOptions(
|
|
||||||
true,
|
|
||||||
PropertyBuilder: builder =>
|
|
||||||
{
|
|
||||||
builder
|
|
||||||
.WriteLine("[MapProperty(SourcePropertyName = nameof(Baz.Prop3))]")
|
|
||||||
.WriteLine("public int Prop4 { get; set; }");
|
|
||||||
},
|
|
||||||
SourcePropertyBuilder: builder => builder.WriteLine("public int Prop4 { get; set; }")));
|
|
||||||
|
|
||||||
var expectedResult = @"
|
|
||||||
partial class Foo
|
|
||||||
{
|
|
||||||
public Foo(Test.Models.Baz baz)
|
|
||||||
: this(new MappingContext(), baz) { }
|
|
||||||
|
|
||||||
private protected Foo(MappingContext context, Test.Models.Baz baz)
|
|
||||||
{
|
|
||||||
if (context == null) throw new ArgumentNullException(nameof(context));
|
|
||||||
if (baz == null) throw new ArgumentNullException(nameof(baz));
|
|
||||||
|
|
||||||
context.Register(baz, this);
|
|
||||||
|
|
||||||
Prop1 = baz.Prop1;
|
|
||||||
Prop2 = baz.Prop2;
|
|
||||||
Prop3 = baz.Prop3;
|
|
||||||
Prop4 = baz.Prop3;
|
|
||||||
}
|
|
||||||
".Trim();
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
diagnostics.ShouldBeSuccessful();
|
|
||||||
compilation.SyntaxTrees.Last().ShouldContainPartialSource(expectedResult);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory]
|
|
||||||
[MemberData(nameof(MapPropertyWithImplicitConversionFoundData))]
|
|
||||||
public void When_MapPropertyWithImplicitConversionFound_Should_UseItToMapToSourceProperty(string source, string expectedResult, LanguageVersion languageVersion)
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
source = source.Trim();
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions, languageVersion: languageVersion);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
diagnostics.ShouldBeSuccessful();
|
|
||||||
compilation.SyntaxTrees.Last().ShouldContainPartialSource(expectedResult);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static IEnumerable<object[]> MapPropertyWithImplicitConversionFoundData => new List<object[]>
|
|
||||||
{
|
|
||||||
new object[]
|
|
||||||
{
|
|
||||||
@"
|
|
||||||
namespace Test
|
|
||||||
{
|
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
public class InnerClass { public int Prop1 { get; set; } }
|
|
||||||
public class OuterClass
|
|
||||||
{
|
|
||||||
public int Id { get; set; }
|
|
||||||
public List<InnerClass> InnerProp { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace Test.Models
|
|
||||||
{
|
|
||||||
using MapTo;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
[MapFrom(typeof(Test.InnerClass))]
|
|
||||||
public partial class InnerClass { public int Prop1 { get; set; } }
|
|
||||||
|
|
||||||
[MapFrom(typeof(Test.OuterClass))]
|
|
||||||
public partial class OuterClass
|
|
||||||
{
|
|
||||||
public int Id { get; set; }
|
|
||||||
public IReadOnlyList<InnerClass> InnerProp { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
",
|
|
||||||
@"
|
|
||||||
private protected OuterClass(MappingContext context, Test.OuterClass outerClass)
|
|
||||||
{
|
|
||||||
if (context == null) throw new ArgumentNullException(nameof(context));
|
|
||||||
if (outerClass == null) throw new ArgumentNullException(nameof(outerClass));
|
|
||||||
|
|
||||||
context.Register(outerClass, this);
|
|
||||||
|
|
||||||
Id = outerClass.Id;
|
|
||||||
InnerProp = outerClass.InnerProp.Select(context.MapFromWithContext<Test.InnerClass, InnerClass>).ToList();
|
|
||||||
}
|
|
||||||
",
|
|
||||||
LanguageVersion.CSharp7_3
|
|
||||||
},
|
|
||||||
new object[]
|
|
||||||
{
|
|
||||||
@"
|
|
||||||
namespace Test
|
|
||||||
{
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
public class InnerClass
|
|
||||||
{
|
|
||||||
public int Id { get; set; }
|
|
||||||
public string Name { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class OuterClass
|
|
||||||
{
|
|
||||||
public int Id { get; set; }
|
|
||||||
public List<InnerClass> InnerClasses { get; set; }
|
|
||||||
public DateTime? SomeDate { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace Test.Models
|
|
||||||
{
|
|
||||||
using MapTo;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
[MapFrom(typeof(Test.InnerClass))]
|
|
||||||
public partial record InnerClass(int Id, string Name);
|
|
||||||
|
|
||||||
[MapFrom(typeof(Test.OuterClass))]
|
|
||||||
public partial record OuterClass(int Id, DateTime? SomeDate, IReadOnlyList<InnerClass> InnerClasses);
|
|
||||||
}
|
|
||||||
",
|
|
||||||
@"
|
|
||||||
private protected OuterClass(MappingContext context, Test.OuterClass outerClass)
|
|
||||||
: this(Id: outerClass.Id, SomeDate: outerClass.SomeDate, InnerClasses: outerClass.InnerClasses.Select(context.MapFromWithContext<Test.InnerClass, InnerClass>).ToList())
|
|
||||||
{
|
|
||||||
if (context == null) throw new ArgumentNullException(nameof(context));
|
|
||||||
if (outerClass == null) throw new ArgumentNullException(nameof(outerClass));
|
|
||||||
|
|
||||||
context.Register(outerClass, this);
|
|
||||||
}
|
|
||||||
",
|
|
||||||
LanguageVersion.CSharp9
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,37 +0,0 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<TargetFramework>net5.0</TargetFramework>
|
|
||||||
|
|
||||||
<IsPackable>false</IsPackable>
|
|
||||||
|
|
||||||
<Nullable>enable</Nullable>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.1">
|
|
||||||
<PrivateAssets>all</PrivateAssets>
|
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
|
||||||
</PackageReference>
|
|
||||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="3.8.0" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.10.0" />
|
|
||||||
<PackageReference Include="Shouldly" Version="4.0.3" />
|
|
||||||
<PackageReference Include="xunit" Version="2.4.1" />
|
|
||||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
|
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
|
||||||
<PrivateAssets>all</PrivateAssets>
|
|
||||||
</PackageReference>
|
|
||||||
<PackageReference Include="coverlet.collector" Version="3.0.3">
|
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
|
||||||
<PrivateAssets>all</PrivateAssets>
|
|
||||||
</PackageReference>
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<ProjectReference Include="..\..\src\MapTo\MapTo.csproj" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Project>
|
|
|
@ -1,601 +0,0 @@
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using MapTo.Sources;
|
|
||||||
using MapTo.Tests.Extensions;
|
|
||||||
using MapTo.Tests.Infrastructure;
|
|
||||||
using Shouldly;
|
|
||||||
using Xunit;
|
|
||||||
using static MapTo.Extensions.GeneratorExecutionContextExtensions;
|
|
||||||
using static MapTo.Tests.Common;
|
|
||||||
|
|
||||||
namespace MapTo.Tests
|
|
||||||
{
|
|
||||||
public class MapToTests
|
|
||||||
{
|
|
||||||
private static readonly string ExpectedAttribute = $@"{Constants.GeneratedFilesHeader}
|
|
||||||
using System;
|
|
||||||
|
|
||||||
namespace MapTo
|
|
||||||
{{
|
|
||||||
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
|
|
||||||
public sealed class MapFromAttribute : Attribute
|
|
||||||
{{
|
|
||||||
public MapFromAttribute(Type sourceType)
|
|
||||||
{{
|
|
||||||
SourceType = sourceType;
|
|
||||||
}}
|
|
||||||
|
|
||||||
public Type SourceType {{ get; }}
|
|
||||||
}}
|
|
||||||
}}";
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void VerifyMapToAttribute()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
const string source = "";
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
diagnostics.ShouldBeSuccessful();
|
|
||||||
compilation.SyntaxTrees.ShouldContainSource(MapFromAttributeSource.AttributeClassName, ExpectedAttribute);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void When_FoundMatchingPropertyNameWithDifferentTypes_Should_ReportError()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var source = GetSourceText(new SourceGeneratorOptions(
|
|
||||||
true,
|
|
||||||
PropertyBuilder: builder => { builder.WriteLine("public string Prop4 { get; set; }"); },
|
|
||||||
SourcePropertyBuilder: builder => builder.WriteLine("public int Prop4 { get; set; }")));
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
var expectedError = DiagnosticsFactory.NoMatchingPropertyTypeFoundError(GetSourcePropertySymbol("Prop4", compilation));
|
|
||||||
|
|
||||||
diagnostics.ShouldNotBeSuccessful(expectedError);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void When_MappingsModifierOptionIsSetToInternal_Should_GenerateThoseMethodsWithInternalAccessModifier()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var source = GetSourceText();
|
|
||||||
var configOptions = new Dictionary<string, string>
|
|
||||||
{
|
|
||||||
[GetBuildPropertyName(nameof(SourceGenerationOptions.GeneratedMethodsAccessModifier))] = "Internal",
|
|
||||||
[GetBuildPropertyName(nameof(SourceGenerationOptions.GenerateXmlDocument))] = "false"
|
|
||||||
};
|
|
||||||
|
|
||||||
var expectedExtension = @"
|
|
||||||
internal static partial class BazToFooExtensions
|
|
||||||
{
|
|
||||||
internal static Foo ToFoo(this Test.Models.Baz baz)
|
|
||||||
{
|
|
||||||
return baz == null ? null : new Foo(baz);
|
|
||||||
}
|
|
||||||
}".Trim();
|
|
||||||
|
|
||||||
var expectedFactory = @"
|
|
||||||
internal static Foo From(Test.Models.Baz baz)
|
|
||||||
{
|
|
||||||
return baz == null ? null : MappingContext.Create<Test.Models.Baz, Foo>(baz);
|
|
||||||
}".Trim();
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: configOptions);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
diagnostics.ShouldBeSuccessful();
|
|
||||||
|
|
||||||
var syntaxTree = compilation.SyntaxTrees.Last().ToString();
|
|
||||||
syntaxTree.ShouldContain(expectedFactory);
|
|
||||||
syntaxTree.ShouldContain(expectedExtension);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void When_MapToAttributeFound_Should_GenerateTheClass()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
const string source = @"
|
|
||||||
using MapTo;
|
|
||||||
|
|
||||||
namespace Test
|
|
||||||
{
|
|
||||||
[MapFrom(typeof(Baz))]
|
|
||||||
public partial class Foo
|
|
||||||
{
|
|
||||||
public int Prop1 { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class Baz
|
|
||||||
{
|
|
||||||
public int Prop1 { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
";
|
|
||||||
|
|
||||||
const string expectedResult = @"
|
|
||||||
// <auto-generated />
|
|
||||||
using MapTo;
|
|
||||||
using System;
|
|
||||||
|
|
||||||
namespace Test
|
|
||||||
{
|
|
||||||
partial class Foo
|
|
||||||
{
|
|
||||||
public Foo(Test.Baz baz)
|
|
||||||
: this(new MappingContext(), baz) { }
|
|
||||||
|
|
||||||
private protected Foo(MappingContext context, Test.Baz baz)
|
|
||||||
{
|
|
||||||
if (context == null) throw new ArgumentNullException(nameof(context));
|
|
||||||
if (baz == null) throw new ArgumentNullException(nameof(baz));
|
|
||||||
|
|
||||||
context.Register(baz, this);
|
|
||||||
|
|
||||||
Prop1 = baz.Prop1;
|
|
||||||
}
|
|
||||||
";
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
diagnostics.ShouldBeSuccessful();
|
|
||||||
compilation.SyntaxTrees.Last().ToString().ShouldStartWith(expectedResult.Trim());
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void When_MapToAttributeFoundWithoutMatchingProperties_Should_ReportError()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
const string source = @"
|
|
||||||
using MapTo;
|
|
||||||
|
|
||||||
namespace Test
|
|
||||||
{
|
|
||||||
[MapFrom(typeof(Baz))]
|
|
||||||
public partial class Foo { }
|
|
||||||
|
|
||||||
public class Baz { public int Prop1 { get; set; } }
|
|
||||||
}
|
|
||||||
";
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
var fooType = compilation.GetTypeByMetadataName("Test.Foo");
|
|
||||||
fooType.ShouldNotBeNull();
|
|
||||||
|
|
||||||
var bazType = compilation.GetTypeByMetadataName("Test.Baz");
|
|
||||||
bazType.ShouldNotBeNull();
|
|
||||||
|
|
||||||
var expectedDiagnostic = DiagnosticsFactory.NoMatchingPropertyFoundError(fooType.Locations.Single(), fooType, bazType);
|
|
||||||
var error = diagnostics.FirstOrDefault(d => d.Id == expectedDiagnostic.Id);
|
|
||||||
error.ShouldNotBeNull();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void When_MapToAttributeWithNamespaceFound_Should_GenerateTheClass()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
const string source = @"
|
|
||||||
namespace Test
|
|
||||||
{
|
|
||||||
[MapTo.MapFrom(typeof(Baz))]
|
|
||||||
public partial class Foo { public int Prop1 { get; set; } }
|
|
||||||
|
|
||||||
public class Baz { public int Prop1 { get; set; } }
|
|
||||||
}
|
|
||||||
";
|
|
||||||
|
|
||||||
const string expectedResult = @"
|
|
||||||
// <auto-generated />
|
|
||||||
using MapTo;
|
|
||||||
using System;
|
|
||||||
|
|
||||||
namespace Test
|
|
||||||
{
|
|
||||||
partial class Foo
|
|
||||||
{
|
|
||||||
public Foo(Test.Baz baz)
|
|
||||||
: this(new MappingContext(), baz) { }
|
|
||||||
|
|
||||||
private protected Foo(MappingContext context, Test.Baz baz)
|
|
||||||
{
|
|
||||||
if (context == null) throw new ArgumentNullException(nameof(context));
|
|
||||||
if (baz == null) throw new ArgumentNullException(nameof(baz));
|
|
||||||
|
|
||||||
context.Register(baz, this);
|
|
||||||
|
|
||||||
Prop1 = baz.Prop1;
|
|
||||||
}
|
|
||||||
";
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
diagnostics.ShouldBeSuccessful();
|
|
||||||
compilation.SyntaxTrees.Last().ToString().ShouldStartWith(expectedResult.Trim());
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void When_NoMapToAttributeFound_Should_GenerateOnlyTheAttribute()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
const string source = "";
|
|
||||||
var expectedTypes = new[]
|
|
||||||
{
|
|
||||||
IgnorePropertyAttributeSource.AttributeName,
|
|
||||||
MapFromAttributeSource.AttributeName,
|
|
||||||
ITypeConverterSource.InterfaceName,
|
|
||||||
MapPropertyAttributeSource.AttributeName
|
|
||||||
};
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
diagnostics.ShouldBeSuccessful();
|
|
||||||
compilation.SyntaxTrees
|
|
||||||
.Select(s => s.ToString())
|
|
||||||
.Where(s => !string.IsNullOrWhiteSpace(s.ToString()))
|
|
||||||
.All(s => expectedTypes.Any(s.Contains))
|
|
||||||
.ShouldBeTrue();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void When_SourceTypeHasDifferentNamespace_Should_NotAddToUsings()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var source = GetSourceText(new SourceGeneratorOptions(SourceClassNamespace: "Bazaar"));
|
|
||||||
|
|
||||||
const string expectedResult = @"
|
|
||||||
// <auto-generated />
|
|
||||||
using MapTo;
|
|
||||||
using System;
|
|
||||||
|
|
||||||
namespace Test
|
|
||||||
{
|
|
||||||
";
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
diagnostics.ShouldBeSuccessful();
|
|
||||||
compilation.SyntaxTrees.Last().ToString().ShouldStartWith(expectedResult.Trim());
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void When_SourceTypeHasMatchingProperties_Should_CreateConstructorAndAssignSrcToDest()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var source = GetSourceText();
|
|
||||||
|
|
||||||
const string expectedResult = @"
|
|
||||||
partial class Foo
|
|
||||||
{
|
|
||||||
public Foo(Test.Models.Baz baz)
|
|
||||||
: this(new MappingContext(), baz) { }
|
|
||||||
|
|
||||||
private protected Foo(MappingContext context, Test.Models.Baz baz)
|
|
||||||
{
|
|
||||||
if (context == null) throw new ArgumentNullException(nameof(context));
|
|
||||||
if (baz == null) throw new ArgumentNullException(nameof(baz));
|
|
||||||
|
|
||||||
context.Register(baz, this);
|
|
||||||
|
|
||||||
Prop1 = baz.Prop1;
|
|
||||||
Prop2 = baz.Prop2;
|
|
||||||
Prop3 = baz.Prop3;
|
|
||||||
}
|
|
||||||
";
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
diagnostics.ShouldBeSuccessful();
|
|
||||||
compilation.SyntaxTrees.Last().ShouldContainPartialSource(expectedResult.Trim());
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void When_SourceTypeHasMatchingProperties_Should_CreateFromStaticMethod()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var source = GetSourceText();
|
|
||||||
|
|
||||||
const string expectedResult = @"
|
|
||||||
public static Foo From(Test.Models.Baz baz)
|
|
||||||
{
|
|
||||||
return baz == null ? null : MappingContext.Create<Test.Models.Baz, Foo>(baz);
|
|
||||||
}
|
|
||||||
";
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
diagnostics.ShouldBeSuccessful();
|
|
||||||
compilation.SyntaxTrees.Last().ShouldContainPartialSource(expectedResult.Trim());
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void When_SourceTypeHasMatchingProperties_Should_GenerateToExtensionMethodOnSourceType()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var source = GetSourceText();
|
|
||||||
|
|
||||||
const string expectedResult = @"
|
|
||||||
public static partial class BazToFooExtensions
|
|
||||||
{
|
|
||||||
public static Foo ToFoo(this Test.Models.Baz baz)
|
|
||||||
{
|
|
||||||
return baz == null ? null : new Foo(baz);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
";
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
diagnostics.ShouldBeSuccessful();
|
|
||||||
compilation.SyntaxTrees.Last().ShouldContainPartialSource(expectedResult.Trim());
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void When_HasNestedObjectPropertyTypeHasMapFromAttribute_Should_UseContinueToMap()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var source = GetSourceText(new SourceGeneratorOptions(
|
|
||||||
SourceClassNamespace: "Test",
|
|
||||||
PropertyBuilder: b => b.WriteLine("public B InnerProp1 { get; }"),
|
|
||||||
SourcePropertyBuilder: b => b.WriteLine("public A InnerProp1 { get; }")));
|
|
||||||
|
|
||||||
source += @"
|
|
||||||
namespace Test
|
|
||||||
{
|
|
||||||
public class A { public int Prop1 { get; } }
|
|
||||||
|
|
||||||
[MapTo.MapFrom(typeof(A))]
|
|
||||||
public partial class B { public int Prop1 { get; }}
|
|
||||||
}
|
|
||||||
".Trim();
|
|
||||||
|
|
||||||
var expectedResult = @"
|
|
||||||
partial class Foo
|
|
||||||
{
|
|
||||||
public Foo(Test.Baz baz)
|
|
||||||
: this(new MappingContext(), baz) { }
|
|
||||||
|
|
||||||
private protected Foo(MappingContext context, Test.Baz baz)
|
|
||||||
{
|
|
||||||
if (context == null) throw new ArgumentNullException(nameof(context));
|
|
||||||
if (baz == null) throw new ArgumentNullException(nameof(baz));
|
|
||||||
|
|
||||||
context.Register(baz, this);
|
|
||||||
|
|
||||||
Prop1 = baz.Prop1;
|
|
||||||
Prop2 = baz.Prop2;
|
|
||||||
Prop3 = baz.Prop3;
|
|
||||||
InnerProp1 = context.MapFromWithContext<A, B>(baz.InnerProp1);
|
|
||||||
}
|
|
||||||
".Trim();
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
diagnostics.ShouldBeSuccessful();
|
|
||||||
compilation.SyntaxTrees.ToArray()[^2].ShouldContainPartialSource(expectedResult);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void When_HasNestedObjectPropertyTypeDoesNotHaveMapFromAttribute_Should_ReportError()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var source = GetSourceText(new SourceGeneratorOptions(
|
|
||||||
SourceClassNamespace: "Test",
|
|
||||||
PropertyBuilder: b => b.WriteLine("public FooInner1 InnerProp1 { get; }"),
|
|
||||||
SourcePropertyBuilder: b => b.WriteLine("public BazInner1 InnerProp1 { get; }")));
|
|
||||||
|
|
||||||
source += @"
|
|
||||||
namespace Test
|
|
||||||
{
|
|
||||||
public class FooInner1 { public int Prop1 { get; } }
|
|
||||||
|
|
||||||
public partial class BazInner1 { public int Prop1 { get; }}
|
|
||||||
}
|
|
||||||
".Trim();
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
var expectedError = DiagnosticsFactory.NoMatchingPropertyTypeFoundError(GetSourcePropertySymbol("InnerProp1", compilation));
|
|
||||||
diagnostics.ShouldNotBeSuccessful(expectedError);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void When_HasNestedObjectPropertyTypeHasMapFromAttributeToDifferentType_Should_ReportError()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var source = GetSourceText(new SourceGeneratorOptions(
|
|
||||||
SourceClassNamespace: "Test",
|
|
||||||
PropertyBuilder: b => b.WriteLine("public FooInner1 InnerProp1 { get; }"),
|
|
||||||
SourcePropertyBuilder: b => b.WriteLine("public BazInner1 InnerProp1 { get; }")));
|
|
||||||
|
|
||||||
source += @"
|
|
||||||
namespace Test
|
|
||||||
{
|
|
||||||
public class FooInner1 { public int Prop1 { get; } }
|
|
||||||
|
|
||||||
public class FooInner2 { public int Prop1 { get; } }
|
|
||||||
|
|
||||||
[MapTo.MapFrom(typeof(FooInner2))]
|
|
||||||
public partial class BazInner1 { public int Prop1 { get; }}
|
|
||||||
}
|
|
||||||
".Trim();
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
var expectedError = DiagnosticsFactory.NoMatchingPropertyTypeFoundError(GetSourcePropertySymbol("InnerProp1", compilation));
|
|
||||||
diagnostics.ShouldNotBeSuccessful(expectedError);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void When_SourceTypeEnumerableProperties_Should_CreateConstructorAndAssignSrcToDest()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var source = GetSourceText(new SourceGeneratorOptions(
|
|
||||||
Usings: new[] { "System.Collections.Generic"},
|
|
||||||
PropertyBuilder: builder => builder.WriteLine("public IEnumerable<int> Prop4 { get; }"),
|
|
||||||
SourcePropertyBuilder: builder => builder.WriteLine("public IEnumerable<int> Prop4 { get; }")));
|
|
||||||
|
|
||||||
const string expectedResult = @"
|
|
||||||
partial class Foo
|
|
||||||
{
|
|
||||||
public Foo(Test.Models.Baz baz)
|
|
||||||
: this(new MappingContext(), baz) { }
|
|
||||||
|
|
||||||
private protected Foo(MappingContext context, Test.Models.Baz baz)
|
|
||||||
{
|
|
||||||
if (context == null) throw new ArgumentNullException(nameof(context));
|
|
||||||
if (baz == null) throw new ArgumentNullException(nameof(baz));
|
|
||||||
|
|
||||||
context.Register(baz, this);
|
|
||||||
|
|
||||||
Prop1 = baz.Prop1;
|
|
||||||
Prop2 = baz.Prop2;
|
|
||||||
Prop3 = baz.Prop3;
|
|
||||||
Prop4 = baz.Prop4;
|
|
||||||
}
|
|
||||||
";
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
diagnostics.ShouldBeSuccessful();
|
|
||||||
compilation.SyntaxTrees.Last().ShouldContainPartialSource(expectedResult.Trim());
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void When_DestinationTypeHasBaseClass_Should_CallBaseConstructor()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var sources = GetEmployeeManagerSourceText();
|
|
||||||
|
|
||||||
const string expectedResult = @"
|
|
||||||
private protected ManagerViewModel(MappingContext context, Test.Data.Models.Manager manager) : base(context, manager)
|
|
||||||
{
|
|
||||||
if (context == null) throw new ArgumentNullException(nameof(context));
|
|
||||||
if (manager == null) throw new ArgumentNullException(nameof(manager));
|
|
||||||
|
|
||||||
context.Register(manager, this);
|
|
||||||
|
|
||||||
Level = manager.Level;
|
|
||||||
Employees = manager.Employees.Select(context.MapFromWithContext<Test.Data.Models.Employee, EmployeeViewModel>).ToList();
|
|
||||||
}
|
|
||||||
";
|
|
||||||
|
|
||||||
// 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 = @"
|
|
||||||
// <auto-generated />
|
|
||||||
using MapTo;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
namespace Test.ViewModels
|
|
||||||
{
|
|
||||||
partial class ManagerViewModel
|
|
||||||
{
|
|
||||||
public ManagerViewModel(Test.Data.Models.Manager manager)
|
|
||||||
: this(new MappingContext(), manager) { }
|
|
||||||
|
|
||||||
private protected ManagerViewModel(MappingContext context, Test.Data.Models.Manager manager) : base(context, manager)
|
|
||||||
{
|
|
||||||
if (context == null) throw new ArgumentNullException(nameof(context));
|
|
||||||
if (manager == null) throw new ArgumentNullException(nameof(manager));
|
|
||||||
|
|
||||||
context.Register(manager, this);
|
|
||||||
|
|
||||||
Level = manager.Level;
|
|
||||||
Employees = manager.Employees.Select(context.MapFromWithContext<Test.Data.Models.Employee, EmployeeViewModel>).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 MapTo;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
namespace Test.ViewModels2
|
|
||||||
{
|
|
||||||
partial class ManagerViewModel
|
|
||||||
{
|
|
||||||
public ManagerViewModel(Test.Data.Models.Manager manager)
|
|
||||||
: this(new MappingContext(), manager) { }
|
|
||||||
|
|
||||||
private protected ManagerViewModel(MappingContext context, Test.Data.Models.Manager manager) : base(context, manager)
|
|
||||||
{
|
|
||||||
if (context == null) throw new ArgumentNullException(nameof(context));
|
|
||||||
if (manager == null) throw new ArgumentNullException(nameof(manager));
|
|
||||||
|
|
||||||
context.Register(manager, this);
|
|
||||||
|
|
||||||
Level = manager.Level;
|
|
||||||
Employees = manager.Employees.Select(context.MapFromWithContext<Test.Data.Models.Employee, Test.ViewModels.EmployeeViewModel>).ToList();
|
|
||||||
}
|
|
||||||
";
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(sources, analyzerConfigOptions: DefaultAnalyzerOptions);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
diagnostics.ShouldBeSuccessful();
|
|
||||||
compilation.SyntaxTrees.Last().ShouldContainPartialSource(expectedResult);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,283 +0,0 @@
|
||||||
using System.Linq;
|
|
||||||
using MapTo.Sources;
|
|
||||||
using MapTo.Tests.Extensions;
|
|
||||||
using MapTo.Tests.Infrastructure;
|
|
||||||
using Microsoft.CodeAnalysis;
|
|
||||||
using Microsoft.CodeAnalysis.CSharp;
|
|
||||||
using Xunit;
|
|
||||||
using static MapTo.Tests.Common;
|
|
||||||
|
|
||||||
namespace MapTo.Tests
|
|
||||||
{
|
|
||||||
public class MapTypeConverterTests
|
|
||||||
{
|
|
||||||
[Fact]
|
|
||||||
public void VerifyMapTypeConverterAttribute()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
const string source = "";
|
|
||||||
var expectedInterface = $@"
|
|
||||||
{Constants.GeneratedFilesHeader}
|
|
||||||
|
|
||||||
using System;
|
|
||||||
|
|
||||||
namespace MapTo
|
|
||||||
{{
|
|
||||||
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Parameter, AllowMultiple = false)]
|
|
||||||
public sealed class MapTypeConverterAttribute : Attribute
|
|
||||||
{{
|
|
||||||
public MapTypeConverterAttribute(Type converter, object[] converterParameters = null)
|
|
||||||
{{
|
|
||||||
Converter = converter;
|
|
||||||
ConverterParameters = converterParameters;
|
|
||||||
}}
|
|
||||||
|
|
||||||
public Type Converter {{ get; }}
|
|
||||||
|
|
||||||
public object[] ConverterParameters {{ get; }}
|
|
||||||
}}
|
|
||||||
}}
|
|
||||||
".Trim();
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
diagnostics.ShouldBeSuccessful();
|
|
||||||
compilation.SyntaxTrees.ShouldContainSource(MapTypeConverterAttributeSource.AttributeName, expectedInterface);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void VerifyMapTypeConverterAttributeWithNullableOptionOn()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
const string source = "";
|
|
||||||
var expectedInterface = $@"
|
|
||||||
{Constants.GeneratedFilesHeader}
|
|
||||||
#nullable enable
|
|
||||||
|
|
||||||
using System;
|
|
||||||
|
|
||||||
namespace MapTo
|
|
||||||
{{
|
|
||||||
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Parameter, AllowMultiple = false)]
|
|
||||||
public sealed class MapTypeConverterAttribute : Attribute
|
|
||||||
{{
|
|
||||||
public MapTypeConverterAttribute(Type converter, object[]? converterParameters = null)
|
|
||||||
{{
|
|
||||||
Converter = converter;
|
|
||||||
ConverterParameters = converterParameters;
|
|
||||||
}}
|
|
||||||
|
|
||||||
public Type Converter {{ get; }}
|
|
||||||
|
|
||||||
public object[]? ConverterParameters {{ get; }}
|
|
||||||
}}
|
|
||||||
}}
|
|
||||||
".Trim();
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions, nullableContextOptions: NullableContextOptions.Enable, languageVersion: LanguageVersion.CSharp8);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
diagnostics.ShouldBeSuccessful();
|
|
||||||
compilation.SyntaxTrees.ShouldContainSource(MapTypeConverterAttributeSource.AttributeName, expectedInterface);
|
|
||||||
}
|
|
||||||
|
|
||||||
[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, object[] converterParameters);
|
|
||||||
}}
|
|
||||||
}}
|
|
||||||
".Trim();
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
diagnostics.ShouldBeSuccessful();
|
|
||||||
compilation.SyntaxTrees.ShouldContainSource(ITypeConverterSource.InterfaceName, expectedInterface);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void VerifyTypeConverterInterfaceWithNullableOptionOn()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
const string source = "";
|
|
||||||
var expectedInterface = $@"
|
|
||||||
{Constants.GeneratedFilesHeader}
|
|
||||||
#nullable enable
|
|
||||||
|
|
||||||
namespace MapTo
|
|
||||||
{{
|
|
||||||
public interface ITypeConverter<in TSource, out TDestination>
|
|
||||||
{{
|
|
||||||
TDestination Convert(TSource source, object[]? converterParameters);
|
|
||||||
}}
|
|
||||||
}}
|
|
||||||
".Trim();
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions, nullableContextOptions: NullableContextOptions.Enable, languageVersion: LanguageVersion.CSharp8);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
diagnostics.ShouldBeSuccessful();
|
|
||||||
compilation.SyntaxTrees.ShouldContainSource(ITypeConverterSource.InterfaceName, expectedInterface);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void When_FoundMatchingPropertyNameWithConverterType_ShouldUseTheConverterAndItsParametersToAssignProperties()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var source = GetSourceText(new SourceGeneratorOptions(
|
|
||||||
true,
|
|
||||||
PropertyBuilder: builder =>
|
|
||||||
{
|
|
||||||
builder
|
|
||||||
.WriteLine("[MapTypeConverter(typeof(Prop4Converter), new object[]{\"G\", 'C', 10})]")
|
|
||||||
.WriteLine("public string Prop4 { get; set; }");
|
|
||||||
},
|
|
||||||
SourcePropertyBuilder: builder => builder.WriteLine("public long Prop4 { get; set; }")));
|
|
||||||
|
|
||||||
source += @"
|
|
||||||
namespace Test
|
|
||||||
{
|
|
||||||
using MapTo;
|
|
||||||
|
|
||||||
public class Prop4Converter: ITypeConverter<long, string>
|
|
||||||
{
|
|
||||||
public string Convert(long source, object[] converterParameters) => source.ToString(converterParameters[0] as string);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
";
|
|
||||||
|
|
||||||
const string expectedSyntax = "Prop4 = new Test.Prop4Converter().Convert(baz.Prop4, new object[] { \"G\", 'C', 10 });";
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
diagnostics.ShouldBeSuccessful();
|
|
||||||
compilation.SyntaxTrees.Last().ShouldContainPartialSource(expectedSyntax);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void When_FoundMatchingPropertyNameWithConverterType_ShouldUseTheConverterToAssignProperties()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var source = GetSourceText(new SourceGeneratorOptions(
|
|
||||||
true,
|
|
||||||
PropertyBuilder: builder =>
|
|
||||||
{
|
|
||||||
builder
|
|
||||||
.WriteLine("[MapTypeConverter(typeof(Prop4Converter))]")
|
|
||||||
.WriteLine("public long Prop4 { get; set; }");
|
|
||||||
},
|
|
||||||
SourcePropertyBuilder: builder => builder.WriteLine("public string Prop4 { get; set; }")));
|
|
||||||
|
|
||||||
source += @"
|
|
||||||
namespace Test
|
|
||||||
{
|
|
||||||
using MapTo;
|
|
||||||
|
|
||||||
public class Prop4Converter: ITypeConverter<string, long>
|
|
||||||
{
|
|
||||||
public long Convert(string source, object[] converterParameters) => long.Parse(source);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
";
|
|
||||||
|
|
||||||
const string expectedSyntax = "Prop4 = new Test.Prop4Converter().Convert(baz.Prop4, null);";
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
diagnostics.ShouldBeSuccessful();
|
|
||||||
compilation.SyntaxTrees.Last().ShouldContainPartialSource(expectedSyntax);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void When_FoundMatchingPropertyNameWithDifferentImplicitlyConvertibleType_Should_GenerateTheProperty()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var source = GetSourceText(new SourceGeneratorOptions(
|
|
||||||
true,
|
|
||||||
PropertyBuilder: builder => { builder.WriteLine("public long Prop4 { get; set; }"); },
|
|
||||||
SourcePropertyBuilder: builder => builder.WriteLine("public int Prop4 { get; set; }")));
|
|
||||||
|
|
||||||
var expectedResult = @"
|
|
||||||
partial class Foo
|
|
||||||
{
|
|
||||||
public Foo(Test.Models.Baz baz)
|
|
||||||
: this(new MappingContext(), baz) { }
|
|
||||||
|
|
||||||
private protected Foo(MappingContext context, Test.Models.Baz baz)
|
|
||||||
{
|
|
||||||
if (context == null) throw new ArgumentNullException(nameof(context));
|
|
||||||
if (baz == null) throw new ArgumentNullException(nameof(baz));
|
|
||||||
|
|
||||||
context.Register(baz, this);
|
|
||||||
|
|
||||||
Prop1 = baz.Prop1;
|
|
||||||
Prop2 = baz.Prop2;
|
|
||||||
Prop3 = baz.Prop3;
|
|
||||||
Prop4 = baz.Prop4;
|
|
||||||
}
|
|
||||||
".Trim();
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
diagnostics.ShouldBeSuccessful();
|
|
||||||
compilation.SyntaxTrees.Last().ShouldContainPartialSource(expectedResult);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void When_FoundMatchingPropertyNameWithIncorrectConverterType_ShouldReportError()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var source = GetSourceText(new SourceGeneratorOptions(
|
|
||||||
true,
|
|
||||||
PropertyBuilder: builder =>
|
|
||||||
{
|
|
||||||
builder
|
|
||||||
.WriteLine("[IgnoreProperty]")
|
|
||||||
.WriteLine("public long IgnoreMe { get; set; }")
|
|
||||||
.WriteLine("[MapTypeConverter(typeof(Prop4Converter))]")
|
|
||||||
.WriteLine("public long Prop4 { get; set; }");
|
|
||||||
},
|
|
||||||
SourcePropertyBuilder: builder => builder.WriteLine("public string Prop4 { get; set; }")));
|
|
||||||
|
|
||||||
source += @"
|
|
||||||
namespace Test
|
|
||||||
{
|
|
||||||
using MapTo;
|
|
||||||
|
|
||||||
public class Prop4Converter: ITypeConverter<string, int>
|
|
||||||
{
|
|
||||||
public int Convert(string source, object[] converterParameters) => int.Parse(source);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
";
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
var expectedError = DiagnosticsFactory.InvalidTypeConverterGenericTypesError(GetSourcePropertySymbol("Prop4", compilation), GetSourcePropertySymbol("Prop4", compilation, "Baz"));
|
|
||||||
diagnostics.ShouldNotBeSuccessful(expectedError);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,562 +0,0 @@
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using MapTo.Tests.Extensions;
|
|
||||||
using MapTo.Tests.Infrastructure;
|
|
||||||
using Microsoft.CodeAnalysis.CSharp;
|
|
||||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
|
||||||
using Shouldly;
|
|
||||||
using Xunit;
|
|
||||||
using Xunit.Abstractions;
|
|
||||||
using static MapTo.Tests.Common;
|
|
||||||
|
|
||||||
namespace MapTo.Tests
|
|
||||||
{
|
|
||||||
public class MappedClassesTests
|
|
||||||
{
|
|
||||||
private readonly ITestOutputHelper _output;
|
|
||||||
|
|
||||||
public MappedClassesTests(ITestOutputHelper output)
|
|
||||||
{
|
|
||||||
_output = output;
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory]
|
|
||||||
[MemberData(nameof(SecondaryConstructorCheckData))]
|
|
||||||
public void When_SecondaryConstructorExists_Should_NotGenerateOne(string source, LanguageVersion languageVersion)
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
source = source.Trim();
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions, languageVersion: languageVersion);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
diagnostics.ShouldBeSuccessful();
|
|
||||||
compilation
|
|
||||||
.GetGeneratedSyntaxTree("DestinationClass")
|
|
||||||
.ShouldNotBeNull()
|
|
||||||
.GetRoot()
|
|
||||||
.DescendantNodes()
|
|
||||||
.OfType<ConstructorDeclarationSyntax>()
|
|
||||||
.Count()
|
|
||||||
.ShouldBe(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static IEnumerable<object[]> SecondaryConstructorCheckData => new List<object[]>
|
|
||||||
{
|
|
||||||
new object[]
|
|
||||||
{
|
|
||||||
@"
|
|
||||||
using MapTo;
|
|
||||||
namespace Test.Data.Models
|
|
||||||
{
|
|
||||||
public class SourceClass { public string Prop1 { get; set; } }
|
|
||||||
|
|
||||||
[MapFrom(typeof(SourceClass))]
|
|
||||||
public partial class DestinationClass
|
|
||||||
{
|
|
||||||
public DestinationClass(SourceClass source) : this(new MappingContext(), source) { }
|
|
||||||
public string Prop1 { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
",
|
|
||||||
LanguageVersion.CSharp7_3
|
|
||||||
},
|
|
||||||
new object[]
|
|
||||||
{
|
|
||||||
@"
|
|
||||||
using MapTo;
|
|
||||||
namespace Test.Data.Models
|
|
||||||
{
|
|
||||||
public record SourceClass(string Prop1);
|
|
||||||
|
|
||||||
[MapFrom(typeof(SourceClass))]
|
|
||||||
public partial record DestinationClass(string Prop1)
|
|
||||||
{
|
|
||||||
public DestinationClass(SourceClass source) : this(new MappingContext(), source) { }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
",
|
|
||||||
LanguageVersion.CSharp9
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
[Theory]
|
|
||||||
[MemberData(nameof(SecondaryCtorWithoutPrivateCtorData))]
|
|
||||||
public void When_SecondaryConstructorExistsButDoNotReferencePrivateConstructor_Should_ReportError(string source, LanguageVersion languageVersion)
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
source = source.Trim();
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions, languageVersion: languageVersion);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
var constructorSyntax = compilation.SyntaxTrees
|
|
||||||
.First()
|
|
||||||
.GetRoot()
|
|
||||||
.DescendantNodes()
|
|
||||||
.OfType<ConstructorDeclarationSyntax>()
|
|
||||||
.Single();
|
|
||||||
|
|
||||||
diagnostics.ShouldNotBeSuccessful(DiagnosticsFactory.MissingConstructorArgument(constructorSyntax));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static IEnumerable<object[]> SecondaryCtorWithoutPrivateCtorData => new List<object[]>
|
|
||||||
{
|
|
||||||
new object[]
|
|
||||||
{
|
|
||||||
@"
|
|
||||||
using MapTo;
|
|
||||||
namespace Test.Data.Models
|
|
||||||
{
|
|
||||||
public class SourceClass { public string Prop1 { get; set; } }
|
|
||||||
|
|
||||||
[MapFrom(typeof(SourceClass))]
|
|
||||||
public partial class DestinationClass
|
|
||||||
{
|
|
||||||
public DestinationClass(SourceClass source) { }
|
|
||||||
public string Prop1 { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
",
|
|
||||||
LanguageVersion.CSharp7_3
|
|
||||||
},
|
|
||||||
new object[]
|
|
||||||
{
|
|
||||||
@"
|
|
||||||
using MapTo;
|
|
||||||
namespace Test.Data.Models
|
|
||||||
{
|
|
||||||
public record SourceClass(string Prop1);
|
|
||||||
|
|
||||||
[MapFrom(typeof(SourceClass))]
|
|
||||||
public partial record DestinationClass(string Prop1)
|
|
||||||
{
|
|
||||||
public DestinationClass(SourceClass source) : this(""invalid"") { }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
",
|
|
||||||
LanguageVersion.CSharp9
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void When_PropertyNameIsTheSameAsClassName_Should_MapAccordingly()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var source = @"
|
|
||||||
namespace Sale
|
|
||||||
{
|
|
||||||
public class Sale { public Sale Prop1 { get; set; } }
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace SaleModel
|
|
||||||
{
|
|
||||||
using MapTo;
|
|
||||||
using Sale;
|
|
||||||
|
|
||||||
[MapFrom(typeof(Sale))]
|
|
||||||
public partial class SaleModel
|
|
||||||
{
|
|
||||||
[MapProperty(SourcePropertyName = nameof(global::Sale.Sale.Prop1))]
|
|
||||||
public Sale Sale { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
".Trim();
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var (_, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
diagnostics.ShouldBeSuccessful();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory]
|
|
||||||
[MemberData(nameof(SameSourceAndDestinationTypeNameData))]
|
|
||||||
public void When_SourceAndDestinationNamesAreTheSame_Should_MapAccordingly(string source, LanguageVersion languageVersion)
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
source = source.Trim();
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var (_, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions, languageVersion: languageVersion);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
diagnostics.ShouldBeSuccessful();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static IEnumerable<object> SameSourceAndDestinationTypeNameData => new List<object>
|
|
||||||
{
|
|
||||||
new object[]
|
|
||||||
{
|
|
||||||
@"
|
|
||||||
namespace Test
|
|
||||||
{
|
|
||||||
public class TypeName { public int Prop2 { get; set; } }
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace Test2
|
|
||||||
{
|
|
||||||
using MapTo;
|
|
||||||
|
|
||||||
[MapFrom(typeof(Test.TypeName))]
|
|
||||||
public partial class TypeName
|
|
||||||
{
|
|
||||||
[MapProperty(SourcePropertyName=""Prop2"")]
|
|
||||||
public int Prop1 { get; set; }
|
|
||||||
}
|
|
||||||
}",
|
|
||||||
LanguageVersion.CSharp7_3
|
|
||||||
},
|
|
||||||
new object[]
|
|
||||||
{
|
|
||||||
@"
|
|
||||||
namespace Test
|
|
||||||
{
|
|
||||||
public record TypeName(int Prop2);
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace Test2
|
|
||||||
{
|
|
||||||
using MapTo;
|
|
||||||
|
|
||||||
[MapFrom(typeof(Test.TypeName))]
|
|
||||||
public partial record TypeName([MapProperty(SourcePropertyName=""Prop2"")] int Prop1);
|
|
||||||
}",
|
|
||||||
LanguageVersion.CSharp9
|
|
||||||
},
|
|
||||||
new object[]
|
|
||||||
{
|
|
||||||
@"
|
|
||||||
namespace Test
|
|
||||||
{
|
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
public class SourceType2 { public int Id { get; set; } }
|
|
||||||
public class SourceType
|
|
||||||
{
|
|
||||||
public int Id { get; set; }
|
|
||||||
public List<SourceType2> Prop1 { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace Test2
|
|
||||||
{
|
|
||||||
using MapTo;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
[MapFrom(typeof(Test.SourceType2))]
|
|
||||||
public partial class SourceType2 { public int Id { get; set; } }
|
|
||||||
|
|
||||||
[MapFrom(typeof(Test.SourceType))]
|
|
||||||
public partial class SourceType
|
|
||||||
{
|
|
||||||
public int Id { get; set; }
|
|
||||||
public IReadOnlyList<SourceType2> Prop1 { get; set; }
|
|
||||||
}
|
|
||||||
}",
|
|
||||||
LanguageVersion.CSharp7_3
|
|
||||||
},
|
|
||||||
new object[]
|
|
||||||
{
|
|
||||||
@"
|
|
||||||
namespace Test
|
|
||||||
{
|
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
public record SourceType(int Id, List<SourceType2> Prop1);
|
|
||||||
public record SourceType2(int Id);
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace Test2
|
|
||||||
{
|
|
||||||
using MapTo;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
[MapFrom(typeof(Test.SourceType2))]
|
|
||||||
public partial record SourceType2(int Id);
|
|
||||||
|
|
||||||
[MapFrom(typeof(Test.SourceType))]
|
|
||||||
public partial record SourceType(int Id, IReadOnlyList<SourceType2> Prop1);
|
|
||||||
}",
|
|
||||||
LanguageVersion.CSharp9
|
|
||||||
},
|
|
||||||
new object[]
|
|
||||||
{
|
|
||||||
@"
|
|
||||||
namespace Test
|
|
||||||
{
|
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
public record SourceType1(int Id);
|
|
||||||
public record SourceType2(int Id, List<SourceType1> Prop1);
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace Test
|
|
||||||
{
|
|
||||||
using MapTo;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
[MapFrom(typeof(Test.SourceType1))]
|
|
||||||
public partial record SourceType3(int Id);
|
|
||||||
|
|
||||||
[MapFrom(typeof(Test.SourceType2))]
|
|
||||||
public partial record SourceType4(int Id, IReadOnlyList<SourceType3> Prop1);
|
|
||||||
}",
|
|
||||||
LanguageVersion.CSharp9
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
[Theory]
|
|
||||||
[MemberData(nameof(VerifyMappedTypesData))]
|
|
||||||
public void VerifyMappedTypes(string[] sources, LanguageVersion languageVersion)
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
// Act
|
|
||||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(sources, analyzerConfigOptions: DefaultAnalyzerOptions, languageVersion: languageVersion);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
diagnostics.ShouldBeSuccessful();
|
|
||||||
_output.WriteLine(compilation.PrintSyntaxTree());
|
|
||||||
}
|
|
||||||
|
|
||||||
public static IEnumerable<object[]> VerifyMappedTypesData => new List<object[]>
|
|
||||||
{
|
|
||||||
new object[] { new[] { MainSourceClass, NestedSourceClass, MainDestinationClass, NestedDestinationClass }, LanguageVersion.CSharp7_3 },
|
|
||||||
new object[] { new[] { MainSourceRecord, NestedSourceRecord, MainDestinationRecord, NestedDestinationRecord }, LanguageVersion.CSharp9 },
|
|
||||||
new object[]
|
|
||||||
{
|
|
||||||
new[]
|
|
||||||
{
|
|
||||||
@"
|
|
||||||
namespace Test.Classes.Classes1
|
|
||||||
{
|
|
||||||
public class Class1
|
|
||||||
{
|
|
||||||
public int Id { get; set; }
|
|
||||||
public string Name { get; set; }
|
|
||||||
}
|
|
||||||
}",
|
|
||||||
@"
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using Test.Classes.Classes1;
|
|
||||||
|
|
||||||
namespace Test.Classes.Classes2
|
|
||||||
{
|
|
||||||
public class Class2
|
|
||||||
{
|
|
||||||
public int Id { get; set; }
|
|
||||||
public List<Class1> Genres { get; set; }
|
|
||||||
public DateTime? ReleaseDate { get; set; }
|
|
||||||
}
|
|
||||||
}",
|
|
||||||
@"
|
|
||||||
using MapTo;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using TC = Test.Classes;
|
|
||||||
|
|
||||||
namespace Tests.Records
|
|
||||||
{
|
|
||||||
[MapFrom(typeof(Test.Classes.Classes1.Class1))]
|
|
||||||
public partial record Class1(int Id, string Name);
|
|
||||||
|
|
||||||
[MapFrom(typeof(Test.Classes.Classes2.Class2))]
|
|
||||||
public partial record Class2(int Id, IReadOnlyList<Class1> Genres);
|
|
||||||
}"
|
|
||||||
},
|
|
||||||
LanguageVersion.CSharp9
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void VerifySelfReferencingRecords()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var source = @"
|
|
||||||
namespace Tests.Data.Models
|
|
||||||
{
|
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
public record Employee(int Id, string EmployeeCode, Manager Manager);
|
|
||||||
|
|
||||||
public record Manager(int Id, string EmployeeCode, Manager Manager, int Level, List<Employee> Employees) : Employee(Id, EmployeeCode, Manager);
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace Tests.Data.ViewModels
|
|
||||||
{
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using Tests.Data.Models;
|
|
||||||
using MapTo;
|
|
||||||
|
|
||||||
[MapFrom(typeof(Employee))]
|
|
||||||
public partial record EmployeeViewModel(int Id, string EmployeeCode, ManagerViewModel Manager);
|
|
||||||
|
|
||||||
[MapFrom(typeof(Manager))]
|
|
||||||
public partial record ManagerViewModel(int Id, string EmployeeCode, ManagerViewModel Manager, int Level, List<EmployeeViewModel> Employees) : EmployeeViewModel(Id, EmployeeCode, Manager);
|
|
||||||
}
|
|
||||||
".Trim();
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions, languageVersion: LanguageVersion.CSharp9);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
diagnostics.ShouldBeSuccessful();
|
|
||||||
_output.WriteLine(compilation.PrintSyntaxTree());
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void VerifySystemNamespaceConflict()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var source = @"
|
|
||||||
namespace Test
|
|
||||||
{
|
|
||||||
public record SomeRecord(int Id);
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace Test.Models
|
|
||||||
{
|
|
||||||
using MapTo;
|
|
||||||
|
|
||||||
[MapFrom(typeof(Test.SomeRecord))]
|
|
||||||
public partial record SomeRecordModel(int Id);
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace Test.System
|
|
||||||
{
|
|
||||||
public interface IMyInterface { }
|
|
||||||
}
|
|
||||||
".Trim();
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions, languageVersion: LanguageVersion.CSharp9);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
diagnostics.ShouldBeSuccessful();
|
|
||||||
_output.WriteLine(compilation.PrintSyntaxTree());
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string MainSourceClass => @"
|
|
||||||
using System;
|
|
||||||
|
|
||||||
namespace Test.Data.Models
|
|
||||||
{
|
|
||||||
public class User
|
|
||||||
{
|
|
||||||
public int Id { get; set; }
|
|
||||||
|
|
||||||
public DateTimeOffset RegisteredAt { get; set; }
|
|
||||||
|
|
||||||
public Profile Profile { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
".Trim();
|
|
||||||
|
|
||||||
private static string NestedSourceClass => @"
|
|
||||||
namespace Test.Data.Models
|
|
||||||
{
|
|
||||||
public class Profile
|
|
||||||
{
|
|
||||||
public string FirstName { get; set; }
|
|
||||||
|
|
||||||
public string LastName { get; set; }
|
|
||||||
|
|
||||||
public string FullName => $""{FirstName} {LastName}"";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
".Trim();
|
|
||||||
|
|
||||||
private static string MainDestinationClass => @"
|
|
||||||
using System;
|
|
||||||
using MapTo;
|
|
||||||
using Test.Data.Models;
|
|
||||||
|
|
||||||
namespace Test.ViewModels
|
|
||||||
{
|
|
||||||
[MapFrom(typeof(User))]
|
|
||||||
public partial class UserViewModel
|
|
||||||
{
|
|
||||||
[MapProperty(SourcePropertyName = nameof(User.Id))]
|
|
||||||
[MapTypeConverter(typeof(IdConverter))]
|
|
||||||
public string Key { get; }
|
|
||||||
|
|
||||||
public DateTimeOffset RegisteredAt { get; set; }
|
|
||||||
|
|
||||||
// [IgnoreProperty]
|
|
||||||
public ProfileViewModel Profile { get; set; }
|
|
||||||
|
|
||||||
private class IdConverter : ITypeConverter<int, string>
|
|
||||||
{
|
|
||||||
public string Convert(int source, object[] converterParameters) => $""{source:X}"";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
".Trim();
|
|
||||||
|
|
||||||
private static string NestedDestinationClass => @"
|
|
||||||
using MapTo;
|
|
||||||
using Test.Data.Models;
|
|
||||||
|
|
||||||
namespace Test.ViewModels
|
|
||||||
{
|
|
||||||
[MapFrom(typeof(Profile))]
|
|
||||||
public partial class ProfileViewModel
|
|
||||||
{
|
|
||||||
public string FirstName { get; }
|
|
||||||
|
|
||||||
public string LastName { get; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
".Trim();
|
|
||||||
|
|
||||||
private static string MainSourceRecord => BuildSourceRecord("public record User(int Id, DateTimeOffset RegisteredAt, Profile Profile);");
|
|
||||||
|
|
||||||
private static string MainDestinationRecord => BuildDestinationRecord(@"
|
|
||||||
[MapFrom(typeof(User))]
|
|
||||||
public partial record UserViewModel(
|
|
||||||
[MapProperty(SourcePropertyName = nameof(User.Id))]
|
|
||||||
[MapTypeConverter(typeof(UserViewModel.IdConverter))]
|
|
||||||
string Key,
|
|
||||||
DateTimeOffset RegisteredAt,
|
|
||||||
Profile Profile)
|
|
||||||
{
|
|
||||||
private class IdConverter : ITypeConverter<int, string>
|
|
||||||
{
|
|
||||||
public string Convert(int source, object[] converterParameters) => $""{source:X}"";
|
|
||||||
}
|
|
||||||
}");
|
|
||||||
|
|
||||||
private static string NestedSourceRecord => BuildSourceRecord("public record Profile(string FirstName, string LastName) { public string FullName => $\"{FirstName} {LastName}\"; }");
|
|
||||||
|
|
||||||
private static string NestedDestinationRecord => BuildDestinationRecord("[MapFrom(typeof(Profile))] public partial record ProfileViewModel(string FirstName, string LastName);");
|
|
||||||
|
|
||||||
private static string BuildSourceRecord(string record)
|
|
||||||
{
|
|
||||||
return $@"
|
|
||||||
using System;
|
|
||||||
|
|
||||||
namespace RecordTest.Data.Models
|
|
||||||
{{
|
|
||||||
{record}
|
|
||||||
}}
|
|
||||||
".Trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string BuildDestinationRecord(string record)
|
|
||||||
{
|
|
||||||
return $@"
|
|
||||||
using System;
|
|
||||||
using MapTo;
|
|
||||||
using RecordTest.Data.Models;
|
|
||||||
|
|
||||||
namespace RecordTest.ViewModels
|
|
||||||
{{
|
|
||||||
{record}
|
|
||||||
}}
|
|
||||||
".Trim();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,102 +0,0 @@
|
||||||
using MapTo.Sources;
|
|
||||||
using MapTo.Tests.Extensions;
|
|
||||||
using MapTo.Tests.Infrastructure;
|
|
||||||
using Xunit;
|
|
||||||
using static MapTo.Tests.Common;
|
|
||||||
|
|
||||||
namespace MapTo.Tests
|
|
||||||
{
|
|
||||||
public class MappingContextTests
|
|
||||||
{
|
|
||||||
[Fact]
|
|
||||||
public void VerifyMappingContextSource()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
const string source = "";
|
|
||||||
var expected = @"
|
|
||||||
// <auto-generated />
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Reflection;
|
|
||||||
|
|
||||||
namespace MapTo
|
|
||||||
{
|
|
||||||
internal sealed class MappingContext
|
|
||||||
{
|
|
||||||
private readonly Dictionary<object, object> _cache;
|
|
||||||
|
|
||||||
internal MappingContext()
|
|
||||||
{
|
|
||||||
_cache = new Dictionary<object, object>(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static TMapped Create<TOriginal, TMapped>(TOriginal original)
|
|
||||||
{
|
|
||||||
if (original == null) throw new ArgumentNullException(nameof(original));
|
|
||||||
|
|
||||||
var context = new MappingContext();
|
|
||||||
var mapped = context.MapFromWithContext<TOriginal, TMapped>(original);
|
|
||||||
|
|
||||||
if (mapped == null)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
return mapped;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal TMapped MapFromWithContext<TOriginal, TMapped>(TOriginal original)
|
|
||||||
{
|
|
||||||
if (original == null)
|
|
||||||
{
|
|
||||||
return default(TMapped);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!TryGetValue<TOriginal, TMapped>(original, out var mapped))
|
|
||||||
{
|
|
||||||
var instance = Activator.CreateInstance(typeof(TMapped), BindingFlags.Instance | BindingFlags.NonPublic, null, new object[] { this, original }, null);
|
|
||||||
if (instance != null)
|
|
||||||
{
|
|
||||||
mapped = (TMapped)instance;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return mapped;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal void Register<TOriginal, TMapped>(TOriginal original, TMapped mapped)
|
|
||||||
{
|
|
||||||
if (original == null) throw new ArgumentNullException(nameof(original));
|
|
||||||
if (mapped == null) throw new ArgumentNullException(nameof(mapped));
|
|
||||||
|
|
||||||
if (!_cache.ContainsKey(original))
|
|
||||||
{
|
|
||||||
_cache.Add(original, mapped);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool TryGetValue<TOriginal, TMapped>(TOriginal original, out TMapped mapped)
|
|
||||||
{
|
|
||||||
if (original != null && _cache.TryGetValue(original, out var value))
|
|
||||||
{
|
|
||||||
mapped = (TMapped)value;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
mapped = default(TMapped);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
".Trim();
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
diagnostics.ShouldBeSuccessful();
|
|
||||||
compilation.SyntaxTrees.ShouldContainSource(MappingContextSource.ClassName, expected);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,49 +0,0 @@
|
||||||
using System;
|
|
||||||
using MapTo;
|
|
||||||
|
|
||||||
namespace BlueWest.Data
|
|
||||||
{
|
|
||||||
public enum FinanceSymbol
|
|
||||||
{
|
|
||||||
BTC_EUR,
|
|
||||||
BTC_BUSD,
|
|
||||||
BTC_USD,
|
|
||||||
BTC_USDT,
|
|
||||||
LTC_EUR,
|
|
||||||
LTC_BUSD,
|
|
||||||
LTC_USDT
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum FinanceTransactionType
|
|
||||||
{
|
|
||||||
Buy,
|
|
||||||
Sell
|
|
||||||
}
|
|
||||||
|
|
||||||
[MapFrom(typeof(FinanceTransactionInsertDto))]
|
|
||||||
public partial struct FinanceTransaction
|
|
||||||
{
|
|
||||||
public int Id { get; }
|
|
||||||
public int UserId { get; set; }
|
|
||||||
public FinanceTransactionType FinanceTransactionType { get; }
|
|
||||||
public FinanceSymbol FinanceSymbol { get; }
|
|
||||||
public double Amount { get; } // To Buy
|
|
||||||
public double Quantity { get; } // Bought
|
|
||||||
public double Fee { get; }
|
|
||||||
public DateTime DateTime { get; }
|
|
||||||
|
|
||||||
|
|
||||||
public FinanceTransaction(int id, int userId, FinanceTransactionType financeTransactionType,
|
|
||||||
FinanceSymbol financeSymbol, double amount, double quantity, double fee, DateTime dateTime)
|
|
||||||
{
|
|
||||||
Id = id;
|
|
||||||
UserId = userId;
|
|
||||||
FinanceTransactionType = financeTransactionType;
|
|
||||||
FinanceSymbol = financeSymbol;
|
|
||||||
Amount = amount;
|
|
||||||
Quantity = quantity;
|
|
||||||
Fee = fee;
|
|
||||||
DateTime = dateTime;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,16 +0,0 @@
|
||||||
using System;
|
|
||||||
|
|
||||||
namespace BlueWest.Data
|
|
||||||
{
|
|
||||||
|
|
||||||
public partial struct FinanceTransactionInsertDto
|
|
||||||
{
|
|
||||||
public int UserId { get; set; }
|
|
||||||
public FinanceTransactionType FinanceTransactionType { get; }
|
|
||||||
public FinanceSymbol FinanceSymbol { get; }
|
|
||||||
public double Amount { get; } // To Buy
|
|
||||||
public double Quantity { get; } // Bought
|
|
||||||
public double Fee { get; }
|
|
||||||
public DateTime DateTime { get; }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,22 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Text;
|
|
||||||
using MapTo;
|
|
||||||
|
|
||||||
namespace BlueWest.Data
|
|
||||||
{
|
|
||||||
[MapFrom(typeof(FinanceTransaction))]
|
|
||||||
|
|
||||||
partial struct FinanceTransactionReadDto
|
|
||||||
{
|
|
||||||
public int UserId { get; set; }
|
|
||||||
public FinanceTransactionType FinanceTransactionType { get; }
|
|
||||||
public FinanceSymbol FinanceSymbol { get; }
|
|
||||||
public double Amount { get; } // To Buy
|
|
||||||
public double Quantity { get; } // Bought
|
|
||||||
public double Fee { get; }
|
|
||||||
public DateTime DateTime { get; }
|
|
||||||
|
|
||||||
public string ReadData { get; }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,27 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using MapTo;
|
|
||||||
using TestConsoleApp.ViewModels;
|
|
||||||
|
|
||||||
namespace TestConsoleApp.Data.Models
|
|
||||||
{
|
|
||||||
[MapFrom(typeof(CarReadDto))]
|
|
||||||
[UseUpdate]
|
|
||||||
partial class Car
|
|
||||||
{
|
|
||||||
public int Size { get; }
|
|
||||||
public int Id { get; }
|
|
||||||
|
|
||||||
public string Brand { get; }
|
|
||||||
|
|
||||||
public Car(int size, int id, string brand)
|
|
||||||
{
|
|
||||||
Size = size;
|
|
||||||
Id = id;
|
|
||||||
Brand = brand;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,21 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace TestConsoleApp.Data.Models
|
|
||||||
{
|
|
||||||
|
|
||||||
public class Employee
|
|
||||||
{
|
|
||||||
public int Id { get; }
|
|
||||||
|
|
||||||
public string EmployeeCode { get; }
|
|
||||||
|
|
||||||
public Employee(int id, string employeeCode)
|
|
||||||
{
|
|
||||||
Id = id;
|
|
||||||
EmployeeCode = employeeCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,26 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using TestConsoleApp.ViewModels;
|
|
||||||
using MapTo;
|
|
||||||
|
|
||||||
namespace TestConsoleApp.Data.Models
|
|
||||||
{
|
|
||||||
[MapFrom(typeof(MyStructViewModel))]
|
|
||||||
[UseUpdate]
|
|
||||||
public partial struct MyStruct
|
|
||||||
{
|
|
||||||
public int SomeInt { get; set; }
|
|
||||||
|
|
||||||
public string ReadOnlyString { get; }
|
|
||||||
|
|
||||||
public MyStruct(int someInt, string readOnlyString)
|
|
||||||
{
|
|
||||||
SomeInt = someInt;
|
|
||||||
ReadOnlyString = readOnlyString;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,43 +0,0 @@
|
||||||
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using MapTo;
|
|
||||||
|
|
||||||
|
|
||||||
namespace BlueWest.Data
|
|
||||||
{
|
|
||||||
[MapFrom(typeof(UserUpdateDto))]
|
|
||||||
public partial class User
|
|
||||||
{
|
|
||||||
public readonly int Id;
|
|
||||||
public string Name;
|
|
||||||
public string Address;
|
|
||||||
|
|
||||||
public string BTCAddress;
|
|
||||||
public string LTCAddress;
|
|
||||||
|
|
||||||
public double BTCAmount;
|
|
||||||
public double LTCAmount;
|
|
||||||
|
|
||||||
public List<FinanceTransaction> FinanceTransactions;
|
|
||||||
|
|
||||||
public User(int id, string name, string address, string btcAddress, string ltcAddress, double btcAmount, double ltcAmount, List<FinanceTransaction> financeTransactions)
|
|
||||||
{
|
|
||||||
Id = id;
|
|
||||||
Name = name;
|
|
||||||
Address = address;
|
|
||||||
BTCAddress = btcAddress;
|
|
||||||
LTCAddress = ltcAddress;
|
|
||||||
BTCAmount = btcAmount;
|
|
||||||
LTCAmount = ltcAmount;
|
|
||||||
FinanceTransactions = financeTransactions;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void AddTransaction(FinanceTransaction financeTransaction)
|
|
||||||
{
|
|
||||||
FinanceTransactions.Add(financeTransaction);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
|
@ -1,16 +0,0 @@
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace BlueWest.Data
|
|
||||||
{
|
|
||||||
public class UserList
|
|
||||||
{
|
|
||||||
public List<User> Users;
|
|
||||||
|
|
||||||
public UserList(List<User> users)
|
|
||||||
{
|
|
||||||
Users = users;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int Length => Users.Count;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,19 +0,0 @@
|
||||||
using MapTo;
|
|
||||||
|
|
||||||
namespace BlueWest.Data
|
|
||||||
{
|
|
||||||
[MapFrom(typeof(User))]
|
|
||||||
|
|
||||||
public partial class UserUpdateDto
|
|
||||||
{
|
|
||||||
public string Name;
|
|
||||||
public string Address;
|
|
||||||
|
|
||||||
public string BTCAddress;
|
|
||||||
public string LTCAddress;
|
|
||||||
|
|
||||||
public double BTCAmount;
|
|
||||||
public double LTCAmount;
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,32 +0,0 @@
|
||||||
using System;
|
|
||||||
using MapTo;
|
|
||||||
using TestConsoleApp.Data.Models;
|
|
||||||
using TestConsoleApp.ViewModels;
|
|
||||||
|
|
||||||
namespace TestConsoleApp
|
|
||||||
{
|
|
||||||
internal class Program
|
|
||||||
{
|
|
||||||
private static void Main(string[] args)
|
|
||||||
{
|
|
||||||
//UserTest();
|
|
||||||
|
|
||||||
// EmployeeManagerTest();
|
|
||||||
Console.WriteLine("done");
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void EmployeeManagerTest()
|
|
||||||
{
|
|
||||||
|
|
||||||
|
|
||||||
var employee = new Employee(1, "hello");
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,18 +0,0 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<OutputType>Exe</OutputType>
|
|
||||||
<TargetFramework>net471</TargetFramework>
|
|
||||||
<LangVersion>latest</LangVersion>
|
|
||||||
<Nullable>enable</Nullable>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<ProjectReference Include="..\..\src\MapTo\MapTo.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<Import Project="..\..\src\MapTo\MapTo.props" />
|
|
||||||
<PropertyGroup>
|
|
||||||
<MapTo_ConstructorAccessModifier>Internal</MapTo_ConstructorAccessModifier>
|
|
||||||
</PropertyGroup>
|
|
||||||
</Project>
|
|
|
@ -1,23 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using MapTo;
|
|
||||||
using TestConsoleApp.Data.Models;
|
|
||||||
|
|
||||||
namespace TestConsoleApp.ViewModels
|
|
||||||
{
|
|
||||||
[MapFrom(typeof(Car))]
|
|
||||||
partial class CarReadDto
|
|
||||||
{
|
|
||||||
public int Size { get; }
|
|
||||||
public string Brand { get; }
|
|
||||||
|
|
||||||
public CarReadDto(int size, string brand)
|
|
||||||
{
|
|
||||||
Size = size;
|
|
||||||
Brand = brand;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,13 +0,0 @@
|
||||||
using MapTo;
|
|
||||||
using TestConsoleApp.Data.Models;
|
|
||||||
|
|
||||||
namespace TestConsoleApp.ViewModels
|
|
||||||
{
|
|
||||||
[MapFrom(typeof(Employee))]
|
|
||||||
public partial class EmployeeViewModel
|
|
||||||
{
|
|
||||||
public int Id { get; }
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,22 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using TestConsoleApp.Data.Models;
|
|
||||||
using MapTo;
|
|
||||||
|
|
||||||
namespace TestConsoleApp.ViewModels
|
|
||||||
{
|
|
||||||
[MapFrom(typeof(MyStruct))]
|
|
||||||
|
|
||||||
public partial struct MyStructViewModel
|
|
||||||
{
|
|
||||||
public int SomeInt { get; set; }
|
|
||||||
|
|
||||||
public MyStructViewModel(int someInt)
|
|
||||||
{
|
|
||||||
SomeInt = someInt;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue