Fix cyclic reference issue.
This commit is contained in:
parent
9f9fb2d158
commit
3d0f9e5bbb
|
@ -6,6 +6,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MapToTests", "test\MapTo.Te
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestConsoleApp", "test\TestConsoleApp\TestConsoleApp.csproj", "{5BE2551A-9EF9-42FA-B6D1-5B5E6A90CC85}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestConsoleApp", "test\TestConsoleApp\TestConsoleApp.csproj", "{5BE2551A-9EF9-42FA-B6D1-5B5E6A90CC85}"
|
||||||
EndProject
|
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
|
||||||
|
@ -24,5 +26,9 @@ Global
|
||||||
{5BE2551A-9EF9-42FA-B6D1-5B5E6A90CC85}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{5BE2551A-9EF9-42FA-B6D1-5B5E6A90CC85}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{5BE2551A-9EF9-42FA-B6D1-5B5E6A90CC85}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{5BE2551A-9EF9-42FA-B6D1-5B5E6A90CC85}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{5BE2551A-9EF9-42FA-B6D1-5B5E6A90CC85}.Release|Any CPU.Build.0 = Release|Any CPU
|
{5BE2551A-9EF9-42FA-B6D1-5B5E6A90CC85}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{23B46FDF-6A1E-4287-88C9-C8C5D7EECB8C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{23B46FDF-6A1E-4287-88C9-C8C5D7EECB8C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{23B46FDF-6A1E-4287-88C9-C8C5D7EECB8C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{23B46FDF-6A1E-4287-88C9-C8C5D7EECB8C}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
EndGlobal
|
EndGlobal
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
A convention based object to object mapper using [Roslyn source generator](https://github.com/dotnet/roslyn/blob/master/docs/features/source-generators.md).
|
A convention based object to object mapper using [Roslyn source generator](https://github.com/dotnet/roslyn/blob/master/docs/features/source-generators.md).
|
||||||
|
|
||||||
MapTo is a library to programmatically generate the necessary code to map one object to another during compile-time. It creates mappings during compile-time, eliminating the need to use reflection to map one object to another and make it simpler to use faster than other libraries at runtime.
|
MapTo is a library to programmatically generate the necessary code to map one object to another during compile-time, eliminating the need to use reflection to map objects and make it much faster in runtime. It provides compile-time safety checks and ease of use by leveraging extension methods.
|
||||||
|
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
|
@ -75,5 +75,21 @@ namespace MapTo.Extensions
|
||||||
compilation.GetSpecialType(SpecialType.System_Collections_Generic_IEnumerable_T).Equals(typeSymbol.OriginalDefinition, SymbolEqualityComparer.Default);
|
compilation.GetSpecialType(SpecialType.System_Collections_Generic_IEnumerable_T).Equals(typeSymbol.OriginalDefinition, SymbolEqualityComparer.Default);
|
||||||
|
|
||||||
public static bool IsArray(this Compilation compilation, ITypeSymbol typeSymbol) => typeSymbol is IArrayTypeSymbol;
|
public static bool IsArray(this Compilation compilation, ITypeSymbol typeSymbol) => typeSymbol is IArrayTypeSymbol;
|
||||||
|
|
||||||
|
public static bool IsPrimitiveType(this ITypeSymbol type) => type.SpecialType is
|
||||||
|
SpecialType.System_String or
|
||||||
|
SpecialType.System_Boolean or
|
||||||
|
SpecialType.System_SByte or
|
||||||
|
SpecialType.System_Int16 or
|
||||||
|
SpecialType.System_Int32 or
|
||||||
|
SpecialType.System_Int64 or
|
||||||
|
SpecialType.System_Byte or
|
||||||
|
SpecialType.System_UInt16 or
|
||||||
|
SpecialType.System_UInt32 or
|
||||||
|
SpecialType.System_UInt64 or
|
||||||
|
SpecialType.System_Single or
|
||||||
|
SpecialType.System_Double or
|
||||||
|
SpecialType.System_Char or
|
||||||
|
SpecialType.System_Object;
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
using System.Collections.Generic;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using MapTo.Extensions;
|
using MapTo.Extensions;
|
||||||
using MapTo.Sources;
|
using MapTo.Sources;
|
||||||
|
@ -22,18 +23,27 @@ namespace MapTo
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public void Execute(GeneratorExecutionContext context)
|
public void Execute(GeneratorExecutionContext context)
|
||||||
{
|
{
|
||||||
var options = SourceGenerationOptions.From(context);
|
try
|
||||||
|
|
||||||
var compilation = context.Compilation
|
|
||||||
.AddSource(ref context, MapFromAttributeSource.Generate(options))
|
|
||||||
.AddSource(ref context, IgnorePropertyAttributeSource.Generate(options))
|
|
||||||
.AddSource(ref context, ITypeConverterSource.Generate(options))
|
|
||||||
.AddSource(ref context, MapTypeConverterAttributeSource.Generate(options))
|
|
||||||
.AddSource(ref context, MapPropertyAttributeSource.Generate(options));
|
|
||||||
|
|
||||||
if (context.SyntaxReceiver is MapToSyntaxReceiver receiver && receiver.CandidateClasses.Any())
|
|
||||||
{
|
{
|
||||||
AddGeneratedMappingsClasses(context, compilation, receiver.CandidateClasses, options);
|
var options = SourceGenerationOptions.From(context);
|
||||||
|
|
||||||
|
var compilation = context.Compilation
|
||||||
|
.AddSource(ref context, MapFromAttributeSource.Generate(options))
|
||||||
|
.AddSource(ref context, IgnorePropertyAttributeSource.Generate(options))
|
||||||
|
.AddSource(ref context, ITypeConverterSource.Generate(options))
|
||||||
|
.AddSource(ref context, MapTypeConverterAttributeSource.Generate(options))
|
||||||
|
.AddSource(ref context, MapPropertyAttributeSource.Generate(options))
|
||||||
|
.AddSource(ref context, MappingContextSource.Generate(options));
|
||||||
|
|
||||||
|
if (context.SyntaxReceiver is MapToSyntaxReceiver receiver && receiver.CandidateClasses.Any())
|
||||||
|
{
|
||||||
|
AddGeneratedMappingsClasses(context, compilation, receiver.CandidateClasses, options);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine(ex);
|
||||||
|
throw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -24,7 +24,7 @@ namespace MapTo
|
||||||
internal MappingContext(Compilation compilation, SourceGenerationOptions sourceGenerationOptions, ClassDeclarationSyntax classSyntax)
|
internal MappingContext(Compilation compilation, SourceGenerationOptions sourceGenerationOptions, ClassDeclarationSyntax classSyntax)
|
||||||
{
|
{
|
||||||
_diagnostics = new List<Diagnostic>();
|
_diagnostics = new List<Diagnostic>();
|
||||||
_usings = new List<string> { "System" };
|
_usings = new List<string> { "System", Constants.RootNamespace };
|
||||||
_sourceGenerationOptions = sourceGenerationOptions;
|
_sourceGenerationOptions = sourceGenerationOptions;
|
||||||
_classSyntax = classSyntax;
|
_classSyntax = classSyntax;
|
||||||
_compilation = compilation;
|
_compilation = compilation;
|
||||||
|
@ -35,7 +35,7 @@ namespace MapTo
|
||||||
_mapPropertyAttributeTypeSymbol = compilation.GetTypeByMetadataNameOrThrow(MapPropertyAttributeSource.FullyQualifiedName);
|
_mapPropertyAttributeTypeSymbol = compilation.GetTypeByMetadataNameOrThrow(MapPropertyAttributeSource.FullyQualifiedName);
|
||||||
_mapFromAttributeTypeSymbol = compilation.GetTypeByMetadataNameOrThrow(MapFromAttributeSource.FullyQualifiedName);
|
_mapFromAttributeTypeSymbol = compilation.GetTypeByMetadataNameOrThrow(MapFromAttributeSource.FullyQualifiedName);
|
||||||
|
|
||||||
AddUsingIfRequired(sourceGenerationOptions.SupportNullableReferenceTypes, "System.Diagnostics.CodeAnalysis");
|
AddUsingIfRequired(sourceGenerationOptions.SupportNullableStaticAnalysis, "System.Diagnostics.CodeAnalysis");
|
||||||
|
|
||||||
Initialize();
|
Initialize();
|
||||||
}
|
}
|
||||||
|
@ -154,7 +154,10 @@ namespace MapTo
|
||||||
}
|
}
|
||||||
|
|
||||||
var mapFromAttribute = property.Type.GetAttribute(_mapFromAttributeTypeSymbol);
|
var mapFromAttribute = property.Type.GetAttribute(_mapFromAttributeTypeSymbol);
|
||||||
if (mapFromAttribute is null && property.Type is INamedTypeSymbol namedTypeSymbol && _compilation.IsGenericEnumerable(property.Type))
|
if (mapFromAttribute is null &&
|
||||||
|
property.Type is INamedTypeSymbol namedTypeSymbol &&
|
||||||
|
!property.Type.IsPrimitiveType() &&
|
||||||
|
(_compilation.IsGenericEnumerable(property.Type) || property.Type.AllInterfaces.Any(i => _compilation.IsGenericEnumerable(i))))
|
||||||
{
|
{
|
||||||
enumerableTypeArgument = namedTypeSymbol.TypeArguments.First();
|
enumerableTypeArgument = namedTypeSymbol.TypeArguments.First();
|
||||||
mapFromAttribute = enumerableTypeArgument.GetAttribute(_mapFromAttributeTypeSymbol);
|
mapFromAttribute = enumerableTypeArgument.GetAttribute(_mapFromAttributeTypeSymbol);
|
||||||
|
|
|
@ -36,17 +36,30 @@ namespace MapTo
|
||||||
AccessModifier ConstructorAccessModifier,
|
AccessModifier ConstructorAccessModifier,
|
||||||
AccessModifier GeneratedMethodsAccessModifier,
|
AccessModifier GeneratedMethodsAccessModifier,
|
||||||
bool GenerateXmlDocument,
|
bool GenerateXmlDocument,
|
||||||
bool SupportNullableReferenceTypes)
|
bool SupportNullableReferenceTypes,
|
||||||
|
bool SupportNullableStaticAnalysis,
|
||||||
|
LanguageVersion LanguageVersion)
|
||||||
{
|
{
|
||||||
internal static SourceGenerationOptions From(GeneratorExecutionContext context)
|
internal static SourceGenerationOptions From(GeneratorExecutionContext context)
|
||||||
{
|
{
|
||||||
var compilationOptions = (context.Compilation as CSharpCompilation)?.Options;
|
var compilation = context.Compilation as CSharpCompilation;
|
||||||
|
var supportNullableReferenceTypes = false;
|
||||||
|
var supportNullableStaticAnalysis = false;
|
||||||
|
|
||||||
|
if (compilation is not null)
|
||||||
|
{
|
||||||
|
supportNullableStaticAnalysis = compilation.LanguageVersion >= LanguageVersion.CSharp8;
|
||||||
|
supportNullableReferenceTypes = compilation.Options.NullableContextOptions == NullableContextOptions.Warnings ||
|
||||||
|
compilation.Options.NullableContextOptions == NullableContextOptions.Enable;
|
||||||
|
}
|
||||||
|
|
||||||
return new(
|
return new(
|
||||||
context.GetBuildGlobalOption(nameof(ConstructorAccessModifier), AccessModifier.Public),
|
context.GetBuildGlobalOption(nameof(ConstructorAccessModifier), AccessModifier.Public),
|
||||||
context.GetBuildGlobalOption(nameof(GeneratedMethodsAccessModifier), AccessModifier.Public),
|
context.GetBuildGlobalOption(nameof(GeneratedMethodsAccessModifier), AccessModifier.Public),
|
||||||
context.GetBuildGlobalOption(nameof(GenerateXmlDocument), true),
|
context.GetBuildGlobalOption(nameof(GenerateXmlDocument), true),
|
||||||
compilationOptions is not null && (compilationOptions.NullableContextOptions == NullableContextOptions.Warnings || compilationOptions.NullableContextOptions == NullableContextOptions.Enable)
|
supportNullableReferenceTypes,
|
||||||
|
supportNullableStaticAnalysis,
|
||||||
|
compilation?.LanguageVersion ?? LanguageVersion.Default
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
using System.Linq;
|
using MapTo.Extensions;
|
||||||
using MapTo.Extensions;
|
|
||||||
using static MapTo.Sources.Constants;
|
using static MapTo.Sources.Constants;
|
||||||
|
|
||||||
namespace MapTo.Sources
|
namespace MapTo.Sources
|
||||||
|
@ -12,7 +11,7 @@ namespace MapTo.Sources
|
||||||
.WriteLine(GeneratedFilesHeader)
|
.WriteLine(GeneratedFilesHeader)
|
||||||
.WriteNullableContextOptionIf(model.Options.SupportNullableReferenceTypes)
|
.WriteNullableContextOptionIf(model.Options.SupportNullableReferenceTypes)
|
||||||
.WriteLine()
|
.WriteLine()
|
||||||
.WriteUsings(model)
|
.WriteUsings(model.Usings)
|
||||||
.WriteLine()
|
.WriteLine()
|
||||||
|
|
||||||
// Namespace declaration
|
// Namespace declaration
|
||||||
|
@ -24,7 +23,9 @@ namespace MapTo.Sources
|
||||||
.WriteOpeningBracket()
|
.WriteOpeningBracket()
|
||||||
|
|
||||||
// Class body
|
// Class body
|
||||||
.GenerateConstructor(model)
|
.GenerateSecondaryConstructor(model)
|
||||||
|
.WriteLine()
|
||||||
|
.GeneratePrivateConstructor(model)
|
||||||
.WriteLine()
|
.WriteLine()
|
||||||
.GenerateFactoryMethod(model)
|
.GenerateFactoryMethod(model)
|
||||||
|
|
||||||
|
@ -41,13 +42,7 @@ namespace MapTo.Sources
|
||||||
return new(builder.ToString(), $"{model.ClassName}.g.cs");
|
return new(builder.ToString(), $"{model.ClassName}.g.cs");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static SourceBuilder WriteUsings(this SourceBuilder builder, MappingModel model)
|
private static SourceBuilder GenerateSecondaryConstructor(this SourceBuilder builder, MappingModel model)
|
||||||
{
|
|
||||||
model.Usings.Sort().ForEach(u => builder.WriteLine($"using {u};"));
|
|
||||||
return builder;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static SourceBuilder GenerateConstructor(this SourceBuilder builder, MappingModel model)
|
|
||||||
{
|
{
|
||||||
var sourceClassParameterName = model.SourceClassName.ToCamelCase();
|
var sourceClassParameterName = model.SourceClassName.ToCamelCase();
|
||||||
|
|
||||||
|
@ -61,12 +56,25 @@ namespace MapTo.Sources
|
||||||
.WriteLine($"/// <exception cref=\"ArgumentNullException\">{sourceClassParameterName} is null</exception>");
|
.WriteLine($"/// <exception cref=\"ArgumentNullException\">{sourceClassParameterName} is null</exception>");
|
||||||
}
|
}
|
||||||
|
|
||||||
var baseConstructor = model.HasMappedBaseClass ? $" : base({sourceClassParameterName})" : string.Empty;
|
return builder
|
||||||
|
.WriteLine($"{model.Options.ConstructorAccessModifier.ToLowercaseString()} {model.ClassName}({model.SourceClassName} {sourceClassParameterName})")
|
||||||
|
.WriteLine($" : this(new {MappingContextSource.ClassName}(), {sourceClassParameterName}) {{ }}");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static SourceBuilder GeneratePrivateConstructor(this SourceBuilder builder, MappingModel model)
|
||||||
|
{
|
||||||
|
var sourceClassParameterName = model.SourceClassName.ToCamelCase();
|
||||||
|
const string mappingContextParameterName = "context";
|
||||||
|
|
||||||
|
var baseConstructor = model.HasMappedBaseClass ? $" : base({mappingContextParameterName}, {sourceClassParameterName})" : string.Empty;
|
||||||
|
|
||||||
builder
|
builder
|
||||||
.WriteLine($"{model.Options.ConstructorAccessModifier.ToLowercaseString()} {model.ClassName}({model.SourceClassName} {sourceClassParameterName}){baseConstructor}")
|
.WriteLine($"private protected {model.ClassName}({MappingContextSource.ClassName} {mappingContextParameterName}, {model.SourceClassName} {sourceClassParameterName}){baseConstructor}")
|
||||||
.WriteOpeningBracket()
|
.WriteOpeningBracket()
|
||||||
|
.WriteLine($"if ({mappingContextParameterName} == null) throw new ArgumentNullException(nameof({mappingContextParameterName}));")
|
||||||
.WriteLine($"if ({sourceClassParameterName} == null) throw new ArgumentNullException(nameof({sourceClassParameterName}));")
|
.WriteLine($"if ({sourceClassParameterName} == null) throw new ArgumentNullException(nameof({sourceClassParameterName}));")
|
||||||
|
.WriteLine()
|
||||||
|
.WriteLine($"{mappingContextParameterName}.{MappingContextSource.RegisterMethodName}({sourceClassParameterName}, this);")
|
||||||
.WriteLine();
|
.WriteLine();
|
||||||
|
|
||||||
foreach (var property in model.MappedProperties)
|
foreach (var property in model.MappedProperties)
|
||||||
|
@ -75,13 +83,13 @@ namespace MapTo.Sources
|
||||||
{
|
{
|
||||||
if (property.IsEnumerable)
|
if (property.IsEnumerable)
|
||||||
{
|
{
|
||||||
builder.WriteLine($"{property.Name} = {sourceClassParameterName}.{property.SourcePropertyName}.Select({property.MappedSourcePropertyTypeName}To{property.EnumerableTypeArgument}Extensions.To{property.EnumerableTypeArgument}).ToList();");
|
builder.WriteLine($"{property.Name} = {sourceClassParameterName}.{property.SourcePropertyName}.Select({mappingContextParameterName}.{MappingContextSource.MapMethodName}<{property.MappedSourcePropertyTypeName}, {property.EnumerableTypeArgument}>).ToList();");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
builder.WriteLine(property.MappedSourcePropertyTypeName is null
|
builder.WriteLine(property.MappedSourcePropertyTypeName is null
|
||||||
? $"{property.Name} = {sourceClassParameterName}.{property.SourcePropertyName};"
|
? $"{property.Name} = {sourceClassParameterName}.{property.SourcePropertyName};"
|
||||||
: $"{property.Name} = {sourceClassParameterName}.{property.SourcePropertyName}.To{property.Type}();");
|
: $"{property.Name} = {mappingContextParameterName}.{MappingContextSource.MapMethodName}<{property.MappedSourcePropertyTypeName}, {property.Type}>({sourceClassParameterName}.{property.SourcePropertyName});");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -104,10 +112,10 @@ namespace MapTo.Sources
|
||||||
|
|
||||||
return builder
|
return builder
|
||||||
.GenerateConvertorMethodsXmlDocs(model, sourceClassParameterName)
|
.GenerateConvertorMethodsXmlDocs(model, sourceClassParameterName)
|
||||||
.WriteLineIf(model.Options.SupportNullableReferenceTypes, $"[return: NotNullIfNotNull(\"{sourceClassParameterName}\")]")
|
.WriteLineIf(model.Options.SupportNullableStaticAnalysis, $"[return: NotNullIfNotNull(\"{sourceClassParameterName}\")]")
|
||||||
.WriteLine($"{model.Options.GeneratedMethodsAccessModifier.ToLowercaseString()} static {model.ClassName}{model.Options.NullableReferenceSyntax} From({model.SourceClassName}{model.Options.NullableReferenceSyntax} {sourceClassParameterName})")
|
.WriteLine($"{model.Options.GeneratedMethodsAccessModifier.ToLowercaseString()} static {model.ClassName}{model.Options.NullableReferenceSyntax} From({model.SourceClassName}{model.Options.NullableReferenceSyntax} {sourceClassParameterName})")
|
||||||
.WriteOpeningBracket()
|
.WriteOpeningBracket()
|
||||||
.WriteLine($"return {sourceClassParameterName} == null ? null : new {model.ClassName}({sourceClassParameterName});")
|
.WriteLine($"return {sourceClassParameterName} == null ? null : {MappingContextSource.ClassName}.{MappingContextSource.FactoryMethodName}<{model.SourceClassName}, {model.ClassName}>({sourceClassParameterName});")
|
||||||
.WriteClosingBracket();
|
.WriteClosingBracket();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -142,7 +150,7 @@ namespace MapTo.Sources
|
||||||
|
|
||||||
return builder
|
return builder
|
||||||
.GenerateConvertorMethodsXmlDocs(model, sourceClassParameterName)
|
.GenerateConvertorMethodsXmlDocs(model, sourceClassParameterName)
|
||||||
.WriteLineIf(model.Options.SupportNullableReferenceTypes, $"[return: NotNullIfNotNull(\"{sourceClassParameterName}\")]")
|
.WriteLineIf(model.Options.SupportNullableStaticAnalysis, $"[return: NotNullIfNotNull(\"{sourceClassParameterName}\")]")
|
||||||
.WriteLine($"{model.Options.GeneratedMethodsAccessModifier.ToLowercaseString()} static {model.ClassName}{model.Options.NullableReferenceSyntax} To{model.ClassName}(this {model.SourceClassName}{model.Options.NullableReferenceSyntax} {sourceClassParameterName})")
|
.WriteLine($"{model.Options.GeneratedMethodsAccessModifier.ToLowercaseString()} static {model.ClassName}{model.Options.NullableReferenceSyntax} To{model.ClassName}(this {model.SourceClassName}{model.Options.NullableReferenceSyntax} {sourceClassParameterName})")
|
||||||
.WriteOpeningBracket()
|
.WriteOpeningBracket()
|
||||||
.WriteLine($"return {sourceClassParameterName} == null ? null : new {model.ClassName}({sourceClassParameterName});")
|
.WriteLine($"return {sourceClassParameterName} == null ? null : new {model.ClassName}({sourceClassParameterName});")
|
||||||
|
|
|
@ -0,0 +1,115 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using static MapTo.Sources.Constants;
|
||||||
|
|
||||||
|
namespace MapTo.Sources
|
||||||
|
{
|
||||||
|
internal static class MappingContextSource
|
||||||
|
{
|
||||||
|
internal const string ClassName = "MappingContext";
|
||||||
|
internal const string FullyQualifiedName = RootNamespace + "." + ClassName;
|
||||||
|
internal const string FactoryMethodName = "Create";
|
||||||
|
internal const string RegisterMethodName = "Register";
|
||||||
|
internal const string MapMethodName = "MapFromWithContext";
|
||||||
|
|
||||||
|
internal static SourceCode Generate(SourceGenerationOptions options)
|
||||||
|
{
|
||||||
|
var usings = new List<string> { "System", "System.Collections.Generic", "System.Reflection" };
|
||||||
|
|
||||||
|
using var builder = new SourceBuilder()
|
||||||
|
.WriteLine(GeneratedFilesHeader)
|
||||||
|
.WriteLine()
|
||||||
|
.WriteUsings(usings)
|
||||||
|
.WriteLine()
|
||||||
|
|
||||||
|
// Namespace declaration
|
||||||
|
.WriteLine($"namespace {RootNamespace}")
|
||||||
|
.WriteOpeningBracket()
|
||||||
|
|
||||||
|
// Class declaration
|
||||||
|
.WriteLine($"internal sealed class {ClassName}")
|
||||||
|
.WriteOpeningBracket()
|
||||||
|
|
||||||
|
.WriteLine("private readonly Dictionary<object, object> _cache;")
|
||||||
|
.WriteLine()
|
||||||
|
|
||||||
|
// Constructor
|
||||||
|
.WriteLine($"internal {ClassName}()")
|
||||||
|
.WriteOpeningBracket()
|
||||||
|
.WriteLine("_cache = new Dictionary<object, object>(1);")
|
||||||
|
.WriteClosingBracket()
|
||||||
|
.WriteLine()
|
||||||
|
|
||||||
|
// Factory
|
||||||
|
.WriteLine($"internal static TMapped {FactoryMethodName}<TOriginal, TMapped>(TOriginal original)")
|
||||||
|
.WriteOpeningBracket()
|
||||||
|
.WriteLine("if (original == null) throw new ArgumentNullException(nameof(original));")
|
||||||
|
.WriteLine()
|
||||||
|
.WriteLine("var context = new MappingContext();")
|
||||||
|
.WriteLine("var mapped = context.MapFromWithContext<TOriginal, TMapped>(original);")
|
||||||
|
.WriteLine()
|
||||||
|
.WriteLine("if (mapped == null)")
|
||||||
|
.WriteOpeningBracket()
|
||||||
|
.WriteLine("throw new InvalidOperationException();")
|
||||||
|
.WriteClosingBracket()
|
||||||
|
.WriteLine()
|
||||||
|
.WriteLine("return mapped;")
|
||||||
|
.WriteClosingBracket()
|
||||||
|
.WriteLine()
|
||||||
|
|
||||||
|
// MapFromWithContext method
|
||||||
|
.WriteLine($"internal TMapped MapFromWithContext<TOriginal, TMapped>(TOriginal original)")
|
||||||
|
.WriteOpeningBracket()
|
||||||
|
.WriteLine("if (original == null)")
|
||||||
|
.WriteOpeningBracket()
|
||||||
|
.WriteLine("return default(TMapped);")
|
||||||
|
.WriteClosingBracket()
|
||||||
|
.WriteLine()
|
||||||
|
.WriteLine("if (!TryGetValue<TOriginal, TMapped>(original, out var mapped))")
|
||||||
|
.WriteOpeningBracket()
|
||||||
|
.WriteLine("var instance = Activator.CreateInstance(typeof(TMapped), BindingFlags.Instance | BindingFlags.NonPublic, null, new object[] { this, original }, null);")
|
||||||
|
.WriteLine("if (instance != null)")
|
||||||
|
.WriteOpeningBracket()
|
||||||
|
.WriteLine("mapped = (TMapped)instance;")
|
||||||
|
.WriteClosingBracket()
|
||||||
|
.WriteClosingBracket()
|
||||||
|
.WriteLine()
|
||||||
|
.WriteLine("return mapped;")
|
||||||
|
.WriteClosingBracket()
|
||||||
|
.WriteLine()
|
||||||
|
|
||||||
|
// Register method
|
||||||
|
.WriteLine("internal void Register<TOriginal, TMapped>(TOriginal original, TMapped mapped)")
|
||||||
|
.WriteOpeningBracket()
|
||||||
|
.WriteLine("if (original == null) throw new ArgumentNullException(nameof(original));")
|
||||||
|
.WriteLine("if (mapped == null) throw new ArgumentNullException(nameof(mapped));")
|
||||||
|
.WriteLine()
|
||||||
|
.WriteLine("if (!_cache.ContainsKey(original))")
|
||||||
|
.WriteOpeningBracket()
|
||||||
|
.WriteLine("_cache.Add(original, mapped);")
|
||||||
|
.WriteClosingBracket()
|
||||||
|
.WriteClosingBracket()
|
||||||
|
.WriteLine()
|
||||||
|
|
||||||
|
// TryGetValue method
|
||||||
|
.WriteLine("private bool TryGetValue<TOriginal, TMapped>(TOriginal original, out TMapped mapped)")
|
||||||
|
.WriteOpeningBracket()
|
||||||
|
.WriteLine("if (original != null && _cache.TryGetValue(original, out var value))")
|
||||||
|
.WriteOpeningBracket()
|
||||||
|
.WriteLine("mapped = (TMapped)value;")
|
||||||
|
.WriteLine("return true;")
|
||||||
|
.WriteClosingBracket()
|
||||||
|
.WriteLine()
|
||||||
|
.WriteLine("mapped = default(TMapped);")
|
||||||
|
.WriteLine("return false;")
|
||||||
|
.WriteClosingBracket()
|
||||||
|
|
||||||
|
// End class declaration
|
||||||
|
.WriteClosingBracket()
|
||||||
|
|
||||||
|
// End namespace declaration
|
||||||
|
.WriteClosingBracket();
|
||||||
|
|
||||||
|
return new(builder.ToString(), $"{ClassName}.g.cs");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,8 @@
|
||||||
using System;
|
using System;
|
||||||
using System.CodeDom.Compiler;
|
using System.CodeDom.Compiler;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
namespace MapTo.Sources
|
namespace MapTo.Sources
|
||||||
{
|
{
|
||||||
|
@ -8,7 +10,7 @@ namespace MapTo.Sources
|
||||||
{
|
{
|
||||||
private readonly StringWriter _writer;
|
private readonly StringWriter _writer;
|
||||||
private readonly IndentedTextWriter _indentedWriter;
|
private readonly IndentedTextWriter _indentedWriter;
|
||||||
|
|
||||||
public SourceBuilder()
|
public SourceBuilder()
|
||||||
{
|
{
|
||||||
_writer = new StringWriter();
|
_writer = new StringWriter();
|
||||||
|
@ -21,12 +23,12 @@ namespace MapTo.Sources
|
||||||
_writer.Dispose();
|
_writer.Dispose();
|
||||||
_indentedWriter.Dispose();
|
_indentedWriter.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
public SourceBuilder WriteLine(string? value = null)
|
public SourceBuilder WriteLine(string? value = null)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(value))
|
if (string.IsNullOrWhiteSpace(value))
|
||||||
{
|
{
|
||||||
_indentedWriter.WriteLineNoTabs(string.Empty);
|
_indentedWriter.WriteLineNoTabs(string.Empty);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -64,6 +66,23 @@ namespace MapTo.Sources
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public SourceBuilder WriteUsings(IEnumerable<string> usings)
|
||||||
|
{
|
||||||
|
foreach (var u in usings.OrderBy(s => s))
|
||||||
|
{
|
||||||
|
WriteUsing(u);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SourceBuilder WriteUsing(string u)
|
||||||
|
{
|
||||||
|
WriteLine($"using {u};");
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override string ToString() => _writer.ToString();
|
public override string ToString() => _writer.ToString();
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,85 @@
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
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; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
<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.7.1" />
|
||||||
|
<PackageReference Include="Shouldly" Version="4.0.1" />
|
||||||
|
<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="1.3.0">
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
</PackageReference>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
|
@ -21,6 +21,16 @@ namespace MapTo.Tests.Extensions
|
||||||
syntax.ShouldBe(expectedSource, customMessage);
|
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)
|
internal static void ShouldContainPartialSource(this SyntaxTree syntaxTree, string expectedSource, string customMessage = null)
|
||||||
{
|
{
|
||||||
var syntax = syntaxTree.ToString();
|
var syntax = syntaxTree.ToString();
|
||||||
|
|
|
@ -53,9 +53,15 @@ namespace MapTo
|
||||||
partial class Foo
|
partial class Foo
|
||||||
{
|
{
|
||||||
public Foo(Baz baz)
|
public Foo(Baz baz)
|
||||||
|
: this(new MappingContext(), baz) { }
|
||||||
|
|
||||||
|
private protected Foo(MappingContext context, Baz baz)
|
||||||
{
|
{
|
||||||
|
if (context == null) throw new ArgumentNullException(nameof(context));
|
||||||
if (baz == null) throw new ArgumentNullException(nameof(baz));
|
if (baz == null) throw new ArgumentNullException(nameof(baz));
|
||||||
|
|
||||||
|
context.Register(baz, this);
|
||||||
|
|
||||||
Prop1 = baz.Prop1;
|
Prop1 = baz.Prop1;
|
||||||
Prop2 = baz.Prop2;
|
Prop2 = baz.Prop2;
|
||||||
Prop3 = baz.Prop3;
|
Prop3 = baz.Prop3;
|
||||||
|
|
|
@ -11,10 +11,25 @@ namespace MapTo.Tests.Infrastructure
|
||||||
{
|
{
|
||||||
internal static class CSharpGenerator
|
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) =>
|
internal static (Compilation compilation, ImmutableArray<Diagnostic> diagnostics) GetOutputCompilation(
|
||||||
GetOutputCompilation(new[] { source }, assertCompilation, analyzerConfigOptions, nullableContextOptions);
|
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)
|
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()
|
var references = AppDomain.CurrentDomain.GetAssemblies()
|
||||||
.Where(a => !a.IsDynamic && !string.IsNullOrWhiteSpace(a.Location))
|
.Where(a => !a.IsDynamic && !string.IsNullOrWhiteSpace(a.Location))
|
||||||
|
@ -23,10 +38,10 @@ namespace MapTo.Tests.Infrastructure
|
||||||
|
|
||||||
var compilation = CSharpCompilation.Create(
|
var compilation = CSharpCompilation.Create(
|
||||||
$"{typeof(CSharpGenerator).Assembly.GetName().Name}.Dynamic",
|
$"{typeof(CSharpGenerator).Assembly.GetName().Name}.Dynamic",
|
||||||
sources.Select((source, index) => CSharpSyntaxTree.ParseText(source, path: $"Test{index:00}.g.cs")),
|
sources.Select((source, index) => CSharpSyntaxTree.ParseText(source, path: $"Test{index:00}.g.cs", options: new CSharpParseOptions(languageVersion))),
|
||||||
references,
|
references,
|
||||||
new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary, nullableContextOptions: nullableContextOptions));
|
new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary, nullableContextOptions: nullableContextOptions));
|
||||||
|
|
||||||
if (assertCompilation)
|
if (assertCompilation)
|
||||||
{
|
{
|
||||||
// NB: fail tests when the injected program isn't valid _before_ running generators
|
// NB: fail tests when the injected program isn't valid _before_ running generators
|
||||||
|
@ -35,7 +50,8 @@ namespace MapTo.Tests.Infrastructure
|
||||||
|
|
||||||
var driver = CSharpGeneratorDriver.Create(
|
var driver = CSharpGeneratorDriver.Create(
|
||||||
new[] { new MapToGenerator() },
|
new[] { new MapToGenerator() },
|
||||||
optionsProvider: new TestAnalyzerConfigOptionsProvider(analyzerConfigOptions)
|
optionsProvider: new TestAnalyzerConfigOptionsProvider(analyzerConfigOptions),
|
||||||
|
parseOptions: new CSharpParseOptions(languageVersion)
|
||||||
);
|
);
|
||||||
|
|
||||||
driver.RunGeneratorsAndUpdateCompilation(compilation, out var outputCompilation, out var generateDiagnostics);
|
driver.RunGeneratorsAndUpdateCompilation(compilation, out var outputCompilation, out var generateDiagnostics);
|
||||||
|
|
|
@ -4,7 +4,7 @@ using MapTo.Sources;
|
||||||
using MapTo.Tests.Extensions;
|
using MapTo.Tests.Extensions;
|
||||||
using MapTo.Tests.Infrastructure;
|
using MapTo.Tests.Infrastructure;
|
||||||
using Microsoft.CodeAnalysis;
|
using Microsoft.CodeAnalysis;
|
||||||
using Shouldly;
|
using Microsoft.CodeAnalysis.CSharp;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
using static MapTo.Tests.Common;
|
using static MapTo.Tests.Common;
|
||||||
|
|
||||||
|
@ -20,6 +20,7 @@ namespace MapTo.Tests
|
||||||
// Arrange
|
// Arrange
|
||||||
const string source = "";
|
const string source = "";
|
||||||
var nullableSyntax = nullableContextOptions == NullableContextOptions.Enable ? "?" : string.Empty;
|
var nullableSyntax = nullableContextOptions == NullableContextOptions.Enable ? "?" : string.Empty;
|
||||||
|
var languageVersion = nullableContextOptions == NullableContextOptions.Enable ? LanguageVersion.CSharp8 : LanguageVersion.CSharp7_3;
|
||||||
var expectedInterface = $@"
|
var expectedInterface = $@"
|
||||||
{Constants.GeneratedFilesHeader}
|
{Constants.GeneratedFilesHeader}
|
||||||
{(nullableContextOptions == NullableContextOptions.Enable ? $"#nullable enable{Environment.NewLine}" : string.Empty)}
|
{(nullableContextOptions == NullableContextOptions.Enable ? $"#nullable enable{Environment.NewLine}" : string.Empty)}
|
||||||
|
@ -36,8 +37,9 @@ namespace MapTo
|
||||||
".Trim();
|
".Trim();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions, nullableContextOptions: nullableContextOptions);
|
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions, nullableContextOptions: nullableContextOptions, languageVersion: languageVersion);
|
||||||
|
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
diagnostics.ShouldBeSuccessful();
|
diagnostics.ShouldBeSuccessful();
|
||||||
compilation.SyntaxTrees.ShouldContainSource(MapPropertyAttributeSource.AttributeName, expectedInterface);
|
compilation.SyntaxTrees.ShouldContainSource(MapPropertyAttributeSource.AttributeName, expectedInterface);
|
||||||
|
@ -61,9 +63,15 @@ namespace MapTo
|
||||||
partial class Foo
|
partial class Foo
|
||||||
{
|
{
|
||||||
public Foo(Baz baz)
|
public Foo(Baz baz)
|
||||||
|
: this(new MappingContext(), baz) { }
|
||||||
|
|
||||||
|
private protected Foo(MappingContext context, Baz baz)
|
||||||
{
|
{
|
||||||
|
if (context == null) throw new ArgumentNullException(nameof(context));
|
||||||
if (baz == null) throw new ArgumentNullException(nameof(baz));
|
if (baz == null) throw new ArgumentNullException(nameof(baz));
|
||||||
|
|
||||||
|
context.Register(baz, this);
|
||||||
|
|
||||||
Prop1 = baz.Prop1;
|
Prop1 = baz.Prop1;
|
||||||
Prop2 = baz.Prop2;
|
Prop2 = baz.Prop2;
|
||||||
Prop3 = baz.Prop3;
|
Prop3 = baz.Prop3;
|
||||||
|
|
|
@ -40,7 +40,7 @@ namespace MapTo
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
diagnostics.ShouldBeSuccessful();
|
diagnostics.ShouldBeSuccessful();
|
||||||
compilation.SyntaxTrees.ShouldContainSource(MapFromAttributeSource.AttributeName, ExpectedAttribute);
|
compilation.SyntaxTrees.ShouldContainSource(MapFromAttributeSource.AttributeClassName, ExpectedAttribute);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
@ -84,7 +84,7 @@ namespace MapTo
|
||||||
var expectedFactory = @"
|
var expectedFactory = @"
|
||||||
internal static Foo From(Baz baz)
|
internal static Foo From(Baz baz)
|
||||||
{
|
{
|
||||||
return baz == null ? null : new Foo(baz);
|
return baz == null ? null : MappingContext.Create<Baz, Foo>(baz);
|
||||||
}".Trim();
|
}".Trim();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
|
@ -123,6 +123,7 @@ namespace Test
|
||||||
const string expectedResult = @"
|
const string expectedResult = @"
|
||||||
// <auto-generated />
|
// <auto-generated />
|
||||||
|
|
||||||
|
using MapTo;
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace Test
|
namespace Test
|
||||||
|
@ -130,9 +131,15 @@ namespace Test
|
||||||
partial class Foo
|
partial class Foo
|
||||||
{
|
{
|
||||||
public Foo(Baz baz)
|
public Foo(Baz baz)
|
||||||
|
: this(new MappingContext(), baz) { }
|
||||||
|
|
||||||
|
private protected Foo(MappingContext context, Baz baz)
|
||||||
{
|
{
|
||||||
|
if (context == null) throw new ArgumentNullException(nameof(context));
|
||||||
if (baz == null) throw new ArgumentNullException(nameof(baz));
|
if (baz == null) throw new ArgumentNullException(nameof(baz));
|
||||||
|
|
||||||
|
context.Register(baz, this);
|
||||||
|
|
||||||
Prop1 = baz.Prop1;
|
Prop1 = baz.Prop1;
|
||||||
}
|
}
|
||||||
";
|
";
|
||||||
|
@ -193,6 +200,7 @@ namespace Test
|
||||||
const string expectedResult = @"
|
const string expectedResult = @"
|
||||||
// <auto-generated />
|
// <auto-generated />
|
||||||
|
|
||||||
|
using MapTo;
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace Test
|
namespace Test
|
||||||
|
@ -200,9 +208,15 @@ namespace Test
|
||||||
partial class Foo
|
partial class Foo
|
||||||
{
|
{
|
||||||
public Foo(Baz baz)
|
public Foo(Baz baz)
|
||||||
|
: this(new MappingContext(), baz) { }
|
||||||
|
|
||||||
|
private protected Foo(MappingContext context, Baz baz)
|
||||||
{
|
{
|
||||||
|
if (context == null) throw new ArgumentNullException(nameof(context));
|
||||||
if (baz == null) throw new ArgumentNullException(nameof(baz));
|
if (baz == null) throw new ArgumentNullException(nameof(baz));
|
||||||
|
|
||||||
|
context.Register(baz, this);
|
||||||
|
|
||||||
Prop1 = baz.Prop1;
|
Prop1 = baz.Prop1;
|
||||||
}
|
}
|
||||||
";
|
";
|
||||||
|
@ -250,6 +264,7 @@ namespace Test
|
||||||
// <auto-generated />
|
// <auto-generated />
|
||||||
|
|
||||||
using Bazaar;
|
using Bazaar;
|
||||||
|
using MapTo;
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace Test
|
namespace Test
|
||||||
|
@ -273,9 +288,15 @@ namespace Test
|
||||||
partial class Foo
|
partial class Foo
|
||||||
{
|
{
|
||||||
public Foo(Baz baz)
|
public Foo(Baz baz)
|
||||||
|
: this(new MappingContext(), baz) { }
|
||||||
|
|
||||||
|
private protected Foo(MappingContext context, Baz baz)
|
||||||
{
|
{
|
||||||
|
if (context == null) throw new ArgumentNullException(nameof(context));
|
||||||
if (baz == null) throw new ArgumentNullException(nameof(baz));
|
if (baz == null) throw new ArgumentNullException(nameof(baz));
|
||||||
|
|
||||||
|
context.Register(baz, this);
|
||||||
|
|
||||||
Prop1 = baz.Prop1;
|
Prop1 = baz.Prop1;
|
||||||
Prop2 = baz.Prop2;
|
Prop2 = baz.Prop2;
|
||||||
Prop3 = baz.Prop3;
|
Prop3 = baz.Prop3;
|
||||||
|
@ -299,7 +320,7 @@ namespace Test
|
||||||
const string expectedResult = @"
|
const string expectedResult = @"
|
||||||
public static Foo From(Baz baz)
|
public static Foo From(Baz baz)
|
||||||
{
|
{
|
||||||
return baz == null ? null : new Foo(baz);
|
return baz == null ? null : MappingContext.Create<Baz, Foo>(baz);
|
||||||
}
|
}
|
||||||
";
|
";
|
||||||
|
|
||||||
|
@ -358,13 +379,19 @@ namespace Test
|
||||||
partial class Foo
|
partial class Foo
|
||||||
{
|
{
|
||||||
public Foo(Baz baz)
|
public Foo(Baz baz)
|
||||||
|
: this(new MappingContext(), baz) { }
|
||||||
|
|
||||||
|
private protected Foo(MappingContext context, Baz baz)
|
||||||
{
|
{
|
||||||
|
if (context == null) throw new ArgumentNullException(nameof(context));
|
||||||
if (baz == null) throw new ArgumentNullException(nameof(baz));
|
if (baz == null) throw new ArgumentNullException(nameof(baz));
|
||||||
|
|
||||||
|
context.Register(baz, this);
|
||||||
|
|
||||||
Prop1 = baz.Prop1;
|
Prop1 = baz.Prop1;
|
||||||
Prop2 = baz.Prop2;
|
Prop2 = baz.Prop2;
|
||||||
Prop3 = baz.Prop3;
|
Prop3 = baz.Prop3;
|
||||||
InnerProp1 = baz.InnerProp1.ToB();
|
InnerProp1 = context.MapFromWithContext<A, B>(baz.InnerProp1);
|
||||||
}
|
}
|
||||||
".Trim();
|
".Trim();
|
||||||
|
|
||||||
|
@ -444,9 +471,15 @@ namespace Test
|
||||||
partial class Foo
|
partial class Foo
|
||||||
{
|
{
|
||||||
public Foo(Baz baz)
|
public Foo(Baz baz)
|
||||||
|
: this(new MappingContext(), baz) { }
|
||||||
|
|
||||||
|
private protected Foo(MappingContext context, Baz baz)
|
||||||
{
|
{
|
||||||
|
if (context == null) throw new ArgumentNullException(nameof(context));
|
||||||
if (baz == null) throw new ArgumentNullException(nameof(baz));
|
if (baz == null) throw new ArgumentNullException(nameof(baz));
|
||||||
|
|
||||||
|
context.Register(baz, this);
|
||||||
|
|
||||||
Prop1 = baz.Prop1;
|
Prop1 = baz.Prop1;
|
||||||
Prop2 = baz.Prop2;
|
Prop2 = baz.Prop2;
|
||||||
Prop3 = baz.Prop3;
|
Prop3 = baz.Prop3;
|
||||||
|
@ -469,11 +502,16 @@ namespace Test
|
||||||
var sources = GetEmployeeManagerSourceText();
|
var sources = GetEmployeeManagerSourceText();
|
||||||
|
|
||||||
const string expectedResult = @"
|
const string expectedResult = @"
|
||||||
public ManagerViewModel(Manager manager) : base(manager)
|
private protected ManagerViewModel(MappingContext context, Manager manager) : base(context, manager)
|
||||||
{
|
{
|
||||||
|
if (context == null) throw new ArgumentNullException(nameof(context));
|
||||||
if (manager == null) throw new ArgumentNullException(nameof(manager));
|
if (manager == null) throw new ArgumentNullException(nameof(manager));
|
||||||
|
|
||||||
|
context.Register(manager, this);
|
||||||
|
|
||||||
Level = manager.Level;
|
Level = manager.Level;
|
||||||
|
Employees = manager.Employees.Select(context.MapFromWithContext<Employee, EmployeeViewModel>).ToList();
|
||||||
|
}
|
||||||
";
|
";
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
|
@ -500,12 +538,19 @@ namespace Test.ViewModels
|
||||||
{
|
{
|
||||||
partial class ManagerViewModel
|
partial class ManagerViewModel
|
||||||
{
|
{
|
||||||
public ManagerViewModel(Manager manager) : base(manager)
|
public ManagerViewModel(Manager manager)
|
||||||
|
: this(new MappingContext(), manager) { }
|
||||||
|
|
||||||
|
private protected ManagerViewModel(MappingContext context, Manager manager) : base(context, manager)
|
||||||
{
|
{
|
||||||
|
if (context == null) throw new ArgumentNullException(nameof(context));
|
||||||
if (manager == null) throw new ArgumentNullException(nameof(manager));
|
if (manager == null) throw new ArgumentNullException(nameof(manager));
|
||||||
|
|
||||||
|
context.Register(manager, this);
|
||||||
|
|
||||||
Level = manager.Level;
|
Level = manager.Level;
|
||||||
Employees = manager.Employees.Select(EmployeeToEmployeeViewModelExtensions.ToEmployeeViewModel).ToList();
|
Employees = manager.Employees.Select(context.MapFromWithContext<Employee, EmployeeViewModel>).ToList();
|
||||||
|
}
|
||||||
";
|
";
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
|
@ -533,12 +578,19 @@ namespace Test.ViewModels2
|
||||||
{
|
{
|
||||||
partial class ManagerViewModel
|
partial class ManagerViewModel
|
||||||
{
|
{
|
||||||
public ManagerViewModel(Manager manager) : base(manager)
|
public ManagerViewModel(Manager manager)
|
||||||
|
: this(new MappingContext(), manager) { }
|
||||||
|
|
||||||
|
private protected ManagerViewModel(MappingContext context, Manager manager) : base(context, manager)
|
||||||
{
|
{
|
||||||
|
if (context == null) throw new ArgumentNullException(nameof(context));
|
||||||
if (manager == null) throw new ArgumentNullException(nameof(manager));
|
if (manager == null) throw new ArgumentNullException(nameof(manager));
|
||||||
|
|
||||||
|
context.Register(manager, this);
|
||||||
|
|
||||||
Level = manager.Level;
|
Level = manager.Level;
|
||||||
Employees = manager.Employees.Select(EmployeeToEmployeeViewModelExtensions.ToEmployeeViewModel).ToList();
|
Employees = manager.Employees.Select(context.MapFromWithContext<Employee, EmployeeViewModel>).ToList();
|
||||||
|
}
|
||||||
";
|
";
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
|
|
|
@ -3,7 +3,7 @@ using MapTo.Sources;
|
||||||
using MapTo.Tests.Extensions;
|
using MapTo.Tests.Extensions;
|
||||||
using MapTo.Tests.Infrastructure;
|
using MapTo.Tests.Infrastructure;
|
||||||
using Microsoft.CodeAnalysis;
|
using Microsoft.CodeAnalysis;
|
||||||
using Shouldly;
|
using Microsoft.CodeAnalysis.CSharp;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
using static MapTo.Tests.Common;
|
using static MapTo.Tests.Common;
|
||||||
|
|
||||||
|
@ -77,7 +77,7 @@ namespace MapTo
|
||||||
".Trim();
|
".Trim();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions, nullableContextOptions: NullableContextOptions.Enable);
|
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions, nullableContextOptions: NullableContextOptions.Enable, languageVersion: LanguageVersion.CSharp8);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
diagnostics.ShouldBeSuccessful();
|
diagnostics.ShouldBeSuccessful();
|
||||||
|
@ -128,7 +128,7 @@ namespace MapTo
|
||||||
".Trim();
|
".Trim();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions, nullableContextOptions: NullableContextOptions.Enable);
|
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions, nullableContextOptions: NullableContextOptions.Enable, languageVersion: LanguageVersion.CSharp8);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
diagnostics.ShouldBeSuccessful();
|
diagnostics.ShouldBeSuccessful();
|
||||||
|
@ -220,9 +220,15 @@ namespace Test
|
||||||
partial class Foo
|
partial class Foo
|
||||||
{
|
{
|
||||||
public Foo(Baz baz)
|
public Foo(Baz baz)
|
||||||
|
: this(new MappingContext(), baz) { }
|
||||||
|
|
||||||
|
private protected Foo(MappingContext context, Baz baz)
|
||||||
{
|
{
|
||||||
|
if (context == null) throw new ArgumentNullException(nameof(context));
|
||||||
if (baz == null) throw new ArgumentNullException(nameof(baz));
|
if (baz == null) throw new ArgumentNullException(nameof(baz));
|
||||||
|
|
||||||
|
context.Register(baz, this);
|
||||||
|
|
||||||
Prop1 = baz.Prop1;
|
Prop1 = baz.Prop1;
|
||||||
Prop2 = baz.Prop2;
|
Prop2 = baz.Prop2;
|
||||||
Prop3 = baz.Prop3;
|
Prop3 = baz.Prop3;
|
||||||
|
|
|
@ -0,0 +1,102 @@
|
||||||
|
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,4 +1,5 @@
|
||||||
using System;
|
using System;
|
||||||
|
using MapTo;
|
||||||
using TestConsoleApp.Data.Models;
|
using TestConsoleApp.Data.Models;
|
||||||
using TestConsoleApp.ViewModels;
|
using TestConsoleApp.ViewModels;
|
||||||
using TestConsoleApp.ViewModels2;
|
using TestConsoleApp.ViewModels2;
|
||||||
|
@ -10,6 +11,14 @@ namespace TestConsoleApp
|
||||||
private static void Main(string[] args)
|
private static void Main(string[] args)
|
||||||
{
|
{
|
||||||
//UserTest();
|
//UserTest();
|
||||||
|
CyclicReferenceTest();
|
||||||
|
|
||||||
|
// EmployeeManagerTest();
|
||||||
|
Console.WriteLine("done");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void EmployeeManagerTest()
|
||||||
|
{
|
||||||
var manager1 = new Manager
|
var manager1 = new Manager
|
||||||
{
|
{
|
||||||
Id = 1,
|
Id = 1,
|
||||||
|
@ -46,6 +55,19 @@ namespace TestConsoleApp
|
||||||
employee1.ToEmployeeViewModel();
|
employee1.ToEmployeeViewModel();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static ManagerViewModel CyclicReferenceTest()
|
||||||
|
{
|
||||||
|
var manager1 = new Manager
|
||||||
|
{
|
||||||
|
Id = 1,
|
||||||
|
EmployeeCode = "M001",
|
||||||
|
Level = 100
|
||||||
|
};
|
||||||
|
|
||||||
|
manager1.Manager = manager1;
|
||||||
|
return manager1.ToManagerViewModel();
|
||||||
|
}
|
||||||
|
|
||||||
private static void UserTest()
|
private static void UserTest()
|
||||||
{
|
{
|
||||||
var user = new User
|
var user = new User
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json",
|
"$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json",
|
||||||
"version": "0.6",
|
"version": "0.7",
|
||||||
"semVer1NumericIdentifierPadding": 1,
|
"semVer1NumericIdentifierPadding": 1,
|
||||||
"publicReleaseRefSpec": [
|
"publicReleaseRefSpec": [
|
||||||
"^refs/heads/master$",
|
"^refs/heads/master$",
|
||||||
|
|
Loading…
Reference in New Issue