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