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:
parent
619cbc3716
commit
a1df1702e5
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -82,6 +82,7 @@ namespace Microsoft.AspNet.Razor.Tokenizer.Symbols
|
|||
Interface,
|
||||
Break,
|
||||
Checked,
|
||||
Namespace
|
||||
Namespace,
|
||||
When
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Reference in New Issue