2010-05-27 Marek Safar <marek.safar@gmail.com>
[mono.git] / mcs / mcs / doc.cs
1 //
2 // doc.cs: Support for XML documentation comment.
3 //
4 // Author:
5 //      Atsushi Enomoto <atsushi@ximian.com>
6 //
7 // Dual licensed under the terms of the MIT X11 or GNU GPL
8 //
9 // Copyright 2004 Novell, Inc.
10 //
11 //
12
13 using System;
14 using System.Collections.Generic;
15 using System.IO;
16 using System.Reflection;
17 using System.Reflection.Emit;
18 using System.Runtime.CompilerServices;
19 using System.Runtime.InteropServices;
20 using System.Security;
21 using System.Security.Permissions;
22 using System.Text;
23 using System.Xml;
24 using System.Linq;
25
26 using Mono.CompilerServices.SymbolWriter;
27
28 namespace Mono.CSharp {
29
30         //
31         // Support class for XML documentation.
32         //
33         static class DocUtil
34         {
35                 // TypeContainer
36
37                 //
38                 // Generates xml doc comments (if any), and if required,
39                 // handle warning report.
40                 //
41                 internal static void GenerateTypeDocComment (TypeContainer t,
42                         DeclSpace ds, Report Report)
43                 {
44                         GenerateDocComment (t, ds, Report);
45
46                         if (t.DefaultStaticConstructor != null)
47                                 t.DefaultStaticConstructor.GenerateDocComment (t);
48
49                         if (t.InstanceConstructors != null)
50                                 foreach (Constructor c in t.InstanceConstructors)
51                                         c.GenerateDocComment (t);
52
53                         if (t.Types != null)
54                                 foreach (TypeContainer tc in t.Types)
55                                         tc.GenerateDocComment (t);
56
57                         if (t.Constants != null)
58                                 foreach (Const c in t.Constants)
59                                         c.GenerateDocComment (t);
60
61                         if (t.Fields != null)
62                                 foreach (FieldBase f in t.Fields)
63                                         f.GenerateDocComment (t);
64
65                         if (t.Events != null)
66                                 foreach (Event e in t.Events)
67                                         e.GenerateDocComment (t);
68
69                         if (t.Indexers != null)
70                                 foreach (Indexer ix in t.Indexers)
71                                         ix.GenerateDocComment (t);
72
73                         if (t.Properties != null)
74                                 foreach (Property p in t.Properties)
75                                         p.GenerateDocComment (t);
76
77                         if (t.Methods != null)
78                                 foreach (MethodOrOperator m in t.Methods)
79                                         m.GenerateDocComment (t);
80
81                         if (t.Operators != null)
82                                 foreach (Operator o in t.Operators)
83                                         o.GenerateDocComment (t);
84                 }
85
86                 // MemberCore
87                 private static readonly string line_head =
88                         Environment.NewLine + "            ";
89
90                 private static XmlNode GetDocCommentNode (MemberCore mc,
91                         string name, Report Report)
92                 {
93                         // FIXME: It could be even optimizable as not
94                         // to use XmlDocument. But anyways the nodes
95                         // are not kept in memory.
96                         XmlDocument doc = RootContext.Documentation.XmlDocumentation;
97                         try {
98                                 XmlElement el = doc.CreateElement ("member");
99                                 el.SetAttribute ("name", name);
100                                 string normalized = mc.DocComment;
101                                 el.InnerXml = normalized;
102                                 // csc keeps lines as written in the sources
103                                 // and inserts formatting indentation (which 
104                                 // is different from XmlTextWriter.Formatting
105                                 // one), but when a start tag contains an 
106                                 // endline, it joins the next line. We don't
107                                 // have to follow such a hacky behavior.
108                                 string [] split =
109                                         normalized.Split ('\n');
110                                 int j = 0;
111                                 for (int i = 0; i < split.Length; i++) {
112                                         string s = split [i].TrimEnd ();
113                                         if (s.Length > 0)
114                                                 split [j++] = s;
115                                 }
116                                 el.InnerXml = line_head + String.Join (
117                                         line_head, split, 0, j);
118                                 return el;
119                         } catch (Exception ex) {
120                                 Report.Warning (1570, 1, mc.Location, "XML comment on `{0}' has non-well-formed XML ({1})", name, ex.Message);
121                                 XmlComment com = doc.CreateComment (String.Format ("FIXME: Invalid documentation markup was found for member {0}", name));
122                                 return com;
123                         }
124                 }
125
126                 //
127                 // Generates xml doc comments (if any), and if required,
128                 // handle warning report.
129                 //
130                 internal static void GenerateDocComment (MemberCore mc,
131                         DeclSpace ds, Report Report)
132                 {
133                         if (mc.DocComment != null) {
134                                 string name = mc.GetDocCommentName (ds);
135
136                                 XmlNode n = GetDocCommentNode (mc, name, Report);
137
138                                 XmlElement el = n as XmlElement;
139                                 if (el != null) {
140                                         mc.OnGenerateDocComment (el);
141
142                                         // FIXME: it could be done with XmlReader
143                                         XmlNodeList nl = n.SelectNodes (".//include");
144                                         if (nl.Count > 0) {
145                                                 // It could result in current node removal, so prepare another list to iterate.
146                                                 var al = new List<XmlNode> (nl.Count);
147                                                 foreach (XmlNode inc in nl)
148                                                         al.Add (inc);
149                                                 foreach (XmlElement inc in al)
150                                                         if (!HandleInclude (mc, inc, Report))
151                                                                 inc.ParentNode.RemoveChild (inc);
152                                         }
153
154                                         // FIXME: it could be done with XmlReader
155                                         DeclSpace ds_target = mc as DeclSpace;
156                                         if (ds_target == null)
157                                                 ds_target = ds;
158
159                                         foreach (XmlElement see in n.SelectNodes (".//see"))
160                                                 HandleSee (mc, ds_target, see, Report);
161                                         foreach (XmlElement seealso in n.SelectNodes (".//seealso"))
162                                                 HandleSeeAlso (mc, ds_target, seealso ,Report);
163                                         foreach (XmlElement see in n.SelectNodes (".//exception"))
164                                                 HandleException (mc, ds_target, see, Report);
165                                 }
166
167                                 n.WriteTo (RootContext.Documentation.XmlCommentOutput);
168                         }
169                         else if (mc.IsExposedFromAssembly ()) {
170                                 Constructor c = mc as Constructor;
171                                 if (c == null || !c.IsDefault ())
172                                         Report.Warning (1591, 4, mc.Location,
173                                                 "Missing XML comment for publicly visible type or member `{0}'", mc.GetSignatureForError ());
174                         }
175                 }
176
177                 //
178                 // Processes "include" element. Check included file and
179                 // embed the document content inside this documentation node.
180                 //
181                 private static bool HandleInclude (MemberCore mc, XmlElement el, Report Report)
182                 {
183                         bool keep_include_node = false;
184                         string file = el.GetAttribute ("file");
185                         string path = el.GetAttribute ("path");
186                         if (file == "") {
187                                 Report.Warning (1590, 1, mc.Location, "Invalid XML `include' element. Missing `file' attribute");
188                                 el.ParentNode.InsertBefore (el.OwnerDocument.CreateComment (" Include tag is invalid "), el);
189                                 keep_include_node = true;
190                         }
191                         else if (path.Length == 0) {
192                                 Report.Warning (1590, 1, mc.Location, "Invalid XML `include' element. Missing `path' attribute");
193                                 el.ParentNode.InsertBefore (el.OwnerDocument.CreateComment (" Include tag is invalid "), el);
194                                 keep_include_node = true;
195                         }
196                         else {
197                                 XmlDocument doc;
198                                 if (!RootContext.Documentation.StoredDocuments.TryGetValue (file, out doc)) {
199                                         try {
200                                                 doc = new XmlDocument ();
201                                                 doc.Load (file);
202                                                 RootContext.Documentation.StoredDocuments.Add (file, doc);
203                                         } catch (Exception) {
204                                                 el.ParentNode.InsertBefore (el.OwnerDocument.CreateComment (String.Format (" Badly formed XML in at comment file `{0}': cannot be included ", file)), el);
205                                                 Report.Warning (1592, 1, mc.Location, "Badly formed XML in included comments file -- `{0}'", file);
206                                         }
207                                 }
208                                 if (doc != null) {
209                                         try {
210                                                 XmlNodeList nl = doc.SelectNodes (path);
211                                                 if (nl.Count == 0) {
212                                                         el.ParentNode.InsertBefore (el.OwnerDocument.CreateComment (" No matching elements were found for the include tag embedded here. "), el);
213                                         
214                                                         keep_include_node = true;
215                                                 }
216                                                 foreach (XmlNode n in nl)
217                                                         el.ParentNode.InsertBefore (el.OwnerDocument.ImportNode (n, true), el);
218                                         } catch (Exception ex) {
219                                                 el.ParentNode.InsertBefore (el.OwnerDocument.CreateComment (" Failed to insert some or all of included XML "), el);
220                                                 Report.Warning (1589, 1, mc.Location, "Unable to include XML fragment `{0}' of file `{1}' ({2})", path, file, ex.Message);
221                                         }
222                                 }
223                         }
224                         return keep_include_node;
225                 }
226
227                 //
228                 // Handles <see> elements.
229                 //
230                 private static void HandleSee (MemberCore mc,
231                         DeclSpace ds, XmlElement see, Report r)
232                 {
233                         HandleXrefCommon (mc, ds, see, r);
234                 }
235
236                 //
237                 // Handles <seealso> elements.
238                 //
239                 private static void HandleSeeAlso (MemberCore mc,
240                         DeclSpace ds, XmlElement seealso, Report r)
241                 {
242                         HandleXrefCommon (mc, ds, seealso, r);
243                 }
244
245                 //
246                 // Handles <exception> elements.
247                 //
248                 private static void HandleException (MemberCore mc,
249                         DeclSpace ds, XmlElement seealso, Report r)
250                 {
251                         HandleXrefCommon (mc, ds, seealso, r);
252                 }
253
254                 static readonly char [] wsChars =
255                         new char [] {' ', '\t', '\n', '\r'};
256
257                 //
258                 // returns a full runtime type name from a name which might
259                 // be C# specific type name.
260                 //
261                 private static TypeSpec FindDocumentedType (MemberCore mc, string name, DeclSpace ds, string cref, Report r)
262                 {
263                         bool is_array = false;
264                         string identifier = name;
265                         if (name [name.Length - 1] == ']') {
266                                 string tmp = name.Substring (0, name.Length - 1).Trim (wsChars);
267                                 if (tmp [tmp.Length - 1] == '[') {
268                                         identifier = tmp.Substring (0, tmp.Length - 1).Trim (wsChars);
269                                         is_array = true;
270                                 }
271                         }
272                         TypeSpec t = FindDocumentedTypeNonArray (mc, identifier, ds, cref, r);
273                         if (t != null && is_array)
274                                 t = Import.ImportType (Array.CreateInstance (t.GetMetaInfo (), 0).GetType ());
275                         return t;
276                 }
277
278                 private static TypeSpec FindDocumentedTypeNonArray (MemberCore mc, 
279                         string identifier, DeclSpace ds, string cref, Report r)
280                 {
281                         switch (identifier) {
282                         case "int":
283                                 return TypeManager.int32_type;
284                         case "uint":
285                                 return TypeManager.uint32_type;
286                         case "short":
287                                 return TypeManager.short_type;;
288                         case "ushort":
289                                 return TypeManager.ushort_type;
290                         case "long":
291                                 return TypeManager.int64_type;
292                         case "ulong":
293                                 return TypeManager.uint64_type;;
294                         case "float":
295                                 return TypeManager.float_type;;
296                         case "double":
297                                 return TypeManager.double_type;
298                         case "char":
299                                 return TypeManager.char_type;;
300                         case "decimal":
301                                 return TypeManager.decimal_type;;
302                         case "byte":
303                                 return TypeManager.byte_type;;
304                         case "sbyte":
305                                 return TypeManager.sbyte_type;;
306                         case "object":
307                                 return TypeManager.object_type;;
308                         case "bool":
309                                 return TypeManager.bool_type;;
310                         case "string":
311                                 return TypeManager.string_type;;
312                         case "void":
313                                 return TypeManager.void_type;;
314                         }
315                         FullNamedExpression e = ds.LookupNamespaceOrType (identifier, 0, mc.Location, false);
316                         if (e != null) {
317                                 if (!(e is TypeExpr))
318                                         return null;
319                                 return e.Type;
320                         }
321                         int index = identifier.LastIndexOf ('.');
322                         if (index < 0)
323                                 return null;
324                         int warn;
325                         TypeSpec parent = FindDocumentedType (mc, identifier.Substring (0, index), ds, cref, r);
326                         if (parent == null)
327                                 return null;
328                         // no need to detect warning 419 here
329                         var ts = FindDocumentedMember (mc, parent,
330                                 identifier.Substring (index + 1),
331                                 null, ds, out warn, cref, false, null, r) as TypeSpec;
332                         if (ts != null)
333                                 return ts;
334                         return null;
335                 }
336
337                 //
338                 // Returns a MemberInfo that is referenced in XML documentation
339                 // (by "see" or "seealso" elements).
340                 //
341                 private static MemberSpec FindDocumentedMember (MemberCore mc,
342                         TypeSpec type, string member_name, AParametersCollection param_list, 
343                         DeclSpace ds, out int warning_type, string cref,
344                         bool warn419, string name_for_error, Report r)
345                 {
346 //                      for (; type != null; type = type.DeclaringType) {
347                                 var mi = FindDocumentedMemberNoNest (
348                                         mc, type, member_name, param_list, ds,
349                                         out warning_type, cref, warn419,
350                                         name_for_error, r);
351                                 if (mi != null)
352                                         return mi; // new FoundMember (type, mi);
353 //                      }
354                         warning_type = 0;
355                         return null;
356                 }
357
358                 private static MemberSpec FindDocumentedMemberNoNest (
359                         MemberCore mc, TypeSpec type, string member_name,
360                         AParametersCollection param_list, DeclSpace ds, out int warning_type, 
361                         string cref, bool warn419, string name_for_error, Report Report)
362                 {
363                         warning_type = 0;
364                         var filter = new MemberFilter (member_name, 0, MemberKind.All, param_list, null);
365                         IList<MemberSpec> found = null;
366                         while (type != null && found == null) {
367                                 found = MemberCache.FindMembers (type, filter, BindingRestriction.None);
368                                 type = type.DeclaringType;
369                         }
370
371                         if (found == null)
372                                 return null;
373
374                         if (warn419 && found.Count > 1) {
375                                 Report419 (mc, name_for_error, found.ToArray (), Report);
376                         }
377
378                         return found [0];
379
380 /*
381                         if (param_list == null) {
382                                 // search for fields/events etc.
383                                 mis = TypeManager.MemberLookup (type, null,
384                                         type, MemberKind.All,
385                                         BindingRestriction.None,
386                                         member_name, null);
387                                 mis = FilterOverridenMembersOut (mis);
388                                 if (mis == null || mis.Length == 0)
389                                         return null;
390                                 if (warn419 && IsAmbiguous (mis))
391                                         Report419 (mc, name_for_error, mis, Report);
392                                 return mis [0];
393                         }
394
395                         MethodSignature msig = new MethodSignature (member_name, null, param_list);
396                         mis = FindMethodBase (type, 
397                                 BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance,
398                                 msig);
399
400                         if (warn419 && mis.Length > 0) {
401                                 if (IsAmbiguous (mis))
402                                         Report419 (mc, name_for_error, mis, Report);
403                                 return mis [0];
404                         }
405
406                         // search for operators (whose parameters exactly
407                         // matches with the list) and possibly report CS1581.
408                         string oper = null;
409                         string return_type_name = null;
410                         if (member_name.StartsWith ("implicit operator ")) {
411                                 Operator.GetMetadataName (Operator.OpType.Implicit);
412                                 return_type_name = member_name.Substring (18).Trim (wsChars);
413                         }
414                         else if (member_name.StartsWith ("explicit operator ")) {
415                                 oper = Operator.GetMetadataName (Operator.OpType.Explicit);
416                                 return_type_name = member_name.Substring (18).Trim (wsChars);
417                         }
418                         else if (member_name.StartsWith ("operator ")) {
419                                 oper = member_name.Substring (9).Trim (wsChars);
420                                 switch (oper) {
421                                 // either unary or binary
422                                 case "+":
423                                         oper = param_list.Length == 2 ?
424                                                 Operator.GetMetadataName (Operator.OpType.Addition) :
425                                                 Operator.GetMetadataName (Operator.OpType.UnaryPlus);
426                                         break;
427                                 case "-":
428                                         oper = param_list.Length == 2 ?
429                                                 Operator.GetMetadataName (Operator.OpType.Subtraction) :
430                                                 Operator.GetMetadataName (Operator.OpType.UnaryNegation);
431                                         break;
432                                 default:
433                                         oper = Operator.GetMetadataName (oper);
434                                         if (oper != null)
435                                                 break;
436
437                                         warning_type = 1584;
438                                         Report.Warning (1020, 1, mc.Location, "Overloadable {0} operator is expected", param_list.Length == 2 ? "binary" : "unary");
439                                         Report.Warning (1584, 1, mc.Location, "XML comment on `{0}' has syntactically incorrect cref attribute `{1}'",
440                                                 mc.GetSignatureForError (), cref);
441                                         return null;
442                                 }
443                         }
444                         // here we still don't consider return type (to
445                         // detect CS1581 or CS1002+CS1584).
446                         msig = new MethodSignature (oper, null, param_list);
447
448                         mis = FindMethodBase (type, 
449                                 BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance,
450                                 msig);
451                         if (mis.Length == 0)
452                                 return null; // CS1574
453                         var mi = mis [0];
454                         TypeSpec expected = mi is MethodSpec ?
455                                 ((MethodSpec) mi).ReturnType :
456                                 mi is PropertySpec ?
457                                 ((PropertySpec) mi).PropertyType :
458                                 null;
459                         if (return_type_name != null) {
460                                 TypeSpec returnType = FindDocumentedType (mc, return_type_name, ds, cref, Report);
461                                 if (returnType == null || returnType != expected) {
462                                         warning_type = 1581;
463                                         Report.Warning (1581, 1, mc.Location, "Invalid return type in XML comment cref attribute `{0}'", cref);
464                                         return null;
465                                 }
466                         }
467                         return mis [0];
468 */ 
469                 }
470
471                 //
472                 // Processes "see" or "seealso" elements.
473                 // Checks cref attribute.
474                 //
475                 private static void HandleXrefCommon (MemberCore mc,
476                         DeclSpace ds, XmlElement xref, Report Report)
477                 {
478                         string cref = xref.GetAttribute ("cref").Trim (wsChars);
479                         // when, XmlReader, "if (cref == null)"
480                         if (!xref.HasAttribute ("cref"))
481                                 return;
482                         if (cref.Length == 0)
483                                 Report.Warning (1001, 1, mc.Location, "Identifier expected");
484                                 // ... and continue until CS1584.
485
486                         string signature; // "x:" are stripped
487                         string name; // method invokation "(...)" are removed
488                         string parameters; // method parameter list
489
490                         // When it found '?:' ('T:' 'M:' 'F:' 'P:' 'E:' etc.),
491                         // MS ignores not only its member kind, but also
492                         // the entire syntax correctness. Nor it also does
493                         // type fullname resolution i.e. "T:List(int)" is kept
494                         // as T:List(int), not
495                         // T:System.Collections.Generic.List&lt;System.Int32&gt;
496                         if (cref.Length > 2 && cref [1] == ':')
497                                 return;
498                         else
499                                 signature = cref;
500
501                         // Also note that without "T:" any generic type 
502                         // indication fails.
503
504                         int parens_pos = signature.IndexOf ('(');
505                         int brace_pos = parens_pos >= 0 ? -1 :
506                                 signature.IndexOf ('[');
507                         if (parens_pos > 0 && signature [signature.Length - 1] == ')') {
508                                 name = signature.Substring (0, parens_pos).Trim (wsChars);
509                                 parameters = signature.Substring (parens_pos + 1, signature.Length - parens_pos - 2).Trim (wsChars);
510                         }
511                         else if (brace_pos > 0 && signature [signature.Length - 1] == ']') {
512                                 name = signature.Substring (0, brace_pos).Trim (wsChars);
513                                 parameters = signature.Substring (brace_pos + 1, signature.Length - brace_pos - 2).Trim (wsChars);
514                         }
515                         else {
516                                 name = signature;
517                                 parameters = null;
518                         }
519                         Normalize (mc, ref name, Report);
520
521                         string identifier = GetBodyIdentifierFromName (name);
522
523                         // Check if identifier is valid.
524                         // This check is not necessary to mark as error, but
525                         // csc specially reports CS1584 for wrong identifiers.
526                         string [] name_elems = identifier.Split ('.');
527                         for (int i = 0; i < name_elems.Length; i++) {
528                                 string nameElem = GetBodyIdentifierFromName (name_elems [i]);
529                                 if (i > 0)
530                                         Normalize (mc, ref nameElem, Report);
531                                 if (!Tokenizer.IsValidIdentifier (nameElem)
532                                         && nameElem.IndexOf ("operator") < 0) {
533                                         Report.Warning (1584, 1, mc.Location, "XML comment on `{0}' has syntactically incorrect cref attribute `{1}'",
534                                                 mc.GetSignatureForError (), cref);
535                                         xref.SetAttribute ("cref", "!:" + signature);
536                                         return;
537                                 }
538                         }
539
540                         // check if parameters are valid
541                         AParametersCollection parameter_types;
542                         if (parameters == null)
543                                 parameter_types = null;
544                         else if (parameters.Length == 0)
545                                 parameter_types = ParametersCompiled.EmptyReadOnlyParameters;
546                         else {
547                                 string [] param_list = parameters.Split (',');
548                                 var plist = new List<TypeSpec> ();
549                                 for (int i = 0; i < param_list.Length; i++) {
550                                         string param_type_name = param_list [i].Trim (wsChars);
551                                         Normalize (mc, ref param_type_name, Report);
552                                         TypeSpec param_type = FindDocumentedType (mc, param_type_name, ds, cref, Report);
553                                         if (param_type == null) {
554                                                 Report.Warning (1580, 1, mc.Location, "Invalid type for parameter `{0}' in XML comment cref attribute `{1}'",
555                                                         (i + 1).ToString (), cref);
556                                                 return;
557                                         }
558                                         plist.Add (param_type);
559                                 }
560
561                                 parameter_types = ParametersCompiled.CreateFullyResolved (plist.ToArray ());
562                         }
563
564                         TypeSpec type = FindDocumentedType (mc, name, ds, cref, Report);
565                         if (type != null
566                                 // delegate must not be referenced with args
567                                 && (!type.IsDelegate
568                                 || parameter_types == null)) {
569                                 string result = GetSignatureForDoc (type)
570                                         + (brace_pos < 0 ? String.Empty : signature.Substring (brace_pos));
571                                 xref.SetAttribute ("cref", "T:" + result);
572                                 return; // a type
573                         }
574
575                         int period = name.LastIndexOf ('.');
576                         if (period > 0) {
577                                 string typeName = name.Substring (0, period);
578                                 string member_name = name.Substring (period + 1);
579                                 string lookup_name = member_name == "this" ? MemberCache.IndexerNameAlias : member_name;
580                                 Normalize (mc, ref lookup_name, Report);
581                                 Normalize (mc, ref member_name, Report);
582                                 type = FindDocumentedType (mc, typeName, ds, cref, Report);
583                                 int warn_result;
584                                 if (type != null) {
585                                         var mi = FindDocumentedMember (mc, type, lookup_name, parameter_types, ds, out warn_result, cref, true, name, Report);
586                                         if (warn_result > 0)
587                                                 return;
588                                         if (mi != null) {
589                                                 // we cannot use 'type' directly
590                                                 // to get its name, since mi
591                                                 // could be from DeclaringType
592                                                 // for nested types.
593                                                 xref.SetAttribute ("cref", GetMemberDocHead (mi) + GetSignatureForDoc (mi.DeclaringType) + "." + member_name + GetParametersFormatted (mi));
594                                                 return; // a member of a type
595                                         }
596                                 }
597                         } else {
598                                 int warn_result;
599                                 var mi = FindDocumentedMember (mc, ds.PartialContainer.Definition, name, parameter_types, ds, out warn_result, cref, true, name, Report);
600
601                                 if (warn_result > 0)
602                                         return;
603                                 if (mi != null) {
604                                         // we cannot use 'type' directly
605                                         // to get its name, since mi
606                                         // could be from DeclaringType
607                                         // for nested types.
608                                         xref.SetAttribute ("cref", GetMemberDocHead (mi) + GetSignatureForDoc (mi.DeclaringType) + "." + name + GetParametersFormatted (mi));
609                                         return; // local member name
610                                 }
611                         }
612
613                         // It still might be part of namespace name.
614                         Namespace ns = ds.NamespaceEntry.NS.GetNamespace (name, false);
615                         if (ns != null) {
616                                 xref.SetAttribute ("cref", "N:" + ns.GetSignatureForError ());
617                                 return; // a namespace
618                         }
619                         if (GlobalRootNamespace.Instance.IsNamespace (name)) {
620                                 xref.SetAttribute ("cref", "N:" + name);
621                                 return; // a namespace
622                         }
623
624                         Report.Warning (1574, 1, mc.Location, "XML comment on `{0}' has cref attribute `{1}' that could not be resolved",
625                                 mc.GetSignatureForError (), cref);
626
627                         xref.SetAttribute ("cref", "!:" + name);
628                 }
629
630                 static string GetParametersFormatted (MemberSpec mi)
631                 {
632                         var pm = mi as IParametersMember;
633                         if (pm == null || pm.Parameters.IsEmpty)
634                                 return string.Empty;
635
636                         AParametersCollection parameters = pm.Parameters;
637 /*
638                         if (parameters == null || parameters.Count == 0)
639                                 return String.Empty;
640 */
641                         StringBuilder sb = new StringBuilder ();
642                         sb.Append ('(');
643                         for (int i = 0; i < parameters.Count; i++) {
644 //                              if (is_setter && i + 1 == parameters.Count)
645 //                                      break; // skip "value".
646                                 if (i > 0)
647                                         sb.Append (',');
648                                 TypeSpec t = parameters.Types [i];
649                                 sb.Append (GetSignatureForDoc (t));
650                         }
651                         sb.Append (')');
652                         return sb.ToString ();
653                 }
654
655                 static string GetBodyIdentifierFromName (string name)
656                 {
657                         string identifier = name;
658
659                         if (name.Length > 0 && name [name.Length - 1] == ']') {
660                                 string tmp = name.Substring (0, name.Length - 1).Trim (wsChars);
661                                 int last = tmp.LastIndexOf ('[');
662                                 if (last > 0)
663                                         identifier = tmp.Substring (0, last).Trim (wsChars);
664                         }
665
666                         return identifier;
667                 }
668
669                 static void Report419 (MemberCore mc, string member_name, MemberSpec [] mis, Report Report)
670                 {
671                         Report.Warning (419, 3, mc.Location, 
672                                 "Ambiguous reference in cref attribute `{0}'. Assuming `{1}' but other overloads including `{2}' have also matched",
673                                 member_name,
674                                 TypeManager.GetFullNameSignature (mis [0]),
675                                 TypeManager.GetFullNameSignature (mis [1]));
676                 }
677
678                 //
679                 // Get a prefix from member type for XML documentation (used
680                 // to formalize cref target name).
681                 //
682                 static string GetMemberDocHead (MemberSpec type)
683                 {
684                         if (type is FieldSpec)
685                                 return "F:";
686                         if (type is MethodSpec)
687                                 return "M:";
688                         if (type is EventSpec)
689                                 return "E:";
690                         if (type is PropertySpec)
691                                 return "P:";
692                         if (type is TypeSpec)
693                                 return "T:";
694
695                         return "!:";
696                 }
697
698                 // MethodCore
699
700                 //
701                 // Returns a string that represents the signature for this 
702                 // member which should be used in XML documentation.
703                 //
704                 public static string GetMethodDocCommentName (MemberCore mc, ParametersCompiled parameters, DeclSpace ds)
705                 {
706                         IParameterData [] plist = parameters.FixedParameters;
707                         string paramSpec = String.Empty;
708                         if (plist != null) {
709                                 StringBuilder psb = new StringBuilder ();
710                                 int i = 0;
711                                 foreach (Parameter p in plist) {
712                                         psb.Append (psb.Length != 0 ? "," : "(");
713                                         psb.Append (GetSignatureForDoc (parameters.Types [i++]));
714                                         if ((p.ModFlags & Parameter.Modifier.ISBYREF) != 0)
715                                                 psb.Append ('@');
716                                 }
717                                 paramSpec = psb.ToString ();
718                         }
719
720                         if (paramSpec.Length > 0)
721                                 paramSpec += ")";
722
723                         string name = mc is Constructor ? "#ctor" : mc.Name;
724                         if (mc.MemberName.IsGeneric)
725                                 name += "``" + mc.MemberName.CountTypeArguments;
726
727                         string suffix = String.Empty;
728                         Operator op = mc as Operator;
729                         if (op != null) {
730                                 switch (op.OperatorType) {
731                                 case Operator.OpType.Implicit:
732                                 case Operator.OpType.Explicit:
733                                         suffix = "~" + GetSignatureForDoc (op.ReturnType);
734                                         break;
735                                 }
736                         }
737                         return String.Concat (mc.DocCommentHeader, ds.Name, ".", name, paramSpec, suffix);
738                 }
739
740                 static string GetSignatureForDoc (TypeSpec type)
741                 {
742                         var tp = type as TypeParameterSpec;
743                         if (tp != null) {
744                                 var prefix = tp.IsMethodOwned ? "``" : "`";
745                                 return prefix + tp.DeclaredPosition;
746                         }
747
748                         if (TypeManager.IsGenericType (type)) {
749                                 string g = type.MemberDefinition.Namespace;
750                                 if (g != null && g.Length > 0)
751                                         g += '.';
752                                 int idx = type.Name.LastIndexOf ('`');
753                                 g += (idx < 0 ? type.Name : type.Name.Substring (0, idx)) + '{';
754                                 int argpos = 0;
755                                 foreach (TypeSpec t in TypeManager.GetTypeArguments (type))
756                                         g += (argpos++ > 0 ? "," : String.Empty) + GetSignatureForDoc (t);
757                                 g += '}';
758                                 return g;
759                         }
760
761                         string name = type.GetMetaInfo ().FullName != null ? type.GetMetaInfo ().FullName : type.Name;
762                         return name.Replace ("+", ".").Replace ('&', '@');
763                 }
764
765                 //
766                 // Raised (and passed an XmlElement that contains the comment)
767                 // when GenerateDocComment is writing documentation expectedly.
768                 //
769                 // FIXME: with a few effort, it could be done with XmlReader,
770                 // that means removal of DOM use.
771                 //
772                 internal static void OnMethodGenerateDocComment (
773                         MethodCore mc, XmlElement el, Report Report)
774                 {
775                         var paramTags = new Dictionary<string, string> ();
776                         foreach (XmlElement pelem in el.SelectNodes ("param")) {
777                                 string xname = pelem.GetAttribute ("name");
778                                 if (xname.Length == 0)
779                                         continue; // really? but MS looks doing so
780                                 if (xname != "" && mc.ParameterInfo.GetParameterIndexByName (xname) < 0)
781                                         Report.Warning (1572, 2, mc.Location, "XML comment on `{0}' has a param tag for `{1}', but there is no parameter by that name",
782                                                 mc.GetSignatureForError (), xname);
783                                 else if (paramTags.ContainsKey (xname))
784                                         Report.Warning (1571, 2, mc.Location, "XML comment on `{0}' has a duplicate param tag for `{1}'",
785                                                 mc.GetSignatureForError (), xname);
786                                 paramTags [xname] = xname;
787                         }
788                         IParameterData [] plist = mc.ParameterInfo.FixedParameters;
789                         foreach (Parameter p in plist) {
790                                 if (paramTags.Count > 0 && !paramTags.ContainsKey (p.Name))
791                                         Report.Warning (1573, 4, mc.Location, "Parameter `{0}' has no matching param tag in the XML comment for `{1}'",
792                                                 p.Name, mc.GetSignatureForError ());
793                         }
794                 }
795
796                 private static void Normalize (MemberCore mc, ref string name, Report Report)
797                 {
798                         if (name.Length > 0 && name [0] == '@')
799                                 name = name.Substring (1);
800                         else if (name == "this")
801                                 name = "Item";
802                         else if (Tokenizer.IsKeyword (name) && !IsTypeName (name))
803                                 Report.Warning (1041, 1, mc.Location, "Identifier expected. `{0}' is a keyword", name);
804                 }
805
806                 private static bool IsTypeName (string name)
807                 {
808                         switch (name) {
809                         case "bool":
810                         case "byte":
811                         case "char":
812                         case "decimal":
813                         case "double":
814                         case "float":
815                         case "int":
816                         case "long":
817                         case "object":
818                         case "sbyte":
819                         case "short":
820                         case "string":
821                         case "uint":
822                         case "ulong":
823                         case "ushort":
824                         case "void":
825                                 return true;
826                         }
827                         return false;
828                 }
829         }
830
831         //
832         // Implements XML documentation generation.
833         //
834         public class Documentation
835         {
836                 public Documentation (string xml_output_filename)
837                 {
838                         docfilename = xml_output_filename;
839                         XmlDocumentation = new XmlDocument ();
840                         XmlDocumentation.PreserveWhitespace = false;
841                 }
842
843                 private string docfilename;
844
845                 //
846                 // Used to create element which helps well-formedness checking.
847                 //
848                 public XmlDocument XmlDocumentation;
849
850                 //
851                 // The output for XML documentation.
852                 //
853                 public XmlWriter XmlCommentOutput;
854
855                 //
856                 // Stores XmlDocuments that are included in XML documentation.
857                 // Keys are included filenames, values are XmlDocuments.
858                 //
859                 public Dictionary<string, XmlDocument> StoredDocuments = new Dictionary<string, XmlDocument> ();
860
861                 //
862                 // Outputs XML documentation comment from tokenized comments.
863                 //
864                 public bool OutputDocComment (string asmfilename, Report Report)
865                 {
866                         XmlTextWriter w = null;
867                         try {
868                                 w = new XmlTextWriter (docfilename, null);
869                                 w.Indentation = 4;
870                                 w.Formatting = Formatting.Indented;
871                                 w.WriteStartDocument ();
872                                 w.WriteStartElement ("doc");
873                                 w.WriteStartElement ("assembly");
874                                 w.WriteStartElement ("name");
875                                 w.WriteString (Path.ChangeExtension (asmfilename, null));
876                                 w.WriteEndElement (); // name
877                                 w.WriteEndElement (); // assembly
878                                 w.WriteStartElement ("members");
879                                 XmlCommentOutput = w;
880                                 GenerateDocComment (Report);
881                                 w.WriteFullEndElement (); // members
882                                 w.WriteEndElement ();
883                                 w.WriteWhitespace (Environment.NewLine);
884                                 w.WriteEndDocument ();
885                                 return true;
886                         } catch (Exception ex) {
887                                 Report.Error (1569, "Error generating XML documentation file `{0}' (`{1}')", docfilename, ex.Message);
888                                 return false;
889                         } finally {
890                                 if (w != null)
891                                         w.Close ();
892                         }
893                 }
894
895                 //
896                 // Fixes full type name of each documented types/members up.
897                 //
898                 public void GenerateDocComment (Report r)
899                 {
900                         TypeContainer root = RootContext.ToplevelTypes;
901
902                         if (root.Types != null)
903                                 foreach (TypeContainer tc in root.Types)
904                                         DocUtil.GenerateTypeDocComment (tc, null, r);
905                 }
906         }
907 }