Add support for null conditional operators in implicit expressions.

- Added case in ImplicitExpression handling to understand question marks.
- text?. is special compared to text. because with text. we currently validate content after text. to determine if it's an expression or if it's a period. Now with ?. we always treat it as an expression because ?. is not a useful sentance of any kind.
- Added unit tests to validate new implicit expression handling
- Added runtime and design time code generation tests to validate null conditional operators.

#44
This commit is contained in:
N. Taylor Mullen 2015-05-18 15:29:13 -07:00
parent b25bf01158
commit 58c0a36200
6 changed files with 375 additions and 1 deletions

View File

@ -388,7 +388,32 @@ namespace Microsoft.AspNet.Razor.Parser
}
return MethodCallOrArrayIndex(acceptedCharacters);
}
if (CurrentSymbol.Type == CSharpSymbolType.Dot)
if (At(CSharpSymbolType.QuestionMark))
{
var next = Lookahead(count: 1);
if (next != null)
{
if (next.Type == CSharpSymbolType.Dot)
{
// Accept null conditional dot operator (?.).
AcceptAndMoveNext();
AcceptAndMoveNext();
// If the next piece after the ?. is a keyword or identifier then we want to continue.
return At(CSharpSymbolType.Identifier) || At(CSharpSymbolType.Keyword);
}
else if (next.Type == CSharpSymbolType.LeftBracket)
{
// We're at the ? for a null conditional bracket operator (?[).
AcceptAndMoveNext();
// Accept the [ and any content inside (it will attempt to balance).
return MethodCallOrArrayIndex(acceptedCharacters);
}
}
}
else if (At(CSharpSymbolType.Dot))
{
var dot = CurrentSymbol;
if (NextToken())

View File

@ -49,6 +49,7 @@ namespace Microsoft.AspNet.Razor.Test.Generator
}
[Theory]
[InlineData("NullConditionalExpressions")]
[InlineData("NestedCodeBlocks")]
[InlineData("CodeBlock")]
[InlineData("ExplicitExpression")]
@ -72,6 +73,31 @@ namespace Microsoft.AspNet.Razor.Test.Generator
RunTest(testType);
}
[Fact]
public void CSharpCodeGeneratorCorrectlyGeneratesMappingsForNullConditionalOperator()
{
RunTest("NullConditionalExpressions",
"NullConditionalExpressions.DesignTime",
designTimeMode: true,
tabTest: TabTest.NoTabs,
expectedDesignTimePragmas: new List<LineMapping>()
{
BuildLineMapping(2, 0, 564, 22, 2, 6),
BuildLineMapping(9, 1, 5, 656, 29, 6, 13),
BuildLineMapping(22, 1, 766, 34, 18, 6),
BuildLineMapping(29, 2, 5, 858, 41, 6, 22),
BuildLineMapping(51, 2, 986, 46, 27, 6),
BuildLineMapping(58, 3, 5, 1078, 53, 6, 26),
BuildLineMapping(84, 3, 1214, 58, 31, 6),
BuildLineMapping(91, 4, 5, 1306, 65, 6, 41),
BuildLineMapping(132, 4, 1472, 70, 46, 2),
BuildLineMapping(140, 7, 1, 1558, 76, 6, 13),
BuildLineMapping(156, 8, 1, 1656, 81, 6, 22),
BuildLineMapping(181, 9, 1, 1764, 86, 6, 26),
BuildLineMapping(210, 10, 1, 1876, 91, 6, 41)
});
}
[Fact]
public void CSharpCodeGeneratorCorrectlyGeneratesMappingsForAwait()
{

View File

@ -1,6 +1,7 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.AspNet.Razor.Parser;
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
using Microsoft.AspNet.Razor.Test.Framework;
@ -17,6 +18,96 @@ namespace Microsoft.AspNet.Razor.Test.Parser.CSharp
return new CSharpCodeParser();
}
public static TheoryData NullConditionalOperatorData_Bracket
{
get
{
var noErrors = new RazorError[0];
Func<int, RazorError[]> missingEndParenError = (index) =>
new RazorError[1]
{
new RazorError("An opening \"(\" is missing the corresponding closing \")\".", index, 0, index)
};
Func<int, RazorError[]> missingEndBracketError = (index) =>
new RazorError[1]
{
new RazorError("An opening \"[\" is missing the corresponding closing \"]\".", index, 0, index)
};
// implicitExpression, expectedImplicitExpression, acceptedCharacters, expectedErrors
return new TheoryData<string, string, AcceptedCharacters, RazorError[]>
{
{ "val??[", "val", AcceptedCharacters.NonWhiteSpace, noErrors },
{ "val??[0", "val", AcceptedCharacters.NonWhiteSpace, noErrors },
{ "val?[", "val?[", AcceptedCharacters.Any, missingEndBracketError(5) },
{ "val?(", "val", AcceptedCharacters.NonWhiteSpace, noErrors },
{ "val?[more", "val?[more", AcceptedCharacters.Any, missingEndBracketError(5) },
{ "val?[0]", "val?[0]", AcceptedCharacters.NonWhiteSpace, noErrors },
{ "val?[<p>", "val?[", AcceptedCharacters.Any, missingEndBracketError(5) },
{ "val?[more.<p>", "val?[more.", AcceptedCharacters.Any, missingEndBracketError(5) },
{ "val??[more<p>", "val", AcceptedCharacters.NonWhiteSpace, noErrors },
{ "val?[-1]?", "val?[-1]", AcceptedCharacters.NonWhiteSpace, noErrors },
{ "val?[abc]?[def", "val?[abc]?[def", AcceptedCharacters.Any, missingEndBracketError(11) },
{ "val?[abc]?[2]", "val?[abc]?[2]", AcceptedCharacters.NonWhiteSpace, noErrors },
{ "val?[abc]?.more?[def]", "val?[abc]?.more?[def]", AcceptedCharacters.NonWhiteSpace, noErrors },
{ "val?[abc]?.more?.abc", "val?[abc]?.more?.abc", AcceptedCharacters.NonWhiteSpace, noErrors },
{ "val?[null ?? true]", "val?[null ?? true]", AcceptedCharacters.NonWhiteSpace, noErrors },
{ "val?[abc?.gef?[-1]]", "val?[abc?.gef?[-1]]", AcceptedCharacters.NonWhiteSpace, noErrors },
};
}
}
[Theory]
[MemberData(nameof(NullConditionalOperatorData_Bracket))]
public void ParseBlockMethodParsesNullConditionalOperatorImplicitExpression_Bracket(
string implicitExpresison,
string expectedImplicitExpression,
AcceptedCharacters acceptedCharacters,
RazorError[] expectedErrors)
{
// Act & Assert
ImplicitExpressionTest(
implicitExpresison,
expectedImplicitExpression,
acceptedCharacters,
expectedErrors);
}
public static TheoryData NullConditionalOperatorData_Dot
{
get
{
// implicitExpression, expectedImplicitExpression
return new TheoryData<string, string>
{
{ "val?", "val" },
{ "val??", "val" },
{ "val??more", "val" },
{ "val?!", "val" },
{ "val?.", "val?." },
{ "val??.", "val" },
{ "val?.(abc)", "val?." },
{ "val?.<p>", "val?." },
{ "val?.more", "val?.more" },
{ "val?.more<p>", "val?.more" },
{ "val??.more<p>", "val" },
{ "val?.more(false)?.<p>", "val?.more(false)?." },
{ "val?.more(false)?.abc", "val?.more(false)?.abc" },
{ "val?.more(null ?? true)?.abc", "val?.more(null ?? true)?.abc" },
};
}
}
[Theory]
[MemberData(nameof(NullConditionalOperatorData_Dot))]
public void ParseBlockMethodParsesNullConditionalOperatorImplicitExpression_Dot(
string implicitExpresison,
string expectedImplicitExpression)
{
// Act & Assert
ImplicitExpressionTest(implicitExpresison, expectedImplicitExpression);
}
[Fact]
public void NestedImplicitExpression()
{

View File

@ -0,0 +1,99 @@
namespace TestOutput
{
using System;
using System.Threading.Tasks;
public class NullConditionalExpressions
{
private static object @__o;
private void @__RazorDesignTimeHelpers__()
{
#pragma warning disable 219
#pragma warning restore 219
}
#line hidden
public NullConditionalExpressions()
{
}
#pragma warning disable 1998
public override async Task ExecuteAsync()
{
#line 1 "NullConditionalExpressions.cshtml"
#line default
#line hidden
#line 2 "NullConditionalExpressions.cshtml"
__o = ViewBag?.Data;
#line default
#line hidden
#line 2 "NullConditionalExpressions.cshtml"
#line default
#line hidden
#line 3 "NullConditionalExpressions.cshtml"
__o = ViewBag.IntIndexer?[0];
#line default
#line hidden
#line 3 "NullConditionalExpressions.cshtml"
#line default
#line hidden
#line 4 "NullConditionalExpressions.cshtml"
__o = ViewBag.StrIndexer?["key"];
#line default
#line hidden
#line 4 "NullConditionalExpressions.cshtml"
#line default
#line hidden
#line 5 "NullConditionalExpressions.cshtml"
__o = ViewBag?.Method(Value?[23]?.More)?["key"];
#line default
#line hidden
#line 5 "NullConditionalExpressions.cshtml"
#line default
#line hidden
#line 8 "NullConditionalExpressions.cshtml"
__o = ViewBag?.Data;
#line default
#line hidden
#line 9 "NullConditionalExpressions.cshtml"
__o = ViewBag.IntIndexer?[0];
#line default
#line hidden
#line 10 "NullConditionalExpressions.cshtml"
__o = ViewBag.StrIndexer?["key"];
#line default
#line hidden
#line 11 "NullConditionalExpressions.cshtml"
__o = ViewBag?.Method(Value?[23]?.More)?["key"];
#line default
#line hidden
}
#pragma warning restore 1998
}
}

View File

@ -0,0 +1,122 @@
#pragma checksum "NullConditionalExpressions.cshtml" "{ff1816ec-aa5e-4d10-87f7-6f4963833460}" "c8c4f34e0768aea12ef6ce8e3fe0e384ad023faf"
namespace TestOutput
{
using System;
using System.Threading.Tasks;
public class NullConditionalExpressions
{
#line hidden
public NullConditionalExpressions()
{
}
#pragma warning disable 1998
public override async Task ExecuteAsync()
{
#line 1 "NullConditionalExpressions.cshtml"
#line default
#line hidden
Instrumentation.BeginContext(9, 13, false);
#line 2 "NullConditionalExpressions.cshtml"
Write(ViewBag?.Data);
#line default
#line hidden
Instrumentation.EndContext();
#line 2 "NullConditionalExpressions.cshtml"
#line default
#line hidden
Instrumentation.BeginContext(29, 22, false);
#line 3 "NullConditionalExpressions.cshtml"
Write(ViewBag.IntIndexer?[0]);
#line default
#line hidden
Instrumentation.EndContext();
#line 3 "NullConditionalExpressions.cshtml"
#line default
#line hidden
Instrumentation.BeginContext(58, 26, false);
#line 4 "NullConditionalExpressions.cshtml"
Write(ViewBag.StrIndexer?["key"]);
#line default
#line hidden
Instrumentation.EndContext();
#line 4 "NullConditionalExpressions.cshtml"
#line default
#line hidden
Instrumentation.BeginContext(91, 41, false);
#line 5 "NullConditionalExpressions.cshtml"
Write(ViewBag?.Method(Value?[23]?.More)?["key"]);
#line default
#line hidden
Instrumentation.EndContext();
#line 5 "NullConditionalExpressions.cshtml"
#line default
#line hidden
Instrumentation.BeginContext(135, 4, true);
WriteLiteral("\r\n\r\n");
Instrumentation.EndContext();
Instrumentation.BeginContext(140, 13, false);
#line 8 "NullConditionalExpressions.cshtml"
Write(ViewBag?.Data);
#line default
#line hidden
Instrumentation.EndContext();
Instrumentation.BeginContext(153, 2, true);
WriteLiteral("\r\n");
Instrumentation.EndContext();
Instrumentation.BeginContext(156, 22, false);
#line 9 "NullConditionalExpressions.cshtml"
Write(ViewBag.IntIndexer?[0]);
#line default
#line hidden
Instrumentation.EndContext();
Instrumentation.BeginContext(178, 2, true);
WriteLiteral("\r\n");
Instrumentation.EndContext();
Instrumentation.BeginContext(181, 26, false);
#line 10 "NullConditionalExpressions.cshtml"
Write(ViewBag.StrIndexer?["key"]);
#line default
#line hidden
Instrumentation.EndContext();
Instrumentation.BeginContext(207, 2, true);
WriteLiteral("\r\n");
Instrumentation.EndContext();
Instrumentation.BeginContext(210, 41, false);
#line 11 "NullConditionalExpressions.cshtml"
Write(ViewBag?.Method(Value?[23]?.More)?["key"]);
#line default
#line hidden
Instrumentation.EndContext();
}
#pragma warning restore 1998
}
}

View File

@ -0,0 +1,11 @@
@{
@ViewBag?.Data
@ViewBag.IntIndexer?[0]
@ViewBag.StrIndexer?["key"]
@ViewBag?.Method(Value?[23]?.More)?["key"]
}
@ViewBag?.Data
@ViewBag.IntIndexer?[0]
@ViewBag.StrIndexer?["key"]
@ViewBag?.Method(Value?[23]?.More)?["key"]