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