1 // Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
2 
3 using System.Collections.Generic;
4 using System.Linq;
5 using System.Web.Razor;
6 using System.Web.Razor.Generator;
7 using System.Web.Razor.Parser;
8 using System.Web.Razor.Parser.SyntaxTree;
9 using System.Web.Razor.Test.Framework;
10 using System.Web.Razor.Text;
11 using Xunit;
12 
13 namespace System.Web.Mvc.Razor.Test
14 {
15     public class MvcVBRazorCodeParserTest
16     {
17         [Fact]
Constructor_AddsModelKeyword()18         public void Constructor_AddsModelKeyword()
19         {
20             var parser = new MvcVBRazorCodeParser();
21 
22             Assert.True(parser.IsDirectiveDefined(MvcVBRazorCodeParser.ModelTypeKeyword));
23         }
24 
25         [Fact]
ParseModelKeyword_HandlesSingleInstance()26         public void ParseModelKeyword_HandlesSingleInstance()
27         {
28             // Arrange + Act
29             var document = "@ModelType    Foo";
30             var spans = ParseDocument(document);
31 
32             // Assert
33             var factory = SpanFactory.CreateVbHtml();
34             var expectedSpans = new Span[]
35             {
36                 factory.EmptyHtml(),
37                 factory.CodeTransition(SyntaxConstants.TransitionString)
38                     .Accepts(AcceptedCharacters.None),
39                 factory.MetaCode("ModelType    ")
40                     .Accepts(AcceptedCharacters.None),
41                 factory.Code("Foo")
42                     .As(new SetModelTypeCodeGenerator("Foo", "{0}(Of {1})"))
43             };
44             Assert.Equal(expectedSpans, spans.ToArray());
45         }
46 
47         [Fact]
ParseModelKeyword_HandlesNullableTypes()48         public void ParseModelKeyword_HandlesNullableTypes()
49         {
50             // Arrange + Act
51             var document = "@ModelType Foo?\r\nBar";
52             var spans = ParseDocument(document);
53 
54             // Assert
55             var factory = SpanFactory.CreateVbHtml();
56             var expectedSpans = new Span[]
57             {
58                 factory.EmptyHtml(),
59                 factory.CodeTransition(SyntaxConstants.TransitionString)
60                     .Accepts(AcceptedCharacters.None),
61                 factory.MetaCode("ModelType ")
62                     .Accepts(AcceptedCharacters.None),
63                 factory.Code("Foo?\r\n")
64                     .As(new SetModelTypeCodeGenerator("Foo?", "{0}(Of {1})")),
65                 factory.Markup("Bar")
66             };
67             Assert.Equal(expectedSpans, spans.ToArray());
68         }
69 
70         [Fact]
ParseModelKeyword_HandlesArrays()71         public void ParseModelKeyword_HandlesArrays()
72         {
73             // Arrange + Act
74             var document = "@ModelType Foo(())()\r\nBar";
75             var spans = ParseDocument(document);
76 
77             // Assert
78             var factory = SpanFactory.CreateVbHtml();
79             var expectedSpans = new Span[]
80             {
81                 factory.EmptyHtml(),
82                 factory.CodeTransition(SyntaxConstants.TransitionString)
83                     .Accepts(AcceptedCharacters.None),
84                 factory.MetaCode("ModelType ")
85                     .Accepts(AcceptedCharacters.None),
86                 factory.Code("Foo(())()\r\n")
87                     .As(new SetModelTypeCodeGenerator("Foo(())()", "{0}(Of {1})")),
88                 factory.Markup("Bar")
89             };
90             Assert.Equal(expectedSpans, spans.ToArray());
91         }
92 
93         [Fact]
ParseModelKeyword_HandlesVSTemplateSyntax()94         public void ParseModelKeyword_HandlesVSTemplateSyntax()
95         {
96             // Arrange + Act
97             var document = "@ModelType $rootnamespace$.MyModel";
98             var spans = ParseDocument(document);
99 
100             // Assert
101             var factory = SpanFactory.CreateVbHtml();
102             var expectedSpans = new Span[]
103             {
104                 factory.EmptyHtml(),
105                 factory.CodeTransition(SyntaxConstants.TransitionString)
106                     .Accepts(AcceptedCharacters.None),
107                 factory.MetaCode("ModelType ")
108                     .Accepts(AcceptedCharacters.None),
109                 factory.Code("$rootnamespace$.MyModel")
110                     .As(new SetModelTypeCodeGenerator("$rootnamespace$.MyModel", "{0}(Of {1})"))
111             };
112             Assert.Equal(expectedSpans, spans.ToArray());
113         }
114 
115         [Fact]
ParseModelKeyword_ErrorOnMissingModelType()116         public void ParseModelKeyword_ErrorOnMissingModelType()
117         {
118             // Arrange + Act
119             List<RazorError> errors = new List<RazorError>();
120             var document = "@ModelType   ";
121             var spans = ParseDocument(document, errors);
122 
123             // Assert
124             var factory = SpanFactory.CreateVbHtml();
125             var expectedSpans = new Span[]
126             {
127                 factory.EmptyHtml(),
128                 factory.CodeTransition(SyntaxConstants.TransitionString)
129                     .Accepts(AcceptedCharacters.None),
130                 factory.MetaCode("ModelType   ")
131                     .Accepts(AcceptedCharacters.None),
132                 factory.EmptyVB()
133                     .As(new SetModelTypeCodeGenerator(String.Empty, "{0}(Of {1})"))
134                     .Accepts(AcceptedCharacters.Any)
135             };
136             var expectedErrors = new[]
137             {
138                 new RazorError("The 'ModelType' keyword must be followed by a type name on the same line.", new SourceLocation(10, 0, 10), 1)
139             };
140             Assert.Equal(expectedSpans, spans.ToArray());
141             Assert.Equal(expectedErrors, errors.ToArray());
142         }
143 
144         [Fact]
ParseModelKeyword_DoesNotAcceptNewlineIfInDesignTimeMode()145         public void ParseModelKeyword_DoesNotAcceptNewlineIfInDesignTimeMode()
146         {
147             // Arrange + Act
148             List<RazorError> errors = new List<RazorError>();
149             var document = "@ModelType foo\r\n";
150             var spans = ParseDocument(document, errors, designTimeMode: true);
151 
152             // Assert
153             var factory = SpanFactory.CreateVbHtml();
154             var expectedSpans = new Span[]
155             {
156                 factory.EmptyHtml(),
157                 factory.CodeTransition(SyntaxConstants.TransitionString)
158                     .Accepts(AcceptedCharacters.None),
159                 factory.MetaCode("ModelType ")
160                     .Accepts(AcceptedCharacters.None),
161                 factory.Code("foo")
162                     .As(new SetModelTypeCodeGenerator("foo", "{0}(Of {1})"))
163                     .Accepts(AcceptedCharacters.Any),
164                 factory.Markup("\r\n")
165             };
166             Assert.Equal(expectedSpans, spans.ToArray());
167             Assert.Equal(0, errors.Count);
168         }
169 
170         [Fact]
ParseModelKeyword_ErrorOnMultipleModelStatements()171         public void ParseModelKeyword_ErrorOnMultipleModelStatements()
172         {
173             // Arrange + Act
174             List<RazorError> errors = new List<RazorError>();
175             var document =
176                 @"@ModelType Foo
177 @ModelType Bar";
178             var spans = ParseDocument(document, errors);
179 
180             // Assert
181             var factory = SpanFactory.CreateVbHtml();
182             var expectedSpans = new Span[]
183             {
184                 factory.EmptyHtml(),
185                 factory.CodeTransition(SyntaxConstants.TransitionString)
186                     .Accepts(AcceptedCharacters.None),
187                 factory.MetaCode("ModelType ")
188                     .Accepts(AcceptedCharacters.None),
189                 factory.Code("Foo\r\n")
190                     .As(new SetModelTypeCodeGenerator("Foo", "{0}(Of {1})")),
191                 factory.CodeTransition(SyntaxConstants.TransitionString)
192                     .Accepts(AcceptedCharacters.None),
193                 factory.MetaCode("ModelType ")
194                     .Accepts(AcceptedCharacters.None),
195                 factory.Code("Bar")
196                     .As(new SetModelTypeCodeGenerator("Bar", "{0}(Of {1})"))
197             };
198 
199             var expectedErrors = new[]
200             {
201                 new RazorError("Only one 'ModelType' statement is allowed in a file.", new SourceLocation(26, 1, 10), 1)
202             };
203             expectedSpans.Zip(spans, (exp, span) => new { expected = exp, span = span }).ToList().ForEach(i => Assert.Equal(i.expected, i.span));
204             Assert.Equal(expectedSpans, spans.ToArray());
205             Assert.Equal(expectedErrors, errors.ToArray());
206         }
207 
208         [Fact]
ParseModelKeyword_ErrorOnModelFollowedByInherits()209         public void ParseModelKeyword_ErrorOnModelFollowedByInherits()
210         {
211             // Arrange + Act
212             List<RazorError> errors = new List<RazorError>();
213             var document =
214                 @"@ModelType Foo
215 @Inherits Bar";
216             var spans = ParseDocument(document, errors);
217 
218             // Assert
219             var factory = SpanFactory.CreateVbHtml();
220             var expectedSpans = new Span[]
221             {
222                 factory.EmptyHtml(),
223                 factory.CodeTransition(SyntaxConstants.TransitionString)
224                     .Accepts(AcceptedCharacters.None),
225                 factory.MetaCode("ModelType ")
226                     .Accepts(AcceptedCharacters.None),
227                 factory.Code("Foo\r\n")
228                     .As(new SetModelTypeCodeGenerator("Foo", "{0}(Of {1})")),
229                 factory.CodeTransition(SyntaxConstants.TransitionString)
230                     .Accepts(AcceptedCharacters.None),
231                 factory.MetaCode("Inherits ")
232                     .Accepts(AcceptedCharacters.None),
233                 factory.Code("Bar")
234                     .As(new SetBaseTypeCodeGenerator("Bar"))
235             };
236 
237             var expectedErrors = new[]
238             {
239                 new RazorError("The 'inherits' keyword is not allowed when a 'ModelType' keyword is used.", new SourceLocation(25, 1, 9), 1)
240             };
241             expectedSpans.Zip(spans, (exp, span) => new { expected = exp, span = span }).ToList().ForEach(i => Assert.Equal(i.expected, i.span));
242             Assert.Equal(expectedSpans, spans.ToArray());
243             Assert.Equal(expectedErrors, errors.ToArray());
244         }
245 
246         [Fact]
ParseModelKeyword_ErrorOnInheritsFollowedByModel()247         public void ParseModelKeyword_ErrorOnInheritsFollowedByModel()
248         {
249             // Arrange + Act
250             List<RazorError> errors = new List<RazorError>();
251             var document =
252                 @"@Inherits Bar
253 @ModelType Foo";
254             var spans = ParseDocument(document, errors);
255 
256             // Assert
257             var factory = SpanFactory.CreateVbHtml();
258             var expectedSpans = new Span[]
259             {
260                 factory.EmptyHtml(),
261                 factory.CodeTransition(SyntaxConstants.TransitionString)
262                     .Accepts(AcceptedCharacters.None),
263                 factory.MetaCode("Inherits ")
264                     .Accepts(AcceptedCharacters.None),
265                 factory.Code("Bar\r\n")
266                     .AsBaseType("Bar"),
267                 factory.CodeTransition(SyntaxConstants.TransitionString)
268                     .Accepts(AcceptedCharacters.None),
269                 factory.MetaCode("ModelType ")
270                     .Accepts(AcceptedCharacters.None),
271                 factory.Code("Foo")
272                     .As(new SetModelTypeCodeGenerator("Foo", "{0}(Of {1})"))
273             };
274 
275             var expectedErrors = new[]
276             {
277                 new RazorError("The 'inherits' keyword is not allowed when a 'ModelType' keyword is used.", new SourceLocation(9, 0, 9), 1)
278             };
279             expectedSpans.Zip(spans, (exp, span) => new { expected = exp, span = span }).ToList().ForEach(i => Assert.Equal(i.expected, i.span));
280             Assert.Equal(expectedSpans, spans.ToArray());
281             Assert.Equal(expectedErrors, errors.ToArray());
282         }
283 
ParseDocument(string documentContents, List<RazorError> errors = null, bool designTimeMode = false)284         private static List<Span> ParseDocument(string documentContents, List<RazorError> errors = null, bool designTimeMode = false)
285         {
286             errors = errors ?? new List<RazorError>();
287             var markupParser = new HtmlMarkupParser();
288             var codeParser = new MvcVBRazorCodeParser();
289             var context = new ParserContext(new SeekableTextReader(documentContents), codeParser, markupParser, markupParser);
290             context.DesignTimeMode = designTimeMode;
291             codeParser.Context = context;
292             markupParser.Context = context;
293             markupParser.ParseDocument();
294 
295             ParserResults results = context.CompleteParse();
296             foreach (RazorError error in results.ParserErrors)
297             {
298                 errors.Add(error);
299             }
300             return results.Document.Flatten().ToList();
301         }
302     }
303 }
304