~ubuntu-branches/ubuntu/trusty/monodevelop/trusty-proposed

« back to all changes in this revision

Viewing changes to src/addins/AspNet/MonoDevelop.AspNet.Mvc/Parser/RazorCSharpParser.cs

  • Committer: Package Import Robot
  • Author(s): Jo Shields
  • Date: 2013-05-12 09:46:03 UTC
  • mto: This revision was merged to the branch mainline in revision 29.
  • Revision ID: package-import@ubuntu.com-20130512094603-mad323bzcxvmcam0
Tags: upstream-4.0.5+dfsg
ImportĀ upstreamĀ versionĀ 4.0.5+dfsg

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
ļ»æ//
 
2
// RazorCSharpParser.cs
 
3
//
 
4
// Author:
 
5
//              Piotr Dowgiallo <sparekd@gmail.com>
 
6
//
 
7
// Copyright (c) 2012 Piotr Dowgiallo
 
8
//
 
9
// Permission is hereby granted, free of charge, to any person obtaining a copy
 
10
// of this software and associated documentation files (the "Software"), to deal
 
11
// in the Software without restriction, including without limitation the rights
 
12
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 
13
// copies of the Software, and to permit persons to whom the Software is
 
14
// furnished to do so, subject to the following conditions:
 
15
//
 
16
// The above copyright notice and this permission notice shall be included in
 
17
// all copies or substantial portions of the Software.
 
18
//
 
19
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 
20
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 
21
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 
22
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 
23
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 
24
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 
25
// THE SOFTWARE.
 
26
 
 
27
using System;
 
28
using System.Collections.Generic;
 
29
using System.Linq;
 
30
using System.Text;
 
31
using MonoDevelop.Ide.TypeSystem;
 
32
using System.Web.Razor;
 
33
using System.Web.Mvc.Razor;
 
34
using System.Threading;
 
35
using System.Web.Razor.Text;
 
36
using MonoDevelop.Ide;
 
37
using Mono.TextEditor;
 
38
using MonoDevelop.Ide.Gui;
 
39
using ICSharpCode.NRefactory.TypeSystem;
 
40
using System.Web.Razor.Parser;
 
41
using System.Web.Razor.Parser.SyntaxTree;
 
42
using System.IO;
 
43
using MonoDevelop.Core;
 
44
using MonoDevelop.AspNet.Parser;
 
45
using System.Web.Configuration;
 
46
using System.Web.WebPages.Razor.Configuration;
 
47
using System.Web.WebPages.Razor;
 
48
using System.Configuration;
 
49
using MonoDevelop.Projects;
 
50
using MonoDevelop.AspNet.StateEngine;
 
51
 
 
52
namespace MonoDevelop.AspNet.Mvc.Parser
 
53
{
 
54
        public class RazorCSharpParser : TypeSystemParser
 
55
        {
 
56
                RazorEditorParserFixed.RazorEditorParser editorParser;
 
57
                DocumentParseCompleteEventArgs capturedArgs;
 
58
                AutoResetEvent parseComplete;
 
59
                ChangeInfo lastChange;
 
60
                string lastParsedFile;
 
61
                TextDocument currentDocument;
 
62
                AspMvcProject aspProject;
 
63
                DotNetProject project;
 
64
                IList<TextDocument> openDocuments;
 
65
 
 
66
                public IList<TextDocument> OpenDocuments { get { return openDocuments; } }
 
67
 
 
68
                public RazorCSharpParser ()
 
69
                {
 
70
                        openDocuments = new List<TextDocument> ();
 
71
 
 
72
                        IdeApp.Exited += delegate {
 
73
                                //HACK: workaround for Mono's not shutting downs IsBackground threads in WaitAny calls
 
74
                                if (editorParser != null) {
 
75
                                        DisposeCurrentParser ();
 
76
                                }
 
77
                        };
 
78
                }
 
79
 
 
80
                public override ParsedDocument Parse (bool storeAst, string fileName, System.IO.TextReader content, Projects.Project project = null)
 
81
                {
 
82
                        currentDocument = openDocuments.FirstOrDefault (d => d != null && d.FileName == fileName);
 
83
                        // We need document and project to be loaded to correctly initialize Razor Host.
 
84
                        this.project = project as DotNetProject;
 
85
                        if (this.project == null || (currentDocument == null && !TryAddDocument (fileName)))
 
86
                                return new RazorCSharpParsedDocument (fileName, new RazorCSharpPageInfo ());
 
87
 
 
88
                        this.aspProject = project as AspMvcProject;
 
89
 
 
90
                        EnsureParserInitializedFor (fileName);
 
91
 
 
92
                        var errors = new List<Error> ();
 
93
 
 
94
                        using (var source = new SeekableTextReader (content)) {
 
95
                                var textChange = CreateTextChange (source);
 
96
                                var parseResult = editorParser.CheckForStructureChanges (textChange);
 
97
                                if (parseResult == PartialParseResult.Rejected) {
 
98
                                        parseComplete.WaitOne ();
 
99
                                        if (!capturedArgs.GeneratorResults.Success)
 
100
                                                GetRazorErrors (errors);
 
101
                                }
 
102
                        }
 
103
 
 
104
                        ParseHtmlDocument (errors);
 
105
                        CreateCSharpParsedDocument ();
 
106
                        ClearLastChange ();
 
107
 
 
108
                        RazorHostKind kind = RazorHostKind.WebPage;
 
109
                        if (editorParser.Host is WebCodeRazorHost) {
 
110
                                kind = RazorHostKind.WebCode;
 
111
                        } else if (editorParser.Host is MonoDevelop.RazorGenerator.RazorHost) {
 
112
                                kind = RazorHostKind.Template;
 
113
                        }
 
114
 
 
115
                        var pageInfo = new RazorCSharpPageInfo () {
 
116
                                HtmlRoot = htmlParsedDocument,
 
117
                                GeneratorResults = capturedArgs.GeneratorResults,
 
118
                                Spans = editorParser.CurrentParseTree.Flatten (),
 
119
                                CSharpParsedFile = parsedCodeFile,
 
120
                                CSharpCode = csharpCode,
 
121
                                Errors = errors,
 
122
                                FoldingRegions = GetFoldingRegions (),
 
123
                                Comments = comments,
 
124
                                Compilation = CreateCompilation (),
 
125
                                HostKind = kind,
 
126
                        };
 
127
 
 
128
                        return new RazorCSharpParsedDocument (fileName, pageInfo);
 
129
                }
 
130
 
 
131
                bool TryAddDocument (string fileName)
 
132
                {
 
133
                        var guiDoc = IdeApp.Workbench.GetDocument (fileName);
 
134
                        if (guiDoc != null && guiDoc.Editor != null) {
 
135
                                currentDocument = guiDoc.Editor.Document;
 
136
                                currentDocument.TextReplacing += OnTextReplacing;
 
137
                                lock (this) {
 
138
                                        var newDocs = new List<TextDocument> (openDocuments);
 
139
                                        newDocs.Add (currentDocument);
 
140
                                        openDocuments = newDocs;
 
141
                                }
 
142
                                guiDoc.Closed += (sender, args) =>
 
143
                                {
 
144
                                        var doc = sender as Document;
 
145
                                        if (doc.Editor != null && doc.Editor.Document != null) {
 
146
                                                lock (this) {
 
147
                                                        openDocuments = new List<TextDocument> (openDocuments.Where (d => d != doc.Editor.Document));
 
148
                                                }
 
149
                                        }
 
150
 
 
151
                                        if (lastParsedFile == doc.FileName && editorParser != null) {
 
152
                                                DisposeCurrentParser ();
 
153
                                        }
 
154
                                };
 
155
                                return true;
 
156
                        }
 
157
                        return false;
 
158
                }
 
159
 
 
160
                void EnsureParserInitializedFor (string fileName)
 
161
                {
 
162
                        if (lastParsedFile == fileName && editorParser != null)
 
163
                                return;
 
164
 
 
165
                        if (editorParser != null)
 
166
                                DisposeCurrentParser ();
 
167
 
 
168
                        CreateParserFor (fileName);
 
169
                }
 
170
 
 
171
                void CreateParserFor (string fileName)
 
172
                {
 
173
                        editorParser = new RazorEditorParserFixed.RazorEditorParser (CreateRazorHost (fileName), fileName);
 
174
 
 
175
                        parseComplete = new AutoResetEvent (false);
 
176
                        editorParser.DocumentParseComplete += (sender, args) =>
 
177
                        {
 
178
                                capturedArgs = args;
 
179
                                parseComplete.Set ();
 
180
                        };
 
181
 
 
182
                        lastParsedFile = fileName;
 
183
                }
 
184
 
 
185
                RazorEngineHost CreateRazorHost (string fileName)
 
186
                {
 
187
                        var projectFile = project.GetProjectFile (fileName);
 
188
                        if (projectFile != null && projectFile.Generator == "RazorTemplatePreprocessor") {
 
189
                                var h = MonoDevelop.RazorGenerator.PreprocessedRazorHost.Create (fileName);
 
190
                                h.DesignTimeMode = true;
 
191
                                h.EnableLinePragmas = false;
 
192
                                return h;
 
193
                        }
 
194
 
 
195
                        string virtualPath = "~/Views/Default.cshtml";
 
196
                        if (aspProject != null)
 
197
                                virtualPath = aspProject.LocalToVirtualPath (fileName);
 
198
 
 
199
                        WebPageRazorHost host = null;
 
200
 
 
201
                        // Try to create host using web.config file
 
202
                        var webConfigMap = new WebConfigurationFileMap ();
 
203
                        if (aspProject != null) {
 
204
                                var vdm = new VirtualDirectoryMapping (aspProject.BaseDirectory.Combine ("Views"), true, "web.config");
 
205
                        webConfigMap.VirtualDirectories.Add ("/", vdm);
 
206
                        }
 
207
                        Configuration configuration;
 
208
                        try {
 
209
                                configuration = WebConfigurationManager.OpenMappedWebConfiguration (webConfigMap, "/");
 
210
                        } catch {
 
211
                                configuration = null;
 
212
                        }
 
213
                        if (configuration != null) {
 
214
                                var rws = configuration.GetSectionGroup (RazorWebSectionGroup.GroupName) as RazorWebSectionGroup;
 
215
                                if (rws != null) {
 
216
                                        host = WebRazorHostFactory.CreateHostFromConfig (rws, virtualPath, fileName);
 
217
                                        host.DesignTimeMode = true;
 
218
                                }
 
219
                        }
 
220
 
 
221
                        if (host == null) {
 
222
                                host = new MvcWebPageRazorHost (virtualPath, fileName) { DesignTimeMode = true };
 
223
                                // Add default namespaces from Razor section
 
224
                                host.NamespaceImports.Add ("System.Web.Mvc");
 
225
                                host.NamespaceImports.Add ("System.Web.Mvc.Ajax");
 
226
                                host.NamespaceImports.Add ("System.Web.Mvc.Html");
 
227
                                host.NamespaceImports.Add ("System.Web.Routing");
 
228
                        }
 
229
 
 
230
                        return host;
 
231
                }
 
232
 
 
233
                void DisposeCurrentParser ()
 
234
                {
 
235
                        editorParser.Dispose ();
 
236
                        editorParser = null;
 
237
                        parseComplete.Dispose ();
 
238
                        parseComplete = null;
 
239
                        ClearLastChange ();
 
240
                }
 
241
 
 
242
                void ClearLastChange ()
 
243
                {
 
244
                        lastChange = null;
 
245
                }
 
246
 
 
247
                TextChange CreateTextChange (SeekableTextReader source)
 
248
                {
 
249
                        if (lastChange == null)
 
250
                                return new TextChange (0, 0, new SeekableTextReader (String.Empty), 0, source.Length, source);
 
251
                        if (lastChange.DeleteChange)
 
252
                                return new TextChange (lastChange.StartOffset, lastChange.AbsoluteLength, lastChange.Buffer,
 
253
                                        lastChange.StartOffset, 0, source);
 
254
                        return new TextChange (lastChange.StartOffset, 0, lastChange.Buffer, lastChange.StartOffset,
 
255
                                lastChange.AbsoluteLength, source);
 
256
                }
 
257
 
 
258
                void GetRazorErrors (List<Error> errors)
 
259
                {
 
260
                        foreach (var error in capturedArgs.GeneratorResults.ParserErrors) {
 
261
                                int off = error.Location.AbsoluteIndex;
 
262
                                if (error.Location.CharacterIndex > 0 && error.Length == 1)
 
263
                                        off--;
 
264
                                errors.Add (new Error (ErrorType.Error, error.Message, currentDocument.OffsetToLocation (off)));
 
265
                        }
 
266
                }
 
267
 
 
268
                Xml.StateEngine.XDocument htmlParsedDocument;
 
269
                IList<Comment> comments;
 
270
 
 
271
                void ParseHtmlDocument (List<Error> errors)
 
272
                {
 
273
                        var sb = new StringBuilder ();
 
274
                        var spanList = new List<Span> ();
 
275
                        comments = new List<Comment> ();
 
276
 
 
277
                        Action<Span> action = (Span span) =>
 
278
                        {
 
279
                                if (span.Kind == SpanKind.Markup) {
 
280
                                        sb.Append (span.Content);
 
281
                                        spanList.Add (span);
 
282
                                } else {
 
283
                                        for (int i = 0; i < span.Content.Length; i++) {
 
284
                                                char ch = span.Content[i];
 
285
                                                if (ch != '\r' && ch != '\n')
 
286
                                                        sb.Append (' ');
 
287
                                                else
 
288
                                                        sb.Append (ch);
 
289
                                        }
 
290
                                        if (span.Kind == SpanKind.Comment) {
 
291
                                                var comment = new Comment (span.Content)
 
292
                                                {
 
293
                                                        OpenTag = "@*",
 
294
                                                        ClosingTag = "*@",
 
295
                                                        CommentType = CommentType.Block,
 
296
                                                };
 
297
                                                comment.Region = new DomRegion (
 
298
                                                        currentDocument.OffsetToLocation (span.Start.AbsoluteIndex - comment.OpenTag.Length),
 
299
                                                        currentDocument.OffsetToLocation (span.Start.AbsoluteIndex + span.Length + comment.ClosingTag.Length));
 
300
                                                comments.Add (comment);
 
301
                                        }
 
302
                                }
 
303
                        };
 
304
 
 
305
                        editorParser.CurrentParseTree.Accept (new CallbackVisitor (action));
 
306
 
 
307
                        var parser = new Xml.StateEngine.Parser (new AspNetFreeState (), true);
 
308
 
 
309
                        try {
 
310
                                parser.Parse (new StringReader (sb.ToString ()));
 
311
                        } catch (Exception ex) {
 
312
                                LoggingService.LogError ("Unhandled error parsing html in Razor document '" + (lastParsedFile ?? "") + "'", ex);
 
313
                        }
 
314
 
 
315
                        htmlParsedDocument = parser.Nodes.GetRoot ();
 
316
                        errors.AddRange (parser.Errors);
 
317
                }
 
318
 
 
319
                IEnumerable<FoldingRegion> GetFoldingRegions ()
 
320
                {
 
321
                        var foldingRegions = new List<FoldingRegion> ();
 
322
                        GetHtmlFoldingRegions (foldingRegions);
 
323
                        GetRazorFoldingRegions (foldingRegions);
 
324
                        return foldingRegions;
 
325
                }
 
326
 
 
327
                void GetHtmlFoldingRegions (List<FoldingRegion> foldingRegions)
 
328
                {
 
329
                        if (htmlParsedDocument != null) {
 
330
                                var d = new AspNetParsedDocument (null, WebSubtype.Html, null, htmlParsedDocument);
 
331
                                foldingRegions.AddRange (d.Foldings);
 
332
                        }
 
333
                }
 
334
 
 
335
                void GetRazorFoldingRegions (List<FoldingRegion> foldingRegions)
 
336
                {
 
337
                        var blocks = new List<Block> ();
 
338
                        GetBlocks (editorParser.CurrentParseTree, blocks);
 
339
                        foreach (var block in blocks) {
 
340
                                var beginLine = currentDocument.GetLineByOffset (block.Start.AbsoluteIndex);
 
341
                                var endLine = currentDocument.GetLineByOffset (block.Start.AbsoluteIndex + block.Length);
 
342
                                if (beginLine != endLine)
 
343
                                        foldingRegions.Add (new FoldingRegion (RazorUtils.GetShortName (block),
 
344
                                                new DomRegion (currentDocument.OffsetToLocation (block.Start.AbsoluteIndex),
 
345
                                                        currentDocument.OffsetToLocation (block.Start.AbsoluteIndex + block.Length))));
 
346
                        }
 
347
                }
 
348
 
 
349
                void GetBlocks (Block root, IList<Block> blocks)
 
350
                {
 
351
                        foreach (var block in root.Children.Where (n => n.IsBlock).Select (n => n as Block)) {
 
352
                                if (block.Type != BlockType.Comment && block.Type != BlockType.Markup)
 
353
                                        blocks.Add (block);
 
354
                                if (block.Type != BlockType.Helper)
 
355
                                        GetBlocks (block, blocks);
 
356
                        }
 
357
                }
 
358
 
 
359
                ParsedDocumentDecorator parsedCodeFile;
 
360
                string csharpCode;
 
361
 
 
362
                void CreateCSharpParsedDocument ()
 
363
                {
 
364
                        var parser = new ICSharpCode.NRefactory.CSharp.CSharpParser ();
 
365
                        ICSharpCode.NRefactory.CSharp.SyntaxTree unit;
 
366
                        csharpCode = CreateCodeFile ();
 
367
                        using (var sr = new StringReader (csharpCode)) {
 
368
                                unit = parser.Parse (sr, "Generated.cs");
 
369
                        }
 
370
                        unit.Freeze ();
 
371
                        var parsedDoc = unit.ToTypeSystem ();
 
372
                        parsedCodeFile = new ParsedDocumentDecorator (parsedDoc) { Ast = unit };
 
373
                }
 
374
 
 
375
                string CreateCodeFile ()
 
376
                {
 
377
                        var unit = capturedArgs.GeneratorResults.GeneratedCode;
 
378
                        var provider = project.LanguageBinding.GetCodeDomProvider ();
 
379
                        using (var sw = new StringWriter ()) {
 
380
                                provider.GenerateCodeFromCompileUnit (unit, sw, new System.CodeDom.Compiler.CodeGeneratorOptions ()     {
 
381
                                        // HACK: we use true, even though razor uses false, to work around a mono bug where it omits the 
 
382
                                        // line ending after "#line hidden", resulting in the unparseable "#line hiddenpublic"
 
383
                                        BlankLinesBetweenMembers = true,
 
384
                                        // matches Razor built-in settings
 
385
                                        IndentString = String.Empty,
 
386
                                });
 
387
                                return sw.ToString ();
 
388
                        }
 
389
                }
 
390
 
 
391
                // Creates compilation that includes underlying C# file for Razor view
 
392
                ICompilation CreateCompilation ()
 
393
                {
 
394
                        return TypeSystemService.GetProjectContext (project).AddOrUpdateFiles (parsedCodeFile.ParsedFile).CreateCompilation ();
 
395
                }
 
396
 
 
397
                void OnTextReplacing (object sender, DocumentChangeEventArgs e)
 
398
                {
 
399
                        if (lastChange == null)
 
400
                                lastChange = new ChangeInfo (e.Offset, new SeekableTextReader((sender as TextDocument).Text));
 
401
                        if (e.ChangeDelta > 0) {
 
402
                                lastChange.Length += e.InsertionLength;
 
403
                        } else {
 
404
                                lastChange.Length -= e.RemovalLength;
 
405
                        }
 
406
                }
 
407
        }
 
408
 
 
409
        class ChangeInfo
 
410
        {
 
411
                int offset;
 
412
 
 
413
                public ChangeInfo (int off, SeekableTextReader buffer)
 
414
                {
 
415
                        offset = off;
 
416
                        Length = 0;
 
417
                        Buffer = buffer;
 
418
                }
 
419
 
 
420
                public int StartOffset {
 
421
                        get     { return offset; }
 
422
                        private set { }
 
423
                }
 
424
 
 
425
                public int Length { get; set; }
 
426
                public int AbsoluteLength {
 
427
                        get { return Math.Abs (Length); }
 
428
                        private set { }
 
429
                }
 
430
 
 
431
                public SeekableTextReader Buffer { get; set; }
 
432
                public bool DeleteChange { get { return Length < 0; } }
 
433
        }
 
434
}