Add support for C# 6 exception filters.

- Added new handling of the C# try catch statement to allow exception filters after catch statements.
- Added tests to validate valid and invalid scenarios.

#402
This commit is contained in:
N. Taylor Mullen 2015-06-08 16:49:53 -07:00
parent 619cbc3716
commit a1df1702e5
5 changed files with 211 additions and 7 deletions

View File

@ -13,6 +13,9 @@ namespace Microsoft.AspNet.Razor.Parser
{ {
public partial class CSharpCodeParser public partial class CSharpCodeParser
{ {
private static readonly Func<CSharpSymbol, bool> IsValidStatementSpacingSymbol =
IsSpacingToken(includeNewLines: true, includeComments: true);
private void SetUpKeywords() private void SetUpKeywords()
{ {
MapKeywords(ConditionalBlock, CSharpKeyword.For, CSharpKeyword.Foreach, CSharpKeyword.While, CSharpKeyword.Switch, CSharpKeyword.Lock); MapKeywords(ConditionalBlock, CSharpKeyword.For, CSharpKeyword.Foreach, CSharpKeyword.While, CSharpKeyword.Switch, CSharpKeyword.Lock);
@ -247,19 +250,19 @@ namespace Microsoft.AspNet.Razor.Parser
private void AfterTryClause() private void AfterTryClause()
{ {
// Grab whitespace // Grab whitespace
IEnumerable<CSharpSymbol> ws = SkipToNextImportantToken(); var whitespace = SkipToNextImportantToken();
// Check for a catch or finally part // Check for a catch or finally part
if (At(CSharpKeyword.Catch)) if (At(CSharpKeyword.Catch))
{ {
Accept(ws); Accept(whitespace);
Assert(CSharpKeyword.Catch); Assert(CSharpKeyword.Catch);
ConditionalBlock(topLevel: false); FilterableCatchBlock();
AfterTryClause(); AfterTryClause();
} }
else if (At(CSharpKeyword.Finally)) else if (At(CSharpKeyword.Finally))
{ {
Accept(ws); Accept(whitespace);
Assert(CSharpKeyword.Finally); Assert(CSharpKeyword.Finally);
UnconditionalBlock(); UnconditionalBlock();
} }
@ -267,7 +270,7 @@ namespace Microsoft.AspNet.Razor.Parser
{ {
// Return whitespace and end the block // Return whitespace and end the block
PutCurrentBack(); PutCurrentBack();
PutBack(ws); PutBack(whitespace);
Span.EditHandler.AcceptedCharacters = AcceptedCharacters.Any; Span.EditHandler.AcceptedCharacters = AcceptedCharacters.Any;
} }
} }
@ -344,6 +347,41 @@ namespace Microsoft.AspNet.Razor.Parser
ExpectCodeBlock(block); ExpectCodeBlock(block);
} }
private void FilterableCatchBlock()
{
Assert(CSharpKeyword.Catch);
var block = new Block(CurrentSymbol);
// Accept "catch"
AcceptAndMoveNext();
AcceptWhile(IsValidStatementSpacingSymbol);
// Parse the catch condition if present. If not present, let the C# compiler complain.
if (AcceptCondition())
{
AcceptWhile(IsValidStatementSpacingSymbol);
if (At(CSharpKeyword.When))
{
// Accept "when".
AcceptAndMoveNext();
AcceptWhile(IsValidStatementSpacingSymbol);
// Parse the filter condition if present. If not present, let the C# compiler complain.
if (!AcceptCondition())
{
// Incomplete condition.
return;
}
AcceptWhile(IsValidStatementSpacingSymbol);
}
ExpectCodeBlock(block);
}
}
private void ConditionalBlock(bool topLevel) private void ConditionalBlock(bool topLevel)
{ {
Assert(CSharpSymbolType.Keyword); Assert(CSharpSymbolType.Keyword);

View File

@ -88,7 +88,8 @@ namespace Microsoft.AspNet.Razor.Tokenizer
{ "interface", CSharpKeyword.Interface }, { "interface", CSharpKeyword.Interface },
{ "break", CSharpKeyword.Break }, { "break", CSharpKeyword.Break },
{ "checked", CSharpKeyword.Checked }, { "checked", CSharpKeyword.Checked },
{ "namespace", CSharpKeyword.Namespace } { "namespace", CSharpKeyword.Namespace },
{ "when", CSharpKeyword.When }
}; };
public static CSharpKeyword? SymbolTypeForIdentifier(string id) public static CSharpKeyword? SymbolTypeForIdentifier(string id)

View File

@ -82,6 +82,7 @@ namespace Microsoft.AspNet.Razor.Tokenizer.Symbols
Interface, Interface,
Break, Break,
Checked, Checked,
Namespace Namespace,
When
} }
} }

View File

