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
{
private static readonly Func<CSharpSymbol, bool> IsValidStatementSpacingSymbol =
IsSpacingToken(includeNewLines: true, includeComments: true);
private void SetUpKeywords()
{
MapKeywords(ConditionalBlock, CSharpKeyword.For, CSharpKeyword.Foreach, CSharpKeyword.While, CSharpKeyword.Switch, CSharpKeyword.Lock);
@ -247,19 +250,19 @@ namespace Microsoft.AspNet.Razor.Parser
private void AfterTryClause()
{
// Grab whitespace
IEnumerable<CSharpSymbol> ws = SkipToNextImportantToken();
var whitespace = SkipToNextImportantToken();
// Check for a catch or finally part
if (At(CSharpKeyword.Catch))
{
Accept(ws);
Accept(whitespace);
Assert(CSharpKeyword.Catch);
ConditionalBlock(topLevel: false);
FilterableCatchBlock();
AfterTryClause();
}
else if (At(CSharpKeyword.Finally))
{
Accept(ws);
Accept(whitespace);
Assert(CSharpKeyword.Finally);
UnconditionalBlock();
}
@ -267,7 +270,7 @@ namespace Microsoft.AspNet.Razor.Parser
{
// Return whitespace and end the block
PutCurrentBack();
PutBack(ws);
PutBack(whitespace);
Span.EditHandler.AcceptedCharacters = AcceptedCharacters.Any;
}
}
@ -344,6 +347,41 @@ namespace Microsoft.AspNet.Razor.Parser
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)
{
Assert(CSharpSymbolType.Keyword);

View File

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

View File

@ -82,6 +82,7 @@ namespace Microsoft.AspNet.Razor.Tokenizer.Symbols
Interface,
Break,
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]
public void FinallyClause()
{

View File

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