// 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(() => 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;

Foo

foo++; } while (foo);", new StatementBlock( Factory.CodeTransition(), Factory.Code("do { var foo = bar;").AsStatement(), new MarkupBlock( Factory.Markup("

Foo

").Accepts(AcceptedCharacters.None) ), Factory.Code("foo++; } while (foo);").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 {", "

Foo

", "}"); } [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) {", "

Foo

", "}"); } [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) {", "

Foo

", "}"); } [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 {", "

Foo

", "}", 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) {

@c.Name

    @foreach(var p in c.Products) {
  • @p.Name
  • }
}", new StatementBlock( Factory.Code("foreach(var c in db.Categories) {\r\n").AsStatement(), new MarkupBlock( Factory.Markup("
\r\n

"), new ExpressionBlock( Factory.CodeTransition(), Factory.Code("c.Name") .AsImplicitExpression(CSharpCodeParser.DefaultKeywords) .Accepts(AcceptedCharacters.NonWhiteSpace)), Factory.Markup("

\r\n
    \r\n"), new StatementBlock( Factory.Code(@" ").AsStatement(), Factory.CodeTransition(), Factory.Code("foreach(var p in c.Products) {\r\n").AsStatement(), new MarkupBlock( Factory.Markup("
  • (" href=\"", 193, 5, 30), new LocationTagged("\"", 256, 5, 93)), Factory.Markup(" href=\"").With(SpanCodeGenerator.Null), new MarkupBlock(new DynamicAttributeBlockCodeGenerator(new LocationTagged(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("
  • \r\n").Accepts(AcceptedCharacters.None)), Factory.Code(" }\r\n").AsStatement().Accepts(AcceptedCharacters.None)), Factory.Markup("
\r\n
\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); } } }