Skip to content

Commit 8ce26d0

Browse files
committed
ExpressionParser updated with case insensitive checks when IsCaseSensitive was set to true
1 parent c0e417c commit 8ce26d0

File tree

2 files changed

+71
-5
lines changed

2 files changed

+71
-5
lines changed

src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2139,7 +2139,7 @@ private bool TryParseEnumerable(Expression instance, Type enumerableType, string
21392139
// Create a new innerIt based on the elementType.
21402140
var innerIt = ParameterExpressionHelper.CreateParameterExpression(elementType, string.Empty, _parsingConfig.RenameEmptyParameterExpressionNames);
21412141

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

21962196
Type[] typeArgs;
2197-
if (new[] { "OfType", "Cast" }.Contains(methodName))
2197+
if (CanonicalContains(["OfType", "Cast"], ref methodName))
21982198
{
21992199
if (args.Length != 1)
22002200
{
@@ -2204,7 +2204,7 @@ private bool TryParseEnumerable(Expression instance, Type enumerableType, string
22042204
typeArgs = [ResolveTypeFromArgumentExpression(methodName, args[0])];
22052205
args = [];
22062206
}
2207-
else if (new[] { "Max", "Min", "Select", "OrderBy", "OrderByDescending", "ThenBy", "ThenByDescending", "GroupBy" }.Contains(methodName))
2207+
else if (CanonicalContains(["Max", "Min", "Select", "OrderBy", "OrderByDescending", "ThenBy", "ThenByDescending", "GroupBy"], ref methodName))
22082208
{
22092209
if (args.Length == 2)
22102210
{
@@ -2219,7 +2219,7 @@ private bool TryParseEnumerable(Expression instance, Type enumerableType, string
22192219
typeArgs = [elementType];
22202220
}
22212221
}
2222-
else if (methodName == "SelectMany")
2222+
else if (CanonicalContains(["SelectMany"], ref methodName))
22232223
{
22242224
var bodyType = Expression.Lambda(args[0], innerIt).Body.Type;
22252225
var interfaces = bodyType.GetInterfaces().Union([bodyType]);
@@ -2238,7 +2238,7 @@ private bool TryParseEnumerable(Expression instance, Type enumerableType, string
22382238
}
22392239
else
22402240
{
2241-
if (new[] { "Concat", "Contains", "ContainsKey", "DefaultIfEmpty", "Except", "Intersect", "Skip", "Take", "Union", "SequenceEqual" }.Contains(methodName))
2241+
if (CanonicalContains(["Concat", "Contains", "ContainsKey", "DefaultIfEmpty", "Except", "Intersect", "Skip", "Take", "Union", "SequenceEqual"], ref methodName))
22422242
{
22432243
args = [instance, args[0]];
22442244
}
@@ -2259,6 +2259,25 @@ private bool TryParseEnumerable(Expression instance, Type enumerableType, string
22592259
return true;
22602260
}
22612261

2262+
private bool CanonicalContains(string[] haystack, ref string needle)
2263+
{
2264+
if (_parsingConfig.IsCaseSensitive)
2265+
{
2266+
return haystack.Contains(needle);
2267+
}
2268+
2269+
var element = needle;
2270+
var index = Array.FindIndex(haystack, item => item.Equals(element, StringComparison.OrdinalIgnoreCase));
2271+
2272+
if (index == -1)
2273+
{
2274+
return false;
2275+
}
2276+
2277+
needle = haystack[index];
2278+
return true;
2279+
}
2280+
22622281
private Type ResolveTypeFromArgumentExpression(string functionName, Expression argumentExpression, int? arguments = null)
22632282
{
22642283
var argument = arguments == null ? string.Empty : arguments == 1 ? "first " : "second ";

test/System.Linq.Dynamic.Core.Tests/Parser/ExpressionParserTests.cs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -471,4 +471,51 @@ public void Parse_InvalidExpressionShouldThrowArgumentException()
471471
// Assert
472472
act.Should().Throw<ArgumentException>().WithMessage("Method 'Compare' not found on type 'System.String' or 'System.Int32'");
473473
}
474+
475+
[Theory]
476+
[InlineData("List.MAX(x => x)", false, "List.Max(Param_0 => Param_0)")]
477+
[InlineData("List.min(x => x)", false, "List.Min(Param_0 => Param_0)")]
478+
[InlineData("List.Min(x => x)", false, "List.Min(Param_0 => Param_0)")]
479+
[InlineData("List.Min(x => x)", true, "List.Min(Param_0 => Param_0)")]
480+
[InlineData("List.WHERE(x => x.Ticks >= 100000).max()", false, "List.Where(Param_0 => (Param_0.Ticks >= 100000)).Max()")]
481+
public void Parse_LinqMethodsRespectCasing(string expression, bool caseSensitive, string result)
482+
{
483+
// Arrange
484+
var parameters = new[] { Expression.Parameter(typeof(DateTime[]), "List") };
485+
486+
var parser = new ExpressionParser(
487+
parameters,
488+
expression,
489+
[],
490+
new ParsingConfig
491+
{
492+
IsCaseSensitive = caseSensitive
493+
});
494+
495+
// Act
496+
var parsedExpression = parser.Parse(typeof(DateTime)).ToString();
497+
498+
// Assert
499+
parsedExpression.Should().Be(result);
500+
}
501+
502+
[Fact]
503+
public void Parse_InvalidCasingShouldThrowInvalidOperationException()
504+
{
505+
// Arrange & Act
506+
var parameters = new[] { Expression.Parameter(typeof(DateTime[]), "List") };
507+
508+
Action act = () => new ExpressionParser(
509+
parameters,
510+
"List.MAX(x => x)",
511+
[],
512+
new ParsingConfig
513+
{
514+
IsCaseSensitive = true
515+
})
516+
.Parse(typeof(DateTime));
517+
518+
// Assert
519+
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.*");
520+
}
474521
}

0 commit comments

Comments
 (0)