Free cookie consent management tool by TermsFeed Policy Generator

source: trunk/HeuristicLab.ExtLibs/HeuristicLab.NRefactory/5.5.0/NRefactory.CSharp-5.5.0/Parser/mcs/doc.cs @ 15888

Last change on this file since 15888 was 11700, checked in by jkarder, 10 years ago

#2077: created branch and added first version

File size: 20.7 KB
Line 
1//
2// doc.cs: Support for XML documentation comment.
3//
4// Authors:
5//  Atsushi Enomoto <atsushi@ximian.com>
6//  Marek Safar (marek.safar@gmail.com>
7//
8// Dual licensed under the terms of the MIT X11 or GNU GPL
9//
10// Copyright 2004 Novell, Inc.
11// Copyright 2011 Xamarin Inc
12//
13//
14
15using System;
16using System.Collections.Generic;
17using System.IO;
18using System.Text;
19using System.Xml;
20using System.Linq;
21
22namespace Mono.CSharp
23{
24  //
25  // Implements XML documentation generation.
26  //
27  class DocumentationBuilder
28  {
29    //
30    // Used to create element which helps well-formedness checking.
31    //
32    readonly XmlDocument XmlDocumentation;
33
34    readonly ModuleContainer module;
35    readonly ModuleContainer doc_module;
36
37    //
38    // The output for XML documentation.
39    //
40    XmlWriter XmlCommentOutput;
41
42    static readonly string line_head = Environment.NewLine + "            ";
43
44    //
45    // Stores XmlDocuments that are included in XML documentation.
46    // Keys are included filenames, values are XmlDocuments.
47    //
48    Dictionary<string, XmlDocument> StoredDocuments = new Dictionary<string, XmlDocument> ();
49
50    ParserSession session;
51
52    public DocumentationBuilder (ModuleContainer module)
53    {
54      doc_module = new ModuleContainer (module.Compiler);
55      doc_module.DocumentationBuilder = this;
56
57      this.module = module;
58      XmlDocumentation = new XmlDocument ();
59      XmlDocumentation.PreserveWhitespace = false;
60    }
61
62    Report Report {
63      get {
64        return module.Compiler.Report;
65      }
66    }
67
68    public MemberName ParsedName {
69      get; set;
70    }
71
72    public List<DocumentationParameter> ParsedParameters {
73      get; set;
74    }
75
76    public TypeExpression ParsedBuiltinType {
77      get; set;
78    }
79
80    public Operator.OpType? ParsedOperator {
81      get; set;
82    }
83
84    XmlNode GetDocCommentNode (MemberCore mc, string name)
85    {
86      // FIXME: It could be even optimizable as not
87      // to use XmlDocument. But anyways the nodes
88      // are not kept in memory.
89      XmlDocument doc = XmlDocumentation;
90      try {
91        XmlElement el = doc.CreateElement ("member");
92        el.SetAttribute ("name", name);
93        string normalized = mc.DocComment;
94        el.InnerXml = normalized;
95        // csc keeps lines as written in the sources
96        // and inserts formatting indentation (which
97        // is different from XmlTextWriter.Formatting
98        // one), but when a start tag contains an
99        // endline, it joins the next line. We don't
100        // have to follow such a hacky behavior.
101        string [] split =
102          normalized.Split ('\n');
103        int j = 0;
104        for (int i = 0; i < split.Length; i++) {
105          string s = split [i].TrimEnd ();
106          if (s.Length > 0)
107            split [j++] = s;
108        }
109        el.InnerXml = line_head + String.Join (
110          line_head, split, 0, j);
111        return el;
112      } catch (Exception ex) {
113        Report.Warning (1570, 1, mc.Location, "XML documentation comment on `{0}' is not well-formed XML markup ({1})",
114          mc.GetSignatureForError (), ex.Message);
115
116        return doc.CreateComment (String.Format ("FIXME: Invalid documentation markup was found for member {0}", name));
117      }
118    }
119
120    //
121    // Generates xml doc comments (if any), and if required,
122    // handle warning report.
123    //
124    internal void GenerateDocumentationForMember (MemberCore mc)
125    {
126      string name = mc.DocCommentHeader + mc.GetSignatureForDocumentation ();
127
128      XmlNode n = GetDocCommentNode (mc, name);
129
130      XmlElement el = n as XmlElement;
131      if (el != null) {
132        var pm = mc as IParametersMember;
133        if (pm != null) {
134          CheckParametersComments (mc, pm, el);
135        }
136
137        // FIXME: it could be done with XmlReader
138        XmlNodeList nl = n.SelectNodes (".//include");
139        if (nl.Count > 0) {
140          // It could result in current node removal, so prepare another list to iterate.
141          var al = new List<XmlNode> (nl.Count);
142          foreach (XmlNode inc in nl)
143            al.Add (inc);
144          foreach (XmlElement inc in al)
145            if (!HandleInclude (mc, inc))
146              inc.ParentNode.RemoveChild (inc);
147        }
148
149        // FIXME: it could be done with XmlReader
150
151        foreach (XmlElement see in n.SelectNodes (".//see"))
152          HandleSee (mc, see);
153        foreach (XmlElement seealso in n.SelectNodes (".//seealso"))
154          HandleSeeAlso (mc, seealso);
155        foreach (XmlElement see in n.SelectNodes (".//exception"))
156          HandleException (mc, see);
157        foreach (XmlElement node in n.SelectNodes (".//typeparam"))
158          HandleTypeParam (mc, node);
159        foreach (XmlElement node in n.SelectNodes (".//typeparamref"))
160          HandleTypeParamRef (mc, node);
161      }
162
163      n.WriteTo (XmlCommentOutput);
164    }
165
166    //
167    // Processes "include" element. Check included file and
168    // embed the document content inside this documentation node.
169    //
170    bool HandleInclude (MemberCore mc, XmlElement el)
171    {
172      bool keep_include_node = false;
173      string file = el.GetAttribute ("file");
174      string path = el.GetAttribute ("path");
175
176      if (file == "") {
177        Report.Warning (1590, 1, mc.Location, "Invalid XML `include' element. Missing `file' attribute");
178        el.ParentNode.InsertBefore (el.OwnerDocument.CreateComment (" Include tag is invalid "), el);
179        keep_include_node = true;
180      } else if (path.Length == 0) {
181        Report.Warning (1590, 1, mc.Location, "Invalid XML `include' element. Missing `path' attribute");
182        el.ParentNode.InsertBefore (el.OwnerDocument.CreateComment (" Include tag is invalid "), el);
183        keep_include_node = true;
184      } else {
185        XmlDocument doc;
186        Exception exception = null;
187        var full_path = Path.Combine (Path.GetDirectoryName (mc.Location.NameFullPath), file);
188
189        if (!StoredDocuments.TryGetValue (full_path, out doc)) {
190          try {
191            doc = new XmlDocument ();
192            doc.Load (full_path);
193            StoredDocuments.Add (full_path, doc);
194          } catch (Exception e) {
195            exception = e;
196            el.ParentNode.InsertBefore (el.OwnerDocument.CreateComment (String.Format (" Badly formed XML in at comment file `{0}': cannot be included ", file)), el);
197          }
198        }
199
200        if (doc != null) {
201          try {
202            XmlNodeList nl = doc.SelectNodes (path);
203            if (nl.Count == 0) {
204              el.ParentNode.InsertBefore (el.OwnerDocument.CreateComment (" No matching elements were found for the include tag embedded here. "), el);
205         
206              keep_include_node = true;
207            }
208            foreach (XmlNode n in nl)
209              el.ParentNode.InsertBefore (el.OwnerDocument.ImportNode (n, true), el);
210          } catch (Exception ex) {
211            exception = ex;
212            el.ParentNode.InsertBefore (el.OwnerDocument.CreateComment (" Failed to insert some or all of included XML "), el);
213          }
214        }
215
216        if (exception != null) {
217          Report.Warning (1589, 1, mc.Location, "Unable to include XML fragment `{0}' of file `{1}'. {2}",
218            path, file, exception.Message);
219        }
220      }
221
222      return keep_include_node;
223    }
224
225    //
226    // Handles <see> elements.
227    //
228    void HandleSee (MemberCore mc, XmlElement see)
229    {
230      HandleXrefCommon (mc, see);
231    }
232
233    //
234    // Handles <seealso> elements.
235    //
236    void HandleSeeAlso (MemberCore mc, XmlElement seealso)
237    {
238      HandleXrefCommon (mc, seealso);
239    }
240
241    //
242    // Handles <exception> elements.
243    //
244    void HandleException (MemberCore mc, XmlElement seealso)
245    {
246      HandleXrefCommon (mc, seealso);
247    }
248
249    //
250    // Handles <typeparam /> node
251    //
252    static void HandleTypeParam (MemberCore mc, XmlElement node)
253    {
254      if (!node.HasAttribute ("name"))
255        return;
256
257      string tp_name = node.GetAttribute ("name");
258      if (mc.CurrentTypeParameters != null) {
259        if (mc.CurrentTypeParameters.Find (tp_name) != null)
260          return;
261      }
262     
263      // TODO: CS1710, CS1712
264     
265      mc.Compiler.Report.Warning (1711, 2, mc.Location,
266        "XML comment on `{0}' has a typeparam name `{1}' but there is no type parameter by that name",
267        mc.GetSignatureForError (), tp_name);
268    }
269
270    //
271    // Handles <typeparamref /> node
272    //
273    static void HandleTypeParamRef (MemberCore mc, XmlElement node)
274    {
275      if (!node.HasAttribute ("name"))
276        return;
277
278      string tp_name = node.GetAttribute ("name");
279      var member = mc;
280      do {
281        if (member.CurrentTypeParameters != null) {
282          if (member.CurrentTypeParameters.Find (tp_name) != null)
283            return;
284        }
285
286        member = member.Parent;
287      } while (member != null);
288
289      mc.Compiler.Report.Warning (1735, 2, mc.Location,
290        "XML comment on `{0}' has a typeparamref name `{1}' that could not be resolved",
291        mc.GetSignatureForError (), tp_name);
292    }
293
294    FullNamedExpression ResolveMemberName (IMemberContext context, MemberName mn)
295    {
296      if (mn.Left == null)
297        return context.LookupNamespaceOrType (mn.Name, mn.Arity, LookupMode.Probing, Location.Null);
298
299      var left = ResolveMemberName (context, mn.Left);
300      var ns = left as NamespaceExpression;
301      if (ns != null)
302        return ns.LookupTypeOrNamespace (context, mn.Name, mn.Arity, LookupMode.Probing, Location.Null);
303
304      TypeExpr texpr = left as TypeExpr;
305      if (texpr != null) {
306        var found = MemberCache.FindNestedType (texpr.Type, mn.Name, mn.Arity);
307        if (found != null)
308          return new TypeExpression (found, Location.Null);
309
310        return null;
311      }
312
313      return left;
314    }
315
316    //
317    // Processes "see" or "seealso" elements from cref attribute.
318    //
319    void HandleXrefCommon (MemberCore mc, XmlElement xref)
320    {
321      string cref = xref.GetAttribute ("cref");
322      // when, XmlReader, "if (cref == null)"
323      if (!xref.HasAttribute ("cref"))
324        return;
325
326      // Nothing to be resolved the reference is marked explicitly
327      if (cref.Length > 2 && cref [1] == ':')
328        return;
329
330      // Additional symbols for < and > are allowed for easier XML typing
331      cref = cref.Replace ('{', '<').Replace ('}', '>');
332
333      var encoding = module.Compiler.Settings.Encoding;
334      var s = new MemoryStream (encoding.GetBytes (cref));
335
336      var source_file = new CompilationSourceFile (doc_module, mc.Location.SourceFile);
337      var report = new Report (doc_module.Compiler, new NullReportPrinter ());
338
339      if (session == null)
340        session = new ParserSession {
341          UseJayGlobalArrays = true
342        };
343
344      SeekableStreamReader seekable = new SeekableStreamReader (s, encoding, session.StreamReaderBuffer);
345
346      var parser = new CSharpParser (seekable, source_file, report, session);
347      ParsedParameters = null;
348      ParsedName = null;
349      ParsedBuiltinType = null;
350      ParsedOperator = null;
351      parser.Lexer.putback_char = Tokenizer.DocumentationXref;
352      parser.Lexer.parsing_generic_declaration_doc = true;
353      parser.parse ();
354      if (report.Errors > 0) {
355        Report.Warning (1584, 1, mc.Location, "XML comment on `{0}' has syntactically incorrect cref attribute `{1}'",
356          mc.GetSignatureForError (), cref);
357
358        xref.SetAttribute ("cref", "!:" + cref);
359        return;
360      }
361
362      MemberSpec member;
363      string prefix = null;
364      FullNamedExpression fne = null;
365
366      //
367      // Try built-in type first because we are using ParsedName as identifier of
368      // member names on built-in types
369      //
370      if (ParsedBuiltinType != null && (ParsedParameters == null || ParsedName != null)) {
371        member = ParsedBuiltinType.Type;
372      } else {
373        member = null;
374      }
375
376      if (ParsedName != null || ParsedOperator.HasValue) {
377        TypeSpec type = null;
378        string member_name = null;
379
380        if (member == null) {
381          if (ParsedOperator.HasValue) {
382            type = mc.CurrentType;
383          } else if (ParsedName.Left != null) {
384            fne = ResolveMemberName (mc, ParsedName.Left);
385            if (fne != null) {
386              var ns = fne as NamespaceExpression;
387              if (ns != null) {
388                fne = ns.LookupTypeOrNamespace (mc, ParsedName.Name, ParsedName.Arity, LookupMode.Probing, Location.Null);
389                if (fne != null) {
390                  member = fne.Type;
391                }
392              } else {
393                type = fne.Type;
394              }
395            }
396          } else {
397            fne = ResolveMemberName (mc, ParsedName);
398            if (fne == null) {
399              type = mc.CurrentType;
400            } else if (ParsedParameters == null) {
401              member = fne.Type;
402            } else if (fne.Type.MemberDefinition == mc.CurrentType.MemberDefinition) {
403              member_name = Constructor.ConstructorName;
404              type = fne.Type;
405            }
406          }
407        } else {
408          type = (TypeSpec) member;
409          member = null;
410        }
411
412        if (ParsedParameters != null) {
413          var old_printer = mc.Module.Compiler.Report.SetPrinter (new NullReportPrinter ());
414          try {
415            var context = new DocumentationMemberContext (mc, ParsedName ?? MemberName.Null);
416
417            foreach (var pp in ParsedParameters) {
418              pp.Resolve (context);
419            }
420          } finally {
421            mc.Module.Compiler.Report.SetPrinter (old_printer);
422          }
423        }
424
425        if (type != null) {
426          if (member_name == null)
427            member_name = ParsedOperator.HasValue ?
428              Operator.GetMetadataName (ParsedOperator.Value) : ParsedName.Name;
429
430          int parsed_param_count;
431          if (ParsedOperator == Operator.OpType.Explicit || ParsedOperator == Operator.OpType.Implicit) {
432            parsed_param_count = ParsedParameters.Count - 1;
433          } else if (ParsedParameters != null) {
434            parsed_param_count = ParsedParameters.Count;
435          } else {
436            parsed_param_count = 0;
437          }
438
439          int parameters_match = -1;
440          do {
441            var members = MemberCache.FindMembers (type, member_name, true);
442            if (members != null) {
443              foreach (var m in members) {
444                if (ParsedName != null && m.Arity != ParsedName.Arity)
445                  continue;
446
447                if (ParsedParameters != null) {
448                  IParametersMember pm = m as IParametersMember;
449                  if (pm == null)
450                    continue;
451
452                  if (m.Kind == MemberKind.Operator && !ParsedOperator.HasValue)
453                    continue;
454
455                  var pm_params = pm.Parameters;
456
457                  int i;
458                  for (i = 0; i < parsed_param_count; ++i) {
459                    var pparam = ParsedParameters[i];
460
461                    if (i >= pm_params.Count || pparam == null || pparam.TypeSpec == null ||
462                      !TypeSpecComparer.Override.IsEqual (pparam.TypeSpec, pm_params.Types[i]) ||
463                      (pparam.Modifier & Parameter.Modifier.RefOutMask) != (pm_params.FixedParameters[i].ModFlags & Parameter.Modifier.RefOutMask)) {
464
465                      if (i > parameters_match) {
466                        parameters_match = i;
467                      }
468
469                      i = -1;
470                      break;
471                    }
472                  }
473
474                  if (i < 0)
475                    continue;
476
477                  if (ParsedOperator == Operator.OpType.Explicit || ParsedOperator == Operator.OpType.Implicit) {
478                    if (pm.MemberType != ParsedParameters[parsed_param_count].TypeSpec) {
479                      parameters_match = parsed_param_count + 1;
480                      continue;
481                    }
482                  } else {
483                    if (parsed_param_count != pm_params.Count)
484                      continue;
485                  }
486                }
487
488                if (member != null) {
489                  Report.Warning (419, 3, mc.Location,
490                    "Ambiguous reference in cref attribute `{0}'. Assuming `{1}' but other overloads including `{2}' have also matched",
491                    cref, member.GetSignatureForError (), m.GetSignatureForError ());
492
493                  break;
494                }
495
496                member = m;
497              }
498            }
499
500            // Continue with parent type for nested types
501            if (member == null) {
502              type = type.DeclaringType;
503            } else {
504              type = null;
505            }
506          } while (type != null);
507
508          if (member == null && parameters_match >= 0) {
509            for (int i = parameters_match; i < parsed_param_count; ++i) {
510              Report.Warning (1580, 1, mc.Location, "Invalid type for parameter `{0}' in XML comment cref attribute `{1}'",
511                  (i + 1).ToString (), cref);
512            }
513
514            if (parameters_match == parsed_param_count + 1) {
515              Report.Warning (1581, 1, mc.Location, "Invalid return type in XML comment cref attribute `{0}'", cref);
516            }
517          }
518        }
519      }
520
521      if (member == null) {
522        Report.Warning (1574, 1, mc.Location, "XML comment on `{0}' has cref attribute `{1}' that could not be resolved",
523          mc.GetSignatureForError (), cref);
524        cref = "!:" + cref;
525      } else if (member == InternalType.Namespace) {
526        cref = "N:" + fne.GetSignatureForError ();
527      } else {
528        prefix = GetMemberDocHead (member);
529        cref = prefix + member.GetSignatureForDocumentation ();
530      }
531
532      xref.SetAttribute ("cref", cref);
533    }
534
535    //
536    // Get a prefix from member type for XML documentation (used
537    // to formalize cref target name).
538    //
539    static string GetMemberDocHead (MemberSpec type)
540    {
541      if (type is FieldSpec)
542        return "F:";
543      if (type is MethodSpec)
544        return "M:";
545      if (type is EventSpec)
546        return "E:";
547      if (type is PropertySpec)
548        return "P:";
549      if (type is TypeSpec)
550        return "T:";
551
552      throw new NotImplementedException (type.GetType ().ToString ());
553    }
554
555    //
556    // Raised (and passed an XmlElement that contains the comment)
557    // when GenerateDocComment is writing documentation expectedly.
558    //
559    // FIXME: with a few effort, it could be done with XmlReader,
560    // that means removal of DOM use.
561    //
562    void CheckParametersComments (MemberCore member, IParametersMember paramMember, XmlElement el)
563    {
564      HashSet<string> found_tags = null;
565      foreach (XmlElement pelem in el.SelectNodes ("param")) {
566        string xname = pelem.GetAttribute ("name");
567        if (xname.Length == 0)
568          continue; // really? but MS looks doing so
569
570        if (found_tags == null) {
571          found_tags = new HashSet<string> ();
572        }
573
574        if (xname != "" && paramMember.Parameters.GetParameterIndexByName (xname) < 0) {
575          Report.Warning (1572, 2, member.Location,
576            "XML comment on `{0}' has a param tag for `{1}', but there is no parameter by that name",
577            member.GetSignatureForError (), xname);
578          continue;
579        }
580
581        if (found_tags.Contains (xname)) {
582          Report.Warning (1571, 2, member.Location,
583            "XML comment on `{0}' has a duplicate param tag for `{1}'",
584            member.GetSignatureForError (), xname);
585          continue;
586        }
587
588        found_tags.Add (xname);
589      }
590
591      if (found_tags != null) {
592        foreach (Parameter p in paramMember.Parameters.FixedParameters) {
593          if (!found_tags.Contains (p.Name) && !(p is ArglistParameter))
594            Report.Warning (1573, 4, member.Location,
595              "Parameter `{0}' has no matching param tag in the XML comment for `{1}'",
596              p.Name, member.GetSignatureForError ());
597        }
598      }
599    }
600
601    //
602    // Outputs XML documentation comment from tokenized comments.
603    //
604    public bool OutputDocComment (string asmfilename, string xmlFileName)
605    {
606      XmlTextWriter w = null;
607      try {
608        w = new XmlTextWriter (xmlFileName, null);
609        w.Indentation = 4;
610        w.Formatting = Formatting.Indented;
611        w.WriteStartDocument ();
612        w.WriteStartElement ("doc");
613        w.WriteStartElement ("assembly");
614        w.WriteStartElement ("name");
615        w.WriteString (Path.GetFileNameWithoutExtension (asmfilename));
616        w.WriteEndElement (); // name
617        w.WriteEndElement (); // assembly
618        w.WriteStartElement ("members");
619        XmlCommentOutput = w;
620        module.GenerateDocComment (this);
621        w.WriteFullEndElement (); // members
622        w.WriteEndElement ();
623        w.WriteWhitespace (Environment.NewLine);
624        w.WriteEndDocument ();
625        return true;
626      } catch (Exception ex) {
627        Report.Error (1569, "Error generating XML documentation file `{0}' (`{1}')", xmlFileName, ex.Message);
628        return false;
629      } finally {
630        if (w != null)
631          w.Close ();
632      }
633    }
634  }
635
636  //
637  // Type lookup of documentation references uses context of type where
638  // the reference is used but type parameters from cref value
639  //
640  sealed class DocumentationMemberContext : IMemberContext
641  {
642    readonly MemberCore host;
643    MemberName contextName;
644
645    public DocumentationMemberContext (MemberCore host, MemberName contextName)
646    {
647      this.host = host;
648      this.contextName = contextName;
649    }
650
651    public TypeSpec CurrentType {
652      get {
653        return host.CurrentType;
654      }
655    }
656
657    public TypeParameters CurrentTypeParameters {
658      get {
659        return contextName.TypeParameters;
660      }
661    }
662
663    public MemberCore CurrentMemberDefinition {
664      get {
665        return host.CurrentMemberDefinition;
666      }
667    }
668
669    public bool IsObsolete {
670      get {
671        return false;
672      }
673    }
674
675    public bool IsUnsafe {
676      get {
677        return host.IsStatic;
678      }
679    }
680
681    public bool IsStatic {
682      get {
683        return host.IsStatic;
684      }
685    }
686
687    public ModuleContainer Module {
688      get {
689        return host.Module;
690      }
691    }
692
693    public string GetSignatureForError ()
694    {
695      return host.GetSignatureForError ();
696    }
697
698    public ExtensionMethodCandidates LookupExtensionMethod (TypeSpec extensionType, string name, int arity)
699    {
700      return null;
701    }
702
703    public FullNamedExpression LookupNamespaceOrType (string name, int arity, LookupMode mode, Location loc)
704    {
705      if (arity == 0) {
706        var tp = CurrentTypeParameters;
707        if (tp != null) {
708          for (int i = 0; i < tp.Count; ++i) {
709            var t = tp[i];
710            if (t.Name == name) {
711              t.Type.DeclaredPosition = i;
712              return new TypeParameterExpr (t, loc);
713            }
714          }
715        }
716      }
717
718      return host.Parent.LookupNamespaceOrType (name, arity, mode, loc);
719    }
720
721    public FullNamedExpression LookupNamespaceAlias (string name)
722    {
723      throw new NotImplementedException ();
724    }
725  }
726
727  class DocumentationParameter
728  {
729    public readonly Parameter.Modifier Modifier;
730    public FullNamedExpression Type;
731    TypeSpec type;
732
733    public DocumentationParameter (Parameter.Modifier modifier, FullNamedExpression type)
734      : this (type)
735    {
736      this.Modifier = modifier;
737    }
738
739    public DocumentationParameter (FullNamedExpression type)
740    {
741      this.Type = type;
742    }
743
744    public TypeSpec TypeSpec {
745      get {
746        return type;
747      }
748    }
749
750    public void Resolve (IMemberContext context)
751    {
752      type = Type.ResolveAsType (context);
753    }
754  }
755}
Note: See TracBrowser for help on using the repository browser.