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:
parent
b25bf01158
commit
58c0a36200
|
|
@ -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())
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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"]
|
||||
Loading…
Reference in New Issue