// 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.Test.Framework; using System.Web.Razor.Text; using System.Web.Razor.Tokenizer.Symbols; using Xunit; namespace System.Web.Razor.Test.Parser.Html { public class HtmlToCodeSwitchTest : CsHtmlMarkupParserTestBase { [Fact] public void ParseBlockSwitchesWhenCharacterBeforeSwapIsNonAlphanumeric() { ParseBlockTest("<p>foo#@i</p>", new MarkupBlock( Factory.Markup("<p>foo#"), new ExpressionBlock( Factory.CodeTransition(), Factory.Code("i").AsImplicitExpression(CSharpCodeParser.DefaultKeywords).Accepts(AcceptedCharacters.NonWhiteSpace)), Factory.Markup("</p>").Accepts(AcceptedCharacters.None))); } [Fact] public void ParseBlockSwitchesToCodeWhenSwapCharacterEncounteredMidTag() { ParseBlockTest("<foo @bar />", new MarkupBlock( Factory.Markup("<foo "), new ExpressionBlock( Factory.CodeTransition(), Factory.Code("bar") .AsImplicitExpression(CSharpCodeParser.DefaultKeywords) .Accepts(AcceptedCharacters.NonWhiteSpace)), Factory.Markup(" />").Accepts(AcceptedCharacters.None))); } [Fact] public void ParseBlockSwitchesToCodeWhenSwapCharacterEncounteredInAttributeValue() { ParseBlockTest("<foo bar=\"@baz\" />", new MarkupBlock( Factory.Markup("<foo"), new MarkupBlock(new AttributeBlockCodeGenerator("bar", new LocationTagged<string>(" bar=\"", 4, 0, 4), new LocationTagged<string>("\"", 14, 0, 14)), Factory.Markup(" bar=\"").With(SpanCodeGenerator.Null), new MarkupBlock(new DynamicAttributeBlockCodeGenerator(new LocationTagged<string>(String.Empty, 10, 0, 10), 10, 0, 10), new ExpressionBlock( Factory.CodeTransition(), Factory.Code("baz") .AsImplicitExpression(CSharpCodeParser.DefaultKeywords) .Accepts(AcceptedCharacters.NonWhiteSpace))), Factory.Markup("\"").With(SpanCodeGenerator.Null)), Factory.Markup(" />").Accepts(AcceptedCharacters.None))); } [Fact] public void ParseBlockSwitchesToCodeWhenSwapCharacterEncounteredInTagContent() { ParseBlockTest("<foo>@bar<baz>@boz</baz></foo>", new MarkupBlock( Factory.Markup("<foo>"), new ExpressionBlock( Factory.CodeTransition(), Factory.Code("bar") .AsImplicitExpression(CSharpCodeParser.DefaultKeywords) .Accepts(AcceptedCharacters.NonWhiteSpace)), Factory.Markup("<baz>"), new ExpressionBlock( Factory.CodeTransition(), Factory.Code("boz") .AsImplicitExpression(CSharpCodeParser.DefaultKeywords) .Accepts(AcceptedCharacters.NonWhiteSpace)), Factory.Markup("</baz></foo>").Accepts(AcceptedCharacters.None))); } [Fact] public void ParseBlockParsesCodeWithinSingleLineMarkup() { ParseBlockTest(@"@:<li>Foo @Bar Baz bork", new MarkupBlock( Factory.MarkupTransition(), Factory.MetaMarkup(":", HtmlSymbolType.Colon), Factory.Markup("<li>Foo ").With(new SingleLineMarkupEditHandler(CSharpLanguageCharacteristics.Instance.TokenizeString)), new ExpressionBlock( Factory.CodeTransition(), Factory.Code("Bar") .AsImplicitExpression(CSharpCodeParser.DefaultKeywords) .Accepts(AcceptedCharacters.NonWhiteSpace)), Factory.Markup(" Baz\r\n") .With(new SingleLineMarkupEditHandler(CSharpLanguageCharacteristics.Instance.TokenizeString, AcceptedCharacters.None)))); } [Fact] public void ParseBlockSupportsCodeWithinComment() { ParseBlockTest("<foo><!-- @foo --></foo>", new MarkupBlock( Factory.Markup("<foo><!-- "), new ExpressionBlock( Factory.CodeTransition(), Factory.Code("foo") .AsImplicitExpression(CSharpCodeParser.DefaultKeywords) .Accepts(AcceptedCharacters.NonWhiteSpace)), Factory.Markup(" --></foo>").Accepts(AcceptedCharacters.None))); } [Fact] public void ParseBlockSupportsCodeWithinSGMLDeclaration() { ParseBlockTest("<foo><!DOCTYPE foo @bar baz></foo>", new MarkupBlock( Factory.Markup("<foo><!DOCTYPE foo "), new ExpressionBlock( Factory.CodeTransition(), Factory.Code("bar") .AsImplicitExpression(CSharpCodeParser.DefaultKeywords) .Accepts(AcceptedCharacters.NonWhiteSpace)), Factory.Markup(" baz></foo>").Accepts(AcceptedCharacters.None))); } [Fact] public void ParseBlockSupportsCodeWithinCDataDeclaration() { ParseBlockTest("<foo><![CDATA[ foo @bar baz]]></foo>", new MarkupBlock( Factory.Markup("<foo><![CDATA[ foo "), new ExpressionBlock( Factory.CodeTransition(), Factory.Code("bar") .AsImplicitExpression(CSharpCodeParser.DefaultKeywords) .Accepts(AcceptedCharacters.NonWhiteSpace)), Factory.Markup(" baz]]></foo>").Accepts(AcceptedCharacters.None))); } [Fact] public void ParseBlockSupportsCodeWithinXMLProcessingInstruction() { ParseBlockTest("<foo><?xml foo @bar baz?></foo>", new MarkupBlock( Factory.Markup("<foo><?xml foo "), new ExpressionBlock( Factory.CodeTransition(), Factory.Code("bar") .AsImplicitExpression(CSharpCodeParser.DefaultKeywords) .Accepts(AcceptedCharacters.NonWhiteSpace)), Factory.Markup(" baz?></foo>").Accepts(AcceptedCharacters.None))); } [Fact] public void ParseBlockDoesNotSwitchToCodeOnEmailAddressInText() { SingleSpanBlockTest("<foo>anurse@microsoft.com</foo>", BlockType.Markup, SpanKind.Markup, acceptedCharacters: AcceptedCharacters.None); } [Fact] public void ParseBlockDoesNotSwitchToCodeOnEmailAddressInAttribute() { ParseBlockTest("<a href=\"mailto:anurse@microsoft.com\">Email me</a>", new MarkupBlock( Factory.Markup("<a"), new MarkupBlock(new AttributeBlockCodeGenerator("href", new LocationTagged<string>(" href=\"", 2, 0, 2), new LocationTagged<string>("\"", 36, 0, 36)), Factory.Markup(" href=\"").With(SpanCodeGenerator.Null), Factory.Markup("mailto:anurse@microsoft.com") .With(new LiteralAttributeCodeGenerator(new LocationTagged<string>(String.Empty, 9, 0, 9), new LocationTagged<string>("mailto:anurse@microsoft.com", 9, 0, 9))), Factory.Markup("\"").With(SpanCodeGenerator.Null)), Factory.Markup(">Email me</a>").Accepts(AcceptedCharacters.None))); } [Fact] public void ParseBlockGivesWhitespacePreceedingAtToCodeIfThereIsNoMarkupOnThatLine() { ParseBlockTest(@" <ul> @foreach(var p in Products) { <li>Product: @p.Name</li> } </ul>", new MarkupBlock( Factory.Markup(" <ul>\r\n"), new StatementBlock( Factory.Code(" ").AsStatement(), Factory.CodeTransition(), Factory.Code("foreach(var p in Products) {\r\n").AsStatement(), new MarkupBlock( Factory.Markup(" <li>Product: "), new ExpressionBlock( Factory.CodeTransition(), Factory.Code("p.Name") .AsImplicitExpression(CSharpCodeParser.DefaultKeywords) .Accepts(AcceptedCharacters.NonWhiteSpace)), Factory.Markup("</li>\r\n").Accepts(AcceptedCharacters.None)), Factory.Code(" }\r\n").AsStatement().Accepts(AcceptedCharacters.None)), Factory.Markup(" </ul>").Accepts(AcceptedCharacters.None))); } [Fact] public void ParseDocumentGivesWhitespacePreceedingAtToCodeIfThereIsNoMarkupOnThatLine() { ParseDocumentTest(@" <ul> @foreach(var p in Products) { <li>Product: @p.Name</li> } </ul>", new MarkupBlock( Factory.Markup(" <ul>\r\n"), new StatementBlock( Factory.Code(" ").AsStatement(), Factory.CodeTransition(), Factory.Code("foreach(var p in Products) {\r\n").AsStatement(), new MarkupBlock( Factory.Markup(" <li>Product: "), new ExpressionBlock( Factory.CodeTransition(), Factory.Code("p.Name") .AsImplicitExpression(CSharpCodeParser.DefaultKeywords) .Accepts(AcceptedCharacters.NonWhiteSpace)), Factory.Markup("</li>\r\n").Accepts(AcceptedCharacters.None)), Factory.Code(" }\r\n").AsStatement().Accepts(AcceptedCharacters.None)), Factory.Markup(" </ul>"))); } [Fact] public void SectionContextGivesWhitespacePreceedingAtToCodeIfThereIsNoMarkupOnThatLine() { ParseDocumentTest(@"@section foo { <ul> @foreach(var p in Products) { <li>Product: @p.Name</li> } </ul> }", new MarkupBlock( Factory.EmptyHtml(), new SectionBlock(new SectionCodeGenerator("foo"), Factory.CodeTransition(), Factory.MetaCode("section foo {").AutoCompleteWith(null, atEndOfSpan: true), new MarkupBlock( Factory.Markup("\r\n <ul>\r\n"), new StatementBlock( Factory.Code(" ").AsStatement(), Factory.CodeTransition(), Factory.Code("foreach(var p in Products) {\r\n").AsStatement(), new MarkupBlock( Factory.Markup(" <li>Product: "), new ExpressionBlock( Factory.CodeTransition(), Factory.Code("p.Name") .AsImplicitExpression(CSharpCodeParser.DefaultKeywords) .Accepts(AcceptedCharacters.NonWhiteSpace)), Factory.Markup("</li>\r\n").Accepts(AcceptedCharacters.None)), Factory.Code(" }\r\n").AsStatement().Accepts(AcceptedCharacters.None)), Factory.Markup(" </ul>\r\n")), Factory.MetaCode("}").Accepts(AcceptedCharacters.None)), Factory.EmptyHtml())); } [Fact] public void CSharpCodeParserDoesNotAcceptLeadingOrTrailingWhitespaceInDesignMode() { ParseBlockTest(@" <ul> @foreach(var p in Products) { <li>Product: @p.Name</li> } </ul>", new MarkupBlock( Factory.Markup(" <ul>\r\n "), new StatementBlock( Factory.CodeTransition(), Factory.Code("foreach(var p in Products) {\r\n ").AsStatement(), new MarkupBlock( Factory.Markup("<li>Product: "), new ExpressionBlock( Factory.CodeTransition(), Factory.Code("p.Name").AsImplicitExpression(CSharpCodeParser.DefaultKeywords).Accepts(AcceptedCharacters.NonWhiteSpace)), Factory.Markup("</li>").Accepts(AcceptedCharacters.None)), Factory.Code("\r\n }").AsStatement().Accepts(AcceptedCharacters.None)), Factory.Markup("\r\n </ul>").Accepts(AcceptedCharacters.None)), designTimeParser: true); } // Tests for "@@" escape sequence: [Fact] public void ParseBlockTreatsTwoAtSignsAsEscapeSequence() { HtmlParserTestUtils.RunSingleAtEscapeTest(ParseBlockTest); } [Fact] public void ParseBlockTreatsPairsOfAtSignsAsEscapeSequence() { HtmlParserTestUtils.RunMultiAtEscapeTest(ParseBlockTest); } [Fact] public void ParseDocumentTreatsTwoAtSignsAsEscapeSequence() { HtmlParserTestUtils.RunSingleAtEscapeTest(ParseDocumentTest, lastSpanAcceptedCharacters: AcceptedCharacters.Any); } [Fact] public void ParseDocumentTreatsPairsOfAtSignsAsEscapeSequence() { HtmlParserTestUtils.RunMultiAtEscapeTest(ParseDocumentTest, lastSpanAcceptedCharacters: AcceptedCharacters.Any); } [Fact] public void SectionBodyTreatsTwoAtSignsAsEscapeSequence() { ParseDocumentTest("@section Foo { <foo>@@bar</foo> }", new MarkupBlock( Factory.EmptyHtml(), new SectionBlock(new SectionCodeGenerator("Foo"), Factory.CodeTransition(), Factory.MetaCode("section Foo {").AutoCompleteWith(null, atEndOfSpan: true), new MarkupBlock( Factory.Markup(" <foo>"), Factory.Markup("@").Hidden(), Factory.Markup("@bar</foo> ")), Factory.MetaCode("}").Accepts(AcceptedCharacters.None)), Factory.EmptyHtml())); } [Fact] public void SectionBodyTreatsPairsOfAtSignsAsEscapeSequence() { ParseDocumentTest("@section Foo { <foo>@@@@@bar</foo> }", new MarkupBlock( Factory.EmptyHtml(), new SectionBlock(new SectionCodeGenerator("Foo"), Factory.CodeTransition(), Factory.MetaCode("section Foo {").AutoCompleteWith(null, atEndOfSpan: true), new MarkupBlock( Factory.Markup(" <foo>"), Factory.Markup("@").Hidden(), Factory.Markup("@"), Factory.Markup("@").Hidden(), Factory.Markup("@"), new ExpressionBlock( Factory.CodeTransition(), Factory.Code("bar") .AsImplicitExpression(CSharpCodeParser.DefaultKeywords) .Accepts(AcceptedCharacters.NonWhiteSpace)), Factory.Markup("</foo> ")), Factory.MetaCode("}").Accepts(AcceptedCharacters.None)), Factory.EmptyHtml())); } } }