Skip to content

Commit

Permalink
Add ValidateMemberPath to validate but not construct a MemberPath
Browse files Browse the repository at this point in the history
  • Loading branch information
jnyrup committed Feb 11, 2022
1 parent 2c14921 commit 62f0d76
Show file tree
Hide file tree
Showing 2 changed files with 64 additions and 8 deletions.
14 changes: 6 additions & 8 deletions Src/FluentAssertions/Collections/GenericCollectionAssertions.cs
Expand Up @@ -2993,13 +2993,11 @@ public AndConstraint<TAssertions> StartWith(T element, string because = "", para
direction,
unordered);

string orderString = GetExpressionOrderString(propertyExpression);

Execute.Assertion
.ForCondition(unordered.SequenceEqual(expectation))
.BecauseOf(because, becauseArgs)
.FailWith("Expected {context:collection} {0} to be ordered {1}{reason} and result in {2}.",
Subject, orderString, expectation);
() => Subject, () => GetExpressionOrderString(propertyExpression), () => expectation);

return new AndConstraint<SubsequentOrderingAssertions<T>>(
new SubsequentOrderingAssertions<T>(Subject, expectation));
Expand Down Expand Up @@ -3188,11 +3186,13 @@ private bool IsValidProperty<TSelector>(Expression<Func<T, TSelector>> propertyE
Guard.ThrowIfArgumentIsNull(propertyExpression, nameof(propertyExpression),
"Cannot assert collection ordering without specifying a property.");

propertyExpression.ValidateMemberPath();

return Execute.Assertion
.ForCondition(Subject is not null)
.BecauseOf(because, becauseArgs)
.ForCondition(Subject is not null)
.FailWith("Expected {context:collection} to be ordered by {0}{reason} but found <null>.",
propertyExpression.GetMemberPath());
() => propertyExpression.GetMemberPath());
}

private AndConstraint<TAssertions> NotBeOrderedBy<TSelector>(
Expand All @@ -3214,13 +3214,11 @@ private bool IsValidProperty<TSelector>(Expression<Func<T, TSelector>> propertyE
direction,
unordered);

string orderString = GetExpressionOrderString(propertyExpression);

Execute.Assertion
.ForCondition(!unordered.SequenceEqual(expectation))
.BecauseOf(because, becauseArgs)
.FailWith("Expected {context:collection} {0} to not be ordered {1}{reason} and not result in {2}.",
Subject, orderString, expectation);
() => Subject, () => GetExpressionOrderString(propertyExpression), () => expectation);
}

return new AndConstraint<TAssertions>((TAssertions)this);
Expand Down
58 changes: 58 additions & 0 deletions Src/FluentAssertions/Common/ExpressionExtensions.cs
Expand Up @@ -103,6 +103,64 @@ internal static class ExpressionExtensions
return new MemberPath(typeof(TDeclaringType), declaringType, segmentPath.Replace(".[", "[", StringComparison.Ordinal));
}

/// <summary>
/// Validates that the expression can be used to construct a <see cref="MemberPath"/>.
/// </summary>
public static void ValidateMemberPath<TDeclaringType, TPropertyType>(
this Expression<Func<TDeclaringType, TPropertyType>> expression)
{
Guard.ThrowIfArgumentIsNull(expression, nameof(expression), "Expected an expression, but found <null>.");

Expression node = expression;

while (node is not null)
{
#pragma warning disable IDE0010 // System.Linq.Expressions.ExpressionType has many members we do not care about
switch (node.NodeType)
#pragma warning restore IDE0010
{
case ExpressionType.Lambda:
node = ((LambdaExpression)node).Body;
break;

case ExpressionType.Convert:
case ExpressionType.ConvertChecked:
var unaryExpression = (UnaryExpression)node;
node = unaryExpression.Operand;
break;

case ExpressionType.MemberAccess:
var memberExpression = (MemberExpression)node;
node = memberExpression.Expression;

break;

case ExpressionType.ArrayIndex:
var binaryExpression = (BinaryExpression)node;
node = binaryExpression.Left;

break;

case ExpressionType.Parameter:
node = null;
break;

case ExpressionType.Call:
var methodCallExpression = (MethodCallExpression)node;
if (methodCallExpression.Method.Name != "get_Item" || methodCallExpression.Arguments.Count != 1 || methodCallExpression.Arguments[0] is not ConstantExpression)
{
throw new ArgumentException(GetUnsupportedExpressionMessage(expression.Body), nameof(expression));
}

node = methodCallExpression.Object;
break;

default:
throw new ArgumentException(GetUnsupportedExpressionMessage(expression.Body), nameof(expression));
}
}
}

private static string GetUnsupportedExpressionMessage(Expression expression) =>
$"Expression <{expression}> cannot be used to select a member.";
}
Expand Down

0 comments on commit 62f0d76

Please sign in to comment.