Move tests into separate classes.
This commit is contained in:
parent
acd98e72ff
commit
2a2f46def7
|
@ -0,0 +1,131 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using MapTo.Extensions;
|
||||
using MapTo.Sources;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
|
||||
namespace MapTo.Tests
|
||||
{
|
||||
internal static class Common
|
||||
{
|
||||
internal const int Indent1 = 4;
|
||||
internal const int Indent2 = Indent1 * 2;
|
||||
internal const int Indent3 = Indent1 * 3;
|
||||
internal static readonly Location IgnoreLocation = Location.None;
|
||||
|
||||
internal static readonly Dictionary<string, string> DefaultAnalyzerOptions = new()
|
||||
{
|
||||
[GeneratorExecutionContextExtensions.GetBuildPropertyName(nameof(SourceGenerationOptions.GenerateXmlDocument))] = "false"
|
||||
};
|
||||
|
||||
internal static string GetSourceText(SourceGeneratorOptions options = null)
|
||||
{
|
||||
const string ns = "Test";
|
||||
options ??= new SourceGeneratorOptions();
|
||||
var hasDifferentSourceNamespace = options.SourceClassNamespace != ns;
|
||||
var builder = new StringBuilder();
|
||||
|
||||
builder.AppendLine("//");
|
||||
builder.AppendLine("// Test source code.");
|
||||
builder.AppendLine("//");
|
||||
builder.AppendLine();
|
||||
|
||||
if (options.UseMapToNamespace)
|
||||
{
|
||||
builder.AppendFormat("using {0};", Constants.RootNamespace).AppendLine();
|
||||
}
|
||||
|
||||
builder
|
||||
.AppendFormat("using {0};", options.SourceClassNamespace)
|
||||
.AppendLine()
|
||||
.AppendLine();
|
||||
|
||||
builder
|
||||
.AppendFormat("namespace {0}", ns)
|
||||
.AppendOpeningBracket();
|
||||
|
||||
if (hasDifferentSourceNamespace && options.UseMapToNamespace)
|
||||
{
|
||||
builder
|
||||
.PadLeft(Indent1)
|
||||
.AppendFormat("using {0};", options.SourceClassNamespace)
|
||||
.AppendLine()
|
||||
.AppendLine();
|
||||
}
|
||||
|
||||
builder
|
||||
.PadLeft(Indent1)
|
||||
.AppendLine(options.UseMapToNamespace ? "[MapFrom(typeof(Baz))]" : "[MapTo.MapFrom(typeof(Baz))]")
|
||||
.PadLeft(Indent1).Append("public partial class Foo")
|
||||
.AppendOpeningBracket(Indent1);
|
||||
|
||||
for (var i = 1; i <= options.ClassPropertiesCount; i++)
|
||||
{
|
||||
builder
|
||||
.PadLeft(Indent2)
|
||||
.AppendLine(i % 2 == 0 ? $"public int Prop{i} {{ get; set; }}" : $"public int Prop{i} {{ get; }}");
|
||||
}
|
||||
|
||||
options.PropertyBuilder?.Invoke(builder);
|
||||
|
||||
builder
|
||||
.AppendClosingBracket(Indent1, false)
|
||||
.AppendClosingBracket()
|
||||
.AppendLine()
|
||||
.AppendLine();
|
||||
|
||||
builder
|
||||
.AppendFormat("namespace {0}", options.SourceClassNamespace)
|
||||
.AppendOpeningBracket()
|
||||
.PadLeft(Indent1).Append("public class Baz")
|
||||
.AppendOpeningBracket(Indent1);
|
||||
|
||||
for (var i = 1; i <= options.SourceClassPropertiesCount; i++)
|
||||
{
|
||||
builder
|
||||
.PadLeft(Indent2)
|
||||
.AppendLine(i % 2 == 0 ? $"public int Prop{i} {{ get; set; }}" : $"public int Prop{i} {{ get; }}");
|
||||
}
|
||||
|
||||
options.SourcePropertyBuilder?.Invoke(builder);
|
||||
|
||||
builder
|
||||
.AppendClosingBracket(Indent1, false)
|
||||
.AppendClosingBracket();
|
||||
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
internal static PropertyDeclarationSyntax GetPropertyDeclarationSyntax(SyntaxTree syntaxTree, string targetPropertyName, string targetClass = "Foo")
|
||||
{
|
||||
return syntaxTree.GetRoot()
|
||||
.DescendantNodes()
|
||||
.OfType<ClassDeclarationSyntax>()
|
||||
.Single(c => c.Identifier.ValueText == targetClass)
|
||||
.DescendantNodes()
|
||||
.OfType<PropertyDeclarationSyntax>()
|
||||
.Single(p => p.Identifier.ValueText == targetPropertyName);
|
||||
}
|
||||
|
||||
internal static IPropertySymbol GetSourcePropertySymbol(string propertyName, Compilation compilation, string targetClass = "Foo")
|
||||
{
|
||||
var syntaxTree = compilation.SyntaxTrees.First();
|
||||
var propSyntax = GetPropertyDeclarationSyntax(syntaxTree, propertyName, targetClass);
|
||||
|
||||
var semanticModel = compilation.GetSemanticModel(syntaxTree);
|
||||
return semanticModel.GetDeclaredSymbol(propSyntax);
|
||||
}
|
||||
|
||||
internal record SourceGeneratorOptions(
|
||||
bool UseMapToNamespace = false,
|
||||
string SourceClassNamespace = "Test.Models",
|
||||
int ClassPropertiesCount = 3,
|
||||
int SourceClassPropertiesCount = 3,
|
||||
Action<StringBuilder> PropertyBuilder = null,
|
||||
Action<StringBuilder> SourcePropertyBuilder = null);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
using System.Linq;
|
||||
using MapTo.Extensions;
|
||||
using MapTo.Sources;
|
||||
using MapTo.Tests.Extensions;
|
||||
using MapTo.Tests.Infrastructure;
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
using static MapTo.Tests.Common;
|
||||
|
||||
namespace MapTo.Tests
|
||||
{
|
||||
public class IgnorePropertyAttributeTests
|
||||
{
|
||||
[Fact]
|
||||
public void VerifyIgnorePropertyAttribute()
|
||||
{
|
||||
// Arrange
|
||||
const string source = "";
|
||||
var expectedAttribute = $@"
|
||||
{Constants.GeneratedFilesHeader}
|
||||
using System;
|
||||
|
||||
namespace MapTo
|
||||
{{
|
||||
[AttributeUsage(AttributeTargets.Property, Inherited = false, AllowMultiple = false)]
|
||||
public sealed class IgnorePropertyAttribute : Attribute {{ }}
|
||||
}}
|
||||
".Trim();
|
||||
|
||||
// Act
|
||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions);
|
||||
|
||||
// Assert
|
||||
diagnostics.ShouldBeSuccessful();
|
||||
compilation.SyntaxTrees.ShouldContainSource(IgnorePropertyAttributeSource.AttributeName, expectedAttribute);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void When_IgnorePropertyAttributeIsSpecified_Should_NotGenerateMappingsForThatProperty()
|
||||
{
|
||||
// Arrange
|
||||
var source = GetSourceText(new SourceGeneratorOptions(
|
||||
true,
|
||||
PropertyBuilder: builder =>
|
||||
{
|
||||
builder
|
||||
.PadLeft(Indent2).AppendLine("[IgnoreProperty]")
|
||||
.PadLeft(Indent2).AppendLine("public int Prop4 { get; set; }");
|
||||
},
|
||||
SourcePropertyBuilder: builder => builder.PadLeft(Indent2).AppendLine("public int Prop4 { get; set; }")));
|
||||
|
||||
var 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;
|
||||
}
|
||||
".Trim();
|
||||
|
||||
// Act
|
||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions);
|
||||
|
||||
// Assert
|
||||
diagnostics.ShouldBeSuccessful();
|
||||
compilation.SyntaxTrees.Last().ToString().ShouldContain(expectedResult);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,83 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using MapTo.Extensions;
|
||||
using MapTo.Sources;
|
||||
using MapTo.Tests.Extensions;
|
||||
using MapTo.Tests.Infrastructure;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
using static MapTo.Tests.Common;
|
||||
|
||||
namespace MapTo.Tests
|
||||
{
|
||||
public class MapPropertyTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(NullableContextOptions.Disable)]
|
||||
[InlineData(NullableContextOptions.Enable)]
|
||||
public void VerifyMapPropertyAttribute(NullableContextOptions nullableContextOptions)
|
||||
{
|
||||
// Arrange
|
||||
const string source = "";
|
||||
var nullableSyntax = nullableContextOptions == NullableContextOptions.Enable ? "?" : string.Empty;
|
||||
var expectedInterface = $@"
|
||||
{Constants.GeneratedFilesHeader}
|
||||
{(nullableContextOptions == NullableContextOptions.Enable ? $"#nullable enable{Environment.NewLine}" : string.Empty)}
|
||||
using System;
|
||||
|
||||
namespace MapTo
|
||||
{{
|
||||
[AttributeUsage(AttributeTargets.Property, AllowMultiple = true)]
|
||||
public sealed class MapPropertyAttribute : Attribute
|
||||
{{
|
||||
public string{nullableSyntax} SourcePropertyName {{ get; set; }}
|
||||
}}
|
||||
}}
|
||||
".Trim();
|
||||
|
||||
// Act
|
||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions, nullableContextOptions: nullableContextOptions);
|
||||
|
||||
// Assert
|
||||
diagnostics.ShouldBeSuccessful();
|
||||
compilation.SyntaxTrees.ShouldContainSource(MapPropertyAttributeSource.AttributeName, expectedInterface);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void When_MapPropertyFound_Should_UseItToMapToSourceProperty()
|
||||
{
|
||||
// Arrange
|
||||
var source = GetSourceText(new SourceGeneratorOptions(
|
||||
true,
|
||||
PropertyBuilder: builder =>
|
||||
{
|
||||
builder
|
||||
.PadLeft(Indent2).AppendLine("[MapProperty(SourcePropertyName = nameof(Baz.Prop3))]")
|
||||
.PadLeft(Indent2).AppendLine("public int Prop4 { get; set; }");
|
||||
},
|
||||
SourcePropertyBuilder: builder => builder.PadLeft(Indent2).AppendLine("public int Prop4 { get; set; }")));
|
||||
|
||||
var 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.Prop3;
|
||||
}
|
||||
".Trim();
|
||||
|
||||
// Act
|
||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions);
|
||||
|
||||
// Assert
|
||||
diagnostics.ShouldBeSuccessful();
|
||||
compilation.SyntaxTrees.Last().ToString().ShouldContain(expectedResult);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,342 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using MapTo.Extensions;
|
||||
using MapTo.Sources;
|
||||
using MapTo.Tests.Extensions;
|
||||
using MapTo.Tests.Infrastructure;
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
using static MapTo.Extensions.GeneratorExecutionContextExtensions;
|
||||
using static MapTo.Tests.Common;
|
||||
|
||||
namespace MapTo.Tests
|
||||
{
|
||||
public class MapToTests
|
||||
{
|
||||
private static readonly string ExpectedAttribute = $@"{Constants.GeneratedFilesHeader}
|
||||
using System;
|
||||
|
||||
namespace MapTo
|
||||
{{
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
|
||||
public sealed class MapFromAttribute : Attribute
|
||||
{{
|
||||
public MapFromAttribute(Type sourceType)
|
||||
{{
|
||||
SourceType = sourceType;
|
||||
}}
|
||||
|
||||
public Type SourceType {{ get; }}
|
||||
}}
|
||||
}}";
|
||||
|
||||
[Fact]
|
||||
public void VerifyMapToAttribute()
|
||||
{
|
||||
// Arrange
|
||||
const string source = "";
|
||||
|
||||
// Act
|
||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions);
|
||||
|
||||
// Assert
|
||||
diagnostics.ShouldBeSuccessful();
|
||||
compilation.SyntaxTrees.ShouldContainSource(MapFromAttributeSource.AttributeName, ExpectedAttribute);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void When_FoundMatchingPropertyNameWithDifferentTypes_Should_ReportError()
|
||||
{
|
||||
// Arrange
|
||||
var source = GetSourceText(new SourceGeneratorOptions(
|
||||
true,
|
||||
PropertyBuilder: builder =>
|
||||
{
|
||||
builder
|
||||
.PadLeft(Indent2).AppendLine("public string Prop4 { get; set; }");
|
||||
},
|
||||
SourcePropertyBuilder: builder => builder.PadLeft(Indent2).AppendLine("public int Prop4 { get; set; }")));
|
||||
|
||||
// Act
|
||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions);
|
||||
|
||||
// Assert
|
||||
var expectedError = DiagnosticProvider.NoMatchingPropertyTypeFoundError(GetSourcePropertySymbol("Prop4", compilation));
|
||||
|
||||
diagnostics.ShouldBeUnsuccessful(expectedError);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void When_MappingsModifierOptionIsSetToInternal_Should_GenerateThoseMethodsWithInternalAccessModifier()
|
||||
{
|
||||
// Arrange
|
||||
var source = GetSourceText();
|
||||
var configOptions = new Dictionary<string, string>
|
||||
{
|
||||
[GetBuildPropertyName(nameof(SourceGenerationOptions.GeneratedMethodsAccessModifier))] = "Internal",
|
||||
[GetBuildPropertyName(nameof(SourceGenerationOptions.GenerateXmlDocument))] = "false"
|
||||
};
|
||||
|
||||
var expectedExtension = @"
|
||||
internal static partial class BazToFooExtensions
|
||||
{
|
||||
internal static Foo ToFoo(this Test.Models.Baz baz)
|
||||
{
|
||||
return baz == null ? null : new Foo(baz);
|
||||
}
|
||||
}".Trim();
|
||||
|
||||
var expectedFactory = @"
|
||||
internal static Foo From(Test.Models.Baz baz)
|
||||
{
|
||||
return baz == null ? null : new Foo(baz);
|
||||
}".Trim();
|
||||
|
||||
// Act
|
||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: configOptions);
|
||||
|
||||
// Assert
|
||||
diagnostics.ShouldBeSuccessful();
|
||||
|
||||
var syntaxTree = compilation.SyntaxTrees.Last().ToString();
|
||||
syntaxTree.ShouldContain(expectedFactory);
|
||||
syntaxTree.ShouldContain(expectedExtension);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void When_MapToAttributeFound_Should_GenerateTheClass()
|
||||
{
|
||||
// Arrange
|
||||
const string source = @"
|
||||
using MapTo;
|
||||
|
||||
namespace Test
|
||||
{
|
||||
[MapFrom(typeof(Baz))]
|
||||
public partial class Foo
|
||||
{
|
||||
public int Prop1 { get; set; }
|
||||
}
|
||||
|
||||
public class Baz
|
||||
{
|
||||
public int Prop1 { get; set; }
|
||||
}
|
||||
}
|
||||
";
|
||||
|
||||
const string expectedResult = @"
|
||||
// <auto-generated />
|
||||
|
||||
using System;
|
||||
|
||||
namespace Test
|
||||
{
|
||||
partial class Foo
|
||||
{
|
||||
public Foo(Test.Baz baz)
|
||||
{
|
||||
if (baz == null) throw new ArgumentNullException(nameof(baz));
|
||||
|
||||
Prop1 = baz.Prop1;
|
||||
}
|
||||
";
|
||||
|
||||
// Act
|
||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions);
|
||||
|
||||
// Assert
|
||||
diagnostics.ShouldBeSuccessful();
|
||||
compilation.SyntaxTrees.Last().ToString().ShouldStartWith(expectedResult.Trim());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void When_MapToAttributeFoundWithoutMatchingProperties_Should_ReportError()
|
||||
{
|
||||
// Arrange
|
||||
const string source = @"
|
||||
using MapTo;
|
||||
|
||||
namespace Test
|
||||
{
|
||||
[MapFrom(typeof(Baz))]
|
||||
public partial class Foo { }
|
||||
|
||||
public class Baz { public int Prop1 { get; set; } }
|
||||
}
|
||||
";
|
||||
|
||||
// Act
|
||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source);
|
||||
|
||||
// Assert
|
||||
var fooType = compilation.GetTypeByMetadataName("Test.Foo");
|
||||
fooType.ShouldNotBeNull();
|
||||
|
||||
var bazType = compilation.GetTypeByMetadataName("Test.Baz");
|
||||
bazType.ShouldNotBeNull();
|
||||
|
||||
var expectedDiagnostic = DiagnosticProvider.NoMatchingPropertyFoundError(fooType.Locations.Single(), fooType, bazType);
|
||||
var error = diagnostics.FirstOrDefault(d => d.Id == expectedDiagnostic.Id);
|
||||
error.ShouldNotBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void When_MapToAttributeWithNamespaceFound_Should_GenerateTheClass()
|
||||
{
|
||||
// Arrange
|
||||
const string source = @"
|
||||
namespace Test
|
||||
{
|
||||
[MapTo.MapFrom(typeof(Baz))]
|
||||
public partial class Foo { public int Prop1 { get; set; } }
|
||||
|
||||
public class Baz { public int Prop1 { get; set; } }
|
||||
}
|
||||
";
|
||||
|
||||
const string expectedResult = @"
|
||||
// <auto-generated />
|
||||
|
||||
using System;
|
||||
|
||||
namespace Test
|
||||
{
|
||||
partial class Foo
|
||||
{
|
||||
public Foo(Test.Baz baz)
|
||||
{
|
||||
if (baz == null) throw new ArgumentNullException(nameof(baz));
|
||||
|
||||
Prop1 = baz.Prop1;
|
||||
}
|
||||
";
|
||||
|
||||
// Act
|
||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions);
|
||||
|
||||
// Assert
|
||||
diagnostics.ShouldBeSuccessful();
|
||||
compilation.SyntaxTrees.Last().ToString().ShouldStartWith(expectedResult.Trim());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void When_NoMapToAttributeFound_Should_GenerateOnlyTheAttribute()
|
||||
{
|
||||
// Arrange
|
||||
const string source = "";
|
||||
var expectedTypes = new[]
|
||||
{
|
||||
IgnorePropertyAttributeSource.AttributeName,
|
||||
MapFromAttributeSource.AttributeName,
|
||||
ITypeConverterSource.InterfaceName,
|
||||
MapPropertyAttributeSource.AttributeName
|
||||
};
|
||||
|
||||
// Act
|
||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source);
|
||||
|
||||
// Assert
|
||||
diagnostics.ShouldBeSuccessful();
|
||||
compilation.SyntaxTrees
|
||||
.Select(s => s.ToString())
|
||||
.Where(s => !string.IsNullOrWhiteSpace(s.ToString()))
|
||||
.All(s => expectedTypes.Any(s.Contains))
|
||||
.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void When_SourceTypeHasDifferentNamespace_Should_NotAddToUsings()
|
||||
{
|
||||
// Arrange
|
||||
var source = GetSourceText(new SourceGeneratorOptions(SourceClassNamespace: "Bazaar"));
|
||||
|
||||
const string expectedResult = @"
|
||||
// <auto-generated />
|
||||
|
||||
using System;
|
||||
|
||||
namespace Test
|
||||
";
|
||||
|
||||
// Act
|
||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source);
|
||||
|
||||
// Assert
|
||||
diagnostics.ShouldBeSuccessful();
|
||||
compilation.SyntaxTrees.Last().ToString().ShouldStartWith(expectedResult.Trim());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void When_SourceTypeHasMatchingProperties_Should_CreateConstructorAndAssignSrcToDest()
|
||||
{
|
||||
// Arrange
|
||||
var source = GetSourceText();
|
||||
|
||||
const string expectedResult = @"
|
||||
partial class Foo
|
||||
{
|
||||
public Foo(Test.Models.Baz baz)
|
||||
{
|
||||
if (baz == null) throw new ArgumentNullException(nameof(baz));
|
||||
|
||||
Prop1 = baz.Prop1;
|
||||
Prop2 = baz.Prop2;
|
||||
Prop3 = baz.Prop3;
|
||||
}
|
||||
";
|
||||
|
||||
// Act
|
||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions);
|
||||
|
||||
// Assert
|
||||
diagnostics.ShouldBeSuccessful();
|
||||
compilation.SyntaxTrees.Last().ToString().ShouldContain(expectedResult.Trim());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void When_SourceTypeHasMatchingProperties_Should_CreateFromStaticMethod()
|
||||
{
|
||||
// Arrange
|
||||
var source = GetSourceText();
|
||||
|
||||
const string expectedResult = @"
|
||||
public static Foo From(Test.Models.Baz baz)
|
||||
{
|
||||
return baz == null ? null : new Foo(baz);
|
||||
}
|
||||
";
|
||||
|
||||
// Act
|
||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source);
|
||||
|
||||
// Assert
|
||||
diagnostics.ShouldBeSuccessful();
|
||||
compilation.SyntaxTrees.Last().ToString().ShouldContain(expectedResult.Trim());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void When_SourceTypeHasMatchingProperties_Should_GenerateToExtensionMethodOnSourceType()
|
||||
{
|
||||
// Arrange
|
||||
var source = GetSourceText();
|
||||
|
||||
const string expectedResult = @"
|
||||
public static partial class BazToFooExtensions
|
||||
{
|
||||
public static Foo ToFoo(this Test.Models.Baz baz)
|
||||
{
|
||||
return baz == null ? null : new Foo(baz);
|
||||
}
|
||||
}
|
||||
";
|
||||
|
||||
// Act
|
||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions);
|
||||
|
||||
// Assert
|
||||
diagnostics.ShouldBeSuccessful();
|
||||
compilation.SyntaxTrees.Last().ToString().ShouldContain(expectedResult.Trim());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,283 @@
|
|||
using System.Linq;
|
||||
using MapTo.Extensions;
|
||||
using MapTo.Sources;
|
||||
using MapTo.Tests.Extensions;
|
||||
using MapTo.Tests.Infrastructure;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
using static MapTo.Extensions.GeneratorExecutionContextExtensions;
|
||||
using static MapTo.Tests.Common;
|
||||
|
||||
namespace MapTo.Tests
|
||||
{
|
||||
public class MapTypeConverterTests
|
||||
{
|
||||
[Fact]
|
||||
public void VerifyTypeConverterInterface()
|
||||
{
|
||||
// Arrange
|
||||
const string source = "";
|
||||
var expectedInterface = $@"
|
||||
{Constants.GeneratedFilesHeader}
|
||||
|
||||
namespace MapTo
|
||||
{{
|
||||
public interface ITypeConverter<in TSource, out TDestination>
|
||||
{{
|
||||
TDestination Convert(TSource source, object[] converterParameters);
|
||||
}}
|
||||
}}
|
||||
".Trim();
|
||||
|
||||
// Act
|
||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions);
|
||||
|
||||
// Assert
|
||||
diagnostics.ShouldBeSuccessful();
|
||||
compilation.SyntaxTrees.ShouldContainSource(ITypeConverterSource.InterfaceName, expectedInterface);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void VerifyTypeConverterInterfaceWithNullableOptionOn()
|
||||
{
|
||||
// Arrange
|
||||
const string source = "";
|
||||
var expectedInterface = $@"
|
||||
{Constants.GeneratedFilesHeader}
|
||||
#nullable enable
|
||||
|
||||
namespace MapTo
|
||||
{{
|
||||
public interface ITypeConverter<in TSource, out TDestination>
|
||||
{{
|
||||
TDestination Convert(TSource source, object[]? converterParameters);
|
||||
}}
|
||||
}}
|
||||
".Trim();
|
||||
|
||||
// Act
|
||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions, nullableContextOptions: NullableContextOptions.Enable);
|
||||
|
||||
// Assert
|
||||
diagnostics.ShouldBeSuccessful();
|
||||
compilation.SyntaxTrees.ShouldContainSource(ITypeConverterSource.InterfaceName, expectedInterface);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void VerifyMapTypeConverterAttribute()
|
||||
{
|
||||
// Arrange
|
||||
const string source = "";
|
||||
var expectedInterface = $@"
|
||||
{Constants.GeneratedFilesHeader}
|
||||
|
||||
using System;
|
||||
|
||||
namespace MapTo
|
||||
{{
|
||||
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
|
||||
public sealed class MapTypeConverterAttribute : Attribute
|
||||
{{
|
||||
public MapTypeConverterAttribute(Type converter, object[] converterParameters = null)
|
||||
{{
|
||||
Converter = converter;
|
||||
ConverterParameters = converterParameters;
|
||||
}}
|
||||
|
||||
public Type Converter {{ get; }}
|
||||
|
||||
public object[] ConverterParameters {{ get; }}
|
||||
}}
|
||||
}}
|
||||
".Trim();
|
||||
|
||||
// Act
|
||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions);
|
||||
|
||||
// Assert
|
||||
diagnostics.ShouldBeSuccessful();
|
||||
compilation.SyntaxTrees.ShouldContainSource(MapTypeConverterAttributeSource.AttributeName, expectedInterface);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void VerifyMapTypeConverterAttributeWithNullableOptionOn()
|
||||
{
|
||||
// Arrange
|
||||
const string source = "";
|
||||
var expectedInterface = $@"
|
||||
{Constants.GeneratedFilesHeader}
|
||||
#nullable enable
|
||||
|
||||
using System;
|
||||
|
||||
namespace MapTo
|
||||
{{
|
||||
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
|
||||
public sealed class MapTypeConverterAttribute : Attribute
|
||||
{{
|
||||
public MapTypeConverterAttribute(Type converter, object[]? converterParameters = null)
|
||||
{{
|
||||
Converter = converter;
|
||||
ConverterParameters = converterParameters;
|
||||
}}
|
||||
|
||||
public Type Converter {{ get; }}
|
||||
|
||||
public object[]? ConverterParameters {{ get; }}
|
||||
}}
|
||||
}}
|
||||
".Trim();
|
||||
|
||||
// Act
|
||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions, nullableContextOptions: NullableContextOptions.Enable);
|
||||
|
||||
// Assert
|
||||
diagnostics.ShouldBeSuccessful();
|
||||
compilation.SyntaxTrees.ShouldContainSource(MapTypeConverterAttributeSource.AttributeName, expectedInterface);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void When_FoundMatchingPropertyNameWithDifferentImplicitlyConvertibleType_Should_GenerateTheProperty()
|
||||
{
|
||||
// Arrange
|
||||
var source = GetSourceText(new SourceGeneratorOptions(
|
||||
true,
|
||||
PropertyBuilder: builder =>
|
||||
{
|
||||
builder
|
||||
.PadLeft(Indent2).AppendLine("public long Prop4 { get; set; }");
|
||||
},
|
||||
SourcePropertyBuilder: builder => builder.PadLeft(Indent2).AppendLine("public int Prop4 { get; set; }")));
|
||||
|
||||
var 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;
|
||||
}
|
||||
".Trim();
|
||||
|
||||
// Act
|
||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions);
|
||||
|
||||
// Assert
|
||||
diagnostics.ShouldBeSuccessful();
|
||||
compilation.SyntaxTrees.Last().ToString().ShouldContain(expectedResult);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void When_FoundMatchingPropertyNameWithIncorrectConverterType_ShouldReportError()
|
||||
{
|
||||
// Arrange
|
||||
var source = GetSourceText(new SourceGeneratorOptions(
|
||||
true,
|
||||
PropertyBuilder: builder =>
|
||||
{
|
||||
builder
|
||||
.PadLeft(Indent2).AppendLine("[IgnoreProperty]")
|
||||
.PadLeft(Indent2).AppendLine("public long IgnoreMe { get; set; }")
|
||||
.PadLeft(Indent2).AppendLine("[MapTypeConverter(typeof(Prop4Converter))]")
|
||||
.PadLeft(Indent2).AppendLine("public long Prop4 { get; set; }");
|
||||
},
|
||||
SourcePropertyBuilder: builder => builder.PadLeft(Indent2).AppendLine("public string Prop4 { get; set; }")));
|
||||
|
||||
source += @"
|
||||
namespace Test
|
||||
{
|
||||
using MapTo;
|
||||
|
||||
public class Prop4Converter: ITypeConverter<string, int>
|
||||
{
|
||||
public int Convert(string source, object[] converterParameters) => int.Parse(source);
|
||||
}
|
||||
}
|
||||
";
|
||||
|
||||
// Act
|
||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions);
|
||||
|
||||
// Assert
|
||||
var expectedError = DiagnosticProvider.InvalidTypeConverterGenericTypesError(GetSourcePropertySymbol("Prop4", compilation), GetSourcePropertySymbol("Prop4", compilation, "Baz"));
|
||||
diagnostics.ShouldBeUnsuccessful(expectedError);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void When_FoundMatchingPropertyNameWithConverterType_ShouldUseTheConverterToAssignProperties()
|
||||
{
|
||||
// Arrange
|
||||
var source = GetSourceText(new SourceGeneratorOptions(
|
||||
true,
|
||||
PropertyBuilder: builder =>
|
||||
{
|
||||
builder
|
||||
.PadLeft(Indent2).AppendLine("[MapTypeConverter(typeof(Prop4Converter))]")
|
||||
.PadLeft(Indent2).AppendLine("public long Prop4 { get; set; }");
|
||||
},
|
||||
SourcePropertyBuilder: builder => builder.PadLeft(Indent2).AppendLine("public string Prop4 { get; set; }")));
|
||||
|
||||
source += @"
|
||||
namespace Test
|
||||
{
|
||||
using MapTo;
|
||||
|
||||
public class Prop4Converter: ITypeConverter<string, long>
|
||||
{
|
||||
public long Convert(string source, object[] converterParameters) => long.Parse(source);
|
||||
}
|
||||
}
|
||||
";
|
||||
|
||||
const string expectedSyntax = "Prop4 = new Test.Prop4Converter().Convert(baz.Prop4, null);";
|
||||
|
||||
// Act
|
||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions);
|
||||
|
||||
// Assert
|
||||
diagnostics.ShouldBeSuccessful();
|
||||
compilation.SyntaxTrees.Last().ToString().ShouldContain(expectedSyntax);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void When_FoundMatchingPropertyNameWithConverterType_ShouldUseTheConverterAndItsParametersToAssignProperties()
|
||||
{
|
||||
// Arrange
|
||||
var source = GetSourceText(new SourceGeneratorOptions(
|
||||
true,
|
||||
PropertyBuilder: builder =>
|
||||
{
|
||||
builder
|
||||
.PadLeft(Indent2).AppendLine("[MapTypeConverter(typeof(Prop4Converter), new object[]{\"G\", 'C', 10})]")
|
||||
.PadLeft(Indent2).AppendLine("public string Prop4 { get; set; }");
|
||||
},
|
||||
SourcePropertyBuilder: builder => builder.PadLeft(Indent2).AppendLine("public long Prop4 { get; set; }")));
|
||||
|
||||
source += @"
|
||||
namespace Test
|
||||
{
|
||||
using MapTo;
|
||||
|
||||
public class Prop4Converter: ITypeConverter<long, string>
|
||||
{
|
||||
public string Convert(long source, object[] converterParameters) => source.ToString(converterParameters[0] as string);
|
||||
}
|
||||
}
|
||||
";
|
||||
|
||||
const string expectedSyntax = "Prop4 = new Test.Prop4Converter().Convert(baz.Prop4, new object[] { \"G\", 'C', 10 });";
|
||||
|
||||
// Act
|
||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions);
|
||||
|
||||
// Assert
|
||||
diagnostics.ShouldBeSuccessful();
|
||||
compilation.SyntaxTrees.Last().ToString().ShouldContain(expectedSyntax);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,855 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using MapTo.Extensions;
|
||||
using MapTo.Sources;
|
||||
using MapTo.Tests.Extensions;
|
||||
using MapTo.Tests.Infrastructure;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
using static MapTo.Extensions.GeneratorExecutionContextExtensions;
|
||||
|
||||
namespace MapTo.Tests
|
||||
{
|
||||
public class Tests
|
||||
{
|
||||
private const int Indent1 = 4;
|
||||
private const int Indent2 = Indent1 * 2;
|
||||
private const int Indent3 = Indent1 * 3;
|
||||
private static readonly Location IgnoreLocation = Location.None;
|
||||
|
||||
private static readonly Dictionary<string, string> DefaultAnalyzerOptions = new()
|
||||
{
|
||||
[GetBuildPropertyName(nameof(SourceGenerationOptions.GenerateXmlDocument))] = "false"
|
||||
};
|
||||
|
||||
private static readonly string ExpectedAttribute = $@"{Constants.GeneratedFilesHeader}
|
||||
using System;
|
||||
|
||||
namespace MapTo
|
||||
{{
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
|
||||
public sealed class MapFromAttribute : Attribute
|
||||
{{
|
||||
public MapFromAttribute(Type sourceType)
|
||||
{{
|
||||
SourceType = sourceType;
|
||||
}}
|
||||
|
||||
public Type SourceType {{ get; }}
|
||||
}}
|
||||
}}";
|
||||
|
||||
private record SourceGeneratorOptions(
|
||||
bool UseMapToNamespace = false,
|
||||
string SourceClassNamespace = "Test.Models",
|
||||
int ClassPropertiesCount = 3,
|
||||
int SourceClassPropertiesCount = 3,
|
||||
Action<StringBuilder> PropertyBuilder = null,
|
||||
Action<StringBuilder> SourcePropertyBuilder = null);
|
||||
|
||||
private static string GetSourceText(SourceGeneratorOptions options = null)
|
||||
{
|
||||
const string ns = "Test";
|
||||
options ??= new SourceGeneratorOptions();
|
||||
var hasDifferentSourceNamespace = options.SourceClassNamespace != ns;
|
||||
var builder = new StringBuilder();
|
||||
|
||||
builder.AppendLine("//");
|
||||
builder.AppendLine("// Test source code.");
|
||||
builder.AppendLine("//");
|
||||
builder.AppendLine();
|
||||
|
||||
if (options.UseMapToNamespace)
|
||||
{
|
||||
builder.AppendFormat("using {0};", Constants.RootNamespace).AppendLine();
|
||||
}
|
||||
|
||||
builder
|
||||
.AppendFormat("using {0};", options.SourceClassNamespace)
|
||||
.AppendLine()
|
||||
.AppendLine();
|
||||
|
||||
builder
|
||||
.AppendFormat("namespace {0}", ns)
|
||||
.AppendOpeningBracket();
|
||||
|
||||
if (hasDifferentSourceNamespace && options.UseMapToNamespace)
|
||||
{
|
||||
builder
|
||||
.PadLeft(Indent1)
|
||||
.AppendFormat("using {0};", options.SourceClassNamespace)
|
||||
.AppendLine()
|
||||
.AppendLine();
|
||||
}
|
||||
|
||||
builder
|
||||
.PadLeft(Indent1)
|
||||
.AppendLine(options.UseMapToNamespace ? "[MapFrom(typeof(Baz))]" : "[MapTo.MapFrom(typeof(Baz))]")
|
||||
.PadLeft(Indent1).Append("public partial class Foo")
|
||||
.AppendOpeningBracket(Indent1);
|
||||
|
||||
for (var i = 1; i <= options.ClassPropertiesCount; i++)
|
||||
{
|
||||
builder
|
||||
.PadLeft(Indent2)
|
||||
.AppendLine(i % 2 == 0 ? $"public int Prop{i} {{ get; set; }}" : $"public int Prop{i} {{ get; }}");
|
||||
}
|
||||
|
||||
options.PropertyBuilder?.Invoke(builder);
|
||||
|
||||
builder
|
||||
.AppendClosingBracket(Indent1, false)
|
||||
.AppendClosingBracket()
|
||||
.AppendLine()
|
||||
.AppendLine();
|
||||
|
||||
builder
|
||||
.AppendFormat("namespace {0}", options.SourceClassNamespace)
|
||||
.AppendOpeningBracket()
|
||||
.PadLeft(Indent1).Append("public class Baz")
|
||||
.AppendOpeningBracket(Indent1);
|
||||
|
||||
for (var i = 1; i <= options.SourceClassPropertiesCount; i++)
|
||||
{
|
||||
builder
|
||||
.PadLeft(Indent2)
|
||||
.AppendLine(i % 2 == 0 ? $"public int Prop{i} {{ get; set; }}" : $"public int Prop{i} {{ get; }}");
|
||||
}
|
||||
|
||||
options.SourcePropertyBuilder?.Invoke(builder);
|
||||
|
||||
builder
|
||||
.AppendClosingBracket(Indent1, false)
|
||||
.AppendClosingBracket();
|
||||
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void VerifyIgnorePropertyAttribute()
|
||||
{
|
||||
// Arrange
|
||||
const string source = "";
|
||||
var expectedAttribute = $@"
|
||||
{Constants.GeneratedFilesHeader}
|
||||
using System;
|
||||
|
||||
namespace MapTo
|
||||
{{
|
||||
[AttributeUsage(AttributeTargets.Property, Inherited = false, AllowMultiple = false)]
|
||||
public sealed class IgnorePropertyAttribute : Attribute {{ }}
|
||||
}}
|
||||
".Trim();
|
||||
|
||||
// Act
|
||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions);
|
||||
|
||||
// Assert
|
||||
diagnostics.ShouldBeSuccessful();
|
||||
compilation.SyntaxTrees.ShouldContainSource(IgnorePropertyAttributeSource.AttributeName, expectedAttribute);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void VerifyMapToAttribute()
|
||||
{
|
||||
// Arrange
|
||||
const string source = "";
|
||||
|
||||
// Act
|
||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions);
|
||||
|
||||
// Assert
|
||||
diagnostics.ShouldBeSuccessful();
|
||||
compilation.SyntaxTrees.ShouldContainSource(MapFromAttributeSource.AttributeName, ExpectedAttribute);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void When_FoundMatchingPropertyNameWithDifferentTypes_Should_ReportError()
|
||||
{
|
||||
// Arrange
|
||||
var source = GetSourceText(new SourceGeneratorOptions(
|
||||
true,
|
||||
PropertyBuilder: builder =>
|
||||
{
|
||||
builder
|
||||
.PadLeft(Indent2).AppendLine("public string Prop4 { get; set; }");
|
||||
},
|
||||
SourcePropertyBuilder: builder => builder.PadLeft(Indent2).AppendLine("public int Prop4 { get; set; }")));
|
||||
|
||||
// Act
|
||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions);
|
||||
|
||||
// Assert
|
||||
var expectedError = DiagnosticProvider.NoMatchingPropertyTypeFoundError(GetSourcePropertySymbol("Prop4", compilation));
|
||||
|
||||
diagnostics.ShouldBeUnsuccessful(expectedError);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void When_IgnorePropertyAttributeIsSpecified_Should_NotGenerateMappingsForThatProperty()
|
||||
{
|
||||
// Arrange
|
||||
var source = GetSourceText(new SourceGeneratorOptions(
|
||||
true,
|
||||
PropertyBuilder: builder =>
|
||||
{
|
||||
builder
|
||||
.PadLeft(Indent2).AppendLine("[IgnoreProperty]")
|
||||
.PadLeft(Indent2).AppendLine("public int Prop4 { get; set; }");
|
||||
},
|
||||
SourcePropertyBuilder: builder => builder.PadLeft(Indent2).AppendLine("public int Prop4 { get; set; }")));
|
||||
|
||||
var 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;
|
||||
}
|
||||
".Trim();
|
||||
|
||||
// Act
|
||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions);
|
||||
|
||||
// Assert
|
||||
diagnostics.ShouldBeSuccessful();
|
||||
compilation.SyntaxTrees.Last().ToString().ShouldContain(expectedResult);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void When_MappingsModifierOptionIsSetToInternal_Should_GenerateThoseMethodsWithInternalAccessModifier()
|
||||
{
|
||||
// Arrange
|
||||
var source = GetSourceText();
|
||||
var configOptions = new Dictionary<string, string>
|
||||
{
|
||||
[GetBuildPropertyName(nameof(SourceGenerationOptions.GeneratedMethodsAccessModifier))] = "Internal",
|
||||
[GetBuildPropertyName(nameof(SourceGenerationOptions.GenerateXmlDocument))] = "false"
|
||||
};
|
||||
|
||||
var expectedExtension = @"
|
||||
internal static partial class BazToFooExtensions
|
||||
{
|
||||
internal static Foo ToFoo(this Test.Models.Baz baz)
|
||||
{
|
||||
return baz == null ? null : new Foo(baz);
|
||||
}
|
||||
}".Trim();
|
||||
|
||||
var expectedFactory = @"
|
||||
internal static Foo From(Test.Models.Baz baz)
|
||||
{
|
||||
return baz == null ? null : new Foo(baz);
|
||||
}".Trim();
|
||||
|
||||
// Act
|
||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: configOptions);
|
||||
|
||||
// Assert
|
||||
diagnostics.ShouldBeSuccessful();
|
||||
|
||||
var syntaxTree = compilation.SyntaxTrees.Last().ToString();
|
||||
syntaxTree.ShouldContain(expectedFactory);
|
||||
syntaxTree.ShouldContain(expectedExtension);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void When_MapToAttributeFound_Should_GenerateTheClass()
|
||||
{
|
||||
// Arrange
|
||||
const string source = @"
|
||||
using MapTo;
|
||||
|
||||
namespace Test
|
||||
{
|
||||
[MapFrom(typeof(Baz))]
|
||||
public partial class Foo
|
||||
{
|
||||
public int Prop1 { get; set; }
|
||||
}
|
||||
|
||||
public class Baz
|
||||
{
|
||||
public int Prop1 { get; set; }
|
||||
}
|
||||
}
|
||||
";
|
||||
|
||||
const string expectedResult = @"
|
||||
// <auto-generated />
|
||||
|
||||
using System;
|
||||
|
||||
namespace Test
|
||||
{
|
||||
partial class Foo
|
||||
{
|
||||
public Foo(Test.Baz baz)
|
||||
{
|
||||
if (baz == null) throw new ArgumentNullException(nameof(baz));
|
||||
|
||||
Prop1 = baz.Prop1;
|
||||
}
|
||||
";
|
||||
|
||||
// Act
|
||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions);
|
||||
|
||||
// Assert
|
||||
diagnostics.ShouldBeSuccessful();
|
||||
compilation.SyntaxTrees.Last().ToString().ShouldStartWith(expectedResult.Trim());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void When_MapToAttributeFoundWithoutMatchingProperties_Should_ReportError()
|
||||
{
|
||||
// Arrange
|
||||
const string source = @"
|
||||
using MapTo;
|
||||
|
||||
namespace Test
|
||||
{
|
||||
[MapFrom(typeof(Baz))]
|
||||
public partial class Foo { }
|
||||
|
||||
public class Baz { public int Prop1 { get; set; } }
|
||||
}
|
||||
";
|
||||
|
||||
// Act
|
||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source);
|
||||
|
||||
// Assert
|
||||
var fooType = compilation.GetTypeByMetadataName("Test.Foo");
|
||||
fooType.ShouldNotBeNull();
|
||||
|
||||
var bazType = compilation.GetTypeByMetadataName("Test.Baz");
|
||||
bazType.ShouldNotBeNull();
|
||||
|
||||
var expectedDiagnostic = DiagnosticProvider.NoMatchingPropertyFoundError(fooType.Locations.Single(), fooType, bazType);
|
||||
var error = diagnostics.FirstOrDefault(d => d.Id == expectedDiagnostic.Id);
|
||||
error.ShouldNotBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void When_MapToAttributeWithNamespaceFound_Should_GenerateTheClass()
|
||||
{
|
||||
// Arrange
|
||||
const string source = @"
|
||||
namespace Test
|
||||
{
|
||||
[MapTo.MapFrom(typeof(Baz))]
|
||||
public partial class Foo { public int Prop1 { get; set; } }
|
||||
|
||||
public class Baz { public int Prop1 { get; set; } }
|
||||
}
|
||||
";
|
||||
|
||||
const string expectedResult = @"
|
||||
// <auto-generated />
|
||||
|
||||
using System;
|
||||
|
||||
namespace Test
|
||||
{
|
||||
partial class Foo
|
||||
{
|
||||
public Foo(Test.Baz baz)
|
||||
{
|
||||
if (baz == null) throw new ArgumentNullException(nameof(baz));
|
||||
|
||||
Prop1 = baz.Prop1;
|
||||
}
|
||||
";
|
||||
|
||||
// Act
|
||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions);
|
||||
|
||||
// Assert
|
||||
diagnostics.ShouldBeSuccessful();
|
||||
compilation.SyntaxTrees.Last().ToString().ShouldStartWith(expectedResult.Trim());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void When_NoMapToAttributeFound_Should_GenerateOnlyTheAttribute()
|
||||
{
|
||||
// Arrange
|
||||
const string source = "";
|
||||
var expectedTypes = new[]
|
||||
{
|
||||
IgnorePropertyAttributeSource.AttributeName,
|
||||
MapFromAttributeSource.AttributeName,
|
||||
ITypeConverterSource.InterfaceName,
|
||||
MapPropertyAttributeSource.AttributeName
|
||||
};
|
||||
|
||||
// Act
|
||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source);
|
||||
|
||||
// Assert
|
||||
diagnostics.ShouldBeSuccessful();
|
||||
compilation.SyntaxTrees
|
||||
.Select(s => s.ToString())
|
||||
.Where(s => !string.IsNullOrWhiteSpace(s.ToString()))
|
||||
.All(s => expectedTypes.Any(s.Contains))
|
||||
.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void When_SourceTypeHasDifferentNamespace_Should_NotAddToUsings()
|
||||
{
|
||||
// Arrange
|
||||
var source = GetSourceText(new SourceGeneratorOptions(SourceClassNamespace: "Bazaar"));
|
||||
|
||||
const string expectedResult = @"
|
||||
// <auto-generated />
|
||||
|
||||
using System;
|
||||
|
||||
namespace Test
|
||||
";
|
||||
|
||||
// Act
|
||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source);
|
||||
|
||||
// Assert
|
||||
diagnostics.ShouldBeSuccessful();
|
||||
compilation.SyntaxTrees.Last().ToString().ShouldStartWith(expectedResult.Trim());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void When_SourceTypeHasMatchingProperties_Should_CreateConstructorAndAssignSrcToDest()
|
||||
{
|
||||
// Arrange
|
||||
var source = GetSourceText();
|
||||
|
||||
const string expectedResult = @"
|
||||
partial class Foo
|
||||
{
|
||||
public Foo(Test.Models.Baz baz)
|
||||
{
|
||||
if (baz == null) throw new ArgumentNullException(nameof(baz));
|
||||
|
||||
Prop1 = baz.Prop1;
|
||||
Prop2 = baz.Prop2;
|
||||
Prop3 = baz.Prop3;
|
||||
}
|
||||
";
|
||||
|
||||
// Act
|
||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions);
|
||||
|
||||
// Assert
|
||||
diagnostics.ShouldBeSuccessful();
|
||||
compilation.SyntaxTrees.Last().ToString().ShouldContain(expectedResult.Trim());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void When_SourceTypeHasMatchingProperties_Should_CreateFromStaticMethod()
|
||||
{
|
||||
// Arrange
|
||||
var source = GetSourceText();
|
||||
|
||||
const string expectedResult = @"
|
||||
public static Foo From(Test.Models.Baz baz)
|
||||
{
|
||||
return baz == null ? null : new Foo(baz);
|
||||
}
|
||||
";
|
||||
|
||||
// Act
|
||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source);
|
||||
|
||||
// Assert
|
||||
diagnostics.ShouldBeSuccessful();
|
||||
compilation.SyntaxTrees.Last().ToString().ShouldContain(expectedResult.Trim());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void When_SourceTypeHasMatchingProperties_Should_GenerateToExtensionMethodOnSourceType()
|
||||
{
|
||||
// Arrange
|
||||
var source = GetSourceText();
|
||||
|
||||
const string expectedResult = @"
|
||||
public static partial class BazToFooExtensions
|
||||
{
|
||||
public static Foo ToFoo(this Test.Models.Baz baz)
|
||||
{
|
||||
return baz == null ? null : new Foo(baz);
|
||||
}
|
||||
}
|
||||
";
|
||||
|
||||
// Act
|
||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions);
|
||||
|
||||
// Assert
|
||||
diagnostics.ShouldBeSuccessful();
|
||||
compilation.SyntaxTrees.Last().ToString().ShouldContain(expectedResult.Trim());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void VerifyTypeConverterInterface()
|
||||
{
|
||||
// Arrange
|
||||
const string source = "";
|
||||
var expectedInterface = $@"
|
||||
{Constants.GeneratedFilesHeader}
|
||||
|
||||
namespace MapTo
|
||||
{{
|
||||
public interface ITypeConverter<in TSource, out TDestination>
|
||||
{{
|
||||
TDestination Convert(TSource source, object[] converterParameters);
|
||||
}}
|
||||
}}
|
||||
".Trim();
|
||||
|
||||
// Act
|
||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions);
|
||||
|
||||
// Assert
|
||||
diagnostics.ShouldBeSuccessful();
|
||||
compilation.SyntaxTrees.ShouldContainSource(ITypeConverterSource.InterfaceName, expectedInterface);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void VerifyTypeConverterInterfaceWithNullableOptionOn()
|
||||
{
|
||||
// Arrange
|
||||
const string source = "";
|
||||
var expectedInterface = $@"
|
||||
{Constants.GeneratedFilesHeader}
|
||||
#nullable enable
|
||||
|
||||
namespace MapTo
|
||||
{{
|
||||
public interface ITypeConverter<in TSource, out TDestination>
|
||||
{{
|
||||
TDestination Convert(TSource source, object[]? converterParameters);
|
||||
}}
|
||||
}}
|
||||
".Trim();
|
||||
|
||||
// Act
|
||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions, nullableContextOptions: NullableContextOptions.Enable);
|
||||
|
||||
// Assert
|
||||
diagnostics.ShouldBeSuccessful();
|
||||
compilation.SyntaxTrees.ShouldContainSource(ITypeConverterSource.InterfaceName, expectedInterface);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void VerifyMapTypeConverterAttribute()
|
||||
{
|
||||
// Arrange
|
||||
const string source = "";
|
||||
var expectedInterface = $@"
|
||||
{Constants.GeneratedFilesHeader}
|
||||
|
||||
using System;
|
||||
|
||||
namespace MapTo
|
||||
{{
|
||||
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
|
||||
public sealed class MapTypeConverterAttribute : Attribute
|
||||
{{
|
||||
public MapTypeConverterAttribute(Type converter, object[] converterParameters = null)
|
||||
{{
|
||||
Converter = converter;
|
||||
ConverterParameters = converterParameters;
|
||||
}}
|
||||
|
||||
public Type Converter {{ get; }}
|
||||
|
||||
public object[] ConverterParameters {{ get; }}
|
||||
}}
|
||||
}}
|
||||
".Trim();
|
||||
|
||||
// Act
|
||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions);
|
||||
|
||||
// Assert
|
||||
diagnostics.ShouldBeSuccessful();
|
||||
compilation.SyntaxTrees.ShouldContainSource(MapTypeConverterAttributeSource.AttributeName, expectedInterface);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void VerifyMapTypeConverterAttributeWithNullableOptionOn()
|
||||
{
|
||||
// Arrange
|
||||
const string source = "";
|
||||
var expectedInterface = $@"
|
||||
{Constants.GeneratedFilesHeader}
|
||||
#nullable enable
|
||||
|
||||
using System;
|
||||
|
||||
namespace MapTo
|
||||
{{
|
||||
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
|
||||
public sealed class MapTypeConverterAttribute : Attribute
|
||||
{{
|
||||
public MapTypeConverterAttribute(Type converter, object[]? converterParameters = null)
|
||||
{{
|
||||
Converter = converter;
|
||||
ConverterParameters = converterParameters;
|
||||
}}
|
||||
|
||||
public Type Converter {{ get; }}
|
||||
|
||||
public object[]? ConverterParameters {{ get; }}
|
||||
}}
|
||||
}}
|
||||
".Trim();
|
||||
|
||||
// Act
|
||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions, nullableContextOptions: NullableContextOptions.Enable);
|
||||
|
||||
// Assert
|
||||
diagnostics.ShouldBeSuccessful();
|
||||
compilation.SyntaxTrees.ShouldContainSource(MapTypeConverterAttributeSource.AttributeName, expectedInterface);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void When_FoundMatchingPropertyNameWithDifferentImplicitlyConvertibleType_Should_GenerateTheProperty()
|
||||
{
|
||||
// Arrange
|
||||
var source = GetSourceText(new SourceGeneratorOptions(
|
||||
true,
|
||||
PropertyBuilder: builder =>
|
||||
{
|
||||
builder
|
||||
.PadLeft(Indent2).AppendLine("public long Prop4 { get; set; }");
|
||||
},
|
||||
SourcePropertyBuilder: builder => builder.PadLeft(Indent2).AppendLine("public int Prop4 { get; set; }")));
|
||||
|
||||
var 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;
|
||||
}
|
||||
".Trim();
|
||||
|
||||
// Act
|
||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions);
|
||||
|
||||
// Assert
|
||||
diagnostics.ShouldBeSuccessful();
|
||||
compilation.SyntaxTrees.Last().ToString().ShouldContain(expectedResult);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void When_FoundMatchingPropertyNameWithIncorrectConverterType_ShouldReportError()
|
||||
{
|
||||
// Arrange
|
||||
var source = GetSourceText(new SourceGeneratorOptions(
|
||||
true,
|
||||
PropertyBuilder: builder =>
|
||||
{
|
||||
builder
|
||||
.PadLeft(Indent2).AppendLine("[IgnoreProperty]")
|
||||
.PadLeft(Indent2).AppendLine("public long IgnoreMe { get; set; }")
|
||||
.PadLeft(Indent2).AppendLine("[MapTypeConverter(typeof(Prop4Converter))]")
|
||||
.PadLeft(Indent2).AppendLine("public long Prop4 { get; set; }");
|
||||
},
|
||||
SourcePropertyBuilder: builder => builder.PadLeft(Indent2).AppendLine("public string Prop4 { get; set; }")));
|
||||
|
||||
source += @"
|
||||
namespace Test
|
||||
{
|
||||
using MapTo;
|
||||
|
||||
public class Prop4Converter: ITypeConverter<string, int>
|
||||
{
|
||||
public int Convert(string source, object[] converterParameters) => int.Parse(source);
|
||||
}
|
||||
}
|
||||
";
|
||||
|
||||
// Act
|
||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions);
|
||||
|
||||
// Assert
|
||||
var expectedError = DiagnosticProvider.InvalidTypeConverterGenericTypesError(GetSourcePropertySymbol("Prop4", compilation), GetSourcePropertySymbol("Prop4", compilation, "Baz"));
|
||||
diagnostics.ShouldBeUnsuccessful(expectedError);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void When_FoundMatchingPropertyNameWithConverterType_ShouldUseTheConverterToAssignProperties()
|
||||
{
|
||||
// Arrange
|
||||
var source = GetSourceText(new SourceGeneratorOptions(
|
||||
true,
|
||||
PropertyBuilder: builder =>
|
||||
{
|
||||
builder
|
||||
.PadLeft(Indent2).AppendLine("[MapTypeConverter(typeof(Prop4Converter))]")
|
||||
.PadLeft(Indent2).AppendLine("public long Prop4 { get; set; }");
|
||||
},
|
||||
SourcePropertyBuilder: builder => builder.PadLeft(Indent2).AppendLine("public string Prop4 { get; set; }")));
|
||||
|
||||
source += @"
|
||||
namespace Test
|
||||
{
|
||||
using MapTo;
|
||||
|
||||
public class Prop4Converter: ITypeConverter<string, long>
|
||||
{
|
||||
public long Convert(string source, object[] converterParameters) => long.Parse(source);
|
||||
}
|
||||
}
|
||||
";
|
||||
|
||||
const string expectedSyntax = "Prop4 = new Test.Prop4Converter().Convert(baz.Prop4, null);";
|
||||
|
||||
// Act
|
||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions);
|
||||
|
||||
// Assert
|
||||
diagnostics.ShouldBeSuccessful();
|
||||
compilation.SyntaxTrees.Last().ToString().ShouldContain(expectedSyntax);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void When_FoundMatchingPropertyNameWithConverterType_ShouldUseTheConverterAndItsParametersToAssignProperties()
|
||||
{
|
||||
// Arrange
|
||||
var source = GetSourceText(new SourceGeneratorOptions(
|
||||
true,
|
||||
PropertyBuilder: builder =>
|
||||
{
|
||||
builder
|
||||
.PadLeft(Indent2).AppendLine("[MapTypeConverter(typeof(Prop4Converter), new object[]{\"G\", 'C', 10})]")
|
||||
.PadLeft(Indent2).AppendLine("public string Prop4 { get; set; }");
|
||||
},
|
||||
SourcePropertyBuilder: builder => builder.PadLeft(Indent2).AppendLine("public long Prop4 { get; set; }")));
|
||||
|
||||
source += @"
|
||||
namespace Test
|
||||
{
|
||||
using MapTo;
|
||||
|
||||
public class Prop4Converter: ITypeConverter<long, string>
|
||||
{
|
||||
public string Convert(long source, object[] converterParameters) => source.ToString(converterParameters[0] as string);
|
||||
}
|
||||
}
|
||||
";
|
||||
|
||||
const string expectedSyntax = "Prop4 = new Test.Prop4Converter().Convert(baz.Prop4, new object[] { \"G\", 'C', 10 });";
|
||||
|
||||
// Act
|
||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions);
|
||||
|
||||
// Assert
|
||||
diagnostics.ShouldBeSuccessful();
|
||||
compilation.SyntaxTrees.Last().ToString().ShouldContain(expectedSyntax);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(NullableContextOptions.Disable)]
|
||||
[InlineData(NullableContextOptions.Enable)]
|
||||
public void VerifyMapPropertyAttribute(NullableContextOptions nullableContextOptions)
|
||||
{
|
||||
// Arrange
|
||||
const string source = "";
|
||||
var nullableSyntax = nullableContextOptions == NullableContextOptions.Enable ? "?" : string.Empty;
|
||||
var expectedInterface = $@"
|
||||
{Constants.GeneratedFilesHeader}
|
||||
{(nullableContextOptions == NullableContextOptions.Enable ? $"#nullable enable{Environment.NewLine}": string.Empty)}
|
||||
using System;
|
||||
|
||||
namespace MapTo
|
||||
{{
|
||||
[AttributeUsage(AttributeTargets.Property, AllowMultiple = true)]
|
||||
public sealed class MapPropertyAttribute : Attribute
|
||||
{{
|
||||
public string{nullableSyntax} SourcePropertyName {{ get; set; }}
|
||||
}}
|
||||
}}
|
||||
".Trim();
|
||||
|
||||
// Act
|
||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions, nullableContextOptions: nullableContextOptions);
|
||||
|
||||
// Assert
|
||||
diagnostics.ShouldBeSuccessful();
|
||||
compilation.SyntaxTrees.ShouldContainSource(MapPropertyAttributeSource.AttributeName, expectedInterface);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void When_MapPropertyFound_Should_UseItToMapToSourceProperty()
|
||||
{
|
||||
// Arrange
|
||||
var source = GetSourceText(new SourceGeneratorOptions(
|
||||
true,
|
||||
PropertyBuilder: builder =>
|
||||
{
|
||||
builder
|
||||
.PadLeft(Indent2).AppendLine("[MapProperty(SourcePropertyName = nameof(Baz.Prop3))]")
|
||||
.PadLeft(Indent2).AppendLine("public int Prop4 { get; set; }");
|
||||
},
|
||||
SourcePropertyBuilder: builder => builder.PadLeft(Indent2).AppendLine("public int Prop4 { get; set; }")));
|
||||
|
||||
var 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.Prop3;
|
||||
}
|
||||
".Trim();
|
||||
|
||||
// Act
|
||||
var (compilation, diagnostics) = CSharpGenerator.GetOutputCompilation(source, analyzerConfigOptions: DefaultAnalyzerOptions);
|
||||
|
||||
// Assert
|
||||
diagnostics.ShouldBeSuccessful();
|
||||
compilation.SyntaxTrees.Last().ToString().ShouldContain(expectedResult);
|
||||
}
|
||||
|
||||
private static PropertyDeclarationSyntax GetPropertyDeclarationSyntax(SyntaxTree syntaxTree, string targetPropertyName, string targetClass = "Foo")
|
||||
{
|
||||
return syntaxTree.GetRoot()
|
||||
.DescendantNodes()
|
||||
.OfType<ClassDeclarationSyntax>()
|
||||
.Single(c => c.Identifier.ValueText == targetClass)
|
||||
.DescendantNodes()
|
||||
.OfType<PropertyDeclarationSyntax>()
|
||||
.Single(p => p.Identifier.ValueText == targetPropertyName);
|
||||
}
|
||||
|
||||
private static IPropertySymbol GetSourcePropertySymbol(string propertyName, Compilation compilation, string targetClass = "Foo")
|
||||
{
|
||||
var syntaxTree = compilation.SyntaxTrees.First();
|
||||
var propSyntax = GetPropertyDeclarationSyntax(syntaxTree, propertyName, targetClass);
|
||||
|
||||
var semanticModel = compilation.GetSemanticModel(syntaxTree);
|
||||
return semanticModel.GetDeclaredSymbol(propSyntax);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue