Simplified using statements and fix nested mappings.
This commit is contained in:
parent
6ad7ea83f9
commit
c1a763a474
|
@ -1,7 +1,6 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using MapTo.Sources;
|
|
||||||
using Microsoft.CodeAnalysis;
|
using Microsoft.CodeAnalysis;
|
||||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||||
|
|
||||||
|
@ -68,7 +67,13 @@ namespace MapTo.Extensions
|
||||||
targetProperty.NullableAnnotation == NullableAnnotation.Annotated));
|
targetProperty.NullableAnnotation == NullableAnnotation.Annotated));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static INamedTypeSymbol GetTypeByMetadataNameOrThrow(this Compilation compilation, string fullyQualifiedMetadataName) =>
|
public static INamedTypeSymbol GetTypeByMetadataNameOrThrow(this Compilation compilation, string fullyQualifiedMetadataName) =>
|
||||||
compilation.GetTypeByMetadataName(fullyQualifiedMetadataName) ?? throw new TypeLoadException($"Unable to find '{fullyQualifiedMetadataName}' type.");
|
compilation.GetTypeByMetadataName(fullyQualifiedMetadataName) ?? throw new TypeLoadException($"Unable to find '{fullyQualifiedMetadataName}' type.");
|
||||||
|
|
||||||
|
public static bool IsGenericEnumerable(this Compilation compilation, ITypeSymbol typeSymbol) =>
|
||||||
|
typeSymbol is INamedTypeSymbol { IsGenericType: true } &&
|
||||||
|
compilation.GetSpecialType(SpecialType.System_Collections_Generic_IEnumerable_T).Equals(typeSymbol.OriginalDefinition, SymbolEqualityComparer.Default);
|
||||||
|
|
||||||
|
public static bool IsArray(this Compilation compilation, ITypeSymbol typeSymbol) => typeSymbol is IArrayTypeSymbol;
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -12,16 +12,19 @@ namespace MapTo
|
||||||
{
|
{
|
||||||
private readonly ClassDeclarationSyntax _classSyntax;
|
private readonly ClassDeclarationSyntax _classSyntax;
|
||||||
private readonly Compilation _compilation;
|
private readonly Compilation _compilation;
|
||||||
|
private readonly List<Diagnostic> _diagnostics;
|
||||||
private readonly INamedTypeSymbol _ignorePropertyAttributeTypeSymbol;
|
private readonly INamedTypeSymbol _ignorePropertyAttributeTypeSymbol;
|
||||||
private readonly INamedTypeSymbol _mapFromAttributeTypeSymbol;
|
private readonly INamedTypeSymbol _mapFromAttributeTypeSymbol;
|
||||||
private readonly INamedTypeSymbol _mapPropertyAttributeTypeSymbol;
|
private readonly INamedTypeSymbol _mapPropertyAttributeTypeSymbol;
|
||||||
private readonly INamedTypeSymbol _mapTypeConverterAttributeTypeSymbol;
|
private readonly INamedTypeSymbol _mapTypeConverterAttributeTypeSymbol;
|
||||||
private readonly SourceGenerationOptions _sourceGenerationOptions;
|
private readonly SourceGenerationOptions _sourceGenerationOptions;
|
||||||
private readonly INamedTypeSymbol _typeConverterInterfaceTypeSymbol;
|
private readonly INamedTypeSymbol _typeConverterInterfaceTypeSymbol;
|
||||||
|
private readonly List<string> _usings;
|
||||||
|
|
||||||
internal MappingContext(Compilation compilation, SourceGenerationOptions sourceGenerationOptions, ClassDeclarationSyntax classSyntax)
|
internal MappingContext(Compilation compilation, SourceGenerationOptions sourceGenerationOptions, ClassDeclarationSyntax classSyntax)
|
||||||
{
|
{
|
||||||
Diagnostics = ImmutableArray<Diagnostic>.Empty;
|
_diagnostics = new List<Diagnostic>();
|
||||||
|
_usings = new List<string> { "System" };
|
||||||
_sourceGenerationOptions = sourceGenerationOptions;
|
_sourceGenerationOptions = sourceGenerationOptions;
|
||||||
_classSyntax = classSyntax;
|
_classSyntax = classSyntax;
|
||||||
_compilation = compilation;
|
_compilation = compilation;
|
||||||
|
@ -32,26 +35,28 @@ namespace MapTo
|
||||||
_mapPropertyAttributeTypeSymbol = compilation.GetTypeByMetadataNameOrThrow(MapPropertyAttributeSource.FullyQualifiedName);
|
_mapPropertyAttributeTypeSymbol = compilation.GetTypeByMetadataNameOrThrow(MapPropertyAttributeSource.FullyQualifiedName);
|
||||||
_mapFromAttributeTypeSymbol = compilation.GetTypeByMetadataNameOrThrow(MapFromAttributeSource.FullyQualifiedName);
|
_mapFromAttributeTypeSymbol = compilation.GetTypeByMetadataNameOrThrow(MapFromAttributeSource.FullyQualifiedName);
|
||||||
|
|
||||||
|
AddUsingIfRequired(sourceGenerationOptions.SupportNullableReferenceTypes, "System.Diagnostics.CodeAnalysis");
|
||||||
|
|
||||||
Initialize();
|
Initialize();
|
||||||
}
|
}
|
||||||
|
|
||||||
public MappingModel? Model { get; private set; }
|
public MappingModel? Model { get; private set; }
|
||||||
|
|
||||||
public ImmutableArray<Diagnostic> Diagnostics { get; private set; }
|
public IEnumerable<Diagnostic> Diagnostics => _diagnostics;
|
||||||
|
|
||||||
private void Initialize()
|
private void Initialize()
|
||||||
{
|
{
|
||||||
var semanticModel = _compilation.GetSemanticModel(_classSyntax.SyntaxTree);
|
var semanticModel = _compilation.GetSemanticModel(_classSyntax.SyntaxTree);
|
||||||
if (!(semanticModel.GetDeclaredSymbol(_classSyntax) is INamedTypeSymbol classTypeSymbol))
|
if (!(semanticModel.GetDeclaredSymbol(_classSyntax) is INamedTypeSymbol classTypeSymbol))
|
||||||
{
|
{
|
||||||
ReportDiagnostic(DiagnosticProvider.TypeNotFoundError(_classSyntax.GetLocation(), _classSyntax.Identifier.ValueText));
|
_diagnostics.Add(DiagnosticProvider.TypeNotFoundError(_classSyntax.GetLocation(), _classSyntax.Identifier.ValueText));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var sourceTypeSymbol = GetSourceTypeSymbol(_classSyntax, semanticModel);
|
var sourceTypeSymbol = GetSourceTypeSymbol(_classSyntax, semanticModel);
|
||||||
if (sourceTypeSymbol is null)
|
if (sourceTypeSymbol is null)
|
||||||
{
|
{
|
||||||
ReportDiagnostic(DiagnosticProvider.MapFromAttributeNotFoundError(_classSyntax.GetLocation()));
|
_diagnostics.Add(DiagnosticProvider.MapFromAttributeNotFoundError(_classSyntax.GetLocation()));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -62,10 +67,13 @@ namespace MapTo
|
||||||
var mappedProperties = GetMappedProperties(classTypeSymbol, sourceTypeSymbol, isClassInheritFromMappedBaseClass);
|
var mappedProperties = GetMappedProperties(classTypeSymbol, sourceTypeSymbol, isClassInheritFromMappedBaseClass);
|
||||||
if (!mappedProperties.Any())
|
if (!mappedProperties.Any())
|
||||||
{
|
{
|
||||||
ReportDiagnostic(DiagnosticProvider.NoMatchingPropertyFoundError(_classSyntax.GetLocation(), classTypeSymbol, sourceTypeSymbol));
|
_diagnostics.Add(DiagnosticProvider.NoMatchingPropertyFoundError(_classSyntax.GetLocation(), classTypeSymbol, sourceTypeSymbol));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AddUsingIfRequired(sourceTypeSymbol);
|
||||||
|
AddUsingIfRequired(mappedProperties.Any(p => p.IsEnumerable), "System.Linq");
|
||||||
|
|
||||||
Model = new MappingModel(
|
Model = new MappingModel(
|
||||||
_sourceGenerationOptions,
|
_sourceGenerationOptions,
|
||||||
_classSyntax.GetNamespace(),
|
_classSyntax.GetNamespace(),
|
||||||
|
@ -75,7 +83,8 @@ namespace MapTo
|
||||||
sourceClassName,
|
sourceClassName,
|
||||||
sourceTypeSymbol.ToString(),
|
sourceTypeSymbol.ToString(),
|
||||||
mappedProperties.ToImmutableArray(),
|
mappedProperties.ToImmutableArray(),
|
||||||
isClassInheritFromMappedBaseClass);
|
isClassInheritFromMappedBaseClass,
|
||||||
|
_usings.ToImmutableArray());
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool IsClassInheritFromMappedBaseClass(SemanticModel semanticModel)
|
private bool IsClassInheritFromMappedBaseClass(SemanticModel semanticModel)
|
||||||
|
@ -103,60 +112,73 @@ namespace MapTo
|
||||||
|
|
||||||
string? converterFullyQualifiedName = null;
|
string? converterFullyQualifiedName = null;
|
||||||
var converterParameters = ImmutableArray<string>.Empty;
|
var converterParameters = ImmutableArray<string>.Empty;
|
||||||
string? mappedSourcePropertyType = null;
|
ITypeSymbol? mappedSourcePropertyType = null;
|
||||||
|
ITypeSymbol? enumerableTypeArgumentType = null;
|
||||||
|
|
||||||
if (!_compilation.HasCompatibleTypes(sourceProperty, property))
|
if (!_compilation.HasCompatibleTypes(sourceProperty, property))
|
||||||
{
|
{
|
||||||
if (!TryGetMapTypeConverter(property, sourceProperty, out converterFullyQualifiedName, out converterParameters) &&
|
if (!TryGetMapTypeConverter(property, sourceProperty, out converterFullyQualifiedName, out converterParameters) &&
|
||||||
!TryGetNestedObjectMappings(property, out mappedSourcePropertyType))
|
!TryGetNestedObjectMappings(property, out mappedSourcePropertyType, out enumerableTypeArgumentType))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mappedProperties.Add(new MappedProperty(
|
AddUsingIfRequired(property.Type);
|
||||||
property.Name,
|
AddUsingIfRequired(sourceTypeSymbol);
|
||||||
property.Type.Name,
|
AddUsingIfRequired(enumerableTypeArgumentType);
|
||||||
converterFullyQualifiedName,
|
AddUsingIfRequired(mappedSourcePropertyType);
|
||||||
converterParameters.ToImmutableArray(),
|
|
||||||
sourceProperty.Name,
|
mappedProperties.Add(
|
||||||
mappedSourcePropertyType));
|
new MappedProperty(
|
||||||
|
property.Name,
|
||||||
|
property.Type.Name,
|
||||||
|
converterFullyQualifiedName,
|
||||||
|
converterParameters.ToImmutableArray(),
|
||||||
|
sourceProperty.Name,
|
||||||
|
mappedSourcePropertyType?.Name,
|
||||||
|
enumerableTypeArgumentType?.Name));
|
||||||
}
|
}
|
||||||
|
|
||||||
return mappedProperties.ToImmutableArray();
|
return mappedProperties.ToImmutableArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool TryGetNestedObjectMappings(IPropertySymbol property, out string? mappedSourcePropertyType)
|
private bool TryGetNestedObjectMappings(IPropertySymbol property, out ITypeSymbol? mappedSourcePropertyType, out ITypeSymbol? enumerableTypeArgument)
|
||||||
{
|
{
|
||||||
mappedSourcePropertyType = null;
|
mappedSourcePropertyType = null;
|
||||||
|
enumerableTypeArgument = null;
|
||||||
|
|
||||||
if (!Diagnostics.IsEmpty)
|
if (!_diagnostics.IsEmpty())
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
var nestedSourceMapFromAttribute = property.Type.GetAttribute(_mapFromAttributeTypeSymbol);
|
var mapFromAttribute = property.Type.GetAttribute(_mapFromAttributeTypeSymbol);
|
||||||
if (nestedSourceMapFromAttribute is null)
|
if (mapFromAttribute is null && property.Type is INamedTypeSymbol namedTypeSymbol && _compilation.IsGenericEnumerable(property.Type))
|
||||||
{
|
{
|
||||||
ReportDiagnostic(DiagnosticProvider.NoMatchingPropertyTypeFoundError(property));
|
enumerableTypeArgument = namedTypeSymbol.TypeArguments.First();
|
||||||
return false;
|
mapFromAttribute = enumerableTypeArgument.GetAttribute(_mapFromAttributeTypeSymbol);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(nestedSourceMapFromAttribute.ApplicationSyntaxReference?.GetSyntax() is AttributeSyntax nestedAttributeSyntax))
|
mappedSourcePropertyType = mapFromAttribute?.ConstructorArguments.First().Value as INamedTypeSymbol;
|
||||||
|
|
||||||
|
if (mappedSourcePropertyType is null && enumerableTypeArgument is null)
|
||||||
{
|
{
|
||||||
ReportDiagnostic(DiagnosticProvider.NoMatchingPropertyTypeFoundError(property));
|
_diagnostics.Add(DiagnosticProvider.NoMatchingPropertyTypeFoundError(property));
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var nestedSourceTypeSymbol = GetSourceTypeSymbol(nestedAttributeSyntax);
|
return _diagnostics.IsEmpty();
|
||||||
if (nestedSourceTypeSymbol is null)
|
}
|
||||||
{
|
|
||||||
ReportDiagnostic(DiagnosticProvider.NoMatchingPropertyTypeFoundError(property));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
mappedSourcePropertyType = nestedSourceTypeSymbol.Name;
|
private void AddUsingIfRequired(ISymbol? namedTypeSymbol) =>
|
||||||
return true;
|
AddUsingIfRequired(namedTypeSymbol?.ContainingNamespace.IsGlobalNamespace == false, namedTypeSymbol?.ContainingNamespace.ToDisplayString());
|
||||||
|
|
||||||
|
private void AddUsingIfRequired(bool condition, string? ns)
|
||||||
|
{
|
||||||
|
if (condition && ns is not null && ns != _classSyntax.GetNamespace() && !_usings.Contains(ns))
|
||||||
|
{
|
||||||
|
_usings.Add(ns);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool TryGetMapTypeConverter(IPropertySymbol property, IPropertySymbol sourceProperty, out string? converterFullyQualifiedName, out ImmutableArray<string> converterParameters)
|
private bool TryGetMapTypeConverter(IPropertySymbol property, IPropertySymbol sourceProperty, out string? converterFullyQualifiedName, out ImmutableArray<string> converterParameters)
|
||||||
|
@ -164,7 +186,7 @@ namespace MapTo
|
||||||
converterFullyQualifiedName = null;
|
converterFullyQualifiedName = null;
|
||||||
converterParameters = ImmutableArray<string>.Empty;
|
converterParameters = ImmutableArray<string>.Empty;
|
||||||
|
|
||||||
if (!Diagnostics.IsEmpty)
|
if (!_diagnostics.IsEmpty())
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -178,7 +200,7 @@ namespace MapTo
|
||||||
var baseInterface = GetTypeConverterBaseInterface(converterTypeSymbol, property, sourceProperty);
|
var baseInterface = GetTypeConverterBaseInterface(converterTypeSymbol, property, sourceProperty);
|
||||||
if (baseInterface is null)
|
if (baseInterface is null)
|
||||||
{
|
{
|
||||||
ReportDiagnostic(DiagnosticProvider.InvalidTypeConverterGenericTypesError(property, sourceProperty));
|
_diagnostics.Add(DiagnosticProvider.InvalidTypeConverterGenericTypesError(property, sourceProperty));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -216,11 +238,6 @@ namespace MapTo
|
||||||
: converterParameter.Values.Where(v => v.Value is not null).Select(v => v.Value!.ToSourceCodeString()).ToImmutableArray();
|
: converterParameter.Values.Where(v => v.Value is not null).Select(v => v.Value!.ToSourceCodeString()).ToImmutableArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ReportDiagnostic(Diagnostic diagnostic)
|
|
||||||
{
|
|
||||||
Diagnostics = Diagnostics.Add(diagnostic);
|
|
||||||
}
|
|
||||||
|
|
||||||
private INamedTypeSymbol? GetSourceTypeSymbol(ClassDeclarationSyntax classDeclarationSyntax, SemanticModel? semanticModel = null) =>
|
private INamedTypeSymbol? GetSourceTypeSymbol(ClassDeclarationSyntax classDeclarationSyntax, SemanticModel? semanticModel = null) =>
|
||||||
GetSourceTypeSymbol(classDeclarationSyntax.GetAttribute(MapFromAttributeSource.AttributeName), semanticModel);
|
GetSourceTypeSymbol(classDeclarationSyntax.GetAttribute(MapFromAttributeSource.AttributeName), semanticModel);
|
||||||
|
|
||||||
|
|
|
@ -8,12 +8,16 @@ namespace MapTo
|
||||||
internal record SourceCode(string Text, string HintName);
|
internal record SourceCode(string Text, string HintName);
|
||||||
|
|
||||||
internal record MappedProperty(
|
internal record MappedProperty(
|
||||||
string Name,
|
string Name,
|
||||||
string Type,
|
string Type,
|
||||||
string? TypeConverter,
|
string? TypeConverter,
|
||||||
ImmutableArray<string> TypeConverterParameters,
|
ImmutableArray<string> TypeConverterParameters,
|
||||||
string SourcePropertyName,
|
string SourcePropertyName,
|
||||||
string? MappedSourcePropertyTypeName);
|
string? MappedSourcePropertyTypeName,
|
||||||
|
string? EnumerableTypeArgument)
|
||||||
|
{
|
||||||
|
public bool IsEnumerable => EnumerableTypeArgument is not null;
|
||||||
|
}
|
||||||
|
|
||||||
internal record MappingModel (
|
internal record MappingModel (
|
||||||
SourceGenerationOptions Options,
|
SourceGenerationOptions Options,
|
||||||
|
@ -24,7 +28,8 @@ namespace MapTo
|
||||||
string SourceClassName,
|
string SourceClassName,
|
||||||
string SourceClassFullName,
|
string SourceClassFullName,
|
||||||
ImmutableArray<MappedProperty> MappedProperties,
|
ImmutableArray<MappedProperty> MappedProperties,
|
||||||
bool HasMappedBaseClass
|
bool HasMappedBaseClass,
|
||||||
|
ImmutableArray<string> Usings
|
||||||
);
|
);
|
||||||
|
|
||||||
internal record SourceGenerationOptions(
|
internal record SourceGenerationOptions(
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
using MapTo.Extensions;
|
using System.Linq;
|
||||||
|
using MapTo.Extensions;
|
||||||
using static MapTo.Sources.Constants;
|
using static MapTo.Sources.Constants;
|
||||||
|
|
||||||
namespace MapTo.Sources
|
namespace MapTo.Sources
|
||||||
|
@ -42,9 +43,8 @@ namespace MapTo.Sources
|
||||||
|
|
||||||
private static SourceBuilder WriteUsings(this SourceBuilder builder, MappingModel model)
|
private static SourceBuilder WriteUsings(this SourceBuilder builder, MappingModel model)
|
||||||
{
|
{
|
||||||
return builder
|
model.Usings.Sort().ForEach(u => builder.WriteLine($"using {u};"));
|
||||||
.WriteLine("using System;")
|
return builder;
|
||||||
.WriteLineIf(model.Options.SupportNullableReferenceTypes, "using System.Diagnostics.CodeAnalysis;");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static SourceBuilder GenerateConstructor(this SourceBuilder builder, MappingModel model)
|
private static SourceBuilder GenerateConstructor(this SourceBuilder builder, MappingModel model)
|
||||||
|
@ -64,7 +64,7 @@ namespace MapTo.Sources
|
||||||
var baseConstructor = model.HasMappedBaseClass ? $" : base({sourceClassParameterName})" : string.Empty;
|
var baseConstructor = model.HasMappedBaseClass ? $" : base({sourceClassParameterName})" : string.Empty;
|
||||||
|
|
||||||
builder
|
builder
|
||||||
.WriteLine($"{model.Options.ConstructorAccessModifier.ToLowercaseString()} {model.ClassName}({model.SourceClassFullName} {sourceClassParameterName}){baseConstructor}")
|
.WriteLine($"{model.Options.ConstructorAccessModifier.ToLowercaseString()} {model.ClassName}({model.SourceClassName} {sourceClassParameterName}){baseConstructor}")
|
||||||
.WriteOpeningBracket()
|
.WriteOpeningBracket()
|
||||||
.WriteLine($"if ({sourceClassParameterName} == null) throw new ArgumentNullException(nameof({sourceClassParameterName}));")
|
.WriteLine($"if ({sourceClassParameterName} == null) throw new ArgumentNullException(nameof({sourceClassParameterName}));")
|
||||||
.WriteLine();
|
.WriteLine();
|
||||||
|
@ -73,9 +73,16 @@ namespace MapTo.Sources
|
||||||
{
|
{
|
||||||
if (property.TypeConverter is null)
|
if (property.TypeConverter is null)
|
||||||
{
|
{
|
||||||
builder.WriteLine(property.MappedSourcePropertyTypeName is null
|
if (property.IsEnumerable)
|
||||||
? $"{property.Name} = {sourceClassParameterName}.{property.SourcePropertyName};"
|
{
|
||||||
: $"{property.Name} = {sourceClassParameterName}.{property.SourcePropertyName}.To{property.Type}();");
|
builder.WriteLine($"{property.Name} = {sourceClassParameterName}.{property.SourcePropertyName}.Select({property.MappedSourcePropertyTypeName}To{property.EnumerableTypeArgument}Extensions.To{property.EnumerableTypeArgument}).ToList();");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
builder.WriteLine(property.MappedSourcePropertyTypeName is null
|
||||||
|
? $"{property.Name} = {sourceClassParameterName}.{property.SourcePropertyName};"
|
||||||
|
: $"{property.Name} = {sourceClassParameterName}.{property.SourcePropertyName}.To{property.Type}();");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -98,7 +105,7 @@ namespace MapTo.Sources
|
||||||
return builder
|
return builder
|
||||||
.GenerateConvertorMethodsXmlDocs(model, sourceClassParameterName)
|
.GenerateConvertorMethodsXmlDocs(model, sourceClassParameterName)
|
||||||
.WriteLineIf(model.Options.SupportNullableReferenceTypes, $"[return: NotNullIfNotNull(\"{sourceClassParameterName}\")]")
|
.WriteLineIf(model.Options.SupportNullableReferenceTypes, $"[return: NotNullIfNotNull(\"{sourceClassParameterName}\")]")
|
||||||
.WriteLine($"{model.Options.GeneratedMethodsAccessModifier.ToLowercaseString()} static {model.ClassName}{model.Options.NullableReferenceSyntax} From({model.SourceClassFullName}{model.Options.NullableReferenceSyntax} {sourceClassParameterName})")
|
.WriteLine($"{model.Options.GeneratedMethodsAccessModifier.ToLowercaseString()} static {model.ClassName}{model.Options.NullableReferenceSyntax} From({model.SourceClassName}{model.Options.NullableReferenceSyntax} {sourceClassParameterName})")
|
||||||
.WriteOpeningBracket()
|
.WriteOpeningBracket()
|
||||||
.WriteLine($"return {sourceClassParameterName} == null ? null : new {model.ClassName}({sourceClassParameterName});")
|
.WriteLine($"return {sourceClassParameterName} == null ? null : new {model.ClassName}({sourceClassParameterName});")
|
||||||
.WriteClosingBracket();
|
.WriteClosingBracket();
|
||||||
|
@ -136,7 +143,7 @@ namespace MapTo.Sources
|
||||||
return builder
|
return builder
|
||||||
.GenerateConvertorMethodsXmlDocs(model, sourceClassParameterName)
|
.GenerateConvertorMethodsXmlDocs(model, sourceClassParameterName)
|
||||||
.WriteLineIf(model.Options.SupportNullableReferenceTypes, $"[return: NotNullIfNotNull(\"{sourceClassParameterName}\")]")
|
.WriteLineIf(model.Options.SupportNullableReferenceTypes, $"[return: NotNullIfNotNull(\"{sourceClassParameterName}\")]")
|
||||||
.WriteLine($"{model.Options.GeneratedMethodsAccessModifier.ToLowercaseString()} static {model.ClassName}{model.Options.NullableReferenceSyntax} To{model.ClassName}(this {model.SourceClassFullName}{model.Options.NullableReferenceSyntax} {sourceClassParameterName})")
|
.WriteLine($"{model.Options.GeneratedMethodsAccessModifier.ToLowercaseString()} static {model.ClassName}{model.Options.NullableReferenceSyntax} To{model.ClassName}(this {model.SourceClassName}{model.Options.NullableReferenceSyntax} {sourceClassParameterName})")
|
||||||
.WriteOpeningBracket()
|
.WriteOpeningBracket()
|
||||||
.WriteLine($"return {sourceClassParameterName} == null ? null : new {model.ClassName}({sourceClassParameterName});")
|
.WriteLine($"return {sourceClassParameterName} == null ? null : new {model.ClassName}({sourceClassParameterName});")
|
||||||
.WriteClosingBracket();
|
.WriteClosingBracket();
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
|
||||||
using MapTo.Extensions;
|
using MapTo.Extensions;
|
||||||
using MapTo.Sources;
|
using MapTo.Sources;
|
||||||
using Microsoft.CodeAnalysis;
|
using Microsoft.CodeAnalysis;
|
||||||
|
@ -96,11 +95,19 @@ namespace MapTo.Tests
|
||||||
return builder.ToString();
|
return builder.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static string[] GetEmployeeManagerSourceText(Func<string> employeeClassSource = null, Func<string> managerClassSource = null, Func<string> employeeViewModelSource = null, Func<string> managerViewModelSource = null)
|
internal static string[] GetEmployeeManagerSourceText(Func<string> employeeClassSource = null, Func<string> managerClassSource = null, Func<string> employeeViewModelSource = null, Func<string> managerViewModelSource = null, bool useDifferentViewModelNamespace = false)
|
||||||
{
|
{
|
||||||
return new[]
|
return new[]
|
||||||
{
|
{
|
||||||
employeeClassSource?.Invoke() ?? @"
|
employeeClassSource?.Invoke() ?? DefaultEmployeeClassSource(),
|
||||||
|
managerClassSource?.Invoke() ?? DefaultManagerClassSource(),
|
||||||
|
employeeViewModelSource?.Invoke() ??
|
||||||
|
DefaultEmployeeViewModelSource(useDifferentViewModelNamespace),
|
||||||
|
managerViewModelSource?.Invoke() ?? DefaultManagerViewModelSource(useDifferentViewModelNamespace)
|
||||||
|
};
|
||||||
|
|
||||||
|
static string DefaultEmployeeClassSource() =>
|
||||||
|
@"
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
@ -115,8 +122,10 @@ namespace Test.Data.Models
|
||||||
|
|
||||||
public Manager Manager { get; set; }
|
public Manager Manager { get; set; }
|
||||||
}
|
}
|
||||||
}".Trim(),
|
}".Trim();
|
||||||
managerClassSource?.Invoke() ?? @"using System;
|
|
||||||
|
static string DefaultManagerClassSource() =>
|
||||||
|
@"using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
|
@ -129,8 +138,28 @@ namespace Test.Data.Models
|
||||||
public IEnumerable<Employee> Employees { get; set; } = Array.Empty<Employee>();
|
public IEnumerable<Employee> Employees { get; set; } = Array.Empty<Employee>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
".Trim(),
|
".Trim();
|
||||||
employeeViewModelSource?.Invoke() ?? @"
|
|
||||||
|
static string DefaultEmployeeViewModelSource(bool useDifferentNamespace) => useDifferentNamespace
|
||||||
|
? @"
|
||||||
|
using MapTo;
|
||||||
|
using Test.Data.Models;
|
||||||
|
using Test.ViewModels2;
|
||||||
|
|
||||||
|
namespace Test.ViewModels
|
||||||
|
{
|
||||||
|
[MapFrom(typeof(Employee))]
|
||||||
|
public partial class EmployeeViewModel
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
|
||||||
|
public string EmployeeCode { get; set; }
|
||||||
|
|
||||||
|
public ManagerViewModel Manager { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
".Trim()
|
||||||
|
: @"
|
||||||
using MapTo;
|
using MapTo;
|
||||||
using Test.Data.Models;
|
using Test.Data.Models;
|
||||||
|
|
||||||
|
@ -146,8 +175,28 @@ namespace Test.ViewModels
|
||||||
public ManagerViewModel Manager { get; set; }
|
public ManagerViewModel Manager { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
".Trim(),
|
".Trim();
|
||||||
managerViewModelSource?.Invoke() ?? @"
|
|
||||||
|
static string DefaultManagerViewModelSource(bool useDifferentNamespace) => useDifferentNamespace
|
||||||
|
? @"
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using MapTo;
|
||||||
|
using Test.Data.Models;
|
||||||
|
using Test.ViewModels;
|
||||||
|
|
||||||
|
namespace Test.ViewModels2
|
||||||
|
{
|
||||||
|
[MapFrom(typeof(Manager))]
|
||||||
|
public partial class ManagerViewModel : EmployeeViewModel
|
||||||
|
{
|
||||||
|
public int Level { get; set; }
|
||||||
|
|
||||||
|
public IEnumerable<EmployeeViewModel> Employees { get; set; } = Array.Empty<EmployeeViewModel>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
".Trim()
|
||||||
|
: @"
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using MapTo;
|
using MapTo;
|
||||||
|
@ -162,8 +211,7 @@ namespace Test.ViewModels
|
||||||
|
|
||||||
public IEnumerable<EmployeeViewModel> Employees { get; set; } = Array.Empty<EmployeeViewModel>();
|
public IEnumerable<EmployeeViewModel> Employees { get; set; } = Array.Empty<EmployeeViewModel>();
|
||||||
}
|
}
|
||||||
}".Trim()
|
}".Trim();
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static PropertyDeclarationSyntax GetPropertyDeclarationSyntax(SyntaxTree syntaxTree, string targetPropertyName, string targetClass = "Foo")
|
internal static PropertyDeclarationSyntax GetPropertyDeclarationSyntax(SyntaxTree syntaxTree, string targetPropertyName, string targetClass = "Foo")
|
||||||
|
|
|
@ -52,7 +52,7 @@ namespace MapTo
|
||||||
var expectedResult = @"
|
var expectedResult = @"
|
||||||
partial class Foo
|
partial class Foo
|
||||||
{
|
{
|
||||||
public Foo(Test.Models.Baz baz)
|
public Foo(Baz baz)
|
||||||
{
|
{
|
||||||
if (baz == null) throw new ArgumentNullException(nameof(baz));
|
if (baz == null) throw new ArgumentNullException(nameof(baz));
|
||||||
|
|
||||||
|
|
|
@ -40,7 +40,7 @@ namespace MapTo.Tests.Infrastructure
|
||||||
|
|
||||||
driver.RunGeneratorsAndUpdateCompilation(compilation, out var outputCompilation, out var generateDiagnostics);
|
driver.RunGeneratorsAndUpdateCompilation(compilation, out var outputCompilation, out var generateDiagnostics);
|
||||||
|
|
||||||
generateDiagnostics.ShouldBeSuccessful();
|
generateDiagnostics.ShouldBeSuccessful(ignoreDiagnosticsIds: new[] { "MT" });
|
||||||
outputCompilation.GetDiagnostics().ShouldBeSuccessful(outputCompilation);
|
outputCompilation.GetDiagnostics().ShouldBeSuccessful(outputCompilation);
|
||||||
|
|
||||||
return (outputCompilation, generateDiagnostics);
|
return (outputCompilation, generateDiagnostics);
|
||||||
|
|
|
@ -60,7 +60,7 @@ namespace MapTo
|
||||||
var expectedResult = @"
|
var expectedResult = @"
|
||||||
partial class Foo
|
partial class Foo
|
||||||
{
|
{
|
||||||
public Foo(Test.Models.Baz baz)
|
public Foo(Baz baz)
|
||||||
{
|
{
|
||||||
if (baz == null) throw new ArgumentNullException(nameof(baz));
|
if (baz == null) throw new ArgumentNullException(nameof(baz));
|
||||||
|
|
||||||
|
|
|
@ -75,14 +75,14 @@ namespace MapTo
|
||||||
var expectedExtension = @"
|
var expectedExtension = @"
|
||||||
internal static partial class BazToFooExtensions
|
internal static partial class BazToFooExtensions
|
||||||
{
|
{
|
||||||
internal static Foo ToFoo(this Test.Models.Baz baz)
|
internal static Foo ToFoo(this Baz baz)
|
||||||
{
|
{
|
||||||
return baz == null ? null : new Foo(baz);
|
return baz == null ? null : new Foo(baz);
|
||||||
}
|
}
|
||||||
}".Trim();
|
}".Trim();
|
||||||
|
|
||||||
var expectedFactory = @"
|
var expectedFactory = @"
|
||||||
internal static Foo From(Test.Models.Baz baz)
|
internal static Foo From(Baz baz)
|
||||||
{
|
{
|
||||||
return baz == null ? null : new Foo(baz);
|
return baz == null ? null : new Foo(baz);
|
||||||
}".Trim();
|
}".Trim();
|
||||||
|
@ -129,7 +129,7 @@ namespace Test
|
||||||
{
|
{
|
||||||
partial class Foo
|
partial class Foo
|
||||||
{
|
{
|
||||||
public Foo(Test.Baz baz)
|
public Foo(Baz baz)
|
||||||
{
|
{
|
||||||
if (baz == null) throw new ArgumentNullException(nameof(baz));
|
if (baz == null) throw new ArgumentNullException(nameof(baz));
|
||||||
|
|
||||||
|
@ -199,7 +199,7 @@ namespace Test
|
||||||
{
|
{
|
||||||
partial class Foo
|
partial class Foo
|
||||||
{
|
{
|
||||||
public Foo(Test.Baz baz)
|
public Foo(Baz baz)
|
||||||
{
|
{
|
||||||
if (baz == null) throw new ArgumentNullException(nameof(baz));
|
if (baz == null) throw new ArgumentNullException(nameof(baz));
|
||||||
|
|
||||||
|
@ -249,6 +249,7 @@ namespace Test
|
||||||
const string expectedResult = @"
|
const string expectedResult = @"
|
||||||
// <auto-generated />
|
// <auto-generated />
|
||||||
|
|
||||||
|
using Bazaar;
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace Test
|
namespace Test
|
||||||
|
@ -271,7 +272,7 @@ namespace Test
|
||||||
const string expectedResult = @"
|
const string expectedResult = @"
|
||||||
partial class Foo
|
partial class Foo
|
||||||
{
|
{
|
||||||
public Foo(Test.Models.Baz baz)
|
public Foo(Baz baz)
|
||||||
{
|
{
|
||||||
if (baz == null) throw new ArgumentNullException(nameof(baz));
|
if (baz == null) throw new ArgumentNullException(nameof(baz));
|
||||||
|
|
||||||
|
@ -296,7 +297,7 @@ namespace Test
|
||||||
var source = GetSourceText();
|
var source = GetSourceText();
|
||||||
|
|
||||||
const string expectedResult = @"
|
const string expectedResult = @"
|
||||||
public static Foo From(Test.Models.Baz baz)
|
public static Foo From(Baz baz)
|
||||||
{
|
{
|
||||||
return baz == null ? null : new Foo(baz);
|
return baz == null ? null : new Foo(baz);
|
||||||
}
|
}
|
||||||
|
@ -319,7 +320,7 @@ namespace Test
|
||||||
const string expectedResult = @"
|
const string expectedResult = @"
|
||||||
public static partial class BazToFooExtensions
|
public static partial class BazToFooExtensions
|
||||||
{
|
{
|
||||||
public static Foo ToFoo(this Test.Models.Baz baz)
|
public static Foo ToFoo(this Baz baz)
|
||||||
{
|
{
|
||||||
return baz == null ? null : new Foo(baz);
|
return baz == null ? null : new Foo(baz);
|
||||||
}
|
}
|
||||||
|
@ -356,7 +357,7 @@ namespace Test
|
||||||
var expectedResult = @"
|
var expectedResult = @"
|
||||||
partial class Foo
|
partial class Foo
|
||||||
{
|
{
|
||||||
public Foo(Test.Baz baz)
|
public Foo(Baz baz)
|
||||||
{
|
{
|
||||||
if (baz == null) throw new ArgumentNullException(nameof(baz));
|
if (baz == null) throw new ArgumentNullException(nameof(baz));
|
||||||
|
|
||||||
|
@ -442,7 +443,7 @@ namespace Test
|
||||||
const string expectedResult = @"
|
const string expectedResult = @"
|
||||||
partial class Foo
|
partial class Foo
|
||||||
{
|
{
|
||||||
public Foo(Test.Models.Baz baz)
|
public Foo(Baz baz)
|
||||||
{
|
{
|
||||||
if (baz == null) throw new ArgumentNullException(nameof(baz));
|
if (baz == null) throw new ArgumentNullException(nameof(baz));
|
||||||
|
|
||||||
|
@ -468,12 +469,76 @@ namespace Test
|
||||||
var sources = GetEmployeeManagerSourceText();
|
var sources = GetEmployeeManagerSourceText();
|
||||||
|
|
||||||
const string expectedResult = @"
|
const string expectedResult = @"
|
||||||
public ManagerViewModel(Test.Data.Models.Manager manager) : base(manager)
|
public ManagerViewModel(Manager manager) : base(manager)
|
||||||
{
|
{
|
||||||
if (manager == null) throw new ArgumentNullException(nameof(manager));
|
if (manager == null) throw new ArgumentNullException(nameof(manager));
|
||||||
|
|
||||||
Level = manager.Level;
|
Level = manager.Level;
|
||||||
}
|
";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(sources, analyzerConfigOptions: DefaultAnalyzerOptions);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
diagnostics.ShouldBeSuccessful();
|
||||||
|
compilation.SyntaxTrees.Last().ShouldContainPartialSource(expectedResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void When_SourceTypeHasEnumerablePropertiesWithMapFromAttribute_Should_CreateANewEnumerableWithMappedObjects()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var sources = GetEmployeeManagerSourceText();
|
||||||
|
|
||||||
|
const string expectedResult = @"
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Test.Data.Models;
|
||||||
|
|
||||||
|
namespace Test.ViewModels
|
||||||
|
{
|
||||||
|
partial class ManagerViewModel
|
||||||
|
{
|
||||||
|
public ManagerViewModel(Manager manager) : base(manager)
|
||||||
|
{
|
||||||
|
if (manager == null) throw new ArgumentNullException(nameof(manager));
|
||||||
|
|
||||||
|
Level = manager.Level;
|
||||||
|
Employees = manager.Employees.Select(EmployeeToEmployeeViewModelExtensions.ToEmployeeViewModel).ToList();
|
||||||
|
";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(sources, analyzerConfigOptions: DefaultAnalyzerOptions);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
diagnostics.ShouldBeSuccessful();
|
||||||
|
compilation.SyntaxTrees.Last().ShouldContainPartialSource(expectedResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void When_SourceTypeHasEnumerablePropertiesWithMapFromAttributeInDifferentNamespaces_Should_CreateANewEnumerableWithMappedObjectsAndImportNamespace()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var sources = GetEmployeeManagerSourceText(useDifferentViewModelNamespace: true);
|
||||||
|
|
||||||
|
const string expectedResult = @"
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Test.Data.Models;
|
||||||
|
using Test.ViewModels;
|
||||||
|
|
||||||
|
namespace Test.ViewModels2
|
||||||
|
{
|
||||||
|
partial class ManagerViewModel
|
||||||
|
{
|
||||||
|
public ManagerViewModel(Manager manager) : base(manager)
|
||||||
|
{
|
||||||
|
if (manager == null) throw new ArgumentNullException(nameof(manager));
|
||||||
|
|
||||||
|
Level = manager.Level;
|
||||||
|
Employees = manager.Employees.Select(EmployeeToEmployeeViewModelExtensions.ToEmployeeViewModel).ToList();
|
||||||
";
|
";
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
|
|
|
@ -219,7 +219,7 @@ namespace Test
|
||||||
var expectedResult = @"
|
var expectedResult = @"
|
||||||
partial class Foo
|
partial class Foo
|
||||||
{
|
{
|
||||||
public Foo(Test.Models.Baz baz)
|
public Foo(Baz baz)
|
||||||
{
|
{
|
||||||
if (baz == null) throw new ArgumentNullException(nameof(baz));
|
if (baz == null) throw new ArgumentNullException(nameof(baz));
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
using System;
|
using System;
|
||||||
using TestConsoleApp.Data.Models;
|
using TestConsoleApp.Data.Models;
|
||||||
using TestConsoleApp.ViewModels;
|
using TestConsoleApp.ViewModels;
|
||||||
|
using TestConsoleApp.ViewModels2;
|
||||||
|
|
||||||
namespace TestConsoleApp
|
namespace TestConsoleApp
|
||||||
{
|
{
|
||||||
|
@ -41,8 +42,8 @@ namespace TestConsoleApp
|
||||||
manager1.Employees = new[] { employee1, manager2 };
|
manager1.Employees = new[] { employee1, manager2 };
|
||||||
manager2.Employees = new[] { employee2 };
|
manager2.Employees = new[] { employee2 };
|
||||||
|
|
||||||
var manager1ViewModel = manager1.ToManagerViewModel();
|
manager1.ToManagerViewModel();
|
||||||
int a = 0;
|
employee1.ToEmployeeViewModel();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void UserTest()
|
private static void UserTest()
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
using MapTo;
|
using MapTo;
|
||||||
using TestConsoleApp.Data.Models;
|
using TestConsoleApp.Data.Models;
|
||||||
|
using TestConsoleApp.ViewModels2;
|
||||||
|
|
||||||
namespace TestConsoleApp.ViewModels
|
namespace TestConsoleApp.ViewModels
|
||||||
{
|
{
|
||||||
|
|
|
@ -2,8 +2,9 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using MapTo;
|
using MapTo;
|
||||||
using TestConsoleApp.Data.Models;
|
using TestConsoleApp.Data.Models;
|
||||||
|
using TestConsoleApp.ViewModels;
|
||||||
|
|
||||||
namespace TestConsoleApp.ViewModels
|
namespace TestConsoleApp.ViewModels2
|
||||||
{
|
{
|
||||||
[MapFrom(typeof(Manager))]
|
[MapFrom(typeof(Manager))]
|
||||||
public partial class ManagerViewModel : EmployeeViewModel
|
public partial class ManagerViewModel : EmployeeViewModel
|
||||||
|
|
Loading…
Reference in New Issue