Fix base constructor call issue.

This commit is contained in:
Mohammadreza Taikandi 2021-02-13 11:27:32 +00:00
parent 1d7ca56916
commit 6ad7ea83f9
16 changed files with 277 additions and 34 deletions

View File

@ -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();

View File

@ -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>

View File

@ -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()

View File

@ -23,7 +23,8 @@ namespace MapTo
string SourceNamespace,
string SourceClassName,
string SourceClassFullName,
ImmutableArray<MappedProperty> MappedProperties
ImmutableArray<MappedProperty> MappedProperties,
bool HasMappedBaseClass
);
internal record SourceGenerationOptions(

View File

@ -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();

View File

@ -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);
}
}

View File

@ -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())

View File

@ -67,7 +67,7 @@ namespace MapTo
// Assert
diagnostics.ShouldBeSuccessful();
compilation.SyntaxTrees.Last().ToString().ShouldContain(expectedResult);
compilation.SyntaxTrees.Last().ShouldContainPartialSource(expectedResult);
}
}
}

View File

@ -76,7 +76,7 @@ namespace MapTo
// Assert
diagnostics.ShouldBeSuccessful();
compilation.SyntaxTrees.Last().ToString().ShouldContain(expectedResult);
compilation.SyntaxTrees.Last().ShouldContainPartialSource(expectedResult);
}
}
}

View File

@ -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);
}
}
}

View File

@ -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]

View File

@ -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; }
}
}

View File

@ -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>();
}
}

View File

@ -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
{

View File

@ -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; }
}
}

View File

@ -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>();
}
}