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