Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
29 changes: 24 additions & 5 deletions src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2139,7 +2139,7 @@ private bool TryParseEnumerable(Expression instance, Type enumerableType, string
// Create a new innerIt based on the elementType.
var innerIt = ParameterExpressionHelper.CreateParameterExpression(elementType, string.Empty, _parsingConfig.RenameEmptyParameterExpressionNames);

if (new[] { "Contains", "ContainsKey", "Skip", "Take" }.Contains(methodName))
if (CanonicalContains(["Contains", "ContainsKey", "Skip", "Take"], ref methodName))
{
// For any method that acts on the parent element type, we need to specify the outerIt as scope.
_it = outerIt;
Expand Down Expand Up @@ -2194,7 +2194,7 @@ private bool TryParseEnumerable(Expression instance, Type enumerableType, string
}

Type[] typeArgs;
if (new[] { "OfType", "Cast" }.Contains(methodName))
if (CanonicalContains(["OfType", "Cast"], ref methodName))
{
if (args.Length != 1)
{
Expand All @@ -2204,7 +2204,7 @@ private bool TryParseEnumerable(Expression instance, Type enumerableType, string
typeArgs = [ResolveTypeFromArgumentExpression(methodName, args[0])];
args = [];
}
else if (new[] { "Max", "Min", "Select", "OrderBy", "OrderByDescending", "ThenBy", "ThenByDescending", "GroupBy" }.Contains(methodName))
else if (CanonicalContains(["Max", "Min", "Select", "OrderBy", "OrderByDescending", "ThenBy", "ThenByDescending", "GroupBy"], ref methodName))
{
if (args.Length == 2)
{
Expand All @@ -2219,7 +2219,7 @@ private bool TryParseEnumerable(Expression instance, Type enumerableType, string
typeArgs = [elementType];
}
}
else if (methodName == "SelectMany")
else if (CanonicalContains(["SelectMany"], ref methodName))
{
var bodyType = Expression.Lambda(args[0], innerIt).Body.Type;
var interfaces = bodyType.GetInterfaces().Union([bodyType]);
Expand All @@ -2238,7 +2238,7 @@ private bool TryParseEnumerable(Expression instance, Type enumerableType, string
}
else
{
if (new[] { "Concat", "Contains", "ContainsKey", "DefaultIfEmpty", "Except", "Intersect", "Skip", "Take", "Union", "SequenceEqual" }.Contains(methodName))
if (CanonicalContains(["Concat", "Contains", "ContainsKey", "DefaultIfEmpty", "Except", "Intersect", "Skip", "Take", "Union", "SequenceEqual"], ref methodName))
{
args = [instance, args[0]];
}
Expand All @@ -2259,6 +2259,25 @@ private bool TryParseEnumerable(Expression instance, Type enumerableType, string
return true;
}

private bool CanonicalContains(string[] haystack, ref string needle)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no need to use ref

just do:

if (_parsingConfig.IsCaseSensitive)
{
    return haystack.Contains(needle);
}

return haystack.Any(s => string.Equals(s, value, StringComparison.OrdinalIgnoreCase));

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've changed the method to just Contains. It indeed works without using ref, so I've removed that.
I've tried to add some unit tests with a max property. Is this what you had in mind?

{
if (_parsingConfig.IsCaseSensitive)
{
return haystack.Contains(needle);
}

var element = needle;
var index = Array.FindIndex(haystack, item => item.Equals(element, StringComparison.OrdinalIgnoreCase));

if (index == -1)
{
return false;
}

needle = haystack[index];
return true;
}

private Type ResolveTypeFromArgumentExpression(string functionName, Expression argumentExpression, int? arguments = null)
{
var argument = arguments == null ? string.Empty : arguments == 1 ? "first " : "second ";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -471,4 +471,51 @@ public void Parse_InvalidExpressionShouldThrowArgumentException()
// Assert
act.Should().Throw<ArgumentException>().WithMessage("Method 'Compare' not found on type 'System.String' or 'System.Int32'");
}

[Theory]
[InlineData("List.MAX(x => x)", false, "List.Max(Param_0 => Param_0)")]
[InlineData("List.min(x => x)", false, "List.Min(Param_0 => Param_0)")]
[InlineData("List.Min(x => x)", false, "List.Min(Param_0 => Param_0)")]
[InlineData("List.Min(x => x)", true, "List.Min(Param_0 => Param_0)")]
[InlineData("List.WHERE(x => x.Ticks >= 100000).max()", false, "List.Where(Param_0 => (Param_0.Ticks >= 100000)).Max()")]
public void Parse_LinqMethodsRespectCasing(string expression, bool caseSensitive, string result)
{
// Arrange
var parameters = new[] { Expression.Parameter(typeof(DateTime[]), "List") };

var parser = new ExpressionParser(
parameters,
expression,
[],
new ParsingConfig
{
IsCaseSensitive = caseSensitive
});

// Act
var parsedExpression = parser.Parse(typeof(DateTime)).ToString();

// Assert
parsedExpression.Should().Be(result);
}

[Fact]
public void Parse_InvalidCasingShouldThrowInvalidOperationException()
{
// Arrange & Act
var parameters = new[] { Expression.Parameter(typeof(DateTime[]), "List") };

Action act = () => new ExpressionParser(
parameters,
"List.MAX(x => x)",
[],
new ParsingConfig
{
IsCaseSensitive = true
})
.Parse(typeof(DateTime));

// Assert
act.Should().Throw<InvalidOperationException>().WithMessage("No generic method 'MAX' on type 'System.Linq.Enumerable' is compatible with the supplied type arguments and arguments. No type arguments should be provided if the method is non-generic.*");
}
}
Loading