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.Editor;
6 using System.Web.Razor.Generator;
7 using System.Web.Razor.Parser;
8 using System.Web.Razor.Parser.SyntaxTree;
9 using System.Web.Razor.Text;
10 using System.Web.Razor.Tokenizer;
11 using System.Web.Razor.Tokenizer.Symbols;
12 
13 namespace System.Web.Razor.Test.Framework
14 {
15     public static class SpanFactoryExtensions
16     {
EmptyCSharp(this SpanFactory self)17         public static UnclassifiedCodeSpanConstructor EmptyCSharp(this SpanFactory self)
18         {
19             return new UnclassifiedCodeSpanConstructor(
20                 self.Span(SpanKind.Code, new CSharpSymbol(self.LocationTracker.CurrentLocation, String.Empty, CSharpSymbolType.Unknown)));
21         }
22 
EmptyVB(this SpanFactory self)23         public static UnclassifiedCodeSpanConstructor EmptyVB(this SpanFactory self)
24         {
25             return new UnclassifiedCodeSpanConstructor(
26                 self.Span(SpanKind.Code, new VBSymbol(self.LocationTracker.CurrentLocation, String.Empty, VBSymbolType.Unknown)));
27         }
28 
EmptyHtml(this SpanFactory self)29         public static SpanConstructor EmptyHtml(this SpanFactory self)
30         {
31             return self.Span(SpanKind.Markup, new HtmlSymbol(self.LocationTracker.CurrentLocation, String.Empty, HtmlSymbolType.Unknown))
32                 .With(new MarkupCodeGenerator());
33         }
34 
Code(this SpanFactory self, string content)35         public static UnclassifiedCodeSpanConstructor Code(this SpanFactory self, string content)
36         {
37             return new UnclassifiedCodeSpanConstructor(
38                 self.Span(SpanKind.Code, content, markup: false));
39         }
40 
CodeTransition(this SpanFactory self)41         public static SpanConstructor CodeTransition(this SpanFactory self)
42         {
43             return self.Span(SpanKind.Transition, SyntaxConstants.TransitionString, markup: false).Accepts(AcceptedCharacters.None);
44         }
45 
CodeTransition(this SpanFactory self, string content)46         public static SpanConstructor CodeTransition(this SpanFactory self, string content)
47         {
48             return self.Span(SpanKind.Transition, content, markup: false).Accepts(AcceptedCharacters.None);
49         }
50 
CodeTransition(this SpanFactory self, CSharpSymbolType type)51         public static SpanConstructor CodeTransition(this SpanFactory self, CSharpSymbolType type)
52         {
53             return self.Span(SpanKind.Transition, SyntaxConstants.TransitionString, type).Accepts(AcceptedCharacters.None);
54         }
55 
CodeTransition(this SpanFactory self, string content, CSharpSymbolType type)56         public static SpanConstructor CodeTransition(this SpanFactory self, string content, CSharpSymbolType type)
57         {
58             return self.Span(SpanKind.Transition, content, type).Accepts(AcceptedCharacters.None);
59         }
60 
CodeTransition(this SpanFactory self, VBSymbolType type)61         public static SpanConstructor CodeTransition(this SpanFactory self, VBSymbolType type)
62         {
63             return self.Span(SpanKind.Transition, SyntaxConstants.TransitionString, type).Accepts(AcceptedCharacters.None);
64         }
65 
CodeTransition(this SpanFactory self, string content, VBSymbolType type)66         public static SpanConstructor CodeTransition(this SpanFactory self, string content, VBSymbolType type)
67         {
68             return self.Span(SpanKind.Transition, content, type).Accepts(AcceptedCharacters.None);
69         }
70 
MarkupTransition(this SpanFactory self)71         public static SpanConstructor MarkupTransition(this SpanFactory self)
72         {
73             return self.Span(SpanKind.Transition, SyntaxConstants.TransitionString, markup: true).Accepts(AcceptedCharacters.None);
74         }
75 
MarkupTransition(this SpanFactory self, string content)76         public static SpanConstructor MarkupTransition(this SpanFactory self, string content)
77         {
78             return self.Span(SpanKind.Transition, content, markup: true).Accepts(AcceptedCharacters.None);
79         }
80 
MarkupTransition(this SpanFactory self, HtmlSymbolType type)81         public static SpanConstructor MarkupTransition(this SpanFactory self, HtmlSymbolType type)
82         {
83             return self.Span(SpanKind.Transition, SyntaxConstants.TransitionString, type).Accepts(AcceptedCharacters.None);
84         }
85 
MarkupTransition(this SpanFactory self, string content, HtmlSymbolType type)86         public static SpanConstructor MarkupTransition(this SpanFactory self, string content, HtmlSymbolType type)
87         {
88             return self.Span(SpanKind.Transition, content, type).Accepts(AcceptedCharacters.None);
89         }
90 
MetaCode(this SpanFactory self, string content)91         public static SpanConstructor MetaCode(this SpanFactory self, string content)
92         {
93             return self.Span(SpanKind.MetaCode, content, markup: false);
94         }
95 
MetaCode(this SpanFactory self, string content, CSharpSymbolType type)96         public static SpanConstructor MetaCode(this SpanFactory self, string content, CSharpSymbolType type)
97         {
98             return self.Span(SpanKind.MetaCode, content, type);
99         }
100 
MetaCode(this SpanFactory self, string content, VBSymbolType type)101         public static SpanConstructor MetaCode(this SpanFactory self, string content, VBSymbolType type)
102         {
103             return self.Span(SpanKind.MetaCode, content, type);
104         }
105 
MetaMarkup(this SpanFactory self, string content)106         public static SpanConstructor MetaMarkup(this SpanFactory self, string content)
107         {
108             return self.Span(SpanKind.MetaCode, content, markup: true);
109         }
110 
MetaMarkup(this SpanFactory self, string content, HtmlSymbolType type)111         public static SpanConstructor MetaMarkup(this SpanFactory self, string content, HtmlSymbolType type)
112         {
113             return self.Span(SpanKind.MetaCode, content, type);
114         }
115 
Comment(this SpanFactory self, string content, CSharpSymbolType type)116         public static SpanConstructor Comment(this SpanFactory self, string content, CSharpSymbolType type)
117         {
118             return self.Span(SpanKind.Comment, content, type);
119         }
120 
Comment(this SpanFactory self, string content, VBSymbolType type)121         public static SpanConstructor Comment(this SpanFactory self, string content, VBSymbolType type)
122         {
123             return self.Span(SpanKind.Comment, content, type);
124         }
125 
Comment(this SpanFactory self, string content, HtmlSymbolType type)126         public static SpanConstructor Comment(this SpanFactory self, string content, HtmlSymbolType type)
127         {
128             return self.Span(SpanKind.Comment, content, type);
129         }
130 
Markup(this SpanFactory self, string content)131         public static SpanConstructor Markup(this SpanFactory self, string content)
132         {
133             return self.Span(SpanKind.Markup, content, markup: true).With(new MarkupCodeGenerator());
134         }
135 
Markup(this SpanFactory self, params string[] content)136         public static SpanConstructor Markup(this SpanFactory self, params string[] content)
137         {
138             return self.Span(SpanKind.Markup, content, markup: true).With(new MarkupCodeGenerator());
139         }
140 
GetLocationAndAdvance(this SourceLocationTracker self, string content)141         public static SourceLocation GetLocationAndAdvance(this SourceLocationTracker self, string content)
142         {
143             SourceLocation ret = self.CurrentLocation;
144             self.UpdateLocation(content);
145             return ret;
146         }
147     }
148 
149     public class SpanFactory
150     {
151         public Func<ITextDocument, ITokenizer> MarkupTokenizerFactory { get; set; }
152         public Func<ITextDocument, ITokenizer> CodeTokenizerFactory { get; set; }
153         public SourceLocationTracker LocationTracker { get; private set; }
154 
CreateCsHtml()155         public static SpanFactory CreateCsHtml()
156         {
157             return new SpanFactory()
158             {
159                 MarkupTokenizerFactory = doc => new HtmlTokenizer(doc),
160                 CodeTokenizerFactory = doc => new CSharpTokenizer(doc)
161             };
162         }
163 
CreateVbHtml()164         public static SpanFactory CreateVbHtml()
165         {
166             return new SpanFactory()
167             {
168                 MarkupTokenizerFactory = doc => new HtmlTokenizer(doc),
169                 CodeTokenizerFactory = doc => new VBTokenizer(doc)
170             };
171         }
172 
SpanFactory()173         public SpanFactory()
174         {
175             LocationTracker = new SourceLocationTracker();
176         }
177 
Span(SpanKind kind, string content, CSharpSymbolType type)178         public SpanConstructor Span(SpanKind kind, string content, CSharpSymbolType type)
179         {
180             return CreateSymbolSpan(kind, content, st => new CSharpSymbol(st, content, type));
181         }
182 
Span(SpanKind kind, string content, VBSymbolType type)183         public SpanConstructor Span(SpanKind kind, string content, VBSymbolType type)
184         {
185             return CreateSymbolSpan(kind, content, st => new VBSymbol(st, content, type));
186         }
187 
Span(SpanKind kind, string content, HtmlSymbolType type)188         public SpanConstructor Span(SpanKind kind, string content, HtmlSymbolType type)
189         {
190             return CreateSymbolSpan(kind, content, st => new HtmlSymbol(st, content, type));
191         }
192 
Span(SpanKind kind, string content, bool markup)193         public SpanConstructor Span(SpanKind kind, string content, bool markup)
194         {
195             return new SpanConstructor(kind, Tokenize(new[] { content }, markup));
196         }
197 
Span(SpanKind kind, string[] content, bool markup)198         public SpanConstructor Span(SpanKind kind, string[] content, bool markup)
199         {
200             return new SpanConstructor(kind, Tokenize(content, markup));
201         }
202 
Span(SpanKind kind, params ISymbol[] symbols)203         public SpanConstructor Span(SpanKind kind, params ISymbol[] symbols)
204         {
205             return new SpanConstructor(kind, symbols);
206         }
207 
CreateSymbolSpan(SpanKind kind, string content, Func<SourceLocation, ISymbol> ctor)208         private SpanConstructor CreateSymbolSpan(SpanKind kind, string content, Func<SourceLocation, ISymbol> ctor)
209         {
210             SourceLocation start = LocationTracker.CurrentLocation;
211             LocationTracker.UpdateLocation(content);
212             return new SpanConstructor(kind, new[] { ctor(start) });
213         }
214 
Reset()215         public void Reset()
216         {
217             LocationTracker.CurrentLocation = SourceLocation.Zero;
218         }
219 
Tokenize(IEnumerable<string> contentFragments, bool markup)220         private IEnumerable<ISymbol> Tokenize(IEnumerable<string> contentFragments, bool markup)
221         {
222             return contentFragments.SelectMany(fragment => Tokenize(fragment, markup));
223         }
224 
Tokenize(string content, bool markup)225         private IEnumerable<ISymbol> Tokenize(string content, bool markup)
226         {
227             ITokenizer tok = MakeTokenizer(markup, new SeekableTextReader(content));
228             ISymbol sym;
229             ISymbol last = null;
230             while ((sym = tok.NextSymbol()) != null)
231             {
232                 OffsetStart(sym, LocationTracker.CurrentLocation);
233                 last = sym;
234                 yield return sym;
235             }
236             LocationTracker.UpdateLocation(content);
237         }
238 
MakeTokenizer(bool markup, SeekableTextReader seekableTextReader)239         private ITokenizer MakeTokenizer(bool markup, SeekableTextReader seekableTextReader)
240         {
241             if (markup)
242             {
243                 return MarkupTokenizerFactory(seekableTextReader);
244             }
245             else
246             {
247                 return CodeTokenizerFactory(seekableTextReader);
248             }
249         }
250 
OffsetStart(ISymbol sym, SourceLocation sourceLocation)251         private void OffsetStart(ISymbol sym, SourceLocation sourceLocation)
252         {
253             sym.OffsetStart(sourceLocation);
254         }
255     }
256 
257     public static class SpanConstructorExtensions
258     {
Accepts(this SpanConstructor self, AcceptedCharacters accepted)259         public static SpanConstructor Accepts(this SpanConstructor self, AcceptedCharacters accepted)
260         {
261             return self.With(eh => eh.AcceptedCharacters = accepted);
262         }
263 
AutoCompleteWith(this SpanConstructor self, string autoCompleteString)264         public static SpanConstructor AutoCompleteWith(this SpanConstructor self, string autoCompleteString)
265         {
266             return AutoCompleteWith(self, autoCompleteString, atEndOfSpan: false);
267         }
268 
AutoCompleteWith(this SpanConstructor self, string autoCompleteString, bool atEndOfSpan)269         public static SpanConstructor AutoCompleteWith(this SpanConstructor self, string autoCompleteString, bool atEndOfSpan)
270         {
271             return self.With(new AutoCompleteEditHandler(SpanConstructor.TestTokenizer) { AutoCompleteString = autoCompleteString, AutoCompleteAtEndOfSpan = atEndOfSpan });
272         }
273 
WithEditorHints(this SpanConstructor self, EditorHints hints)274         public static SpanConstructor WithEditorHints(this SpanConstructor self, EditorHints hints)
275         {
276             return self.With(eh => eh.EditorHints = hints);
277         }
278     }
279 
280     public class UnclassifiedCodeSpanConstructor
281     {
282         SpanConstructor _self;
283 
UnclassifiedCodeSpanConstructor(SpanConstructor self)284         public UnclassifiedCodeSpanConstructor(SpanConstructor self)
285         {
286             _self = self;
287         }
288 
AsMetaCode()289         public SpanConstructor AsMetaCode()
290         {
291             _self.Builder.Kind = SpanKind.MetaCode;
292             return _self;
293         }
294 
AsStatement()295         public SpanConstructor AsStatement()
296         {
297             return _self.With(new StatementCodeGenerator());
298         }
299 
AsExpression()300         public SpanConstructor AsExpression()
301         {
302             return _self.With(new ExpressionCodeGenerator());
303         }
304 
AsImplicitExpression(ISet<string> keywords)305         public SpanConstructor AsImplicitExpression(ISet<string> keywords)
306         {
307             return AsImplicitExpression(keywords, acceptTrailingDot: false);
308         }
309 
AsImplicitExpression(ISet<string> keywords, bool acceptTrailingDot)310         public SpanConstructor AsImplicitExpression(ISet<string> keywords, bool acceptTrailingDot)
311         {
312             return _self.With(new ImplicitExpressionEditHandler(SpanConstructor.TestTokenizer, keywords, acceptTrailingDot))
313                 .With(new ExpressionCodeGenerator());
314         }
315 
AsFunctionsBody()316         public SpanConstructor AsFunctionsBody()
317         {
318             return _self.With(new TypeMemberCodeGenerator());
319         }
320 
AsNamespaceImport(string ns, int namespaceKeywordLength)321         public SpanConstructor AsNamespaceImport(string ns, int namespaceKeywordLength)
322         {
323             return _self.With(new AddImportCodeGenerator(ns, namespaceKeywordLength));
324         }
325 
Hidden()326         public SpanConstructor Hidden()
327         {
328             return _self.With(SpanCodeGenerator.Null);
329         }
330 
AsBaseType(string baseType)331         public SpanConstructor AsBaseType(string baseType)
332         {
333             return _self.With(new SetBaseTypeCodeGenerator(baseType));
334         }
335 
AsRazorDirectiveAttribute(string key, string value)336         public SpanConstructor AsRazorDirectiveAttribute(string key, string value)
337         {
338             return _self.With(new RazorDirectiveAttributeCodeGenerator(key, value));
339         }
340 
As(ISpanCodeGenerator codeGenerator)341         public SpanConstructor As(ISpanCodeGenerator codeGenerator)
342         {
343             return _self.With(codeGenerator);
344         }
345     }
346 
347     public class SpanConstructor
348     {
349         public SpanBuilder Builder { get; private set; }
350 
TestTokenizer(string str)351         internal static IEnumerable<ISymbol> TestTokenizer(string str)
352         {
353             yield return new RawTextSymbol(SourceLocation.Zero, str);
354         }
355 
SpanConstructor(SpanKind kind, IEnumerable<ISymbol> symbols)356         public SpanConstructor(SpanKind kind, IEnumerable<ISymbol> symbols)
357         {
358             Builder = new SpanBuilder();
359             Builder.Kind = kind;
360             Builder.EditHandler = SpanEditHandler.CreateDefault(TestTokenizer);
361             foreach (ISymbol sym in symbols)
362             {
363                 Builder.Accept(sym);
364             }
365         }
366 
Build()367         private Span Build()
368         {
369             return Builder.Build();
370         }
371 
With(ISpanCodeGenerator generator)372         public SpanConstructor With(ISpanCodeGenerator generator)
373         {
374             Builder.CodeGenerator = generator;
375             return this;
376         }
377 
With(SpanEditHandler handler)378         public SpanConstructor With(SpanEditHandler handler)
379         {
380             Builder.EditHandler = handler;
381             return this;
382         }
383 
With(Action<ISpanCodeGenerator> generatorConfigurer)384         public SpanConstructor With(Action<ISpanCodeGenerator> generatorConfigurer)
385         {
386             generatorConfigurer(Builder.CodeGenerator);
387             return this;
388         }
389 
With(Action<SpanEditHandler> handlerConfigurer)390         public SpanConstructor With(Action<SpanEditHandler> handlerConfigurer)
391         {
392             handlerConfigurer(Builder.EditHandler);
393             return this;
394         }
395 
operator Span(SpanConstructor self)396         public static implicit operator Span(SpanConstructor self)
397         {
398             return self.Build();
399         }
400 
Hidden()401         public SpanConstructor Hidden()
402         {
403             Builder.CodeGenerator = SpanCodeGenerator.Null;
404             return this;
405         }
406     }
407 }
408