2 // doc.cs: Support for XML documentation comment.
5 // Atsushi Enomoto <atsushi@ximian.com>
7 // Licensed under the terms of the GNU GPL
9 // (C) 2004 Novell, Inc.
12 #if ! BOOTSTRAP_WITH_OLDLIB
14 using System.Collections;
15 using System.Collections.Specialized;
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;
26 using Mono.CompilerServices.SymbolWriter;
28 namespace Mono.CSharp {
31 // Support class for XML documentation.
46 // Generates xml doc comments (if any), and if required,
47 // handle warning report.
49 internal static void GenerateTypeDocComment (TypeContainer t,
52 GenerateDocComment (t, ds);
54 if (t.DefaultStaticConstructor != null)
55 t.DefaultStaticConstructor.GenerateDocComment (t);
57 if (t.InstanceConstructors != null)
58 foreach (Constructor c in t.InstanceConstructors)
59 c.GenerateDocComment (t);
62 foreach (TypeContainer tc in t.Types)
63 tc.GenerateDocComment (t);
65 if (t.Parts != null) {
66 IDictionary comments = RootContext.Documentation.PartialComments;
67 foreach (ClassPart cp in t.Parts) {
68 if (cp.DocComment == null)
75 foreach (Enum en in t.Enums)
76 en.GenerateDocComment (t);
78 if (t.Constants != null)
79 foreach (Const c in t.Constants)
80 c.GenerateDocComment (t);
83 foreach (FieldBase f in t.Fields)
84 f.GenerateDocComment (t);
87 foreach (Event e in t.Events)
88 e.GenerateDocComment (t);
90 if (t.Indexers != null)
91 foreach (Indexer ix in t.Indexers)
92 ix.GenerateDocComment (t);
94 if (t.Properties != null)
95 foreach (Property p in t.Properties)
96 p.GenerateDocComment (t);
98 if (t.Methods != null)
99 foreach (Method m in t.Methods)
100 m.GenerateDocComment (t);
102 if (t.Operators != null)
103 foreach (Operator o in t.Operators)
104 o.GenerateDocComment (t);
108 private static readonly string lineHead =
109 Environment.NewLine + " ";
111 private static XmlNode GetDocCommentNode (MemberCore mc,
114 // FIXME: It could be even optimizable as not
115 // to use XmlDocument. But anyways the nodes
116 // are not kept in memory.
117 XmlDocument doc = RootContext.Documentation.XmlDocumentation;
119 XmlElement el = doc.CreateElement ("member");
120 el.SetAttribute ("name", name);
121 string normalized = mc.DocComment;
122 el.InnerXml = normalized;
123 // csc keeps lines as written in the sources
124 // and inserts formatting indentation (which
125 // is different from XmlTextWriter.Formatting
126 // one), but when a start tag contains an
127 // endline, it joins the next line. We don't
128 // have to follow such a hacky behavior.
130 normalized.Split ('\n');
132 for (int i = 0; i < split.Length; i++) {
133 string s = split [i].TrimEnd ();
137 el.InnerXml = lineHead + String.Join (
138 lineHead, split, 0, j);
140 } catch (XmlException ex) {
141 Report.Warning (1570, 1, mc.Location, "XML comment on `{0}' has non-well-formed XML ({1})", name, ex.Message);
142 XmlComment com = doc.CreateComment (String.Format ("FIXME: Invalid documentation markup was found for member {0}", name));
148 // Generates xml doc comments (if any), and if required,
149 // handle warning report.
151 internal static void GenerateDocComment (MemberCore mc,
154 if (mc.DocComment != null) {
155 string name = mc.GetDocCommentName (ds);
157 XmlNode n = GetDocCommentNode (mc, name);
159 XmlElement el = n as XmlElement;
161 mc.OnGenerateDocComment (ds, el);
163 // FIXME: it could be done with XmlReader
164 XmlNodeList nl = n.SelectNodes (".//include");
166 // It could result in current node removal, so prepare another list to iterate.
167 ArrayList al = new ArrayList (nl.Count);
168 foreach (XmlNode inc in nl)
170 foreach (XmlElement inc in al)
171 if (!HandleInclude (mc, inc))
172 inc.ParentNode.RemoveChild (inc);
175 // FIXME: it could be done with XmlReader
176 DeclSpace dsTarget = mc as DeclSpace;
177 if (dsTarget == null)
180 foreach (XmlElement see in n.SelectNodes (".//see"))
181 HandleSee (mc, dsTarget, see);
182 foreach (XmlElement seealso in n.SelectNodes (".//seealso"))
183 HandleSeeAlso (mc, dsTarget, seealso);
184 foreach (XmlElement see in n.SelectNodes (".//exception"))
185 HandleException (mc, dsTarget, see);
188 n.WriteTo (RootContext.Documentation.XmlCommentOutput);
190 else if (mc.IsExposedFromAssembly (ds)) {
191 Constructor c = mc as Constructor;
192 if (c == null || !c.IsDefault ())
193 Report.Warning (1591, 4, mc.Location,
194 "Missing XML comment for publicly visible type or member `{0}'", mc.GetSignatureForError ());
199 // Processes "include" element. Check included file and
200 // embed the document content inside this documentation node.
202 private static bool HandleInclude (MemberCore mc, XmlElement el)
204 bool keepIncludeNode = false;
205 string file = el.GetAttribute ("file");
206 string path = el.GetAttribute ("path");
208 Report.Warning (1590, 1, mc.Location, "Invalid XML `include' element. Missing `file' attribute");
209 el.ParentNode.InsertBefore (el.OwnerDocument.CreateComment (" Include tag is invalid "), el);
210 keepIncludeNode = true;
212 else if (path == "") {
213 Report.Warning (1590, 1, mc.Location, "Invalid XML `include' element. Missing `path' attribute");
214 el.ParentNode.InsertBefore (el.OwnerDocument.CreateComment (" Include tag is invalid "), el);
215 keepIncludeNode = true;
218 XmlDocument doc = RootContext.Documentation.StoredDocuments [file] as XmlDocument;
221 doc = new XmlDocument ();
223 RootContext.Documentation.StoredDocuments.Add (file, doc);
224 } catch (Exception) {
225 el.ParentNode.InsertBefore (el.OwnerDocument.CreateComment (String.Format (" Badly formed XML in at comment file `{0}': cannot be included ", file)), el);
226 Report.Warning (1592, 1, mc.Location, "Badly formed XML in included comments file -- `{0}'", file);
231 XmlNodeList nl = doc.SelectNodes (path);
233 el.ParentNode.InsertBefore (el.OwnerDocument.CreateComment (" No matching elements were found for the include tag embedded here. "), el);
235 keepIncludeNode = true;
237 foreach (XmlNode n in nl)
238 el.ParentNode.InsertBefore (el.OwnerDocument.ImportNode (n, true), el);
239 } catch (Exception ex) {
240 el.ParentNode.InsertBefore (el.OwnerDocument.CreateComment (" Failed to insert some or all of included XML "), el);
241 Report.Warning (1589, 1, mc.Location, "Unable to include XML fragment `{0}' of file `{1}' ({2})", path, file, ex.Message);
245 return keepIncludeNode;
249 // Handles <see> elements.
251 private static void HandleSee (MemberCore mc,
252 DeclSpace ds, XmlElement see)
254 HandleXrefCommon (mc, ds, see);
258 // Handles <seealso> elements.
260 private static void HandleSeeAlso (MemberCore mc,
261 DeclSpace ds, XmlElement seealso)
263 HandleXrefCommon (mc, ds, seealso);
267 // Handles <exception> elements.
269 private static void HandleException (MemberCore mc,
270 DeclSpace ds, XmlElement seealso)
272 HandleXrefCommon (mc, ds, seealso);
275 static readonly char [] wsChars =
276 new char [] {' ', '\t', '\n', '\r'};
279 // returns a full runtime type name from a name which might
280 // be C# specific type name.
282 private static Type FindDocumentedType (MemberCore mc, string name, DeclSpace ds, string cref)
284 bool isArray = false;
285 string identifier = name;
286 if (name [name.Length - 1] == ']') {
287 string tmp = name.Substring (0, name.Length - 1).Trim (wsChars);
288 if (tmp [tmp.Length - 1] == '[') {
289 identifier = tmp.Substring (0, tmp.Length - 1).Trim (wsChars);
293 Type t = FindDocumentedTypeNonArray (mc, identifier, ds, cref);
294 if (t != null && isArray)
295 t = Array.CreateInstance (t, 0).GetType ();
299 private static Type FindDocumentedTypeNonArray (MemberCore mc,
300 string identifier, DeclSpace ds, string cref)
302 switch (identifier) {
306 return typeof (uint);
308 return typeof (short);
310 return typeof (ushort);
312 return typeof (long);
314 return typeof (ulong);
316 return typeof (float);
318 return typeof (double);
320 return typeof (char);
322 return typeof (decimal);
324 return typeof (byte);
326 return typeof (sbyte);
328 return typeof (object);
330 return typeof (bool);
332 return typeof (string);
334 return typeof (void);
336 FullNamedExpression e = ds.LookupType (identifier, mc.Location, false);
338 if (!(e is TypeExpr))
342 int index = identifier.LastIndexOf ('.');
346 Type parent = FindDocumentedType (mc, identifier.Substring (0, index), ds, cref);
349 // no need to detect warning 419 here
350 return FindDocumentedMember (mc, parent,
351 identifier.Substring (index + 1),
352 null, ds, out warn, cref, false, null).Member as Type;
355 private static MemberInfo [] empty_member_infos =
358 private static MemberInfo [] FindMethodBase (Type type,
359 BindingFlags bindingFlags, MethodSignature signature)
361 MemberList ml = TypeManager.FindMembers (
363 MemberTypes.Constructor | MemberTypes.Method | MemberTypes.Property | MemberTypes.Custom,
365 MethodSignature.method_signature_filter,
368 return empty_member_infos;
370 return FilterOverridenMembersOut (type, (MemberInfo []) ml);
373 static bool IsOverride (PropertyInfo deriv_prop, PropertyInfo base_prop)
375 if (!Invocation.IsAncestralType (base_prop.DeclaringType, deriv_prop.DeclaringType))
378 Type [] deriv_pd = TypeManager.GetArgumentTypes (deriv_prop);
379 Type [] base_pd = TypeManager.GetArgumentTypes (base_prop);
381 if (deriv_pd.Length != base_pd.Length)
384 for (int j = 0; j < deriv_pd.Length; ++j) {
385 if (deriv_pd [j] != base_pd [j])
387 Type ct = TypeManager.TypeToCoreType (deriv_pd [j]);
388 Type bt = TypeManager.TypeToCoreType (base_pd [j]);
397 private static MemberInfo [] FilterOverridenMembersOut (
398 Type type, MemberInfo [] ml)
401 return empty_member_infos;
403 ArrayList al = new ArrayList (ml.Length);
404 for (int i = 0; i < ml.Length; i++) {
405 MethodBase mx = ml [i] as MethodBase;
406 PropertyInfo px = ml [i] as PropertyInfo;
407 if (mx != null || px != null) {
408 bool overriden = false;
409 for (int j = 0; j < ml.Length; j++) {
412 MethodBase my = ml [j] as MethodBase;
413 if (mx != null && my != null &&
414 Invocation.IsOverride (my, mx)) {
420 PropertyInfo py = ml [j] as PropertyInfo;
421 if (px != null && py != null &&
422 IsOverride (py, px)) {
432 return al.ToArray (typeof (MemberInfo)) as MemberInfo [];
437 public static FoundMember Empty = new FoundMember (true);
440 public readonly MemberInfo Member;
441 public readonly Type Type;
443 public FoundMember (bool regardlessOfThisValueItsEmpty)
450 public FoundMember (Type foundType, MemberInfo member)
459 // Returns a MemberInfo that is referenced in XML documentation
460 // (by "see" or "seealso" elements).
462 private static FoundMember FindDocumentedMember (MemberCore mc,
463 Type type, string memberName, Type [] paramList,
464 DeclSpace ds, out int warningType, string cref,
465 bool warn419, string nameForError)
467 for (; type != null; type = type.DeclaringType) {
468 MemberInfo mi = FindDocumentedMemberNoNest (
469 mc, type, memberName, paramList, ds,
470 out warningType, cref, warn419,
473 return new FoundMember (type, mi);
476 return FoundMember.Empty;
479 private static MemberInfo FindDocumentedMemberNoNest (
480 MemberCore mc, Type type, string memberName,
481 Type [] paramList, DeclSpace ds, out int warningType,
482 string cref, bool warn419, string nameForError)
487 if (paramList == null) {
488 // search for fields/events etc.
489 mis = TypeManager.MemberLookup (type, null,
490 type, MemberTypes.All,
491 BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance,
493 mis = FilterOverridenMembersOut (type, mis);
494 if (mis == null || mis.Length == 0)
496 if (warn419 && IsAmbiguous (mis))
497 Report419 (mc, nameForError, mis);
501 MethodSignature msig = new MethodSignature (memberName, null, paramList);
502 mis = FindMethodBase (type,
503 BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance,
506 if (warn419 && mis.Length > 0) {
507 if (IsAmbiguous (mis))
508 Report419 (mc, nameForError, mis);
512 // search for operators (whose parameters exactly
513 // matches with the list) and possibly report CS1581.
515 string returnTypeName = null;
516 if (memberName.StartsWith ("implicit operator ")) {
517 oper = "op_Implicit";
518 returnTypeName = memberName.Substring (18).Trim (wsChars);
520 else if (memberName.StartsWith ("explicit operator ")) {
521 oper = "op_Explicit";
522 returnTypeName = memberName.Substring (18).Trim (wsChars);
524 else if (memberName.StartsWith ("operator ")) {
525 oper = memberName.Substring (9).Trim (wsChars);
527 // either unary or binary
529 oper = paramList.Length == 2 ?
530 Binary.oper_names [(int) Binary.Operator.Addition] :
531 Unary.oper_names [(int) Unary.Operator.UnaryPlus];
534 oper = paramList.Length == 2 ?
535 Binary.oper_names [(int) Binary.Operator.Subtraction] :
536 Unary.oper_names [(int) Unary.Operator.UnaryNegation];
540 oper = Unary.oper_names [(int) Unary.Operator.LogicalNot]; break;
542 oper = Unary.oper_names [(int) Unary.Operator.OnesComplement]; break;
545 oper = "op_Increment"; break;
547 oper = "op_Decrement"; break;
549 oper = "op_True"; break;
551 oper = "op_False"; break;
554 oper = Binary.oper_names [(int) Binary.Operator.Multiply]; break;
556 oper = Binary.oper_names [(int) Binary.Operator.Division]; break;
558 oper = Binary.oper_names [(int) Binary.Operator.Modulus]; break;
560 oper = Binary.oper_names [(int) Binary.Operator.BitwiseAnd]; break;
562 oper = Binary.oper_names [(int) Binary.Operator.BitwiseOr]; break;
564 oper = Binary.oper_names [(int) Binary.Operator.ExclusiveOr]; break;
566 oper = Binary.oper_names [(int) Binary.Operator.LeftShift]; break;
568 oper = Binary.oper_names [(int) Binary.Operator.RightShift]; break;
570 oper = Binary.oper_names [(int) Binary.Operator.Equality]; break;
572 oper = Binary.oper_names [(int) Binary.Operator.Inequality]; break;
574 oper = Binary.oper_names [(int) Binary.Operator.LessThan]; break;
576 oper = Binary.oper_names [(int) Binary.Operator.GreaterThan]; break;
578 oper = Binary.oper_names [(int) Binary.Operator.LessThanOrEqual]; break;
580 oper = Binary.oper_names [(int) Binary.Operator.GreaterThanOrEqual]; break;
583 Report.Warning (1020, 1, mc.Location, "Overloadable {0} operator is expected", paramList.Length == 2 ? "binary" : "unary");
584 Report.Warning (1584, 1, mc.Location, "XML comment on `{0}' has syntactically incorrect cref attribute `{1}'",
585 mc.GetSignatureForError (), cref);
589 // here we still don't consider return type (to
590 // detect CS1581 or CS1002+CS1584).
591 msig = new MethodSignature (oper, null, paramList);
593 mis = FindMethodBase (type,
594 BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance,
597 return null; // CS1574
598 MemberInfo mi = mis [0];
599 Type expected = mi is MethodInfo ?
600 ((MethodInfo) mi).ReturnType :
602 ((PropertyInfo) mi).PropertyType :
604 if (returnTypeName != null) {
605 Type returnType = FindDocumentedType (mc, returnTypeName, ds, cref);
606 if (returnType == null || returnType != expected) {
608 Report.Warning (1581, 1, mc.Location, "Invalid return type in XML comment cref attribute `{0}'", cref);
615 private static bool IsAmbiguous (MemberInfo [] members)
617 if (members.Length < 2)
619 if (members.Length > 2)
621 if (members [0] is EventInfo && members [1] is FieldInfo)
623 if (members [1] is EventInfo && members [0] is FieldInfo)
629 // Processes "see" or "seealso" elements.
630 // Checks cref attribute.
632 private static void HandleXrefCommon (MemberCore mc,
633 DeclSpace ds, XmlElement xref)
635 string cref = xref.GetAttribute ("cref").Trim (wsChars);
636 // when, XmlReader, "if (cref == null)"
637 if (!xref.HasAttribute ("cref"))
639 if (cref.Length == 0)
640 Report.Warning (1001, 1, mc.Location, "Identifier expected");
641 // ... and continue until CS1584.
643 string signature; // "x:" are stripped
644 string name; // method invokation "(...)" are removed
645 string parameters; // method parameter list
647 // strip 'T:' 'M:' 'F:' 'P:' 'E:' etc.
648 // Here, MS ignores its member kind. No idea why.
649 if (cref.Length > 2 && cref [1] == ':')
650 signature = cref.Substring (2).Trim (wsChars);
654 int parensPos = signature.IndexOf ('(');
655 int bracePos = parensPos >= 0 ? -1 :
656 signature.IndexOf ('[');
657 if (parensPos > 0 && signature [signature.Length - 1] == ')') {
658 name = signature.Substring (0, parensPos).Trim (wsChars);
659 parameters = signature.Substring (parensPos + 1, signature.Length - parensPos - 2).Trim (wsChars);
661 else if (bracePos > 0 && signature [signature.Length - 1] == ']') {
662 name = signature.Substring (0, bracePos).Trim (wsChars);
663 parameters = signature.Substring (bracePos + 1, signature.Length - bracePos - 2).Trim (wsChars);
669 Normalize (mc, ref name);
671 string identifier = GetBodyIdentifierFromName (name);
673 // Check if identifier is valid.
674 // This check is not necessary to mark as error, but
675 // csc specially reports CS1584 for wrong identifiers.
676 string [] nameElems = identifier.Split ('.');
677 for (int i = 0; i < nameElems.Length; i++) {
678 string nameElem = GetBodyIdentifierFromName (nameElems [i]);
680 Normalize (mc, ref nameElem);
681 if (!Tokenizer.IsValidIdentifier (nameElem)
682 && nameElem.IndexOf ("operator") < 0) {
683 Report.Warning (1584, 1, mc.Location, "XML comment on `{0}' has syntactically incorrect cref attribute `{1}'",
684 mc.GetSignatureForError (), cref);
685 xref.SetAttribute ("cref", "!:" + signature);
690 // check if parameters are valid
691 Type [] parameterTypes;
692 if (parameters == null)
693 parameterTypes = null;
694 else if (parameters.Length == 0)
695 parameterTypes = Type.EmptyTypes;
697 string [] paramList = parameters.Split (',');
698 ArrayList plist = new ArrayList ();
699 for (int i = 0; i < paramList.Length; i++) {
700 string paramTypeName = paramList [i].Trim (wsChars);
701 Normalize (mc, ref paramTypeName);
702 Type paramType = FindDocumentedType (mc, paramTypeName, ds, cref);
703 if (paramType == null) {
704 Report.Warning (1580, 1, mc.Location, "Invalid type for parameter `{0}' in XML comment cref attribute `{1}'",
705 (i + 1).ToString (), cref);
708 plist.Add (paramType);
710 parameterTypes = plist.ToArray (typeof (Type)) as Type [];
713 Type type = FindDocumentedType (mc, name, ds, cref);
715 // delegate must not be referenced with args
716 && (!type.IsSubclassOf (typeof (System.Delegate))
717 || parameterTypes == null)) {
718 string result = GetSignatureForDoc (type)
719 + (bracePos < 0 ? String.Empty : signature.Substring (bracePos));
720 xref.SetAttribute ("cref", "T:" + result);
724 int period = name.LastIndexOf ('.');
726 string typeName = name.Substring (0, period);
727 string memberName = name.Substring (period + 1);
728 Normalize (mc, ref memberName);
729 type = FindDocumentedType (mc, typeName, ds, cref);
732 FoundMember fm = FindDocumentedMember (mc, type, memberName, parameterTypes, ds, out warnResult, cref, true, name);
736 MemberInfo mi = fm.Member;
737 // we cannot use 'type' directly
738 // to get its name, since mi
739 // could be from DeclaringType
741 xref.SetAttribute ("cref", GetMemberDocHead (mi.MemberType) + GetSignatureForDoc (fm.Type) + "." + memberName + GetParametersFormatted (mi));
742 return; // a member of a type
748 FoundMember fm = FindDocumentedMember (mc, ds.TypeBuilder, name, parameterTypes, ds, out warnResult, cref, true, name);
752 MemberInfo mi = fm.Member;
753 // we cannot use 'type' directly
754 // to get its name, since mi
755 // could be from DeclaringType
757 xref.SetAttribute ("cref", GetMemberDocHead (mi.MemberType) + GetSignatureForDoc (fm.Type) + "." + name + GetParametersFormatted (mi));
758 return; // local member name
762 // It still might be part of namespace name.
763 Namespace ns = ds.NamespaceEntry.NS.GetNamespace (name, false);
765 xref.SetAttribute ("cref", "N:" + ns.FullName);
766 return; // a namespace
768 if (RootNamespace.Global.IsNamespace (name)) {
769 xref.SetAttribute ("cref", "N:" + name);
770 return; // a namespace
773 Report.Warning (1574, 1, mc.Location, "XML comment on `{0}' has cref attribute `{1}' that could not be resolved",
774 mc.GetSignatureForError (), cref);
776 xref.SetAttribute ("cref", "!:" + name);
779 static string GetParametersFormatted (MemberInfo mi)
781 MethodBase mb = mi as MethodBase;
782 bool isSetter = false;
783 PropertyInfo pi = mi as PropertyInfo;
785 mb = pi.GetGetMethod ();
788 mb = pi.GetSetMethod ();
794 ParameterData parameters = TypeManager.GetParameterData (mb);
795 if (parameters == null || parameters.Count == 0)
798 StringBuilder sb = new StringBuilder ();
800 for (int i = 0; i < parameters.Count; i++) {
801 if (isSetter && i + 1 == parameters.Count)
802 break; // skip "value".
805 Type t = parameters.ParameterType (i);
806 sb.Append (GetSignatureForDoc (t));
809 return sb.ToString ();
812 static string GetBodyIdentifierFromName (string name)
814 string identifier = name;
816 if (name.Length > 0 && name [name.Length - 1] == ']') {
817 string tmp = name.Substring (0, name.Length - 1).Trim (wsChars);
818 int last = tmp.LastIndexOf ('[');
820 identifier = tmp.Substring (0, last).Trim (wsChars);
826 static void Report419 (MemberCore mc, string memberName, MemberInfo [] mis)
828 Report.Warning (419, 3, mc.Location,
829 "Ambiguous reference in cref attribute `{0}'. Assuming `{1}' but other overloads including `{2}' have also matched",
831 TypeManager.GetFullNameSignature (mis [0]),
832 TypeManager.GetFullNameSignature (mis [1]));
836 // Get a prefix from member type for XML documentation (used
837 // to formalize cref target name).
839 static string GetMemberDocHead (MemberTypes type)
842 case MemberTypes.Constructor:
843 case MemberTypes.Method:
845 case MemberTypes.Event:
847 case MemberTypes.Field:
849 case MemberTypes.NestedType:
850 case MemberTypes.TypeInfo:
852 case MemberTypes.Property:
861 // Returns a string that represents the signature for this
862 // member which should be used in XML documentation.
864 public static string GetMethodDocCommentName (MethodCore mc, DeclSpace ds)
866 Parameter [] plist = mc.Parameters.FixedParameters;
867 string paramSpec = String.Empty;
869 StringBuilder psb = new StringBuilder ();
870 foreach (Parameter p in plist) {
871 psb.Append (psb.Length != 0 ? "," : "(");
872 psb.Append (GetSignatureForDoc (p.ExternalType ()));
874 paramSpec = psb.ToString ();
877 if (paramSpec.Length > 0)
880 string name = mc is Constructor ? "#ctor" : mc.Name;
881 string suffix = String.Empty;
882 Operator op = mc as Operator;
884 switch (op.OperatorType) {
885 case Operator.OpType.Implicit:
886 case Operator.OpType.Explicit:
887 suffix = "~" + GetSignatureForDoc (op.OperatorMethodBuilder.ReturnType);
891 return String.Concat (mc.DocCommentHeader, ds.Name, ".", name, paramSpec, suffix);
894 static string GetSignatureForDoc (Type type)
896 return TypeManager.IsGenericParameter (type) ?
897 "`" + type.GenericParameterPosition :
898 type.FullName.Replace ("+", ".").Replace ('&', '@');
902 // Raised (and passed an XmlElement that contains the comment)
903 // when GenerateDocComment is writing documentation expectedly.
905 // FIXME: with a few effort, it could be done with XmlReader,
906 // that means removal of DOM use.
908 internal static void OnMethodGenerateDocComment (
909 MethodCore mc, DeclSpace ds, XmlElement el)
911 Hashtable paramTags = new Hashtable ();
912 foreach (XmlElement pelem in el.SelectNodes ("param")) {
914 string xname = pelem.GetAttribute ("name");
916 continue; // really? but MS looks doing so
917 if (xname != "" && mc.Parameters.GetParameterByName (xname, out i) == null)
918 Report.Warning (1572, 2, mc.Location, "XML comment on `{0}' has a param tag for `{1}', but there is no parameter by that name",
919 mc.GetSignatureForError (), xname);
920 else if (paramTags [xname] != null)
921 Report.Warning (1571, 2, mc.Location, "XML comment on `{0}' has a duplicate param tag for `{1}'",
922 mc.GetSignatureForError (), xname);
923 paramTags [xname] = xname;
925 Parameter [] plist = mc.Parameters.FixedParameters;
926 foreach (Parameter p in plist) {
927 if (paramTags.Count > 0 && paramTags [p.Name] == null)
928 Report.Warning (1573, 4, mc.Location, "Parameter `{0}' has no matching param tag in the XML comment for `{1}'",
929 p.Name, mc.GetSignatureForError ());
933 private static void Normalize (MemberCore mc, ref string name)
935 if (name.Length > 0 && name [0] == '@')
936 name = name.Substring (1);
937 else if (name == "this")
939 else if (Tokenizer.IsKeyword (name) && !IsTypeName (name))
940 Report.Warning (1041, 1, mc.Location, "Identifier expected. `{0}' is a keyword", name);
943 private static bool IsTypeName (string name)
969 // Implements XML documentation generation.
971 public class Documentation
973 public Documentation (string xml_output_filename)
975 docfilename = xml_output_filename;
976 XmlDocumentation = new XmlDocument ();
977 XmlDocumentation.PreserveWhitespace = false;
980 private string docfilename;
983 // Used to create element which helps well-formedness checking.
985 public XmlDocument XmlDocumentation;
988 // The output for XML documentation.
990 public XmlWriter XmlCommentOutput;
993 // Stores XmlDocuments that are included in XML documentation.
994 // Keys are included filenames, values are XmlDocuments.
996 public Hashtable StoredDocuments = new Hashtable ();
999 // Stores comments on partial types (should handle uniquely).
1000 // Keys are PartialContainers, values are comment strings
1001 // (didn't use StringBuilder; usually we have just 2 or more).
1003 public IDictionary PartialComments = new ListDictionary ();
1006 // Outputs XML documentation comment from tokenized comments.
1008 public bool OutputDocComment (string asmfilename)
1010 XmlTextWriter w = null;
1012 w = new XmlTextWriter (docfilename, null);
1014 w.Formatting = Formatting.Indented;
1015 w.WriteStartDocument ();
1016 w.WriteStartElement ("doc");
1017 w.WriteStartElement ("assembly");
1018 w.WriteStartElement ("name");
1019 w.WriteString (Path.ChangeExtension (asmfilename, null));
1020 w.WriteEndElement (); // name
1021 w.WriteEndElement (); // assembly
1022 w.WriteStartElement ("members");
1023 XmlCommentOutput = w;
1024 GenerateDocComment ();
1025 w.WriteFullEndElement (); // members
1026 w.WriteEndElement ();
1027 w.WriteWhitespace (Environment.NewLine);
1028 w.WriteEndDocument ();
1030 } catch (Exception ex) {
1031 Report.Error (1569, "Error generating XML documentation file `{0}' (`{1}')", docfilename, ex.Message);
1040 // Fixes full type name of each documented types/members up.
1042 public void GenerateDocComment ()
1044 TypeContainer root = RootContext.Tree.Types;
1045 if (root.Interfaces != null)
1046 foreach (Interface i in root.Interfaces)
1047 DocUtil.GenerateTypeDocComment (i, null);
1049 if (root.Types != null)
1050 foreach (TypeContainer tc in root.Types)
1051 DocUtil.GenerateTypeDocComment (tc, null);
1053 if (root.Parts != null) {
1054 IDictionary comments = PartialComments;
1055 foreach (ClassPart cp in root.Parts) {
1056 if (cp.DocComment == null)
1062 if (root.Delegates != null)
1063 foreach (Delegate d in root.Delegates)
1064 DocUtil.GenerateDocComment (d, null);
1066 if (root.Enums != null)
1067 foreach (Enum e in root.Enums)
1068 e.GenerateDocComment (null);
1070 IDictionary table = new ListDictionary ();
1071 foreach (ClassPart cp in PartialComments.Keys) {
1072 // FIXME: IDictionary does not guarantee that the keys will be
1073 // accessed in the order they were added.
1074 table [cp.PartialContainer] += cp.DocComment;
1076 foreach (PartialContainer pc in table.Keys) {
1077 pc.DocComment = table [pc] as string;
1078 DocUtil.GenerateDocComment (pc, null);