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