Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,12 @@ internal TreeNodeFilter(string filter)
/// OP = '&' | '|'
/// NODE_VALUE = TOKEN | TOKEN '[' FILTER_EXPR ']'
/// TOKEN = string
///
/// OR expressions are supported for a single path segment, for example:
/// <c>/A/B/C/(Test1|Test2)</c>.
///
/// OR expressions over full paths are not supported, for example:
/// <c>(/A/B/C/Test1)|(/A/B/C/Test2)</c>.
/// </code>
/// </remarks>
/// <exception cref="InvalidOperationException">
Expand Down Expand Up @@ -97,14 +103,14 @@ private static List<FilterExpression> ParseFilter(string filter)
_ => throw ApplicationStateGuard.Unreachable(),
};

ProcessHigherPrecedenceOperators(expressionStack, operatorStack, currentOp);
ProcessHigherPrecedenceOperators(expressionStack, operatorStack, currentOp, filter);

isOperatorAllowed = false;
isPropAllowed = false;
break;

case "/":
ProcessHigherPrecedenceOperators(expressionStack, operatorStack, OperatorKind.Separator);
ProcessHigherPrecedenceOperators(expressionStack, operatorStack, OperatorKind.Separator, filter);

isOperatorAllowed = false;
isPropAllowed = false;
Expand Down Expand Up @@ -147,7 +153,7 @@ private static List<FilterExpression> ParseFilter(string filter)
break;
}

ProcessStackOperator(topStackOperator, expressionStack, operatorStack);
ProcessStackOperator(topStackOperator, expressionStack, operatorStack, filter);
}

isOperatorAllowed = true;
Expand Down Expand Up @@ -179,7 +185,7 @@ private static List<FilterExpression> ParseFilter(string filter)
break;
}

ProcessStackOperator(topStackOperator, expressionStack, operatorStack);
ProcessStackOperator(topStackOperator, expressionStack, operatorStack, filter);
}

// We should end up with an expression and a property.
Expand Down Expand Up @@ -244,7 +250,7 @@ private static List<FilterExpression> ParseFilter(string filter)
while (operatorStack.Count > 0 && operatorStack.Peek() != OperatorKind.Separator)
{
topStackOperator = operatorStack.Pop();
ProcessStackOperator(topStackOperator, expressionStack, operatorStack);
ProcessStackOperator(topStackOperator, expressionStack, operatorStack, filter);
}

var parsedFilter = expressionStack.Reverse().ToList();
Expand All @@ -265,12 +271,13 @@ private static List<FilterExpression> ParseFilter(string filter)
static void ProcessHigherPrecedenceOperators(
Stack<FilterExpression> expressionStack,
Stack<OperatorKind> operatorStack,
OperatorKind currentOp)
OperatorKind currentOp,
string currentFilter)
{
while (operatorStack.Count != 0 && operatorStack.Peek() > currentOp)
{
OperatorKind topStackOperator = operatorStack.Pop();
ProcessStackOperator(topStackOperator, expressionStack, operatorStack);
ProcessStackOperator(topStackOperator, expressionStack, operatorStack, currentFilter);
break;
}

Expand Down Expand Up @@ -303,7 +310,7 @@ private static void ValidateExpression(FilterExpression expr, bool isMatchAllAll
}
}

private static void ProcessStackOperator(OperatorKind op, Stack<FilterExpression> expr, Stack<OperatorKind> ops)
private static void ProcessStackOperator(OperatorKind op, Stack<FilterExpression> expr, Stack<OperatorKind> ops, string filter)
{
switch (op)
{
Expand All @@ -325,14 +332,14 @@ private static void ProcessStackOperator(OperatorKind op, Stack<FilterExpression
subexprs.Add(expr.Pop());
}

FilterOperator filter = op switch
FilterOperator filterOperator = op switch
{
OperatorKind.And => FilterOperator.And,
OperatorKind.Or => FilterOperator.Or,
_ => throw ApplicationStateGuard.Unreachable(),
};

expr.Push(new OperatorExpression(filter, subexprs));
expr.Push(new OperatorExpression(filterOperator, subexprs));
break;

case OperatorKind.FilterEquals:
Expand Down Expand Up @@ -364,7 +371,9 @@ private static void ProcessStackOperator(OperatorKind op, Stack<FilterExpression
// Note: Handling of other operations in valid scenarios should be handled by the caller.
// Reaching this code for instance means that we're trying to process / operator
// in the middle of a ( expression ).
throw new InvalidOperationException(PlatformResources.TreeNodeFilterUnexpectedSlashOperatorErrorMessage);
throw new InvalidOperationException(
$"{PlatformResources.TreeNodeFilterUnexpectedSlashOperatorErrorMessage} "
+ $"Filter: '{filter}'. To combine alternatives for one path segment, use syntax like '/A/B/C/(X|Y)'.");
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,34 @@ public void OrExpression_WorksForLiteralStrings()
Assert.IsFalse(filter.MatchesFilter("/C", new PropertyBag()));
}

[TestMethod]
public void OrExpression_WorksForSinglePathSegmentInsideParentheses()
{
TreeNodeFilter filter = new("/*/*/*/(MyTest1|MyTest2)");

Assert.IsTrue(filter.MatchesFilter("/A/B/C/MyTest1", new PropertyBag()));
Assert.IsTrue(filter.MatchesFilter("/A/B/C/MyTest2", new PropertyBag()));
Assert.IsFalse(filter.MatchesFilter("/A/B/C/MyTest3", new PropertyBag()));
}

[TestMethod]
public void ExactMatch_DoesNotMatchAdditionalSuffixUnlessWildcardIsUsed()
{
TreeNodeFilter filter = new("/*/*/*/(MyTest1|MyTest2)");

Assert.IsFalse(filter.MatchesFilter("/A/B/C/MyTest1()", new PropertyBag()));
Assert.IsFalse(filter.MatchesFilter("/A/B/C/MyTest2()", new PropertyBag()));
}

[TestMethod]
public void FullPathOrInsideParenthesizedExpressions_IsNotSupported_ThrowsActionableMessage()
{
InvalidOperationException exception = Assert.ThrowsExactly<InvalidOperationException>(
() => _ = new TreeNodeFilter("(/*/*/*/MyTest1)|(/*/*/*/MyTest2)"));

Assert.IsTrue(exception.Message.Contains("/A/B/C/(X|Y)", StringComparison.Ordinal));
}

[TestMethod]
public void AndExpression()
{
Expand Down