Fix base constructor call issue.
This commit is contained in:
parent
1d7ca56916
commit
6ad7ea83f9
|
@ -20,9 +20,11 @@ namespace MapTo.Extensions
|
|||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<ISymbol> GetAllMembers(this ITypeSymbol type)
|
||||
public static IEnumerable<ISymbol> GetAllMembers(this ITypeSymbol type, bool includeBaseTypeMembers = true)
|
||||
{
|
||||
return type.GetBaseTypesAndThis().SelectMany(n => n.GetMembers());
|
||||
return includeBaseTypeMembers
|
||||
? type.GetBaseTypesAndThis().SelectMany(t => t.GetMembers())
|
||||
: type.GetMembers();
|
||||
}
|
||||
|
||||
public static CompilationUnitSyntax GetCompilationUnit(this SyntaxNode syntaxNode) => syntaxNode.Ancestors().OfType<CompilationUnitSyntax>().Single();
|
||||
|
|
|
@ -20,10 +20,6 @@
|
|||
<RootNamespace>MapTo</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||
<DocumentationFile>bin\Debug\MapTo.xml</DocumentationFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
|
||||
<DocumentationFile>bin\Release\MapTo.xml</DocumentationFile>
|
||||
</PropertyGroup>
|
||||
|
|
|
@ -57,8 +57,9 @@ namespace MapTo
|
|||
|
||||
var className = _classSyntax.GetClassName();
|
||||
var sourceClassName = sourceTypeSymbol.Name;
|
||||
var isClassInheritFromMappedBaseClass = IsClassInheritFromMappedBaseClass(semanticModel);
|
||||
|
||||
var mappedProperties = GetMappedProperties(classTypeSymbol, sourceTypeSymbol);
|
||||
var mappedProperties = GetMappedProperties(classTypeSymbol, sourceTypeSymbol, isClassInheritFromMappedBaseClass);
|
||||
if (!mappedProperties.Any())
|
||||
{
|
||||
ReportDiagnostic(DiagnosticProvider.NoMatchingPropertyFoundError(_classSyntax.GetLocation(), classTypeSymbol, sourceTypeSymbol));
|
||||
|
@ -73,14 +74,24 @@ namespace MapTo
|
|||
sourceTypeSymbol.ContainingNamespace.ToString(),
|
||||
sourceClassName,
|
||||
sourceTypeSymbol.ToString(),
|
||||
mappedProperties.ToImmutableArray());
|
||||
mappedProperties.ToImmutableArray(),
|
||||
isClassInheritFromMappedBaseClass);
|
||||
}
|
||||
|
||||
private ImmutableArray<MappedProperty> GetMappedProperties(ITypeSymbol classSymbol, ITypeSymbol sourceTypeSymbol)
|
||||
private bool IsClassInheritFromMappedBaseClass(SemanticModel semanticModel)
|
||||
{
|
||||
return _classSyntax.BaseList is not null && _classSyntax.BaseList.Types
|
||||
.Select(t => semanticModel.GetTypeInfo(t.Type).Type)
|
||||
.Any(t => t?.GetAttribute(_mapFromAttributeTypeSymbol) != null);
|
||||
}
|
||||
|
||||
private ImmutableArray<MappedProperty> GetMappedProperties(ITypeSymbol classSymbol, ITypeSymbol sourceTypeSymbol, bool isClassInheritFromMappedBaseClass)
|
||||
{
|
||||
var mappedProperties = new List<MappedProperty>();
|
||||
var sourceProperties = sourceTypeSymbol.GetAllMembers().OfType<IPropertySymbol>().ToArray();
|
||||
var classProperties = classSymbol.GetAllMembers().OfType<IPropertySymbol>().Where(p => !p.HasAttribute(_ignorePropertyAttributeTypeSymbol));
|
||||
var classProperties = classSymbol.GetAllMembers(!isClassInheritFromMappedBaseClass)
|
||||
.OfType<IPropertySymbol>()
|
||||
.Where(p => !p.HasAttribute(_ignorePropertyAttributeTypeSymbol));
|
||||
|
||||
foreach (var property in classProperties)
|
||||
{
|
||||
|
@ -96,7 +107,7 @@ namespace MapTo
|
|||
|
||||
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))
|
||||
{
|
||||
continue;
|
||||
|
@ -104,11 +115,11 @@ namespace MapTo
|
|||
}
|
||||
|
||||
mappedProperties.Add(new MappedProperty(
|
||||
property.Name,
|
||||
property.Name,
|
||||
property.Type.Name,
|
||||
converterFullyQualifiedName,
|
||||
converterParameters.ToImmutableArray(),
|
||||
sourceProperty.Name,
|
||||
converterFullyQualifiedName,
|
||||
converterParameters.ToImmutableArray(),
|
||||
sourceProperty.Name,
|
||||
mappedSourcePropertyType));
|
||||
}
|
||||
|
||||
|
@ -118,12 +129,12 @@ namespace MapTo
|
|||
private bool TryGetNestedObjectMappings(IPropertySymbol property, out string? mappedSourcePropertyType)
|
||||
{
|
||||
mappedSourcePropertyType = null;
|
||||
|
||||
|
||||
if (!Diagnostics.IsEmpty)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
var nestedSourceMapFromAttribute = property.Type.GetAttribute(_mapFromAttributeTypeSymbol);
|
||||
if (nestedSourceMapFromAttribute is null)
|
||||
{
|
||||
|
@ -152,7 +163,7 @@ namespace MapTo
|
|||
{
|
||||
converterFullyQualifiedName = null;
|
||||
converterParameters = ImmutableArray<string>.Empty;
|
||||
|
||||
|
||||
if (!Diagnostics.IsEmpty)
|
||||
{
|
||||
return false;
|
||||
|
@ -204,7 +215,7 @@ namespace MapTo
|
|||
? ImmutableArray<string>.Empty
|
||||
: converterParameter.Values.Where(v => v.Value is not null).Select(v => v.Value!.ToSourceCodeString()).ToImmutableArray();
|
||||
}
|
||||
|
||||
|
||||
private void ReportDiagnostic(Diagnostic diagnostic)
|
||||
{
|
||||
Diagnostics = Diagnostics.Add(diagnostic);
|
||||
|
@ -219,7 +230,7 @@ namespace MapTo
|
|||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
semanticModel ??= _compilation.GetSemanticModel(attributeSyntax.SyntaxTree);
|
||||
var sourceTypeExpressionSyntax = attributeSyntax
|
||||
.DescendantNodes()
|
||||
|
|
|
@ -23,7 +23,8 @@ namespace MapTo
|
|||
string SourceNamespace,
|
||||
string SourceClassName,
|
||||
string SourceClassFullName,
|
||||
ImmutableArray<MappedProperty> MappedProperties
|
||||
ImmutableArray<MappedProperty> MappedProperties,
|
||||
bool HasMappedBaseClass
|
||||
);
|
||||
|
||||
internal record SourceGenerationOptions(
|
||||
|
|
|
@ -61,8 +61,10 @@ namespace MapTo.Sources
|
|||
.WriteLine($"/// <exception cref=\"ArgumentNullException\">{sourceClassParameterName} is null</exception>");
|
||||
}
|
||||
|
||||
var baseConstructor = model.HasMappedBaseClass ? $" : base({sourceClassParameterName})" : string.Empty;
|
||||
|
||||
builder
|
||||
.WriteLine($"{model.Options.ConstructorAccessModifier.ToLowercaseString()} {model.ClassName}({model.SourceClassFullName} {sourceClassParameterName})")
|
||||
.WriteLine($"{model.Options.ConstructorAccessModifier.ToLowercaseString()} {model.ClassName}({model.SourceClassFullName} {sourceClassParameterName}){baseConstructor}")
|
||||
.WriteOpeningBracket()
|
||||
.WriteLine($"if ({sourceClassParameterName} == null) throw new ArgumentNullException(nameof({sourceClassParameterName}));")
|
||||
.WriteLine();
|
||||
|
|
|
@ -34,6 +34,8 @@ namespace MapTo.Tests
|
|||
builder.WriteLine("//");
|
||||
builder.WriteLine();
|
||||
|
||||
options.Usings?.ForEach(s => builder.WriteLine($"using {s};"));
|
||||
|
||||
if (options.UseMapToNamespace)
|
||||
{
|
||||
builder.WriteLine($"using {Constants.RootNamespace};");
|
||||
|
@ -94,6 +96,76 @@ namespace MapTo.Tests
|
|||
return builder.ToString();
|
||||
}
|
||||
|
||||
internal static string[] GetEmployeeManagerSourceText(Func<string> employeeClassSource = null, Func<string> managerClassSource = null, Func<string> employeeViewModelSource = null, Func<string> managerViewModelSource = null)
|
||||
{
|
||||
return new[]
|
||||
{
|
||||
employeeClassSource?.Invoke() ?? @"
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Test.Data.Models
|
||||
{
|
||||
public class Employee
|
||||
{
|
||||
public int Id { get; set; }
|
||||
|
||||
public string EmployeeCode { get; set; }
|
||||
|
||||
public Manager Manager { get; set; }
|
||||
}
|
||||
}".Trim(),
|
||||
managerClassSource?.Invoke() ?? @"using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Test.Data.Models
|
||||
{
|
||||
public class Manager: Employee
|
||||
{
|
||||
public int Level { get; set; }
|
||||
|
||||
public IEnumerable<Employee> Employees { get; set; } = Array.Empty<Employee>();
|
||||
}
|
||||
}
|
||||
".Trim(),
|
||||
employeeViewModelSource?.Invoke() ?? @"
|
||||
using MapTo;
|
||||
using Test.Data.Models;
|
||||
|
||||
namespace Test.ViewModels
|
||||
{
|
||||
[MapFrom(typeof(Employee))]
|
||||
public partial class EmployeeViewModel
|
||||
{
|
||||
public int Id { get; set; }
|
||||
|
||||
public string EmployeeCode { get; set; }
|
||||
|
||||
public ManagerViewModel Manager { get; set; }
|
||||
}
|
||||
}
|
||||
".Trim(),
|
||||
managerViewModelSource?.Invoke() ?? @"
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using MapTo;
|
||||
using Test.Data.Models;
|
||||
|
||||
namespace Test.ViewModels
|
||||
{
|
||||
[MapFrom(typeof(Manager))]
|
||||
public partial class ManagerViewModel : EmployeeViewModel
|
||||
{
|
||||
public int Level { get; set; }
|
||||
|
||||
public IEnumerable<EmployeeViewModel> Employees { get; set; } = Array.Empty<EmployeeViewModel>();
|
||||
}
|
||||
}".Trim()
|
||||
};
|
||||
}
|
||||
|
||||
internal static PropertyDeclarationSyntax GetPropertyDeclarationSyntax(SyntaxTree syntaxTree, string targetPropertyName, string targetClass = "Foo")
|
||||
{
|
||||
return syntaxTree.GetRoot()
|
||||
|
@ -120,6 +192,7 @@ namespace MapTo.Tests
|
|||
int ClassPropertiesCount = 3,
|
||||
int SourceClassPropertiesCount = 3,
|
||||
Action<SourceBuilder> PropertyBuilder = null,
|
||||
Action<SourceBuilder> SourcePropertyBuilder = null);
|
||||
Action<SourceBuilder> SourcePropertyBuilder = null,
|
||||
IEnumerable<string> Usings = null);
|
||||
}
|
||||
}
|
|
@ -21,10 +21,17 @@ namespace MapTo.Tests.Extensions
|
|||
syntax.ShouldBe(expectedSource, customMessage);
|
||||
}
|
||||
|
||||
internal static void ShouldBeSuccessful(this IEnumerable<Diagnostic> diagnostics, Compilation compilation = null)
|
||||
internal static void ShouldContainPartialSource(this SyntaxTree syntaxTree, string expectedSource, string customMessage = null)
|
||||
{
|
||||
var syntax = syntaxTree.ToString();
|
||||
syntax.ShouldNotBeNullOrWhiteSpace();
|
||||
syntax.ShouldContainWithoutWhitespace(expectedSource, customMessage);
|
||||
}
|
||||
|
||||
internal static void ShouldBeSuccessful(this IEnumerable<Diagnostic> diagnostics, Compilation compilation = null, IEnumerable<string> ignoreDiagnosticsIds = null)
|
||||
{
|
||||
var actual = diagnostics
|
||||
.Where(d => !d.Id.StartsWith("MT") && (d.Severity == DiagnosticSeverity.Warning || d.Severity == DiagnosticSeverity.Error))
|
||||
.Where(d => (ignoreDiagnosticsIds is null || ignoreDiagnosticsIds.All(i => !d.Id.StartsWith(i) )) && (d.Severity == DiagnosticSeverity.Warning || d.Severity == DiagnosticSeverity.Error))
|
||||
.Select(c => $"{c.Severity}: {c.Location.GetLineSpan()} - {c.GetMessage()}").ToArray();
|
||||
|
||||
if (!actual.Any())
|
||||
|
|
|
@ -67,7 +67,7 @@ namespace MapTo
|
|||
|
||||
// Assert
|
||||
diagnostics.ShouldBeSuccessful();
|
||||
compilation.SyntaxTrees.Last().ToString().ShouldContain(expectedResult);
|
||||
compilation.SyntaxTrees.Last().ShouldContainPartialSource(expectedResult);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -76,7 +76,7 @@ namespace MapTo
|
|||
|
||||
// Assert
|
||||
diagnostics.ShouldBeSuccessful();
|
||||
compilation.SyntaxTrees.Last().ToString().ShouldContain(expectedResult);
|
||||
compilation.SyntaxTrees.Last().ShouldContainPartialSource(expectedResult);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -286,7 +286,7 @@ namespace Test
|
|||
|
||||
// Assert
|
||||
diagnostics.ShouldBeSuccessful();
|
||||
compilation.SyntaxTrees.Last().ToString().ShouldContain(expectedResult.Trim());
|
||||
compilation.SyntaxTrees.Last().ShouldContainPartialSource(expectedResult.Trim());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
@ -307,7 +307,7 @@ namespace Test
|
|||
|
||||
// Assert
|
||||
diagnostics.ShouldBeSuccessful();
|
||||
compilation.SyntaxTrees.Last().ToString().ShouldContain(expectedResult.Trim());
|
||||
compilation.SyntaxTrees.Last().ShouldContainPartialSource(expectedResult.Trim());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
@ -331,7 +331,7 @@ namespace Test
|
|||
|
||||
// Assert
|
||||
diagnostics.ShouldBeSuccessful();
|
||||
compilation.SyntaxTrees.Last().ToString().ShouldContain(expectedResult.Trim());
|
||||
compilation.SyntaxTrees.Last().ShouldContainPartialSource(expectedResult.Trim());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
@ -372,7 +372,7 @@ namespace Test
|
|||
|
||||
// Assert
|
||||
diagnostics.ShouldBeSuccessful();
|
||||
compilation.SyntaxTrees.ToArray()[^2].ToString().ShouldContain(expectedResult);
|
||||
compilation.SyntaxTrees.ToArray()[^2].ShouldContainPartialSource(expectedResult);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
@ -429,5 +429,59 @@ namespace Test
|
|||
var expectedError = DiagnosticProvider.NoMatchingPropertyTypeFoundError(GetSourcePropertySymbol("InnerProp1", compilation));
|
||||
diagnostics.ShouldBeUnsuccessful(expectedError);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void When_SourceTypeEnumerableProperties_Should_CreateConstructorAndAssignSrcToDest()
|
||||
{
|
||||
// Arrange
|
||||
var source = GetSourceText(new SourceGeneratorOptions(
|
||||
Usings: new[] { "System.Collections.Generic"},
|
||||
PropertyBuilder: builder => builder.WriteLine("public IEnumerable<int> Prop4 { get; }"),
|
||||
SourcePropertyBuilder: builder => builder.WriteLine("public IEnumerable<int> Prop4 { get; }")));
|
||||
|
||||
const string expectedResult = @"
|
||||
partial class Foo
|
||||
{
|
||||
public Foo(Test.Models.Baz baz)
|
||||
{
|
||||
if (baz == null) throw new ArgumentNullException(nameof(baz));
|
||||
|
||||
Prop1 = baz.Prop1;
|
||||
Prop2 = baz.Prop2;
|
||||
Prop3 = baz.Prop3;
|
||||
Prop4 = baz.Prop4;
|
||||
}
|
||||
";
|
||||
|
||||
// Act
|
||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions);
|
||||
|
||||
// Assert
|
||||
diagnostics.ShouldBeSuccessful();
|
||||
compilation.SyntaxTrees.Last().ShouldContainPartialSource(expectedResult.Trim());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void When_DestinationTypeHasBaseClass_Should_CallBaseConstructor()
|
||||
{
|
||||
// Arrange
|
||||
var sources = GetEmployeeManagerSourceText();
|
||||
|
||||
const string expectedResult = @"
|
||||
public ManagerViewModel(Test.Data.Models.Manager manager) : base(manager)
|
||||
{
|
||||
if (manager == null) throw new ArgumentNullException(nameof(manager));
|
||||
|
||||
Level = manager.Level;
|
||||
}
|
||||
";
|
||||
|
||||
// Act
|
||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(sources, analyzerConfigOptions: DefaultAnalyzerOptions);
|
||||
|
||||
// Assert
|
||||
diagnostics.ShouldBeSuccessful();
|
||||
compilation.SyntaxTrees.Last().ShouldContainPartialSource(expectedResult);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -168,7 +168,7 @@ namespace Test
|
|||
|
||||
// Assert
|
||||
diagnostics.ShouldBeSuccessful();
|
||||
compilation.SyntaxTrees.Last().ToString().ShouldContain(expectedSyntax);
|
||||
compilation.SyntaxTrees.Last().ShouldContainPartialSource(expectedSyntax);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
@ -204,7 +204,7 @@ namespace Test
|
|||
|
||||
// Assert
|
||||
diagnostics.ShouldBeSuccessful();
|
||||
compilation.SyntaxTrees.Last().ToString().ShouldContain(expectedSyntax);
|
||||
compilation.SyntaxTrees.Last().ShouldContainPartialSource(expectedSyntax);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
@ -235,7 +235,7 @@ namespace Test
|
|||
|
||||
// Assert
|
||||
diagnostics.ShouldBeSuccessful();
|
||||
compilation.SyntaxTrees.Last().ToString().ShouldContain(expectedResult);
|
||||
compilation.SyntaxTrees.Last().ShouldContainPartialSource(expectedResult);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace TestConsoleApp.Data.Models
|
||||
{
|
||||
public class Employee
|
||||
{
|
||||
public int Id { get; set; }
|
||||
|
||||
public string EmployeeCode { get; set; }
|
||||
|
||||
public Manager Manager { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace TestConsoleApp.Data.Models
|
||||
{
|
||||
public class Manager: Employee
|
||||
{
|
||||
public int Level { get; set; }
|
||||
|
||||
public IEnumerable<Employee> Employees { get; set; } = Array.Empty<Employee>();
|
||||
}
|
||||
}
|
|
@ -7,6 +7,45 @@ namespace TestConsoleApp
|
|||
internal class Program
|
||||
{
|
||||
private static void Main(string[] args)
|
||||
{
|
||||
//UserTest();
|
||||
var manager1 = new Manager
|
||||
{
|
||||
Id = 1,
|
||||
EmployeeCode = "M001",
|
||||
Level = 100
|
||||
};
|
||||
|
||||
var manager2 = new Manager
|
||||
{
|
||||
Id = 2,
|
||||
EmployeeCode = "M002",
|
||||
Level = 100,
|
||||
Manager = manager1
|
||||
};
|
||||
|
||||
var employee1 = new Employee
|
||||
{
|
||||
Id = 101,
|
||||
EmployeeCode = "E101",
|
||||
Manager = manager1
|
||||
};
|
||||
|
||||
var employee2 = new Employee
|
||||
{
|
||||
Id = 102,
|
||||
EmployeeCode = "E102",
|
||||
Manager = manager2
|
||||
};
|
||||
|
||||
manager1.Employees = new[] { employee1, manager2 };
|
||||
manager2.Employees = new[] { employee2 };
|
||||
|
||||
var manager1ViewModel = manager1.ToManagerViewModel();
|
||||
int a = 0;
|
||||
}
|
||||
|
||||
private static void UserTest()
|
||||
{
|
||||
var user = new User
|
||||
{
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
using MapTo;
|
||||
using TestConsoleApp.Data.Models;
|
||||
|
||||
namespace TestConsoleApp.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,15 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using MapTo;
|
||||
using TestConsoleApp.Data.Models;
|
||||
|
||||
namespace TestConsoleApp.ViewModels
|
||||
{
|
||||
[MapFrom(typeof(Manager))]
|
||||
public partial class ManagerViewModel : EmployeeViewModel
|
||||
{
|
||||
public int Level { get; set; }
|
||||
|
||||
public IEnumerable<EmployeeViewModel> Employees { get; set; } = Array.Empty<EmployeeViewModel>();
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue