// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. using System.Web.Razor.Editor; 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.Html { public class HtmlBlockTest : CsHtmlMarkupParserTestBase { [Fact] public void ParseBlockMethodThrowsArgNullExceptionOnNullContext() { // Arrange HtmlMarkupParser parser = new HtmlMarkupParser(); // Act and Assert Assert.Throws<InvalidOperationException>(() => parser.ParseBlock(), RazorResources.Parser_Context_Not_Set); } [Fact] public void ParseBlockHandlesOpenAngleAtEof() { ParseDocumentTest(@"@{ <", new MarkupBlock( Factory.EmptyHtml(), new StatementBlock( Factory.CodeTransition(), Factory.MetaCode("{").Accepts(AcceptedCharacters.None), Factory.Code("\r\n").AsStatement(), new MarkupBlock( Factory.Markup("<")))), new RazorError( String.Format(RazorResources.ParseError_Expected_EndOfBlock_Before_EOF, RazorResources.BlockName_Code, "}", "{"), 1, 0, 1)); } [Fact] public void ParseBlockHandlesOpenAngleWithProperTagFollowingIt() { ParseDocumentTest(@"@{ < </html>", new MarkupBlock( Factory.EmptyHtml(), new StatementBlock( Factory.CodeTransition(), Factory.MetaCode("{").Accepts(AcceptedCharacters.None), Factory.Code("\r\n").AsStatement(), new MarkupBlock( Factory.Markup("<\r\n") ), new MarkupBlock( Factory.Markup(@"</html>").Accepts(AcceptedCharacters.None) ), Factory.EmptyCSharp().AsStatement() ) ), designTimeParser: true, expectedErrors: new[] { new RazorError(String.Format(RazorResources.ParseError_UnexpectedEndTag, "html"), 7, 2, 0), new RazorError(String.Format(RazorResources.ParseError_Expected_EndOfBlock_Before_EOF, "code", "}", "{"), 1, 0, 1) }); } [Fact] public void TagWithoutCloseAngleDoesNotTerminateBlock() { ParseBlockTest(@"< ", new MarkupBlock( Factory.Markup("< \r\n ")), designTimeParser: true, expectedErrors: new RazorError(String.Format(RazorResources.ParseError_UnfinishedTag, String.Empty), 0, 0, 0)); } [Fact] public void ParseBlockAllowsStartAndEndTagsToDifferInCase() { SingleSpanBlockTest("<li><p>Foo</P></lI>", BlockType.Markup, SpanKind.Markup, acceptedCharacters: AcceptedCharacters.None); } [Fact] public void ParseBlockReadsToEndOfLineIfFirstCharacterAfterTransitionIsColon() { ParseBlockTest(@"@:<li>Foo Bar Baz bork", new MarkupBlock( Factory.MarkupTransition(), Factory.MetaMarkup(":", HtmlSymbolType.Colon), Factory.Markup("<li>Foo Bar Baz\r\n") .With(new SingleLineMarkupEditHandler(CSharpLanguageCharacteristics.Instance.TokenizeString, AcceptedCharacters.None)) )); } [Fact] public void ParseBlockStopsParsingSingleLineBlockAtEOFIfNoEOLReached() { ParseBlockTest("@:foo bar", new MarkupBlock( Factory.MarkupTransition(), Factory.MetaMarkup(":", HtmlSymbolType.Colon), Factory.Markup(@"foo bar") .With(new SingleLineMarkupEditHandler(CSharpLanguageCharacteristics.Instance.TokenizeString)) )); } [Fact] public void ParseBlockStopsAtMatchingCloseTagToStartTag() { SingleSpanBlockTest("<a><b></b></a><c></c>", "<a><b></b></a>", BlockType.Markup, SpanKind.Markup, acceptedCharacters: AcceptedCharacters.None); } [Fact] public void ParseBlockParsesUntilMatchingEndTagIfFirstNonWhitespaceCharacterIsStartTag() { SingleSpanBlockTest("<baz><boz><biz></biz></boz></baz>", BlockType.Markup, SpanKind.Markup, acceptedCharacters: AcceptedCharacters.None); } [Fact] public void ParseBlockAllowsUnclosedTagsAsLongAsItCanRecoverToAnExpectedEndTag() { SingleSpanBlockTest("<foo><bar><baz></foo>", BlockType.Markup, SpanKind.Markup, acceptedCharacters: AcceptedCharacters.None); } [Fact] public void ParseBlockWithSelfClosingTagJustEmitsTag() { SingleSpanBlockTest("<foo />", BlockType.Markup, SpanKind.Markup, acceptedCharacters: AcceptedCharacters.None); } [Fact] public void ParseBlockCanHandleSelfClosingTagsWithinBlock() { SingleSpanBlockTest("<foo><bar /></foo>", BlockType.Markup, SpanKind.Markup, acceptedCharacters: AcceptedCharacters.None); } [Fact] public void ParseBlockSupportsTagsWithAttributes() { ParseBlockTest("<foo bar=\"baz\"><biz><boz zoop=zork/></biz></foo>", new MarkupBlock( Factory.Markup("<foo"), new MarkupBlock(new AttributeBlockCodeGenerator("bar", new LocationTagged<string>(" bar=\"", 4, 0, 4), new LocationTagged<string>("\"", 13, 0, 13)), Factory.Markup(" bar=\"").With(SpanCodeGenerator.Null), Factory.Markup("baz").With(new LiteralAttributeCodeGenerator(new LocationTagged<string>(String.Empty, 10, 0, 10), new LocationTagged<string>("baz", 10, 0, 10))), Factory.Markup("\"").With(SpanCodeGenerator.Null)), Factory.Markup("><biz><boz"), new MarkupBlock(new AttributeBlockCodeGenerator("zoop", new LocationTagged<string>(" zoop=", 24, 0, 24), new LocationTagged<string>(String.Empty, 34, 0, 34)), Factory.Markup(" zoop=").With(SpanCodeGenerator.Null), Factory.Markup("zork").With(new LiteralAttributeCodeGenerator(new LocationTagged<string>(String.Empty, 30, 0, 30), new LocationTagged<string>("zork", 30, 0, 30)))), Factory.Markup("/></biz></foo>").Accepts(AcceptedCharacters.None))); } [Fact] public void ParseBlockAllowsCloseAngleBracketInAttributeValueIfDoubleQuoted() { ParseBlockTest("<foo><bar baz=\">\" /></foo>", new MarkupBlock( Factory.Markup("<foo><bar"), new MarkupBlock(new AttributeBlockCodeGenerator("baz", new LocationTagged<string>(" baz=\"", 9, 0, 9), new LocationTagged<string>("\"", 16, 0, 16)), Factory.Markup(" baz=\"").With(SpanCodeGenerator.Null), Factory.Markup(">").With(new LiteralAttributeCodeGenerator(new LocationTagged<string>(String.Empty, 15, 0, 15), new LocationTagged<string>(">", 15, 0, 15))), Factory.Markup("\"").With(SpanCodeGenerator.Null)), Factory.Markup(" /></foo>").Accepts(AcceptedCharacters.None))); } [Fact] public void ParseBlockAllowsCloseAngleBracketInAttributeValueIfSingleQuoted() { ParseBlockTest("<foo><bar baz=\'>\' /></foo>", new MarkupBlock( Factory.Markup("<foo><bar"), new MarkupBlock(new AttributeBlockCodeGenerator("baz", new LocationTagged<string>(" baz='", 9, 0, 9), new LocationTagged<string>("'", 16, 0, 16)), Factory.Markup(" baz='").With(SpanCodeGenerator.Null), Factory.Markup(">").With(new LiteralAttributeCodeGenerator(new LocationTagged<string>(String.Empty, 15, 0, 15), new LocationTagged<string>(">", 15, 0, 15))), Factory.Markup("'").With(SpanCodeGenerator.Null)), Factory.Markup(" /></foo>").Accepts(AcceptedCharacters.None))); } [Fact] public void ParseBlockAllowsSlashInAttributeValueIfDoubleQuoted() { ParseBlockTest("<foo><bar baz=\"/\"></bar></foo>", new MarkupBlock( Factory.Markup("<foo><bar"), new MarkupBlock(new AttributeBlockCodeGenerator("baz", new LocationTagged<string>(" baz=\"", 9, 0, 9), new LocationTagged<string>("\"", 16, 0, 16)), Factory.Markup(" baz=\"").With(SpanCodeGenerator.Null), Factory.Markup("/").With(new LiteralAttributeCodeGenerator(new LocationTagged<string>(String.Empty, 15, 0, 15), new LocationTagged<string>("/", 15, 0, 15))), Factory.Markup("\"").With(SpanCodeGenerator.Null)), Factory.Markup("></bar></foo>").Accepts(AcceptedCharacters.None))); } [Fact] public void ParseBlockAllowsSlashInAttributeValueIfSingleQuoted() { ParseBlockTest("<foo><bar baz=\'/\'></bar></foo>", new MarkupBlock( Factory.Markup("<foo><bar"), new MarkupBlock(new AttributeBlockCodeGenerator("baz", new LocationTagged<string>(" baz='", 9, 0, 9), new LocationTagged<string>("'", 16, 0, 16)), Factory.Markup(" baz='").With(SpanCodeGenerator.Null), Factory.Markup("/").With(new LiteralAttributeCodeGenerator(new LocationTagged<string>(String.Empty, 15, 0, 15), new LocationTagged<string>("/", 15, 0, 15))), Factory.Markup("'").With(SpanCodeGenerator.Null)), Factory.Markup("></bar></foo>").Accepts(AcceptedCharacters.None))); } [Fact] public void ParseBlockTerminatesAtEOF() { SingleSpanBlockTest("<foo>", "<foo>", BlockType.Markup, SpanKind.Markup, new RazorError(String.Format(RazorResources.ParseError_MissingEndTag, "foo"), new SourceLocation(0, 0, 0))); } [Fact] public void ParseBlockSupportsCommentAsBlock() { SingleSpanBlockTest("<!-- foo -->", BlockType.Markup, SpanKind.Markup, acceptedCharacters: AcceptedCharacters.None); } [Fact] public void ParseBlockSupportsCommentWithinBlock() { SingleSpanBlockTest("<foo>bar<!-- zoop -->baz</foo>", BlockType.Markup, SpanKind.Markup, acceptedCharacters: AcceptedCharacters.None); } [Fact] public void ParseBlockProperlyBalancesCommentStartAndEndTags() { SingleSpanBlockTest("<!--<foo></bar>-->", BlockType.Markup, SpanKind.Markup, acceptedCharacters: AcceptedCharacters.None); } [Fact] public void ParseBlockTerminatesAtEOFWhenParsingComment() { SingleSpanBlockTest("<!--<foo>", "<!--<foo>", BlockType.Markup, SpanKind.Markup); } [Fact] public void ParseBlockOnlyTerminatesCommentOnFullEndSequence() { SingleSpanBlockTest("<!--<foo>--</bar>-->", BlockType.Markup, SpanKind.Markup, acceptedCharacters: AcceptedCharacters.None); } [Fact] public void ParseBlockTerminatesCommentAtFirstOccurrenceOfEndSequence() { SingleSpanBlockTest("<foo><!--<foo></bar-->--></foo>", BlockType.Markup, SpanKind.Markup, acceptedCharacters: AcceptedCharacters.None); } [Fact] public void ParseBlockTreatsMalformedTagsAsContent() { SingleSpanBlockTest( "<foo></!-- bar --></foo>", "<foo></!-- bar -->", BlockType.Markup, SpanKind.Markup, AcceptedCharacters.None, new RazorError(String.Format(RazorResources.ParseError_MissingEndTag, "foo"), 0, 0, 0)); } [Fact] public void ParseBlockParsesSGMLDeclarationAsEmptyTag() { SingleSpanBlockTest("<foo><!DOCTYPE foo bar baz></foo>", BlockType.Markup, SpanKind.Markup, acceptedCharacters: AcceptedCharacters.None); } [Fact] public void ParseBlockTerminatesSGMLDeclarationAtFirstCloseAngle() { SingleSpanBlockTest("<foo><!DOCTYPE foo bar> baz></foo>", BlockType.Markup, SpanKind.Markup, acceptedCharacters: AcceptedCharacters.None); } [Fact] public void ParseBlockParsesXMLProcessingInstructionAsEmptyTag() { SingleSpanBlockTest("<foo><?xml foo bar baz?></foo>", BlockType.Markup, SpanKind.Markup, acceptedCharacters: AcceptedCharacters.None); } [Fact] public void ParseBlockTerminatesXMLProcessingInstructionAtQuestionMarkCloseAnglePair() { SingleSpanBlockTest("<foo><?xml foo bar?> baz</foo>", BlockType.Markup, SpanKind.Markup, acceptedCharacters: AcceptedCharacters.None); } [Fact] public void ParseBlockDoesNotTerminateXMLProcessingInstructionAtCloseAngleUnlessPreceededByQuestionMark() { SingleSpanBlockTest("<foo><?xml foo bar> baz?></foo>", BlockType.Markup, SpanKind.Markup, acceptedCharacters: AcceptedCharacters.None); } [Fact] public void ParseBlockSupportsScriptTagsWithLessThanSignsInThem() { SingleSpanBlockTest(@"<script>if(foo<bar) { alert(""baz"");)</script>", BlockType.Markup, SpanKind.Markup, acceptedCharacters: AcceptedCharacters.None); } [Fact] public void ParseBlockSupportsScriptTagsWithSpacedLessThanSignsInThem() { SingleSpanBlockTest(@"<script>if(foo < bar) { alert(""baz"");)</script>", BlockType.Markup, SpanKind.Markup, acceptedCharacters: AcceptedCharacters.None); } [Fact] public void ParseBlockAcceptsEmptyTextTag() { ParseBlockTest("<text/>", new MarkupBlock( Factory.MarkupTransition("<text/>") )); } [Fact] public void ParseBlockAcceptsTextTagAsOuterTagButDoesNotRender() { ParseBlockTest("<text>Foo Bar <foo> Baz</text> zoop", new MarkupBlock( Factory.MarkupTransition("<text>"), Factory.Markup("Foo Bar <foo> Baz"), Factory.MarkupTransition("</text>"), Factory.Markup(" ").Accepts(AcceptedCharacters.None) )); } [Fact] public void ParseBlockRendersLiteralTextTagIfDoubled() { ParseBlockTest("<text><text>Foo Bar <foo> Baz</text></text> zoop", new MarkupBlock( Factory.MarkupTransition("<text>"), Factory.Markup("<text>Foo Bar <foo> Baz</text>"), Factory.MarkupTransition("</text>"), Factory.Markup(" ").Accepts(AcceptedCharacters.None) )); } [Fact] public void ParseBlockDoesNotConsiderPsuedoTagWithinMarkupBlock() { ParseBlockTest("<foo><text><bar></bar></foo>", new MarkupBlock( Factory.Markup("<foo><text><bar></bar></foo>").Accepts(AcceptedCharacters.None) )); } [Fact] public void ParseBlockStopsParsingMidEmptyTagIfEOFReached() { ParseBlockTest("<br/", new MarkupBlock( Factory.Markup("<br/") ), new RazorError(String.Format(RazorResources.ParseError_UnfinishedTag, "br"), SourceLocation.Zero)); } [Fact] public void ParseBlockCorrectlyHandlesSingleLineOfMarkupWithEmbeddedStatement() { ParseBlockTest("<div>Foo @if(true) {} Bar</div>", new MarkupBlock( Factory.Markup("<div>Foo "), new StatementBlock( Factory.CodeTransition(), Factory.Code("if(true) {}").AsStatement()), Factory.Markup(" Bar</div>").Accepts(AcceptedCharacters.None))); } [Fact] public void ParseBlockIgnoresTagsInContentsOfScriptTag() { ParseBlockTest(@"<script>foo<bar baz='@boz'></script>", new MarkupBlock( Factory.Markup("<script>foo<bar baz='"), new ExpressionBlock( Factory.CodeTransition(), Factory.Code("boz") .AsImplicitExpression(CSharpCodeParser.DefaultKeywords, acceptTrailingDot: false) .Accepts(AcceptedCharacters.NonWhiteSpace)), Factory.Markup("'></script>") .Accepts(AcceptedCharacters.None))); } } }