1 // Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. 2 3 using System.Web.Razor.Editor; 4 using System.Web.Razor.Generator; 5 using System.Web.Razor.Parser; 6 using System.Web.Razor.Parser.SyntaxTree; 7 using System.Web.Razor.Test.Framework; 8 using System.Web.Razor.Text; 9 using System.Web.Razor.Tokenizer.Symbols; 10 using Xunit; 11 12 namespace System.Web.Razor.Test.Parser.Html 13 { 14 public class HtmlToCodeSwitchTest : CsHtmlMarkupParserTestBase 15 { 16 [Fact] ParseBlockSwitchesWhenCharacterBeforeSwapIsNonAlphanumeric()17 public void ParseBlockSwitchesWhenCharacterBeforeSwapIsNonAlphanumeric() 18 { 19 ParseBlockTest("<p>foo#@i</p>", 20 new MarkupBlock( 21 Factory.Markup("<p>foo#"), 22 new ExpressionBlock( 23 Factory.CodeTransition(), 24 Factory.Code("i").AsImplicitExpression(CSharpCodeParser.DefaultKeywords).Accepts(AcceptedCharacters.NonWhiteSpace)), 25 Factory.Markup("</p>").Accepts(AcceptedCharacters.None))); 26 } 27 28 [Fact] ParseBlockSwitchesToCodeWhenSwapCharacterEncounteredMidTag()29 public void ParseBlockSwitchesToCodeWhenSwapCharacterEncounteredMidTag() 30 { 31 ParseBlockTest("<foo @bar />", 32 new MarkupBlock( 33 Factory.Markup("<foo "), 34 new ExpressionBlock( 35 Factory.CodeTransition(), 36 Factory.Code("bar") 37 .AsImplicitExpression(CSharpCodeParser.DefaultKeywords) 38 .Accepts(AcceptedCharacters.NonWhiteSpace)), 39 Factory.Markup(" />").Accepts(AcceptedCharacters.None))); 40 } 41 42 [Fact] ParseBlockSwitchesToCodeWhenSwapCharacterEncounteredInAttributeValue()43 public void ParseBlockSwitchesToCodeWhenSwapCharacterEncounteredInAttributeValue() 44 { 45 ParseBlockTest("<foo bar=\"@baz\" />", 46 new MarkupBlock( 47 Factory.Markup("<foo"), 48 new MarkupBlock(new AttributeBlockCodeGenerator("bar", new LocationTagged<string>(" bar=\"", 4, 0, 4), new LocationTagged<string>("\"", 14, 0, 14)), 49 Factory.Markup(" bar=\"").With(SpanCodeGenerator.Null), 50 new MarkupBlock(new DynamicAttributeBlockCodeGenerator(new LocationTagged<string>(String.Empty, 10, 0, 10), 10, 0, 10), 51 new ExpressionBlock( 52 Factory.CodeTransition(), 53 Factory.Code("baz") 54 .AsImplicitExpression(CSharpCodeParser.DefaultKeywords) 55 .Accepts(AcceptedCharacters.NonWhiteSpace))), 56 Factory.Markup("\"").With(SpanCodeGenerator.Null)), 57 Factory.Markup(" />").Accepts(AcceptedCharacters.None))); 58 } 59 60 [Fact] ParseBlockSwitchesToCodeWhenSwapCharacterEncounteredInTagContent()61 public void ParseBlockSwitchesToCodeWhenSwapCharacterEncounteredInTagContent() 62 { 63 ParseBlockTest("<foo>@bar<baz>@boz</baz></foo>", 64 new MarkupBlock( 65 Factory.Markup("<foo>"), 66 new ExpressionBlock( 67 Factory.CodeTransition(), 68 Factory.Code("bar") 69 .AsImplicitExpression(CSharpCodeParser.DefaultKeywords) 70 .Accepts(AcceptedCharacters.NonWhiteSpace)), 71 Factory.Markup("<baz>"), 72 new ExpressionBlock( 73 Factory.CodeTransition(), 74 Factory.Code("boz") 75 .AsImplicitExpression(CSharpCodeParser.DefaultKeywords) 76 .Accepts(AcceptedCharacters.NonWhiteSpace)), 77 Factory.Markup("</baz></foo>").Accepts(AcceptedCharacters.None))); 78 } 79 80 [Fact] ParseBlockParsesCodeWithinSingleLineMarkup()81 public void ParseBlockParsesCodeWithinSingleLineMarkup() 82 { 83 ParseBlockTest(@"@:<li>Foo @Bar Baz 84 bork", 85 new MarkupBlock( 86 Factory.MarkupTransition(), 87 Factory.MetaMarkup(":", HtmlSymbolType.Colon), 88 Factory.Markup("<li>Foo ").With(new SingleLineMarkupEditHandler(CSharpLanguageCharacteristics.Instance.TokenizeString)), 89 new ExpressionBlock( 90 Factory.CodeTransition(), 91 Factory.Code("Bar") 92 .AsImplicitExpression(CSharpCodeParser.DefaultKeywords) 93 .Accepts(AcceptedCharacters.NonWhiteSpace)), 94 Factory.Markup(" Baz\r\n") 95 .With(new SingleLineMarkupEditHandler(CSharpLanguageCharacteristics.Instance.TokenizeString, AcceptedCharacters.None)))); 96 } 97 98 [Fact] ParseBlockSupportsCodeWithinComment()99 public void ParseBlockSupportsCodeWithinComment() 100 { 101 ParseBlockTest("<foo><!-- @foo --></foo>", 102 new MarkupBlock( 103 Factory.Markup("<foo><!-- "), 104 new ExpressionBlock( 105 Factory.CodeTransition(), 106 Factory.Code("foo") 107 .AsImplicitExpression(CSharpCodeParser.DefaultKeywords) 108 .Accepts(AcceptedCharacters.NonWhiteSpace)), 109 Factory.Markup(" --></foo>").Accepts(AcceptedCharacters.None))); 110 } 111 112 [Fact] ParseBlockSupportsCodeWithinSGMLDeclaration()113 public void ParseBlockSupportsCodeWithinSGMLDeclaration() 114 { 115 ParseBlockTest("<foo><!DOCTYPE foo @bar baz></foo>", 116 new MarkupBlock( 117 Factory.Markup("<foo><!DOCTYPE foo "), 118 new ExpressionBlock( 119 Factory.CodeTransition(), 120 Factory.Code("bar") 121 .AsImplicitExpression(CSharpCodeParser.DefaultKeywords) 122 .Accepts(AcceptedCharacters.NonWhiteSpace)), 123 Factory.Markup(" baz></foo>").Accepts(AcceptedCharacters.None))); 124 } 125 126 [Fact] ParseBlockSupportsCodeWithinCDataDeclaration()127 public void ParseBlockSupportsCodeWithinCDataDeclaration() 128 { 129 ParseBlockTest("<foo><![CDATA[ foo @bar baz]]></foo>", 130 new MarkupBlock( 131 Factory.Markup("<foo><![CDATA[ foo "), 132 new ExpressionBlock( 133 Factory.CodeTransition(), 134 Factory.Code("bar") 135 .AsImplicitExpression(CSharpCodeParser.DefaultKeywords) 136 .Accepts(AcceptedCharacters.NonWhiteSpace)), 137 Factory.Markup(" baz]]></foo>").Accepts(AcceptedCharacters.None))); 138 } 139 140 [Fact] ParseBlockSupportsCodeWithinXMLProcessingInstruction()141 public void ParseBlockSupportsCodeWithinXMLProcessingInstruction() 142 { 143 ParseBlockTest("<foo><?xml foo @bar baz?></foo>", 144 new MarkupBlock( 145 Factory.Markup("<foo><?xml foo "), 146 new ExpressionBlock( 147 Factory.CodeTransition(), 148 Factory.Code("bar") 149 .AsImplicitExpression(CSharpCodeParser.DefaultKeywords) 150 .Accepts(AcceptedCharacters.NonWhiteSpace)), 151 Factory.Markup(" baz?></foo>").Accepts(AcceptedCharacters.None))); 152 } 153 154 [Fact] ParseBlockDoesNotSwitchToCodeOnEmailAddressInText()155 public void ParseBlockDoesNotSwitchToCodeOnEmailAddressInText() 156 { 157 SingleSpanBlockTest("<foo>anurse@microsoft.com</foo>", BlockType.Markup, SpanKind.Markup, acceptedCharacters: AcceptedCharacters.None); 158 } 159 160 [Fact] ParseBlockDoesNotSwitchToCodeOnEmailAddressInAttribute()161 public void ParseBlockDoesNotSwitchToCodeOnEmailAddressInAttribute() 162 { 163 ParseBlockTest("<a href=\"mailto:anurse@microsoft.com\">Email me</a>", 164 new MarkupBlock( 165 Factory.Markup("<a"), 166 new MarkupBlock(new AttributeBlockCodeGenerator("href", new LocationTagged<string>(" href=\"", 2, 0, 2), new LocationTagged<string>("\"", 36, 0, 36)), 167 Factory.Markup(" href=\"").With(SpanCodeGenerator.Null), 168 Factory.Markup("mailto:anurse@microsoft.com") 169 .With(new LiteralAttributeCodeGenerator(new LocationTagged<string>(String.Empty, 9, 0, 9), new LocationTagged<string>("mailto:anurse@microsoft.com", 9, 0, 9))), 170 Factory.Markup("\"").With(SpanCodeGenerator.Null)), 171 Factory.Markup(">Email me</a>").Accepts(AcceptedCharacters.None))); 172 } 173 174 [Fact] ParseBlockGivesWhitespacePreceedingAtToCodeIfThereIsNoMarkupOnThatLine()175 public void ParseBlockGivesWhitespacePreceedingAtToCodeIfThereIsNoMarkupOnThatLine() 176 { 177 ParseBlockTest(@" <ul> 178 @foreach(var p in Products) { 179 <li>Product: @p.Name</li> 180 } 181 </ul>", 182 new MarkupBlock( 183 Factory.Markup(" <ul>\r\n"), 184 new StatementBlock( 185 Factory.Code(" ").AsStatement(), 186 Factory.CodeTransition(), 187 Factory.Code("foreach(var p in Products) {\r\n").AsStatement(), 188 new MarkupBlock( 189 Factory.Markup(" <li>Product: "), 190 new ExpressionBlock( 191 Factory.CodeTransition(), 192 Factory.Code("p.Name") 193 .AsImplicitExpression(CSharpCodeParser.DefaultKeywords) 194 .Accepts(AcceptedCharacters.NonWhiteSpace)), 195 Factory.Markup("</li>\r\n").Accepts(AcceptedCharacters.None)), 196 Factory.Code(" }\r\n").AsStatement().Accepts(AcceptedCharacters.None)), 197 Factory.Markup(" </ul>").Accepts(AcceptedCharacters.None))); 198 } 199 200 [Fact] ParseDocumentGivesWhitespacePreceedingAtToCodeIfThereIsNoMarkupOnThatLine()201 public void ParseDocumentGivesWhitespacePreceedingAtToCodeIfThereIsNoMarkupOnThatLine() 202 { 203 ParseDocumentTest(@" <ul> 204 @foreach(var p in Products) { 205 <li>Product: @p.Name</li> 206 } 207 </ul>", 208 new MarkupBlock( 209 Factory.Markup(" <ul>\r\n"), 210 new StatementBlock( 211 Factory.Code(" ").AsStatement(), 212 Factory.CodeTransition(), 213 Factory.Code("foreach(var p in Products) {\r\n").AsStatement(), 214 new MarkupBlock( 215 Factory.Markup(" <li>Product: "), 216 new ExpressionBlock( 217 Factory.CodeTransition(), 218 Factory.Code("p.Name") 219 .AsImplicitExpression(CSharpCodeParser.DefaultKeywords) 220 .Accepts(AcceptedCharacters.NonWhiteSpace)), 221 Factory.Markup("</li>\r\n").Accepts(AcceptedCharacters.None)), 222 Factory.Code(" }\r\n").AsStatement().Accepts(AcceptedCharacters.None)), 223 Factory.Markup(" </ul>"))); 224 } 225 226 [Fact] SectionContextGivesWhitespacePreceedingAtToCodeIfThereIsNoMarkupOnThatLine()227 public void SectionContextGivesWhitespacePreceedingAtToCodeIfThereIsNoMarkupOnThatLine() 228 { 229 ParseDocumentTest(@"@section foo { 230 <ul> 231 @foreach(var p in Products) { 232 <li>Product: @p.Name</li> 233 } 234 </ul> 235 }", 236 new MarkupBlock( 237 Factory.EmptyHtml(), 238 new SectionBlock(new SectionCodeGenerator("foo"), 239 Factory.CodeTransition(), 240 Factory.MetaCode("section foo {").AutoCompleteWith(null, atEndOfSpan: true), 241 new MarkupBlock( 242 Factory.Markup("\r\n <ul>\r\n"), 243 new StatementBlock( 244 Factory.Code(" ").AsStatement(), 245 Factory.CodeTransition(), 246 Factory.Code("foreach(var p in Products) {\r\n").AsStatement(), 247 new MarkupBlock( 248 Factory.Markup(" <li>Product: "), 249 new ExpressionBlock( 250 Factory.CodeTransition(), 251 Factory.Code("p.Name") 252 .AsImplicitExpression(CSharpCodeParser.DefaultKeywords) 253 .Accepts(AcceptedCharacters.NonWhiteSpace)), 254 Factory.Markup("</li>\r\n").Accepts(AcceptedCharacters.None)), 255 Factory.Code(" }\r\n").AsStatement().Accepts(AcceptedCharacters.None)), 256 Factory.Markup(" </ul>\r\n")), 257 Factory.MetaCode("}").Accepts(AcceptedCharacters.None)), 258 Factory.EmptyHtml())); 259 } 260 261 [Fact] CSharpCodeParserDoesNotAcceptLeadingOrTrailingWhitespaceInDesignMode()262 public void CSharpCodeParserDoesNotAcceptLeadingOrTrailingWhitespaceInDesignMode() 263 { 264 ParseBlockTest(@" <ul> 265 @foreach(var p in Products) { 266 <li>Product: @p.Name</li> 267 } 268 </ul>", 269 new MarkupBlock( 270 Factory.Markup(" <ul>\r\n "), 271 new StatementBlock( 272 Factory.CodeTransition(), 273 Factory.Code("foreach(var p in Products) {\r\n ").AsStatement(), 274 new MarkupBlock( 275 Factory.Markup("<li>Product: "), 276 new ExpressionBlock( 277 Factory.CodeTransition(), 278 Factory.Code("p.Name").AsImplicitExpression(CSharpCodeParser.DefaultKeywords).Accepts(AcceptedCharacters.NonWhiteSpace)), 279 Factory.Markup("</li>").Accepts(AcceptedCharacters.None)), 280 Factory.Code("\r\n }").AsStatement().Accepts(AcceptedCharacters.None)), 281 Factory.Markup("\r\n </ul>").Accepts(AcceptedCharacters.None)), 282 designTimeParser: true); 283 } 284 285 // Tests for "@@" escape sequence: 286 [Fact] ParseBlockTreatsTwoAtSignsAsEscapeSequence()287 public void ParseBlockTreatsTwoAtSignsAsEscapeSequence() 288 { 289 HtmlParserTestUtils.RunSingleAtEscapeTest(ParseBlockTest); 290 } 291 292 [Fact] ParseBlockTreatsPairsOfAtSignsAsEscapeSequence()293 public void ParseBlockTreatsPairsOfAtSignsAsEscapeSequence() 294 { 295 HtmlParserTestUtils.RunMultiAtEscapeTest(ParseBlockTest); 296 } 297 298 [Fact] ParseDocumentTreatsTwoAtSignsAsEscapeSequence()299 public void ParseDocumentTreatsTwoAtSignsAsEscapeSequence() 300 { 301 HtmlParserTestUtils.RunSingleAtEscapeTest(ParseDocumentTest, lastSpanAcceptedCharacters: AcceptedCharacters.Any); 302 } 303 304 [Fact] ParseDocumentTreatsPairsOfAtSignsAsEscapeSequence()305 public void ParseDocumentTreatsPairsOfAtSignsAsEscapeSequence() 306 { 307 HtmlParserTestUtils.RunMultiAtEscapeTest(ParseDocumentTest, lastSpanAcceptedCharacters: AcceptedCharacters.Any); 308 } 309 310 [Fact] SectionBodyTreatsTwoAtSignsAsEscapeSequence()311 public void SectionBodyTreatsTwoAtSignsAsEscapeSequence() 312 { 313 ParseDocumentTest("@section Foo { <foo>@@bar</foo> }", 314 new MarkupBlock( 315 Factory.EmptyHtml(), 316 new SectionBlock(new SectionCodeGenerator("Foo"), 317 Factory.CodeTransition(), 318 Factory.MetaCode("section Foo {").AutoCompleteWith(null, atEndOfSpan: true), 319 new MarkupBlock( 320 Factory.Markup(" <foo>"), 321 Factory.Markup("@").Hidden(), 322 Factory.Markup("@bar</foo> ")), 323 Factory.MetaCode("}").Accepts(AcceptedCharacters.None)), 324 Factory.EmptyHtml())); 325 } 326 327 [Fact] SectionBodyTreatsPairsOfAtSignsAsEscapeSequence()328 public void SectionBodyTreatsPairsOfAtSignsAsEscapeSequence() 329 { 330 ParseDocumentTest("@section Foo { <foo>@@@@@bar</foo> }", 331 new MarkupBlock( 332 Factory.EmptyHtml(), 333 new SectionBlock(new SectionCodeGenerator("Foo"), 334 Factory.CodeTransition(), 335 Factory.MetaCode("section Foo {").AutoCompleteWith(null, atEndOfSpan: true), 336 new MarkupBlock( 337 Factory.Markup(" <foo>"), 338 Factory.Markup("@").Hidden(), 339 Factory.Markup("@"), 340 Factory.Markup("@").Hidden(), 341 Factory.Markup("@"), 342 new ExpressionBlock( 343 Factory.CodeTransition(), 344 Factory.Code("bar") 345 .AsImplicitExpression(CSharpCodeParser.DefaultKeywords) 346 .Accepts(AcceptedCharacters.NonWhiteSpace)), 347 Factory.Markup("</foo> ")), 348 Factory.MetaCode("}").Accepts(AcceptedCharacters.None)), 349 Factory.EmptyHtml())); 350 } 351 } 352 } 353