Skip to content

Commit

Permalink
feat: add IQueryable checks and tests
Browse files Browse the repository at this point in the history
  • Loading branch information
TimothyMakkison committed Oct 12, 2023
1 parent 4254764 commit b5313a1
Show file tree
Hide file tree
Showing 7 changed files with 103 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,13 @@ public static bool ValidateMappingSpecification(
}

// cannot access non public member in initializer
if (allowInitOnlyMember && !ctx.BuilderContext.SymbolAccessor.IsAccessible(targetMemberPath.Member.MemberSymbol))
if (
allowInitOnlyMember
&& (
!ctx.BuilderContext.SymbolAccessor.IsAccessible(targetMemberPath.Member.MemberSymbol)
|| !targetMemberPath.Member.CanAccessiblySet
)
)
{
ctx.BuilderContext.ReportDiagnostic(
DiagnosticDescriptors.CannotMapToReadOnlyMember,
Expand All @@ -138,7 +144,14 @@ public static bool ValidateMappingSpecification(
}

// a target member path part is write only or not accessible
if (targetMemberPath.ObjectPath.Any(p => !p.CanGet))
// an expressions target member path is only accessible with unsafe access
if (
targetMemberPath.ObjectPath.Any(p => !p.CanGet)
|| (
ctx.BuilderContext.IsExpression
&& targetMemberPath.ObjectPath.Any(p => !ctx.BuilderContext.SymbolAccessor.IsAccessible(p.MemberSymbol))

Check warning on line 152 in src/Riok.Mapperly/Descriptors/MappingBodyBuilders/ObjectMemberMappingBodyBuilder.cs

View check run for this annotation

Codecov / codecov/patch

src/Riok.Mapperly/Descriptors/MappingBodyBuilders/ObjectMemberMappingBodyBuilder.cs#L152

Added line #L152 was not covered by tests
)
)
{
ctx.BuilderContext.ReportDiagnostic(
DiagnosticDescriptors.CannotMapToWriteOnlyMemberPath,
Expand Down Expand Up @@ -174,7 +187,14 @@ public static bool ValidateMappingSpecification(
}

// a source member path is write only or not accessible
if (sourceMemberPath.Path.Any(p => !p.CanGet))
// an expressions source member path is only accessible with unsafe access
if (
sourceMemberPath.Path.Any(p => !p.CanGet)
|| (
ctx.BuilderContext.IsExpression
&& sourceMemberPath.Path.Any(p => !ctx.BuilderContext.SymbolAccessor.IsAccessible(p.MemberSymbol))
)
)
{
ctx.BuilderContext.ReportDiagnostic(
DiagnosticDescriptors.CannotMapFromWriteOnlyMember,
Expand Down
24 changes: 10 additions & 14 deletions src/Riok.Mapperly/Descriptors/UnsafeAccessorContext.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Text;
using System.Globalization;
using System.Text;

Check failure on line 2 in src/Riok.Mapperly/Descriptors/UnsafeAccessorContext.cs

View workflow job for this annotation

GitHub Actions / lint-dotnet

Using directive is unnecessary.
using Microsoft.CodeAnalysis;
using Riok.Mapperly.Descriptors.Mappings.MemberMappings.UnsafeAccess;
using Riok.Mapperly.Helpers;
Expand Down Expand Up @@ -80,26 +81,21 @@ private string GetValidMethodName(ITypeSymbol source, string name)
return uniqueName;
}

// TODO: Refactor, rename, optimise, handle only underscore???
// strip the leading underscore and capitalise the first letter
private string FormatAccessorName(string name)
{
var result = new StringBuilder();
if (name.Length == 0)
return name;

Check warning on line 88 in src/Riok.Mapperly/Descriptors/UnsafeAccessorContext.cs

View check run for this annotation

Codecov / codecov/patch

src/Riok.Mapperly/Descriptors/UnsafeAccessorContext.cs#L88

Added line #L88 was not covered by tests

var index = 0;
foreach (var c in name)
{
if (name[0] == '_')
index++;
if (c == '_')
{
continue;
}

result.Append(char.ToUpper(c));
result.Append(name.AsSpan().Slice(index, name.Length - index));
break;
}
if (name.Length - index == 0)
return string.Empty;

Check warning on line 95 in src/Riok.Mapperly/Descriptors/UnsafeAccessorContext.cs

View check run for this annotation

Codecov / codecov/patch

src/Riok.Mapperly/Descriptors/UnsafeAccessorContext.cs#L95

Added line #L95 was not covered by tests

return result.ToString();
var ch = char.ToUpper(name[index], CultureInfo.InvariantCulture);
return $"{ch}{name.Substring(index + 1, name.Length - index - 1)}";
}

private readonly struct UnsafeAccessorKey : IEquatable<UnsafeAccessorKey>
Expand Down
2 changes: 1 addition & 1 deletion src/Riok.Mapperly/Symbols/FieldMember.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public FieldMember(IFieldSymbol fieldSymbol)
public bool IsIndexer => false;
public bool CanGet => !_fieldSymbol.IsReadOnly;
public bool CanSet => true;

public bool CanAccessiblySet => true;

Check warning on line 24 in src/Riok.Mapperly/Symbols/FieldMember.cs

View check run for this annotation

Codecov / codecov/patch

src/Riok.Mapperly/Symbols/FieldMember.cs#L24

Added line #L24 was not covered by tests
public bool IsInitOnly => false;

public bool IsRequired
Expand Down
2 changes: 2 additions & 0 deletions src/Riok.Mapperly/Symbols/IMappableMember.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ public interface IMappableMember

bool CanSet { get; }

bool CanAccessiblySet { get; }

bool IsInitOnly { get; }

bool IsRequired { get; }
Expand Down
1 change: 1 addition & 0 deletions src/Riok.Mapperly/Symbols/MethodMember.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ public MethodMember(IMappableMember mappableMember, string methodName, bool isMe
public bool IsIndexer => _mappableMember.IsIndexer;
public bool CanGet => _mappableMember.CanGet;

Check warning on line 25 in src/Riok.Mapperly/Symbols/MethodMember.cs

View check run for this annotation

Codecov / codecov/patch

src/Riok.Mapperly/Symbols/MethodMember.cs#L24-L25

Added lines #L24 - L25 were not covered by tests
public bool CanSet => _mappableMember.CanSet;
public bool CanAccessiblySet => _mappableMember.CanAccessiblySet;
public bool IsInitOnly => _mappableMember.IsInitOnly;
public bool IsRequired => _mappableMember.IsRequired;

Check warning on line 29 in src/Riok.Mapperly/Symbols/MethodMember.cs

View check run for this annotation

Codecov / codecov/patch

src/Riok.Mapperly/Symbols/MethodMember.cs#L27-L29

Added lines #L27 - L29 were not covered by tests

Expand Down
3 changes: 3 additions & 0 deletions src/Riok.Mapperly/Symbols/PropertyMember.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ internal PropertyMember(IPropertySymbol propertySymbol, SymbolAccessor symbolAcc
public bool CanSet =>
!_propertySymbol.IsReadOnly && (_propertySymbol.SetMethod == null || _symbolAccessor.IsUnsafeAccessible(_propertySymbol.SetMethod));

public bool CanAccessiblySet =>
!_propertySymbol.IsReadOnly && (_propertySymbol.SetMethod == null || _symbolAccessor.IsAccessible(_propertySymbol.SetMethod));

public bool IsInitOnly => _propertySymbol.SetMethod?.IsInitOnly == true;

public bool IsRequired
Expand Down
63 changes: 63 additions & 0 deletions test/Riok.Mapperly.Tests/Mapping/UnsafeAccessorTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,69 @@ public void InitPrivateValueShouldNotMap()
);
}

[Fact]
public void RequiredPrivateSetShouldDiagnostic()
{
var source = TestSourceBuilder.Mapping(
"A",
"B",
TestSourceBuilderOptions.WithMemberVisibility(MemberVisibility.AllAccessible),
"class A { public int Value { get; set; } }",
"class B { public required int Value { get; private set; } }"
);

TestHelper
.GenerateMapper(source, TestHelperOptions.AllowDiagnostics)
.Should()
.HaveDiagnostic(DiagnosticDescriptors.SourceMemberNotMapped)
.HaveDiagnostic(DiagnosticDescriptors.CannotMapToReadOnlyMember)
.HaveAssertedAllDiagnostics()
.HaveMapMethodBody(
"""
var target = new global::B();
return target;
"""
);
}

[Fact]
public void QueryablePrivateToPrivateShouldNotGenerate()
{
var source = TestSourceBuilder.Mapping(
"System.Linq.IQueryable<A>",
"System.Linq.IQueryable<B>",
TestSourceBuilderOptions.WithMemberVisibility(MemberVisibility.AllAccessible & ~MemberVisibility.Accessible),
"class A { private int Value { get; set; } }",
"class B { private int Value { get; set; } }"
);

TestHelper
.GenerateMapper(source, TestHelperOptions.AllowDiagnostics)
.Should()
.HaveDiagnostic(DiagnosticDescriptors.CannotMapToReadOnlyMember)
.HaveDiagnostic(DiagnosticDescriptors.SourceMemberNotMapped)
.HaveAssertedAllDiagnostics();
}

[Fact]
public void QueryablePrivateToPublicShouldNotGenerate()
{
var source = TestSourceBuilder.Mapping(
"System.Linq.IQueryable<A>",
"System.Linq.IQueryable<B>",
TestSourceBuilderOptions.WithMemberVisibility(MemberVisibility.AllAccessible & ~MemberVisibility.Accessible),
"class A { private int Value { get; set; } }",
"class B { public int Value { get; set; } }"
);

TestHelper
.GenerateMapper(source, TestHelperOptions.AllowDiagnostics)
.Should()
.HaveDiagnostic(DiagnosticDescriptors.CannotMapFromWriteOnlyMember)
.HaveDiagnostic(DiagnosticDescriptors.SourceMemberNotMapped)
.HaveAssertedAllDiagnostics();
}

[Fact]
public void UnmappedMembersShouldDiagnostic()
{
Expand Down

0 comments on commit b5313a1

Please sign in to comment.