[xbuild] Add new reserved properties $(MSBuildThisFile*).
[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                         switch (identifier) {
275                         case "int":
276                                 return TypeManager.int32_type;
277                         case "uint":
278                                 return TypeManager.uint32_type;
279                         case "short":
280                                 return TypeManager.short_type;;
281                         case "ushort":
282                                 return TypeManager.ushort_type;
283                         case "long":
284                                 return TypeManager.int64_type;
285                         case "ulong":
286                                 return TypeManager.uint64_type;;
287                         case "float":
288                                 return TypeManager.float_type;;
289                         case "double":
290                                 return TypeManager.double_type;
291                         case "char":
292                                 return TypeManager.char_type;;
293                         case "decimal":
294                                 return TypeManager.decimal_type;;
295                         case "byte":
296                                 return TypeManager.byte_type;;
297                         case "sbyte":
298                                 return TypeManager.sbyte_type;;
299                         case "object":
300                                 return TypeManager.object_type;;
301                         case "bool":
302                                 return TypeManager.bool_type;;
303                         case "string":
304                                 return TypeManager.string_type;;
305                         case "void":
306                                 return TypeManager.void_type;;
307                         }
308                         FullNamedExpression e = ds.LookupNamespaceOrType (identifier, 0, mc.Location, false);
309                         if (e != null) {
310                                 if (!(e is TypeExpr))
311                                         return null;
312                                 return e.Type;
313                         }
314                         int index = identifier.LastIndexOf ('.');
315                         if (index < 0)
316                                 return null;
317
318                         var nsName = identifier.Substring (0, index);
319                         var typeName = identifier.Substring (index + 1);
320                         Namespace ns = ds.NamespaceEntry.NS.GetNamespace (nsName, false);
321                         ns = ns ?? mc.Module.GlobalRootNamespace.GetNamespace(nsName, false);
322                         if (ns != null) {
323                                 var te = ns.LookupType(mc, typeName, 0, true, mc.Location);
324                                 if(te != null)
325                                         return te.Type;
326                         }
327
328                         int warn;
329                         TypeSpec 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                         var ts = FindDocumentedMember (mc, parent,
334                                 identifier.Substring (index + 1),
335                                 null, ds, out warn, cref, false, null, r) as TypeSpec;
336                         if (ts != null)
337                                 return ts;
338                         return null;
339                 }
340
341                 //
342                 // Returns a MemberInfo that is referenced in XML documentation
343                 // (by "see" or "seealso" elements).
344                 //
345                 private static MemberSpec FindDocumentedMember (MemberCore mc,
346                         TypeSpec type, string member_name, AParametersCollection param_list, 
347                         DeclSpace ds, out int warning_type, string cref,
348                         bool warn419, string name_for_error, Report r)
349                 {
350 //                      for (; type != null; type = type.DeclaringType) {
351                                 var mi = FindDocumentedMemberNoNest (
352                                         mc, type, member_name, param_list, ds,
353                                         out warning_type, cref, warn419,
354                                         name_for_error, r);
355                                 if (mi != null)
356                                         return mi; // new FoundMember (type, mi);
357 //                      }
358                         warning_type = 0;
359                         return null;
360                 }
361
362                 private static MemberSpec FindDocumentedMemberNoNest (
363                         MemberCore mc, TypeSpec type, string member_name,
364                         AParametersCollection param_list, DeclSpace ds, out int warning_type, 
365                         string cref, bool warn419, string name_for_error, Report Report)
366                 {
367                         warning_type = 0;
368 //                      var filter = new MemberFilter (member_name, 0, MemberKind.All, param_list, null);
369                         IList<MemberSpec> found = null;
370                         while (type != null && found == null) {
371                                 found = MemberCache.FindMembers (type, member_name, false);
372                                 type = type.DeclaringType;
373                         }
374
375                         if (found == null)
376                                 return null;
377
378                         if (warn419 && found.Count > 1) {
379                                 Report419 (mc, name_for_error, found.ToArray (), Report);
380                         }
381
382                         return found [0];
383
384 /*
385                         if (param_list == null) {
386                                 // search for fields/events etc.
387                                 mis = TypeManager.MemberLookup (type, null,
388                                         type, MemberKind.All,
389                                         BindingRestriction.None,
390                                         member_name, null);
391                                 mis = FilterOverridenMembersOut (mis);
392                                 if (mis == null || mis.Length == 0)
393                                         return null;
394                                 if (warn419 && IsAmbiguous (mis))
395                                         Report419 (mc, name_for_error, mis, Report);
396                                 return mis [0];
397                         }
398
399                         MethodSignature msig = new MethodSignature (member_name, null, param_list);
400                         mis = FindMethodBase (type, 
401                                 BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance,
402                                 msig);
403
404                         if (warn419 && mis.Length > 0) {
405                                 if (IsAmbiguous (mis))
406                                         Report419 (mc, name_for_error, mis, Report);
407                                 return mis [0];
408                         }
409
410                         // search for operators (whose parameters exactly
411                         // matches with the list) and possibly report CS1581.
412                         string oper = null;
413                         string return_type_name = null;
414                         if (member_name.StartsWith ("implicit operator ")) {
415                                 Operator.GetMetadataName (Operator.OpType.Implicit);
416                                 return_type_name = member_name.Substring (18).Trim (wsChars);
417                         }
418                         else if (member_name.StartsWith ("explicit operator ")) {
419                                 oper = Operator.GetMetadataName (Operator.OpType.Explicit);
420                                 return_type_name = member_name.Substring (18).Trim (wsChars);
421                         }
422                         else if (member_name.StartsWith ("operator ")) {
423                                 oper = member_name.Substring (9).Trim (wsChars);
424                                 switch (oper) {
425                                 // either unary or binary
426                                 case "+":
427                                         oper = param_list.Length == 2 ?
428                                                 Operator.GetMetadataName (Operator.OpType.Addition) :
429                                                 Operator.GetMetadataName (Operator.OpType.UnaryPlus);
430                                         break;
431                                 case "-":
432                                         oper = param_list.Length == 2 ?
433                                                 Operator.GetMetadataName (Operator.OpType.Subtraction) :
434                                                 Operator.GetMetadataName (Operator.OpType.UnaryNegation);
435                                         break;
436                                 default:
437                                         oper = Operator.GetMetadataName (oper);
438                                         if (oper != null)
439                                                 break;
440
441                                         warning_type = 1584;
442                                         Report.Warning (1020, 1, mc.Location, "Overloadable {0} operator is expected", param_list.Length == 2 ? "binary" : "unary");
443                                         Report.Warning (1584, 1, mc.Location, "XML comment on `{0}' has syntactically incorrect cref attribute `{1}'",
444                                                 mc.GetSignatureForError (), cref);
445                                         return null;
446                                 }
447                         }
448                         // here we still don't consider return type (to
449                         // detect CS1581 or CS1002+CS1584).
450                         msig = new MethodSignature (oper, null, param_list);
451
452                         mis = FindMethodBase (type, 
453                                 BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance,
454                                 msig);
455                         if (mis.Length == 0)
456                                 return null; // CS1574
457                         var mi = mis [0];
458                         TypeSpec expected = mi is MethodSpec ?
459                                 ((MethodSpec) mi).ReturnType :
460                                 mi is PropertySpec ?
461                                 ((PropertySpec) mi).PropertyType :
462                                 null;
463                         if (return_type_name != null) {
464                                 TypeSpec returnType = FindDocumentedType (mc, return_type_name, ds, cref, Report);
465                                 if (returnType == null || returnType != expected) {
466                                         warning_type = 1581;
467                                         Report.Warning (1581, 1, mc.Location, "Invalid return type in XML comment cref attribute `{0}'", cref);
468                                         return null;
469                                 }
470                         }
471                         return mis [0];
472 */ 
473                 }
474
475                 //
476                 // Processes "see" or "seealso" elements.
477                 // Checks cref attribute.
478                 //
479                 private static void HandleXrefCommon (MemberCore mc,
480                         DeclSpace ds, XmlElement xref, Report Report)
481                 {
482                         string cref = xref.GetAttribute ("cref").Trim (wsChars);
483                         // when, XmlReader, "if (cref == null)"
484                         if (!xref.HasAttribute ("cref"))
485                                 return;
486                         if (cref.Length == 0)
487                                 Report.Warning (1001, 1, mc.Location, "Identifier expected");
488                                 // ... and continue until CS1584.
489
490                         string signature; // "x:" are stripped
491                         string name; // method invokation "(...)" are removed
492                         string parameters; // method parameter list
493
494                         // When it found '?:' ('T:' 'M:' 'F:' 'P:' 'E:' etc.),
495                         // MS ignores not only its member kind, but also
496                         // the entire syntax correctness. Nor it also does
497                         // type fullname resolution i.e. "T:List(int)" is kept
498                         // as T:List(int), not
499                         // T:System.Collections.Generic.List&lt;System.Int32&gt;
500                         if (cref.Length > 2 && cref [1] == ':')
501                                 return;
502                         else
503                                 signature = cref;
504
505                         // Also note that without "T:" any generic type 
506                         // indication fails.
507
508                         int parens_pos = signature.IndexOf ('(');
509                         int brace_pos = parens_pos >= 0 ? -1 :
510                                 signature.IndexOf ('[');
511                         if (parens_pos > 0 && signature [signature.Length - 1] == ')') {
512                                 name = signature.Substring (0, parens_pos).Trim (wsChars);
513                                 parameters = signature.Substring (parens_pos + 1, signature.Length - parens_pos - 2).Trim (wsChars);
514                         }
515                         else if (brace_pos > 0 && signature [signature.Length - 1] == ']') {
516                                 name = signature.Substring (0, brace_pos).Trim (wsChars);
517                                 parameters = signature.Substring (brace_pos + 1, signature.Length - brace_pos - 2).Trim (wsChars);
518                         }
519                         else {
520                                 name = signature;
521                                 parameters = null;
522                         }
523                         Normalize (mc, ref name, Report);
524
525                         string identifier = GetBodyIdentifierFromName (name);
526
527                         // Check if identifier is valid.
528                         // This check is not necessary to mark as error, but
529                         // csc specially reports CS1584 for wrong identifiers.
530                         string [] name_elems = identifier.Split ('.');
531                         for (int i = 0; i < name_elems.Length; i++) {
532                                 string nameElem = GetBodyIdentifierFromName (name_elems [i]);
533                                 if (i > 0)
534                                         Normalize (mc, ref nameElem, Report);
535                                 if (!Tokenizer.IsValidIdentifier (nameElem)
536                                         && nameElem.IndexOf ("operator") < 0) {
537                                         Report.Warning (1584, 1, mc.Location, "XML comment on `{0}' has syntactically incorrect cref attribute `{1}'",
538                                                 mc.GetSignatureForError (), cref);
539                                         xref.SetAttribute ("cref", "!:" + signature);
540                                         return;
541                                 }
542                         }
543
544                         // check if parameters are valid
545                         AParametersCollection parameter_types;
546                         if (parameters == null)
547                                 parameter_types = null;
548                         else if (parameters.Length == 0)
549                                 parameter_types = ParametersCompiled.EmptyReadOnlyParameters;
550                         else {
551                                 string [] param_list = parameters.Split (',');
552                                 var plist = new List<TypeSpec> ();
553                                 for (int i = 0; i < param_list.Length; i++) {
554                                         string param_type_name = param_list [i].Trim (wsChars);
555                                         Normalize (mc, ref param_type_name, Report);
556                                         TypeSpec param_type = FindDocumentedType (mc, param_type_name, ds, cref, Report);
557                                         if (param_type == null) {
558                                                 Report.Warning (1580, 1, mc.Location, "Invalid type for parameter `{0}' in XML comment cref attribute `{1}'",
559                                                         (i + 1).ToString (), cref);
560                                                 return;
561                                         }
562                                         plist.Add (param_type);
563                                 }
564
565                                 parameter_types = ParametersCompiled.CreateFullyResolved (plist.ToArray ());
566                         }
567
568                         TypeSpec type = FindDocumentedType (mc, name, ds, cref, Report);
569                         if (type != null
570                                 // delegate must not be referenced with args
571                                 && (!type.IsDelegate
572                                 || parameter_types == null)) {
573                                 string result = GetSignatureForDoc (type)
574                                         + (brace_pos < 0 ? String.Empty : signature.Substring (brace_pos));
575                                 xref.SetAttribute ("cref", "T:" + result);
576                                 return; // a type
577                         }
578
579                         int period = name.LastIndexOf ('.');
580                         if (period > 0) {
581                                 string typeName = name.Substring (0, period);
582                                 string member_name = name.Substring (period + 1);
583                                 string lookup_name = member_name == "this" ? MemberCache.IndexerNameAlias : member_name;
584                                 Normalize (mc, ref lookup_name, Report);
585                                 Normalize (mc, ref member_name, Report);
586                                 type = FindDocumentedType (mc, typeName, ds, cref, Report);
587                                 int warn_result;
588                                 if (type != null) {
589                                         var mi = FindDocumentedMember (mc, type, lookup_name, parameter_types, ds, out warn_result, cref, true, name, Report);
590                                         if (warn_result > 0)
591                                                 return;
592                                         if (mi != null) {
593                                                 // we cannot use 'type' directly
594                                                 // to get its name, since mi
595                                                 // could be from DeclaringType
596                                                 // for nested types.
597                                                 xref.SetAttribute ("cref", GetMemberDocHead (mi) + GetSignatureForDoc (mi.DeclaringType) + "." + member_name + GetParametersFormatted (mi));
598                                                 return; // a member of a type
599                                         }
600                                 }
601                         } else {
602                                 int warn_result;
603                                 var mi = FindDocumentedMember (mc, ds.PartialContainer.Definition, name, parameter_types, ds, out warn_result, cref, true, name, Report);
604
605                                 if (warn_result > 0)
606                                         return;
607                                 if (mi != null) {
608                                         // we cannot use 'type' directly
609                                         // to get its name, since mi
610                                         // could be from DeclaringType
611                                         // for nested types.
612                                         xref.SetAttribute ("cref", GetMemberDocHead (mi) + GetSignatureForDoc (mi.DeclaringType) + "." + name + GetParametersFormatted (mi));
613                                         return; // local member name
614                                 }
615                         }
616
617                         // It still might be part of namespace name.
618                         Namespace ns = ds.NamespaceEntry.NS.GetNamespace (name, false);
619                         if (ns != null) {
620                                 xref.SetAttribute ("cref", "N:" + ns.GetSignatureForError ());
621                                 return; // a namespace
622                         }
623                         if (mc.Module.GlobalRootNamespace.IsNamespace (name)) {
624                                 xref.SetAttribute ("cref", "N:" + name);
625                                 return; // a namespace
626                         }
627
628                         Report.Warning (1574, 1, mc.Location, "XML comment on `{0}' has cref attribute `{1}' that could not be resolved",
629                                 mc.GetSignatureForError (), cref);
630
631                         xref.SetAttribute ("cref", "!:" + name);
632                 }
633
634                 static string GetParametersFormatted (MemberSpec mi)
635                 {
636                         var pm = mi as IParametersMember;
637                         if (pm == null || pm.Parameters.IsEmpty)
638                                 return string.Empty;
639
640                         AParametersCollection parameters = pm.Parameters;
641 /*
642                         if (parameters == null || parameters.Count == 0)
643                                 return String.Empty;
644 */
645                         StringBuilder sb = new StringBuilder ();
646                         sb.Append ('(');
647                         for (int i = 0; i < parameters.Count; i++) {
648 //                              if (is_setter && i + 1 == parameters.Count)
649 //                                      break; // skip "value".
650                                 if (i > 0)
651                                         sb.Append (',');
652                                 TypeSpec t = parameters.Types [i];
653                                 sb.Append (GetSignatureForDoc (t));
654                         }
655                         sb.Append (')');
656                         return sb.ToString ();
657                 }
658
659                 static string GetBodyIdentifierFromName (string name)
660                 {
661                         string identifier = name;
662
663                         if (name.Length > 0 && name [name.Length - 1] == ']') {
664                                 string tmp = name.Substring (0, name.Length - 1).Trim (wsChars);
665                                 int last = tmp.LastIndexOf ('[');
666                                 if (last > 0)
667                                         identifier = tmp.Substring (0, last).Trim (wsChars);
668                         }
669
670                         return identifier;
671                 }
672
673                 static void Report419 (MemberCore mc, string member_name, MemberSpec [] mis, Report Report)
674                 {
675                         Report.Warning (419, 3, mc.Location, 
676                                 "Ambiguous reference in cref attribute `{0}'. Assuming `{1}' but other overloads including `{2}' have also matched",
677                                 member_name,
678                                 TypeManager.GetFullNameSignature (mis [0]),
679                                 TypeManager.GetFullNameSignature (mis [1]));
680                 }
681
682                 //
683                 // Get a prefix from member type for XML documentation (used
684                 // to formalize cref target name).
685                 //
686                 static string GetMemberDocHead (MemberSpec type)
687                 {
688                         if (type is FieldSpec)
689                                 return "F:";
690                         if (type is MethodSpec)
691                                 return "M:";
692                         if (type is EventSpec)
693                                 return "E:";
694                         if (type is PropertySpec)
695                                 return "P:";
696                         if (type is TypeSpec)
697                                 return "T:";
698
699                         return "!:";
700                 }
701
702                 // MethodCore
703
704                 //
705                 // Returns a string that represents the signature for this 
706                 // member which should be used in XML documentation.
707                 //
708                 public static string GetMethodDocCommentName (MemberCore mc, ParametersCompiled parameters, DeclSpace ds)
709                 {
710                         IParameterData [] plist = parameters.FixedParameters;
711                         string paramSpec = String.Empty;
712                         if (plist != null) {
713                                 StringBuilder psb = new StringBuilder ();
714                                 int i = 0;
715                                 foreach (Parameter p in plist) {
716                                         psb.Append (psb.Length != 0 ? "," : "(");
717                                         psb.Append (GetSignatureForDoc (parameters.Types [i++]));
718                                         if ((p.ModFlags & Parameter.Modifier.ISBYREF) != 0)
719                                                 psb.Append ('@');
720                                 }
721                                 paramSpec = psb.ToString ();
722                         }
723
724                         if (paramSpec.Length > 0)
725                                 paramSpec += ")";
726
727                         string name = mc.Name;
728                         if (mc is Constructor)
729                                 name = "#ctor";
730                         else if (mc is InterfaceMemberBase) {
731                                 var imb = (InterfaceMemberBase) mc;
732                                 name = imb.GetFullName (imb.ShortName);
733                         }
734                         name = name.Replace ('.', '#');
735
736                         if (mc.MemberName.TypeArguments != null && mc.MemberName.TypeArguments.Count > 0)
737                                 name += "``" + mc.MemberName.CountTypeArguments;
738
739                         string suffix = String.Empty;
740                         Operator op = mc as Operator;
741                         if (op != null) {
742                                 switch (op.OperatorType) {
743                                 case Operator.OpType.Implicit:
744                                 case Operator.OpType.Explicit:
745                                         suffix = "~" + GetSignatureForDoc (op.ReturnType);
746                                         break;
747                                 }
748                         }
749                         return String.Concat (mc.DocCommentHeader, ds.Name, ".", name, paramSpec, suffix);
750                 }
751
752                 static string GetSignatureForDoc (TypeSpec type)
753                 {
754                         var tp = type as TypeParameterSpec;
755                         if (tp != null) {
756                                 int c = 0;
757                                 type = type.DeclaringType;
758                                 while (type != null && type.DeclaringType != null) {
759                                         type = type.DeclaringType;
760                                         c += type.MemberDefinition.TypeParametersCount;
761                                 }
762                                 var prefix = tp.IsMethodOwned ? "``" : "`";
763                                 return prefix + (c + tp.DeclaredPosition);
764                         }
765
766                         var pp = type as PointerContainer;
767                         if (pp != null)
768                                 return GetSignatureForDoc (pp.Element) + "*";
769
770                         ArrayContainer ap = type as ArrayContainer;
771                         if (ap != null)
772                                 return GetSignatureForDoc (ap.Element) +
773                                         ArrayContainer.GetPostfixSignature (ap.Rank);
774
775                         if (TypeManager.IsGenericType (type)) {
776                                 string g = type.MemberDefinition.Namespace;
777                                 if (g != null && g.Length > 0)
778                                         g += '.';
779                                 int idx = type.Name.LastIndexOf ('`');
780                                 g += (idx < 0 ? type.Name : type.Name.Substring (0, idx)) + '{';
781                                 int argpos = 0;
782                                 foreach (TypeSpec t in TypeManager.GetTypeArguments (type))
783                                         g += (argpos++ > 0 ? "," : String.Empty) + GetSignatureForDoc (t);
784                                 g += '}';
785                                 return g;
786                         }
787
788                         string name = type.GetMetaInfo ().FullName != null ? type.GetMetaInfo ().FullName : type.Name;
789                         return name.Replace ("+", ".").Replace ('&', '@');
790                 }
791
792                 //
793                 // Raised (and passed an XmlElement that contains the comment)
794                 // when GenerateDocComment is writing documentation expectedly.
795                 //
796                 // FIXME: with a few effort, it could be done with XmlReader,
797                 // that means removal of DOM use.
798                 //
799                 internal static void OnMethodGenerateDocComment (
800                         MethodCore mc, XmlElement el, Report Report)
801                 {
802                         var paramTags = new Dictionary<string, string> ();
803                         foreach (XmlElement pelem in el.SelectNodes ("param")) {
804                                 string xname = pelem.GetAttribute ("name");
805                                 if (xname.Length == 0)
806                                         continue; // really? but MS looks doing so
807                                 if (xname != "" && mc.ParameterInfo.GetParameterIndexByName (xname) < 0)
808                                         Report.Warning (1572, 2, mc.Location, "XML comment on `{0}' has a param tag for `{1}', but there is no parameter by that name",
809                                                 mc.GetSignatureForError (), xname);
810                                 else if (paramTags.ContainsKey (xname))
811                                         Report.Warning (1571, 2, mc.Location, "XML comment on `{0}' has a duplicate param tag for `{1}'",
812                                                 mc.GetSignatureForError (), xname);
813                                 paramTags [xname] = xname;
814                         }
815                         IParameterData [] plist = mc.ParameterInfo.FixedParameters;
816                         foreach (Parameter p in plist) {
817                                 if (paramTags.Count > 0 && !paramTags.ContainsKey (p.Name))
818                                         Report.Warning (1573, 4, mc.Location, "Parameter `{0}' has no matching param tag in the XML comment for `{1}'",
819                                                 p.Name, mc.GetSignatureForError ());
820                         }
821                 }
822
823                 private static void Normalize (MemberCore mc, ref string name, Report Report)
824                 {
825                         if (name.Length > 0 && name [0] == '@')
826                                 name = name.Substring (1);
827                         else if (name == "this")
828                                 name = "Item";
829                         else if (Tokenizer.IsKeyword (name) && !IsTypeName (name))
830                                 Report.Warning (1041, 1, mc.Location, "Identifier expected. `{0}' is a keyword", name);
831                 }
832
833                 private static bool IsTypeName (string name)
834                 {
835                         switch (name) {
836                         case "bool":
837                         case "byte":
838                         case "char":
839                         case "decimal":
840                         case "double":
841                         case "float":
842                         case "int":
843                         case "long":
844                         case "object":
845                         case "sbyte":
846                         case "short":
847                         case "string":
848                         case "uint":
849                         case "ulong":
850                         case "ushort":
851                         case "void":
852                                 return true;
853                         }
854                         return false;
855                 }
856         }
857
858         //
859         // Implements XML documentation generation.
860         //
861         public class Documentation
862         {
863                 public Documentation (string xml_output_filename)
864                 {
865                         docfilename = xml_output_filename;
866                         XmlDocumentation = new XmlDocument ();
867                         XmlDocumentation.PreserveWhitespace = false;
868                 }
869
870                 private string docfilename;
871
872                 //
873                 // Used to create element which helps well-formedness checking.
874                 //
875                 public XmlDocument XmlDocumentation;
876
877                 //
878                 // The output for XML documentation.
879                 //
880                 public XmlWriter XmlCommentOutput;
881
882                 //
883                 // Stores XmlDocuments that are included in XML documentation.
884                 // Keys are included filenames, values are XmlDocuments.
885                 //
886                 public Dictionary<string, XmlDocument> StoredDocuments = new Dictionary<string, XmlDocument> ();
887
888                 //
889                 // Outputs XML documentation comment from tokenized comments.
890                 //
891                 public bool OutputDocComment (string asmfilename, Report Report)
892                 {
893                         XmlTextWriter w = null;
894                         try {
895                                 w = new XmlTextWriter (docfilename, null);
896                                 w.Indentation = 4;
897                                 w.Formatting = Formatting.Indented;
898                                 w.WriteStartDocument ();
899                                 w.WriteStartElement ("doc");
900                                 w.WriteStartElement ("assembly");
901                                 w.WriteStartElement ("name");
902                                 w.WriteString (Path.ChangeExtension (asmfilename, null));
903                                 w.WriteEndElement (); // name
904                                 w.WriteEndElement (); // assembly
905                                 w.WriteStartElement ("members");
906                                 XmlCommentOutput = w;
907                                 GenerateDocComment (Report);
908                                 w.WriteFullEndElement (); // members
909                                 w.WriteEndElement ();
910                                 w.WriteWhitespace (Environment.NewLine);
911                                 w.WriteEndDocument ();
912                                 return true;
913                         } catch (Exception ex) {
914                                 Report.Error (1569, "Error generating XML documentation file `{0}' (`{1}')", docfilename, ex.Message);
915                                 return false;
916                         } finally {
917                                 if (w != null)
918                                         w.Close ();
919                         }
920                 }
921
922                 //
923                 // Fixes full type name of each documented types/members up.
924                 //
925                 public void GenerateDocComment (Report r)
926                 {
927                         TypeContainer root = RootContext.ToplevelTypes;
928
929                         if (root.Types != null)
930                                 foreach (TypeContainer tc in root.Types)
931                                         DocUtil.GenerateTypeDocComment (tc, null, r);
932                 }
933         }
934 }