Skip to content

Commit

Permalink
Support embedded quotes in string literal (#65)
Browse files Browse the repository at this point in the history
  • Loading branch information
StefH committed Mar 30, 2017
1 parent 47db7aa commit 5d57461
Show file tree
Hide file tree
Showing 9 changed files with 224 additions and 23 deletions.
33 changes: 33 additions & 0 deletions src/System.Linq.Dynamic.Core/DynamicExpressionParser.cs
Expand Up @@ -9,6 +9,22 @@ namespace System.Linq.Dynamic.Core
/// </summary>
public static class DynamicExpressionParser
{
/// <summary>
/// Parses a expression into a LambdaExpression. (Also create a constructor for all the parameters.)
/// </summary>
/// <param name="itType">The main type from the dynamic class expression.</param>
/// <param name="resultType">Type of the result. If not specified, it will be generated dynamically.</param>
/// <param name="expression">The expression.</param>
/// <param name="values">An object array that contains zero or more objects which are used as replacement values.</param>
/// <returns>The generated <see cref="LambdaExpression"/></returns>
public static LambdaExpression ParseLambda([NotNull] Type itType, [CanBeNull] Type resultType, [NotNull] string expression, params object[] values)
{
Check.NotNull(itType, nameof(itType));
Check.NotEmpty(expression, nameof(expression));

return ParseLambda(true, itType, resultType, expression, values);
}

/// <summary>
/// Parses a expression into a LambdaExpression.
/// </summary>
Expand All @@ -26,6 +42,23 @@ public static LambdaExpression ParseLambda(bool createParameterCtor, [NotNull] T
return ParseLambda(createParameterCtor, new[] { Expression.Parameter(itType, "") }, resultType, expression, values);
}

/// <summary>
/// Parses a expression into a LambdaExpression. (Also create a constructor for all the parameters.)
/// </summary>
/// <param name="parameters">A array from ParameterExpressions.</param>
/// <param name="resultType">Type of the result. If not specified, it will be generated dynamically.</param>
/// <param name="expression">The expression.</param>
/// <param name="values">An object array that contains zero or more objects which are used as replacement values.</param>
/// <returns>The generated <see cref="LambdaExpression"/></returns>
public static LambdaExpression ParseLambda([NotNull] ParameterExpression[] parameters, [CanBeNull] Type resultType, [NotNull] string expression, params object[] values)
{
Check.NotEmpty(parameters, nameof(parameters));
Check.Condition(parameters, p => p.Count(x => x == null) == 0, nameof(parameters));
Check.NotEmpty(expression, nameof(expression));

return ParseLambda(true, parameters, resultType, expression, values);
}

/// <summary>
/// Parses a expression into a LambdaExpression.
/// </summary>
Expand Down
17 changes: 5 additions & 12 deletions src/System.Linq.Dynamic.Core/ExpressionParser.cs
Expand Up @@ -834,14 +834,7 @@ Expression ParseStringLiteral()
_textParser.ValidateToken(TokenId.StringLiteral);
char quote = _textParser.CurrentToken.Text[0];
string s = _textParser.CurrentToken.Text.Substring(1, _textParser.CurrentToken.Text.Length - 2);
int start = 0;
while (true)
{
int i = s.IndexOf(quote, start);
if (i < 0) break;
s = s.Remove(i, 1);
start = i + 1;
}

if (quote == '\'')
{
if (s.Length != 1)
Expand Down Expand Up @@ -1684,11 +1677,11 @@ int FindIndexer(Type type, Expression[] args, out MethodBase method)
{
foreach (Type t in SelfAndBaseTypes(type))
{
//#if !(NETFX_CORE || WINDOWS_APP || DOTNET5_1 || UAP10_0 || NETSTANDARD)
//#if !(NETFX_CORE || WINDOWS_APP || DOTNET5_1 || UAP10_0 || NETSTANDARD)
MemberInfo[] members = t.GetDefaultMembers();
//#else
// MemberInfo[] members = new MemberInfo[0];
//#endif
//#else
// MemberInfo[] members = new MemberInfo[0];
//#endif
if (members.Length != 0)
{
IEnumerable<MethodBase> methods = members
Expand Down
19 changes: 17 additions & 2 deletions src/System.Linq.Dynamic.Core/Tokenizer/TextParser.cs
Expand Up @@ -229,12 +229,27 @@ public void NextToken()
char quote = _ch;
do
{
NextChar();
while (_textPos < _textLen && _ch != quote) NextChar();
bool escaped;

do
{
escaped = false;
NextChar();

if (_ch == '\\')
{
escaped = true;
if (_textPos < _textLen) NextChar();
}
}
while (_textPos < _textLen && (_ch != quote || escaped));

if (_textPos == _textLen)
throw ParseError(_textPos, Res.UnterminatedStringLiteral);

NextChar();
} while (_ch == quote);

t = TokenId.StringLiteral;
break;

Expand Down
1 change: 1 addition & 0 deletions test/EntityFramework.DynamicLinq.Tests/project.json
Expand Up @@ -25,6 +25,7 @@
"../System.Linq.Dynamic.Core.Tests/EntitiesTests.*.cs",
"../System.Linq.Dynamic.Core.Tests/Entities/*.cs",
"../System.Linq.Dynamic.Core.Tests/TestHelpers/*.cs",
"../System.Linq.Dynamic.Core.Tests/Helpers/*.cs",
"../System.Linq.Dynamic.Core.Tests/Helpers/*/*.cs"
]
}
Expand Down
120 changes: 120 additions & 0 deletions test/System.Linq.Dynamic.Core.Tests/DynamicExpressionParserTests.cs
@@ -0,0 +1,120 @@
using System.Linq.Dynamic.Core.Exceptions;
using System.Linq.Expressions;
using Xunit;

namespace System.Linq.Dynamic.Core.Tests
{
public class DynamicExpressionParserTests
{
[Fact]
public void Parse_ParameterExpressionMethodCall_ReturnsIntExpression()
{
var expression = DynamicExpressionParser.ParseLambda(true,
new[] { Expression.Parameter(typeof(int), "x") },
typeof(int),
"x + 1");
Assert.Equal(typeof(int), expression.Body.Type);
}

[Fact]
public void Parse_TupleToStringMethodCall_ReturnsStringLambdaExpression()
{
var expression = DynamicExpressionParser.ParseLambda(
typeof(Tuple<int>),
typeof(string),
"it.ToString()");
Assert.Equal(typeof(string), expression.ReturnType);
}

[Fact]
public void Parse_StringLiteral_ReturnsBooleanLambdaExpression()
{
var expression = DynamicExpressionParser.ParseLambda(new[] { Expression.Parameter(typeof(string), "Property1") }, typeof(Boolean), "Property1 == \"test\"");
Assert.Equal(typeof(Boolean), expression.Body.Type);
}

[Fact]
public void Parse_StringLiteralEmpty_ReturnsBooleanLambdaExpression()
{
var expression = DynamicExpressionParser.ParseLambda(new[] { Expression.Parameter(typeof(string), "Property1") }, typeof(Boolean), "Property1 == \"\"");
Assert.Equal(typeof(Boolean), expression.Body.Type);
}

[Fact]
public void Parse_StringLiteralEmbeddedQuote_ReturnsBooleanLambdaExpression()
{
string expectedRightValue = "\"test \\\"string\"";
var expression = DynamicExpressionParser.ParseLambda(
new[] { Expression.Parameter(typeof(string), "Property1") },
typeof(Boolean),
string.Format("Property1 == {0}", expectedRightValue));

string rightValue = ((BinaryExpression)expression.Body).Right.ToString();
Assert.Equal(typeof(Boolean), expression.Body.Type);
Assert.Equal(expectedRightValue, rightValue);
}

[Fact]
public void Parse_StringLiteralStartEmbeddedQuote_ReturnsBooleanLambdaExpression()
{
string expectedRightValue = "\"\\\"test\"";
var expression = DynamicExpressionParser.ParseLambda(
new[] { Expression.Parameter(typeof(string), "Property1") },
typeof(Boolean),
string.Format("Property1 == {0}", expectedRightValue));

string rightValue = ((BinaryExpression)expression.Body).Right.ToString();
Assert.Equal(typeof(Boolean), expression.Body.Type);
Assert.Equal(expectedRightValue, rightValue);
}

[Fact]
public void Parse_StringLiteral_MissingClosingQuote()
{
string expectedRightValue = "\"test\\\"";

Assert.Throws<ParseException>(() => DynamicExpressionParser.ParseLambda(
new[] { Expression.Parameter(typeof(string), "Property1") },
typeof(Boolean),
string.Format("Property1 == {0}", expectedRightValue)));
}

[Fact]
public void Parse_StringLiteralEscapedBackslash_ReturnsBooleanLambdaExpression()
{
string expectedRightValue = "\"test\\string\"";
var expression = DynamicExpressionParser.ParseLambda(
new[] { Expression.Parameter(typeof(string), "Property1") },
typeof(Boolean),
string.Format("Property1 == {0}", expectedRightValue));

string rightValue = ((BinaryExpression)expression.Body).Right.ToString();
Assert.Equal(typeof(Boolean), expression.Body.Type);
Assert.Equal(expectedRightValue, rightValue);
}

//[Fact]
//public void ParseLambda_DelegateTypeMethodCall_ReturnsEventHandlerLambdaExpression()
//{
// var expression = DynamicExpressionParser.ParseLambda(true,
// typeof(EventHandler),
// null,
// new[] { Expression.Parameter(typeof(object), "sender"), Expression.Parameter(typeof(EventArgs), "e") },
// "sender.ToString()");

// Assert.Equal(typeof(void), expression.ReturnType);
// Assert.Equal(typeof(EventHandler), expression.Type);
//}

//[Fact] this should fail : not allowed
//public void ParseLambda_VoidMethodCall_ReturnsActionDelegate()
//{
// var expression = DynamicExpressionParser.ParseLambda(
// typeof(IO.FileStream),
// null,
// "it.Close()");
// Assert.Equal(typeof(void), expression.ReturnType);
// Assert.Equal(typeof(Action<IO.FileStream>), expression.Type);
//}
}
}
16 changes: 16 additions & 0 deletions test/System.Linq.Dynamic.Core.Tests/ExpressionTests.cs
Expand Up @@ -331,6 +331,22 @@ public void ExpressionTests_Enum()
Assert.Equal(TestEnum.Var5, result7.Single());
}

// [Fact]
public void ExpressionTests_Enum_IN()
{
GlobalConfig.CustomTypeProvider = new NetStandardCustomTypeProvider();

//Arrange
var model1 = new ModelWithEnum { TestEnum = TestEnum.Var1 };
var model2 = new ModelWithEnum { TestEnum = TestEnum.Var2 };
var model3 = new ModelWithEnum { TestEnum = TestEnum.Var3 };
var qry = new [] { model1, model2, model3 }.AsQueryable();

// Act
//var expected = qry.Where(x => x.TestEnum )
//var result = qry.Where("TestEnum IN (@0)", new[] { TestEnum.Var1, TestEnum.Var2 });
}

[Fact]
public void ExpressionTests_Enum_Nullable()
{
Expand Down
@@ -0,0 +1,10 @@

namespace System.Linq.Dynamic.Core.Tests.Helpers.Models
{
public class ModelWithEnum
{
public string Name { get; set; }

public TestEnum TestEnum { get; set; }
}
}
21 changes: 21 additions & 0 deletions test/System.Linq.Dynamic.Core.Tests/QueryableTests.Where.cs
Expand Up @@ -27,6 +27,27 @@ public void Where_Dynamic()
Assert.Equal(testList[1], userByFirstName.Single());
}

[Fact]
public void Where_Dynamic_StringQuoted()
{
//Arrange
var testList = User.GenerateSampleModels(2, allowNullableProfiles: true);
testList[0].UserName = @"This \""is\"" a test.";
var qry = testList.AsQueryable();

//Act
var result1a = qry.Where(@"UserName == ""This \""is\"" a test.""").ToArray();
var result1b = qry.Where("UserName == \"This \\\"is\\\" a test.\"").ToArray();
var result2 = qry.Where("UserName == @0", @"This \""is\"" a test.").ToArray();
var expected = qry.Where(x => x.UserName == @"This \""is\"" a test.").ToArray();

//Assert
Assert.Equal(1, expected.Length);
Assert.Equal(expected, result1a);
Assert.Equal(expected, result1b);
Assert.Equal(expected, result2);
}

[Fact]
public void Where_Dynamic_SelectNewObjects()
{
Expand Down
10 changes: 1 addition & 9 deletions test/System.Linq.Dynamic.Core.Tests/project.json
Expand Up @@ -7,18 +7,10 @@
"Newtonsoft.Json": "9.0.1",
"QueryInterceptor.Core": "1.0.5",
"System.Linq.Dynamic.Core": { "target": "project" },
"xunit": "2.2.0-beta2-build3300"
"xunit": "2.2.0"
},

"frameworks": {
"net461": {
"buildOptions": { "define": [ "EF" ] },
"dependencies": {
"EntityFramework": "6.1.3",
"EntityFramework.DynamicLinq": { "target": "project" },
"Microsoft.AspNet.Identity.EntityFramework": "2.2.1"
}
},
"netcoreapp1.0": {
"buildOptions": { "define": [ "NETSTANDARD", "EFCORE" ] },
"imports": [
Expand Down

0 comments on commit 5d57461

Please sign in to comment.