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
|
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);
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -82,6 +82,7 @@ namespace Microsoft.AspNet.Razor.Tokenizer.Symbols
|
||||||
Interface,
|
Interface,
|
||||||
Break,
|
Break,
|
||||||
Checked,
|
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]
|
[Fact]
|
||||||
public void FinallyClause()
|
public void FinallyClause()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue