Files
acceptance-tests
data
debian
docs
external
Newtonsoft.Json
api-doc-tools
api-snapshot
aspnetwebstack
packages
src
test
Microsoft.TestCommon
Microsoft.Web.Helpers.Test
Microsoft.Web.Http.Data.Test
Microsoft.Web.Mvc.Test
Microsoft.Web.WebPages.OAuth.Test
SPA.Test
System.Json.Test.Integration
System.Json.Test.Unit
System.Net.Http.Formatting.Test.Integration
System.Net.Http.Formatting.Test.Unit
System.Web.Helpers.Test
System.Web.Http.Integration.Test
System.Web.Http.SelfHost.Test
System.Web.Http.Test
System.Web.Http.WebHost.Test
System.Web.Mvc.Test
System.Web.Razor.Test
Editor
Framework
Generator
Parser
CSharp
CSharpAutoCompleteTest.cs
CSharpBlockTest.cs
CSharpDirectivesTest.cs
CSharpErrorTest.cs
CSharpExplicitExpressionTest.cs
CSharpHelperTest.cs
CSharpImplicitExpressionTest.cs
CSharpLayoutDirectiveTest.cs
CSharpNestedStatementsTest.cs
CSharpRazorCommentsTest.cs
CSharpReservedWordsTest.cs
CSharpSectionTest.cs
CSharpSpecialBlockTest.cs
CSharpStatementTest.cs
CSharpTemplateTest.cs
CSharpToMarkupSwitchTest.cs
CSharpVerbatimBlockTest.cs
CSharpWhitespaceHandlingTest.cs
CsHtmlDocumentTest.cs
Html
Old
PartialParsing
VB
BlockTest.cs
CallbackParserListenerTest.cs
ParserContextTest.cs
ParserVisitorExtensionsTest.cs
RazorParserTest.cs
WhitespaceRewriterTest.cs
Properties
TestFiles
Text
Tokenizer
Utils
CSharpRazorCodeLanguageTest.cs
CodeCompileUnitExtensions.cs
RazorCodeLanguageTest.cs
RazorDirectiveAttributeTest.cs
RazorEngineHostTest.cs
RazorTemplateEngineTest.cs
StringTextBuffer.cs
System.Web.Razor.Test.csproj
VBRazorCodeLanguageTest.cs
packages.config
System.Web.WebPages.Administration.Test
System.Web.WebPages.Deployment.Test
System.Web.WebPages.Razor.Test
System.Web.WebPages.Test
WebMatrix.Data.Test
WebMatrix.WebData.Test
Settings.StyleCop
tools
.gitattributes
.gitignore
License.txt
README.md
Runtime.msbuild
Runtime.sln
Runtime.xunit
Settings.StyleCop
build.cmd
binary-reference-assemblies
bockbuild
boringssl
cecil
cecil-legacy
corefx
corert
helix-binaries
ikdasm
ikvm
illinker-test-assets
linker
llvm
nuget-buildtasks
nunit-lite
roslyn-binaries
rx
xunit-binaries
how-to-bump-roslyn-binaries.md
ikvm-native
libgc
llvm
m4
man
mcs
mk
mono
msvc
po
runtime
samples
scripts
support
tools
COPYING.LIB
LICENSE
Makefile.am
Makefile.in
NEWS
README.md
acinclude.m4
aclocal.m4
autogen.sh
code_of_conduct.md
compile
config.guess
config.h.in
config.rpath
config.sub
configure.REMOVED.git-id
configure.ac.REMOVED.git-id
depcomp
install-sh
ltmain.sh.REMOVED.git-id
missing
mkinstalldirs
mono-uninstalled.pc.in
test-driver
winconfig.h
Jo Shields a575963da9 Imported Upstream version 3.6.0
Former-commit-id: da6be194a6b1221998fc28233f2503bd61dd9d14
2014-08-13 10:39:27 +01:00

734 lines
33 KiB
C#

// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
using System.Web.Razor.Generator;
using System.Web.Razor.Parser;
using System.Web.Razor.Parser.SyntaxTree;
using System.Web.Razor.Resources;
using System.Web.Razor.Test.Framework;
using System.Web.Razor.Text;
using System.Web.Razor.Tokenizer.Symbols;
using Xunit;
using Assert = Microsoft.TestCommon.AssertEx;
namespace System.Web.Razor.Test.Parser.CSharp
{
public class CSharpBlockTest : CsHtmlCodeParserTestBase
{
[Fact]
public void ParseBlockMethodThrowsArgNullExceptionOnNullContext()
{
// Arrange
CSharpCodeParser parser = new CSharpCodeParser();
// Act and Assert
Assert.Throws<InvalidOperationException>(() => parser.ParseBlock(), RazorResources.Parser_Context_Not_Set);
}
[Fact]
public void BalancingBracketsIgnoresStringLiteralCharactersAndBracketsInsideSingleLineComments()
{
SingleSpanBlockTest(@"if(foo) {
// bar } "" baz '
zoop();
}", BlockType.Statement, SpanKind.Code);
}
[Fact]
public void NestedCodeBlockWithAtCausesError()
{
ParseBlockTest("if (true) { @if(false) { } }",
new StatementBlock(
Factory.Code("if (true) { ").AsStatement(),
new StatementBlock(
Factory.CodeTransition(),
Factory.Code("if(false) { }").AsStatement()
),
Factory.Code(" }").AsStatement()),
new RazorError(
String.Format(RazorResources.ParseError_Unexpected_Keyword_After_At,
"if"),
new SourceLocation(13, 0, 13)));
}
[Fact]
public void BalancingBracketsIgnoresStringLiteralCharactersAndBracketsInsideBlockComments()
{
SingleSpanBlockTest(
@"if(foo) {
/* bar } "" */ ' baz } '
zoop();
}", BlockType.Statement, SpanKind.Code);
}
[Fact]
public void ParseBlockSkipsParenthesisedExpressionAndThenBalancesBracesIfFirstIdentifierIsForKeyword()
{
SingleSpanBlockTest("for(int i = 0; i < 10; new Foo { Bar = \"baz\" }) { Debug.WriteLine(@\"foo } bar\"); }", BlockType.Statement, SpanKind.Code, acceptedCharacters: AcceptedCharacters.None);
}
[Fact]
public void ParseBlockSkipsParenthesisedExpressionAndThenBalancesBracesIfFirstIdentifierIsForeachKeyword()
{
SingleSpanBlockTest("foreach(int i = 0; i < 10; new Foo { Bar = \"baz\" }) { Debug.WriteLine(@\"foo } bar\"); }", BlockType.Statement, SpanKind.Code, acceptedCharacters: AcceptedCharacters.None);
}
[Fact]
public void ParseBlockSkipsParenthesisedExpressionAndThenBalancesBracesIfFirstIdentifierIsWhileKeyword()
{
SingleSpanBlockTest("while(int i = 0; i < 10; new Foo { Bar = \"baz\" }) { Debug.WriteLine(@\"foo } bar\"); }", BlockType.Statement, SpanKind.Code, acceptedCharacters: AcceptedCharacters.None);
}
[Fact]
public void ParseBlockSkipsParenthesisedExpressionAndThenBalancesBracesIfFirstIdentifierIsUsingKeywordFollowedByParen()
{
SingleSpanBlockTest("using(int i = 0; i < 10; new Foo { Bar = \"baz\" }) { Debug.WriteLine(@\"foo } bar\"); }", BlockType.Statement, SpanKind.Code, acceptedCharacters: AcceptedCharacters.None);
}
[Fact]
public void ParseBlockSupportsUsingsNestedWithinOtherBlocks()
{
SingleSpanBlockTest("if(foo) { using(int i = 0; i < 10; new Foo { Bar = \"baz\" }) { Debug.WriteLine(@\"foo } bar\"); } }", BlockType.Statement, SpanKind.Code);
}
[Fact]
public void ParseBlockSkipsParenthesisedExpressionAndThenBalancesBracesIfFirstIdentifierIsIfKeywordWithNoElseBranches()
{
SingleSpanBlockTest("if(int i = 0; i < 10; new Foo { Bar = \"baz\" }) { Debug.WriteLine(@\"foo } bar\"); }", BlockType.Statement, SpanKind.Code);
}
[Fact]
public void ParseBlockAllowsEmptyBlockStatement()
{
SingleSpanBlockTest("if(false) { }", BlockType.Statement, SpanKind.Code);
}
[Fact]
public void ParseBlockTerminatesParenBalancingAtEOF()
{
ImplicitExpressionTest("Html.En(code()", "Html.En(code()",
AcceptedCharacters.Any,
new RazorError(
String.Format(RazorResources.ParseError_Expected_CloseBracket_Before_EOF,
"(", ")"),
new SourceLocation(8, 0, 8)));
}
[Fact]
public void ParseBlockSupportsBlockCommentBetweenIfAndElseClause()
{
SingleSpanBlockTest("if(foo) { bar(); } /* Foo */ /* Bar */ else { baz(); }", BlockType.Statement, SpanKind.Code, acceptedCharacters: AcceptedCharacters.None);
}
[Fact]
public void ParseBlockSupportsRazorCommentBetweenIfAndElseClause()
{
RunRazorCommentBetweenClausesTest("if(foo) { bar(); } ", " else { baz(); }", acceptedCharacters: AcceptedCharacters.None);
}
[Fact]
public void ParseBlockSupportsBlockCommentBetweenElseIfAndElseClause()
{
SingleSpanBlockTest("if(foo) { bar(); } else if(bar) { baz(); } /* Foo */ /* Bar */ else { biz(); }", BlockType.Statement, SpanKind.Code, acceptedCharacters: AcceptedCharacters.None);
}
[Fact]
public void ParseBlockSupportsRazorCommentBetweenElseIfAndElseClause()
{
RunRazorCommentBetweenClausesTest("if(foo) { bar(); } else if(bar) { baz(); } ", " else { baz(); }", acceptedCharacters: AcceptedCharacters.None);
}
[Fact]
public void ParseBlockSupportsBlockCommentBetweenIfAndElseIfClause()
{
SingleSpanBlockTest("if(foo) { bar(); } /* Foo */ /* Bar */ else if(bar) { baz(); }", BlockType.Statement, SpanKind.Code);
}
[Fact]
public void ParseBlockSupportsRazorCommentBetweenIfAndElseIfClause()
{
RunRazorCommentBetweenClausesTest("if(foo) { bar(); } ", " else if(bar) { baz(); }");
}
[Fact]
public void ParseBlockSupportsLineCommentBetweenIfAndElseClause()
{
SingleSpanBlockTest(@"if(foo) { bar(); }
// Foo
// Bar
else { baz(); }", BlockType.Statement, SpanKind.Code, acceptedCharacters: AcceptedCharacters.None);
}
[Fact]
public void ParseBlockSupportsLineCommentBetweenElseIfAndElseClause()
{
SingleSpanBlockTest(@"if(foo) { bar(); } else if(bar) { baz(); }
// Foo
// Bar
else { biz(); }", BlockType.Statement, SpanKind.Code, acceptedCharacters: AcceptedCharacters.None);
}
[Fact]
public void ParseBlockSupportsLineCommentBetweenIfAndElseIfClause()
{
SingleSpanBlockTest(@"if(foo) { bar(); }
// Foo
// Bar
else if(bar) { baz(); }", BlockType.Statement, SpanKind.Code);
}
[Fact]
public void ParseBlockParsesElseIfBranchesOfIfStatement()
{
const string ifStatement = @"if(int i = 0; i < 10; new Foo { Bar = ""baz"" }) {
Debug.WriteLine(@""foo } bar"");
}";
const string elseIfBranch = @" else if(int i = 0; i < 10; new Foo { Bar = ""baz"" }) {
Debug.WriteLine(@""bar } baz"");
}";
const string document = ifStatement + elseIfBranch;
SingleSpanBlockTest(document, BlockType.Statement, SpanKind.Code);
}
[Fact]
public void ParseBlockParsesMultipleElseIfBranchesOfIfStatement()
{
const string ifStatement = @"if(int i = 0; i < 10; new Foo { Bar = ""baz"" }) {
Debug.WriteLine(@""foo } bar"");
}";
const string elseIfBranch = @" else if(int i = 0; i < 10; new Foo { Bar = ""baz"" }) {
Debug.WriteLine(@""bar } baz"");
}";
const string document = ifStatement + elseIfBranch + elseIfBranch + elseIfBranch + elseIfBranch;
SingleSpanBlockTest(document, BlockType.Statement, SpanKind.Code);
}
[Fact]
public void ParseBlockParsesMultipleElseIfBranchesOfIfStatementFollowedByOneElseBranch()
{
const string ifStatement = @"if(int i = 0; i < 10; new Foo { Bar = ""baz"" }) {
Debug.WriteLine(@""foo } bar"");
}";
const string elseIfBranch = @" else if(int i = 0; i < 10; new Foo { Bar = ""baz"" }) {
Debug.WriteLine(@""bar } baz"");
}";
const string elseBranch = @" else { Debug.WriteLine(@""bar } baz""); }";
const string document = ifStatement + elseIfBranch + elseIfBranch + elseBranch;
SingleSpanBlockTest(document, BlockType.Statement, SpanKind.Code, acceptedCharacters: AcceptedCharacters.None);
}
[Fact]
public void ParseBlockStopsParsingCodeAfterElseBranch()
{
const string ifStatement = @"if(int i = 0; i < 10; new Foo { Bar = ""baz"" }) {
Debug.WriteLine(@""foo } bar"");
}";
const string elseIfBranch = @" else if(int i = 0; i < 10; new Foo { Bar = ""baz"" }) {
Debug.WriteLine(@""bar } baz"");
}";
const string elseBranch = @" else { Debug.WriteLine(@""bar } baz""); }";
const string document = ifStatement + elseIfBranch + elseBranch + elseIfBranch;
const string expected = ifStatement + elseIfBranch + elseBranch;
ParseBlockTest(document, new StatementBlock(Factory.Code(expected).AsStatement().Accepts(AcceptedCharacters.None)));
}
[Fact]
public void ParseBlockStopsParsingIfIfStatementNotFollowedByElse()
{
const string document = @"if(int i = 0; i < 10; new Foo { Bar = ""baz"" }) {
Debug.WriteLine(@""foo } bar"");
}";
SingleSpanBlockTest(document, BlockType.Statement, SpanKind.Code);
}
[Fact]
public void ParseBlockAcceptsElseIfWithNoCondition()
{
// We don't want to be a full C# parser - If the else if is missing it's condition, the C# compiler can handle that, we have all the info we need to keep parsing
const string ifBranch = @"if(int i = 0; i < 10; new Foo { Bar = ""baz"" }) {
Debug.WriteLine(@""foo } bar"");
}";
const string elseIfBranch = @" else if { foo(); }";
const string document = ifBranch + elseIfBranch;
SingleSpanBlockTest(document, BlockType.Statement, SpanKind.Code);
}
[Fact]
public void ParseBlockCorrectlyParsesDoWhileBlock()
{
SingleSpanBlockTest("do { var foo = bar; } while(foo != bar);", BlockType.Statement, SpanKind.Code, acceptedCharacters: AcceptedCharacters.None);
}
[Fact]
public void ParseBlockCorrectlyParsesDoWhileBlockMissingSemicolon()
{
SingleSpanBlockTest("do { var foo = bar; } while(foo != bar)", BlockType.Statement, SpanKind.Code);
}
[Fact]
public void ParseBlockCorrectlyParsesDoWhileBlockMissingWhileCondition()
{
SingleSpanBlockTest("do { var foo = bar; } while", BlockType.Statement, SpanKind.Code);
}
[Fact]
public void ParseBlockCorrectlyParsesDoWhileBlockMissingWhileConditionWithSemicolon()
{
SingleSpanBlockTest("do { var foo = bar; } while;", BlockType.Statement, SpanKind.Code, acceptedCharacters: AcceptedCharacters.None);
}
[Fact]
public void ParseBlockCorrectlyParsesDoWhileBlockMissingWhileClauseEntirely()
{
SingleSpanBlockTest("do { var foo = bar; } narf;", "do { var foo = bar; }", BlockType.Statement, SpanKind.Code);
}
[Fact]
public void ParseBlockSupportsBlockCommentBetweenDoAndWhileClause()
{
SingleSpanBlockTest("do { var foo = bar; } /* Foo */ /* Bar */ while(true);", BlockType.Statement, SpanKind.Code, acceptedCharacters: AcceptedCharacters.None);
}
[Fact]
public void ParseBlockSupportsLineCommentBetweenDoAndWhileClause()
{
SingleSpanBlockTest(@"do { var foo = bar; }
// Foo
// Bar
while(true);", BlockType.Statement, SpanKind.Code, acceptedCharacters: AcceptedCharacters.None);
}
[Fact]
public void ParseBlockSupportsRazorCommentBetweenDoAndWhileClause()
{
RunRazorCommentBetweenClausesTest("do { var foo = bar; } ", " while(true);", acceptedCharacters: AcceptedCharacters.None);
}
[Fact]
public void ParseBlockCorrectlyParsesMarkupInDoWhileBlock()
{
ParseBlockTest("@do { var foo = bar; <p>Foo</p> foo++; } while (foo<bar>);",
new StatementBlock(
Factory.CodeTransition(),
Factory.Code("do { var foo = bar;").AsStatement(),
new MarkupBlock(
Factory.Markup(" <p>Foo</p> ").Accepts(AcceptedCharacters.None)
),
Factory.Code("foo++; } while (foo<bar>);").AsStatement().Accepts(AcceptedCharacters.None)
));
}
[Fact]
public void ParseBlockSkipsParenthesisedExpressionAndThenBalancesBracesIfFirstIdentifierIsSwitchKeyword()
{
SingleSpanBlockTest(@"switch(foo) {
case 0:
break;
case 1:
{
break;
}
case 2:
return;
default:
return;
}", BlockType.Statement, SpanKind.Code, acceptedCharacters: AcceptedCharacters.None);
}
[Fact]
public void ParseBlockSkipsParenthesisedExpressionAndThenBalancesBracesIfFirstIdentifierIsLockKeyword()
{
SingleSpanBlockTest("lock(foo) { Debug.WriteLine(@\"foo } bar\"); }", BlockType.Statement, SpanKind.Code, acceptedCharacters: AcceptedCharacters.None);
}
[Fact]
public void ParseBlockHasErrorsIfNamespaceImportMissingSemicolon()
{
NamespaceImportTest("using Foo.Bar.Baz", " Foo.Bar.Baz", acceptedCharacters: AcceptedCharacters.NonWhiteSpace | AcceptedCharacters.WhiteSpace, location: new SourceLocation(17, 0, 17));
}
[Fact]
public void ParseBlockHasErrorsIfNamespaceAliasMissingSemicolon()
{
NamespaceImportTest("using Foo.Bar.Baz = FooBarBaz", " Foo.Bar.Baz = FooBarBaz", acceptedCharacters: AcceptedCharacters.NonWhiteSpace | AcceptedCharacters.WhiteSpace, location: new SourceLocation(29, 0, 29));
}
[Fact]
public void ParseBlockParsesNamespaceImportWithSemicolonForUsingKeywordIfIsInValidFormat()
{
NamespaceImportTest("using Foo.Bar.Baz;", " Foo.Bar.Baz", AcceptedCharacters.NonWhiteSpace | AcceptedCharacters.WhiteSpace);
}
[Fact]
public void ParseBlockDoesntCaptureWhitespaceAfterUsing()
{
ParseBlockTest("using Foo ",
new DirectiveBlock(
Factory.Code("using Foo")
.AsNamespaceImport(" Foo", CSharpCodeParser.UsingKeywordLength)
.Accepts(AcceptedCharacters.NonWhiteSpace | AcceptedCharacters.WhiteSpace)));
}
[Fact]
public void ParseBlockParsesNamespaceAliasWithSemicolonForUsingKeywordIfIsInValidFormat()
{
NamespaceImportTest("using FooBarBaz = FooBarBaz;", " FooBarBaz = FooBarBaz", AcceptedCharacters.NonWhiteSpace | AcceptedCharacters.WhiteSpace);
}
[Fact]
public void ParseBlockTerminatesUsingKeywordAtEOFAndOutputsFileCodeBlock()
{
SingleSpanBlockTest("using ", BlockType.Statement, SpanKind.Code);
}
[Fact]
public void ParseBlockTerminatesSingleLineCommentAtEndOfFile()
{
const string document = "foreach(var f in Foo) { // foo bar baz";
SingleSpanBlockTest(document, document, BlockType.Statement, SpanKind.Code,
new RazorError(String.Format(RazorResources.ParseError_Expected_EndOfBlock_Before_EOF, "foreach", '}', '{'), SourceLocation.Zero));
}
[Fact]
public void ParseBlockTerminatesBlockCommentAtEndOfFile()
{
const string document = "foreach(var f in Foo) { /* foo bar baz";
SingleSpanBlockTest(document, document, BlockType.Statement, SpanKind.Code,
new RazorError(String.Format(RazorResources.ParseError_BlockComment_Not_Terminated), 24, 0, 24),
new RazorError(String.Format(RazorResources.ParseError_Expected_EndOfBlock_Before_EOF, "foreach", '}', '{'), SourceLocation.Zero));
}
[Fact]
public void ParseBlockTerminatesSingleSlashAtEndOfFile()
{
const string document = "foreach(var f in Foo) { / foo bar baz";
SingleSpanBlockTest(document, document, BlockType.Statement, SpanKind.Code,
new RazorError(String.Format(RazorResources.ParseError_Expected_EndOfBlock_Before_EOF, "foreach", '}', '{'), SourceLocation.Zero));
}
[Fact]
public void ParseBlockSupportsBlockCommentBetweenTryAndFinallyClause()
{
SingleSpanBlockTest("try { bar(); } /* Foo */ /* Bar */ finally { baz(); }", BlockType.Statement, SpanKind.Code, acceptedCharacters: AcceptedCharacters.None);
}
[Fact]
public void ParseBlockSupportsRazorCommentBetweenTryAndFinallyClause()
{
RunRazorCommentBetweenClausesTest("try { bar(); } ", " finally { biz(); }", acceptedCharacters: AcceptedCharacters.None);
}
[Fact]
public void ParseBlockSupportsBlockCommentBetweenCatchAndFinallyClause()
{
SingleSpanBlockTest("try { bar(); } catch(bar) { baz(); } /* Foo */ /* Bar */ finally { biz(); }", BlockType.Statement, SpanKind.Code, acceptedCharacters: AcceptedCharacters.None);
}
[Fact]
public void ParseBlockSupportsRazorCommentBetweenCatchAndFinallyClause()
{
RunRazorCommentBetweenClausesTest("try { bar(); } catch(bar) { baz(); } ", " finally { biz(); }", acceptedCharacters: AcceptedCharacters.None);
}
[Fact]
public void ParseBlockSupportsBlockCommentBetweenTryAndCatchClause()
{
SingleSpanBlockTest("try { bar(); } /* Foo */ /* Bar */ catch(bar) { baz(); }", BlockType.Statement, SpanKind.Code);
}
[Fact]
public void ParseBlockSupportsRazorCommentBetweenTryAndCatchClause()
{
RunRazorCommentBetweenClausesTest("try { bar(); }", " catch(bar) { baz(); }");
}
[Fact]
public void ParseBlockSupportsLineCommentBetweenTryAndFinallyClause()
{
SingleSpanBlockTest(@"try { bar(); }
// Foo
// Bar
finally { baz(); }", BlockType.Statement, SpanKind.Code, acceptedCharacters: AcceptedCharacters.None);
}
[Fact]
public void ParseBlockSupportsLineCommentBetweenCatchAndFinallyClause()
{
SingleSpanBlockTest(@"try { bar(); } catch(bar) { baz(); }
// Foo
// Bar
finally { biz(); }", BlockType.Statement, SpanKind.Code, acceptedCharacters: AcceptedCharacters.None);
}
[Fact]
public void ParseBlockSupportsLineCommentBetweenTryAndCatchClause()
{
SingleSpanBlockTest(@"try { bar(); }
// Foo
// Bar
catch(bar) { baz(); }", BlockType.Statement, SpanKind.Code);
}
[Fact]
public void ParseBlockSupportsTryStatementWithNoAdditionalClauses()
{
SingleSpanBlockTest("try { var foo = new { } }", BlockType.Statement, SpanKind.Code);
}
[Fact]
public void ParseBlockSupportsMarkupWithinTryClause()
{
RunSimpleWrappedMarkupTest("try {", " <p>Foo</p> ", "}");
}
[Fact]
public void ParseBlockSupportsTryStatementWithOneCatchClause()
{
SingleSpanBlockTest("try { var foo = new { } } catch(Foo Bar Baz) { var foo = new { } }", BlockType.Statement, SpanKind.Code);
}
[Fact]
public void ParseBlockSupportsMarkupWithinCatchClause()
{
RunSimpleWrappedMarkupTest("try { var foo = new { } } catch(Foo Bar Baz) {", " <p>Foo</p> ", "}");
}
[Fact]
public void ParseBlockSupportsTryStatementWithMultipleCatchClause()
{
SingleSpanBlockTest("try { var foo = new { } } catch(Foo Bar Baz) { var foo = new { } } catch(Foo Bar Baz) { var foo = new { } } catch(Foo Bar Baz) { var foo = new { } }", BlockType.Statement, SpanKind.Code);
}
[Fact]
public void ParseBlockSupportsExceptionLessCatchClauses()
{
SingleSpanBlockTest("try { var foo = new { } } catch { var foo = new { } }", BlockType.Statement, SpanKind.Code);
}
[Fact]
public void ParseBlockSupportsMarkupWithinAdditionalCatchClauses()
{
RunSimpleWrappedMarkupTest("try { var foo = new { } } catch(Foo Bar Baz) { var foo = new { } } catch(Foo Bar Baz) { var foo = new { } } catch(Foo Bar Baz) {", " <p>Foo</p> ", "}");
}
[Fact]
public void ParseBlockSupportsTryStatementWithFinallyClause()
{
SingleSpanBlockTest("try { var foo = new { } } finally { var foo = new { } }", BlockType.Statement, SpanKind.Code, acceptedCharacters: AcceptedCharacters.None);
}
[Fact]
public void ParseBlockSupportsMarkupWithinFinallyClause()
{
RunSimpleWrappedMarkupTest("try { var foo = new { } } finally {", " <p>Foo</p> ", "}", acceptedCharacters: AcceptedCharacters.None);
}
[Fact]
public void ParseBlockStopsParsingCatchClausesAfterFinallyBlock()
{
string expectedContent = "try { var foo = new { } } finally { var foo = new { } }";
SingleSpanBlockTest(expectedContent + " catch(Foo Bar Baz) { }", expectedContent, BlockType.Statement, SpanKind.Code, acceptedCharacters: AcceptedCharacters.None);
}
[Fact]
public void ParseBlockDoesNotAllowMultipleFinallyBlocks()
{
string expectedContent = "try { var foo = new { } } finally { var foo = new { } }";
SingleSpanBlockTest(expectedContent + " finally { }", expectedContent, BlockType.Statement, SpanKind.Code, acceptedCharacters: AcceptedCharacters.None);
}
[Fact]
public void ParseBlockAcceptsTrailingDotIntoImplicitExpressionWhenEmbeddedInCode()
{
// Arrange
ParseBlockTest(@"if(foo) { @foo. }",
new StatementBlock(
Factory.Code("if(foo) { ").AsStatement(),
new ExpressionBlock(
Factory.CodeTransition(),
Factory.Code("foo.")
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords, acceptTrailingDot: true)
.Accepts(AcceptedCharacters.NonWhiteSpace)
),
Factory.Code(" }").AsStatement()
));
}
[Fact]
public void ParseBlockParsesExpressionOnSwitchCharacterFollowedByOpenParen()
{
// Arrange
ParseBlockTest(@"if(foo) { @(foo + bar) }",
new StatementBlock(
Factory.Code("if(foo) { ").AsStatement(),
new ExpressionBlock(
Factory.CodeTransition(),
Factory.MetaCode("(").Accepts(AcceptedCharacters.None),
Factory.Code("foo + bar").AsExpression(),
Factory.MetaCode(")").Accepts(AcceptedCharacters.None)
),
Factory.Code(" }").AsStatement()
));
}
[Fact]
public void ParseBlockParsesExpressionOnSwitchCharacterFollowedByIdentifierStart()
{
// Arrange
ParseBlockTest(@"if(foo) { @foo[4].bar() }",
new StatementBlock(
Factory.Code("if(foo) { ").AsStatement(),
new ExpressionBlock(
Factory.CodeTransition(),
Factory.Code("foo[4].bar()")
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords, acceptTrailingDot: true)
.Accepts(AcceptedCharacters.NonWhiteSpace)
),
Factory.Code(" }").AsStatement()
));
}
[Fact]
public void ParseBlockTreatsDoubleAtSignAsEscapeSequenceIfAtStatementStart()
{
// Arrange
ParseBlockTest(@"if(foo) { @@class.Foo() }",
new StatementBlock(
Factory.Code("if(foo) { ").AsStatement(),
Factory.Code("@").Hidden(),
Factory.Code("@class.Foo() }").AsStatement()
));
}
[Fact]
public void ParseBlockTreatsAtSignsAfterFirstPairAsPartOfCSharpStatement()
{
// Arrange
ParseBlockTest(@"if(foo) { @@@@class.Foo() }",
new StatementBlock(
Factory.Code("if(foo) { ").AsStatement(),
Factory.Code("@").Hidden(),
Factory.Code("@@@class.Foo() }").AsStatement()
));
}
[Fact]
public void ParseBlockDoesNotParseMarkupStatementOrExpressionOnSwitchCharacterNotFollowedByOpenAngleOrColon()
{
// Arrange
ParseBlockTest("if(foo) { @\"Foo\".ToString(); }",
new StatementBlock(
Factory.Code("if(foo) { @\"Foo\".ToString(); }").AsStatement()));
}
[Fact]
public void ParsersCanNestRecursively()
{
// Arrange
ParseBlockTest(@"foreach(var c in db.Categories) {
<div>
<h1>@c.Name</h1>
<ul>
@foreach(var p in c.Products) {
<li><a href=""@Html.ActionUrl(""Products"", ""Detail"", new { id = p.Id })"">@p.Name</a></li>
}
</ul>
</div>
}",
new StatementBlock(
Factory.Code("foreach(var c in db.Categories) {\r\n").AsStatement(),
new MarkupBlock(
Factory.Markup(" <div>\r\n <h1>"),
new ExpressionBlock(
Factory.CodeTransition(),
Factory.Code("c.Name")
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
.Accepts(AcceptedCharacters.NonWhiteSpace)),
Factory.Markup("</h1>\r\n <ul>\r\n"),
new StatementBlock(
Factory.Code(@" ").AsStatement(),
Factory.CodeTransition(),
Factory.Code("foreach(var p in c.Products) {\r\n").AsStatement(),
new MarkupBlock(
Factory.Markup(" <li><a"),
new MarkupBlock(new AttributeBlockCodeGenerator("href", new LocationTagged<string>(" href=\"", 193, 5, 30), new LocationTagged<string>("\"", 256, 5, 93)),
Factory.Markup(" href=\"").With(SpanCodeGenerator.Null),
new MarkupBlock(new DynamicAttributeBlockCodeGenerator(new LocationTagged<string>(String.Empty, 200, 5, 37), 200, 5, 37),
new ExpressionBlock(
Factory.CodeTransition(),
Factory.Code("Html.ActionUrl(\"Products\", \"Detail\", new { id = p.Id })")
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
.Accepts(AcceptedCharacters.NonWhiteSpace))),
Factory.Markup("\"").With(SpanCodeGenerator.Null)),
Factory.Markup(">"),
new ExpressionBlock(
Factory.CodeTransition(),
Factory.Code("p.Name")
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
.Accepts(AcceptedCharacters.NonWhiteSpace)),
Factory.Markup("</a></li>\r\n").Accepts(AcceptedCharacters.None)),
Factory.Code(" }\r\n").AsStatement().Accepts(AcceptedCharacters.None)),
Factory.Markup(" </ul>\r\n </div>\r\n")
.Accepts(AcceptedCharacters.None)),
Factory.Code(" }").AsStatement().Accepts(AcceptedCharacters.None)));
}
private void RunRazorCommentBetweenClausesTest(string preComment, string postComment, AcceptedCharacters acceptedCharacters = AcceptedCharacters.Any)
{
ParseBlockTest(preComment + "@* Foo *@ @* Bar *@" + postComment,
new StatementBlock(
Factory.Code(preComment).AsStatement(),
new CommentBlock(
Factory.CodeTransition(CSharpSymbolType.RazorCommentTransition),
Factory.MetaCode("*", CSharpSymbolType.RazorCommentStar).Accepts(AcceptedCharacters.None),
Factory.Comment(" Foo ", CSharpSymbolType.RazorComment),
Factory.MetaCode("*", CSharpSymbolType.RazorCommentStar).Accepts(AcceptedCharacters.None),
Factory.CodeTransition(CSharpSymbolType.RazorCommentTransition)
),
Factory.Code(" ").AsStatement(),
new CommentBlock(
Factory.CodeTransition(CSharpSymbolType.RazorCommentTransition),
Factory.MetaCode("*", CSharpSymbolType.RazorCommentStar).Accepts(AcceptedCharacters.None),
Factory.Comment(" Bar ", CSharpSymbolType.RazorComment),
Factory.MetaCode("*", CSharpSymbolType.RazorCommentStar).Accepts(AcceptedCharacters.None),
Factory.CodeTransition(CSharpSymbolType.RazorCommentTransition)
),
Factory.Code(postComment).AsStatement().Accepts(acceptedCharacters)));
}
private void RunSimpleWrappedMarkupTest(string prefix, string markup, string suffix, AcceptedCharacters acceptedCharacters = AcceptedCharacters.Any)
{
ParseBlockTest(prefix + markup + suffix,
new StatementBlock(
Factory.Code(prefix).AsStatement(),
new MarkupBlock(
Factory.Markup(markup).Accepts(AcceptedCharacters.None)
),
Factory.Code(suffix).AsStatement().Accepts(acceptedCharacters)
));
}
private void NamespaceImportTest(string content, string expectedNS, AcceptedCharacters acceptedCharacters = AcceptedCharacters.None, string errorMessage = null, SourceLocation? location = null)
{
var errors = new RazorError[0];
if (!String.IsNullOrEmpty(errorMessage) && location.HasValue)
{
errors = new RazorError[]
{
new RazorError(errorMessage, location.Value)
};
}
ParseBlockTest(content,
new DirectiveBlock(
Factory.Code(content)
.AsNamespaceImport(expectedNS, CSharpCodeParser.UsingKeywordLength)
.Accepts(acceptedCharacters)),
errors);
}
}
}