@ -137,6 +137,169 @@ namespace Microsoft.AspNet.Razor.Test.Parser.CSharp
)); ));
} }
public static TheoryData ExceptionFilterData
{
get
{
var factory = SpanFactory.CreateCsHtml();
// document, expectedStatement
return new TheoryData<string, StatementBlock>
{
{
"@try { someMethod(); } catch(Exception) when (true) { handleIO(); }",
new StatementBlock(
factory.CodeTransition(),
factory
.Code("try { someMethod(); } catch(Exception) when (true) { handleIO(); }")
.AsStatement())
},
{
"@try { A(); } catch(Exception) when (true) { B(); } finally { C(); }",
new StatementBlock(
factory.CodeTransition(),
factory
.Code("try { A(); } catch(Exception) when (true) { B(); } finally { C(); }")
.AsStatement()
.Accepts(AcceptedCharacters.None))
},
{
"@try { A(); } catch(Exception) when (true) { B(); } catch(IOException) when (false) { C(); }",
new StatementBlock(
factory.CodeTransition(),
factory
.Code("try { A(); } catch(Exception) when (true) { B(); } catch(IOException) " +
"when (false) { C(); }")
.AsStatement())
},
{
string.Format("@try{0}{{{0} A();{0}}}{0}catch(Exception) when (true)", Environment.NewLine) +
string.Format("{0}{{{0} B();{0}}}{0}catch(IOException) when (false)", Environment.NewLine) +
string.Format("{0}{{{0} C();{0}}}", Environment.NewLine),
new StatementBlock(
factory.CodeTransition(),
factory
.Code(
string.Format("try{0}{{{0} A();{0}}}{0}catch(Exception) ", Environment.NewLine) +
string.Format("when (true){0}{{{0} B();{0}}}{0}", Environment.NewLine) +
string.Format("catch(IOException) when (false){0}{{{0} ", Environment.NewLine) +
string.Format("C();{0}}}", Environment.NewLine))
.AsStatement())
},
// Wrapped in @{ block.
{
"@{try { someMethod(); } catch(Exception) when (true) { handleIO(); }}",
new StatementBlock(
factory.CodeTransition(),
factory.MetaCode("{").Accepts(AcceptedCharacters.None),
factory
.Code("try { someMethod(); } catch(Exception) when (true) { handleIO(); }")
.AsStatement()
.AutoCompleteWith(autoCompleteString: null),
factory.MetaCode("}").Accepts(AcceptedCharacters.None))
},
// Partial exception filter data
{
"@try { someMethod(); } catch(Exception) when",
new StatementBlock(
factory.CodeTransition(),
factory
.Code("try { someMethod(); } catch(Exception) when")
.AsStatement())
},
{
"@try { someMethod(); } when",
new StatementBlock(
factory.CodeTransition(),
factory
.Code("try { someMethod(); }")
.AsStatement())
},
{
"@try { someMethod(); } catch(Exception) when { anotherMethod(); }",
new StatementBlock(
factory.CodeTransition(),
factory
.Code("try { someMethod(); } catch(Exception) when { anotherMethod(); }")
.AsStatement())
},
{
"@try { someMethod(); } catch(Exception) when (true)",
new StatementBlock(
factory.CodeTransition(),
factory
.Code("try { someMethod(); } catch(Exception) when (true)")
.AsStatement())
},
};
}
}
[Theory]
[MemberData(nameof(ExceptionFilterData))]
public void ExceptionFilters(string document, StatementBlock expectedStatement)
{
// Act & Assert
ParseBlockTest(document, expectedStatement);
}
public static TheoryData ExceptionFilterErrorData
{
get
{
var factory = SpanFactory.CreateCsHtml();
var unbalancedParenErrorString = "An opening \"(\" is missing the corresponding closing \")\".";
var unbalancedBracketCatchErrorString = "The catch block is missing a closing \"}\" character. " +
"Make sure you have a matching \"}\" character for all the \"{\" characters within this block, " +
"and that none of the \"}\" characters are being interpreted as markup.";
// document, expectedStatement, expectedErrors
return new TheoryData<string, StatementBlock, RazorError[]>
{
{
"@try { someMethod(); } catch(Exception) when (",
new StatementBlock(
factory.CodeTransition(),
factory
.Code("try { someMethod(); } catch(Exception) when (")
.AsStatement()),
new[] { new RazorError(unbalancedParenErrorString, 45, 0, 45) }
},
{
"@try { someMethod(); } catch(Exception) when (someMethod(",
new StatementBlock(
factory.CodeTransition(),
factory
.Code("try { someMethod(); } catch(Exception) when (someMethod(")
.AsStatement()),
new[] { new RazorError(unbalancedParenErrorString, 45, 0, 45) }
},
{
"@try { someMethod(); } catch(Exception) when (true) {",
new StatementBlock(
factory.CodeTransition(),
factory
.Code("try { someMethod(); } catch(Exception) when (true) {")
.AsStatement()),
new[] { new RazorError(unbalancedBracketCatchErrorString, 23, 0, 23) }
},
};
}
}
[Theory]
[MemberData(nameof(ExceptionFilterErrorData))]
public void ExceptionFilterErrors(
string document,
StatementBlock expectedStatement,
RazorError[] expectedErrors)
{
// Act & Assert
ParseBlockTest(document, expectedStatement, expectedErrors);
}
[Fact] [Fact]
public void FinallyClause() public void FinallyClause()
{ {

View File

@ -160,6 +160,7 @@ namespace Microsoft.AspNet.Razor.Test.Tokenizer
TestKeyword("break", CSharpKeyword.Break); TestKeyword("break", CSharpKeyword.Break);
TestKeyword("checked", CSharpKeyword.Checked); TestKeyword("checked", CSharpKeyword.Checked);
TestKeyword("namespace", CSharpKeyword.Namespace); TestKeyword("namespace", CSharpKeyword.Namespace);
TestKeyword("when", CSharpKeyword.When);
} }
private void TestKeyword(string keyword, CSharpKeyword keywordType) private void TestKeyword(string keyword, CSharpKeyword keywordType)