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.
13 using System.Collections;
14 using System.Collections.Specialized;
16 using System.Reflection;
17 using System.Reflection.Emit;
18 using System.Runtime.CompilerServices;
19 using System.Runtime.InteropServices;
20 using System.Security;
21 using System.Security.Permissions;
25 using Mono.CompilerServices.SymbolWriter;
27 namespace Mono.CSharp {
30 // Support class for XML documentation.
32 public static class DocUtil
37 // Generates xml doc comments (if any), and if required,
38 // handle warning report.
40 internal static void GenerateTypeDocComment (TypeContainer t,
43 GenerateDocComment (t, ds);
45 if (t.DefaultStaticConstructor != null)
46 t.DefaultStaticConstructor.GenerateDocComment (t);
48 if (t.InstanceConstructors != null)
49 foreach (Constructor c in t.InstanceConstructors)
50 c.GenerateDocComment (t);
53 foreach (TypeContainer tc in t.Types)
54 tc.GenerateDocComment (t);
56 if (t.Parts != null) {
57 IDictionary comments = RootContext.Documentation.PartialComments;
58 foreach (ClassPart cp in t.Parts) {
59 if (cp.DocComment == null)
66 foreach (Enum en in t.Enums)
67 en.GenerateDocComment (t);
69 if (t.Constants != null)
70 foreach (Const c in t.Constants)
71 c.GenerateDocComment (t);
74 foreach (FieldBase f in t.Fields)
75 f.GenerateDocComment (t);
78 foreach (Event e in t.Events)
79 e.GenerateDocComment (t);
81 if (t.Indexers != null)
82 foreach (Indexer ix in t.Indexers)
83 ix.GenerateDocComment (t);
85 if (t.Properties != null)
86 foreach (Property p in t.Properties)
87 p.GenerateDocComment (t);
89 if (t.Methods != null)
90 foreach (Method m in t.Methods)
91 m.GenerateDocComment (t);
93 if (t.Operators != null)
94 foreach (Operator o in t.Operators)
95 o.GenerateDocComment (t);
99 private static readonly string lineHead =
100 Environment.NewLine + " ";
102 private static XmlNode GetDocCommentNode (MemberCore mc,
105 // FIXME: It could be even optimizable as not
106 // to use XmlDocument. But anyways the nodes
107 // are not kept in memory.
108 XmlDocument doc = RootContext.Documentation.XmlDocumentation;
110 XmlElement el = doc.CreateElement ("member");
111 el.SetAttribute ("name", name);
112 string normalized = mc.DocComment;
113 el.InnerXml = normalized;
114 // csc keeps lines as written in the sources
115 // and inserts formatting indentation (which
116 // is different from XmlTextWriter.Formatting
117 // one), but when a start tag contains an
118 // endline, it joins the next line. We don't
119 // have to follow such a hacky behavior.
121 normalized.Split ('\n');
123 for (int i = 0; i < split.Length; i++) {
124 string s = split [i].TrimEnd ();
128 el.InnerXml = lineHead + String.Join (
129 lineHead, split, 0, j);
131 } catch (XmlException ex) {
132 Report.Warning (1570, 1, mc.Location, "XML comment on `{0}' has non-well-formed XML ({1})", name, ex.Message);
133 XmlComment com = doc.CreateComment (String.Format ("FIXME: Invalid documentation markup was found for member {0}", name));
139 // Generates xml doc comments (if any), and if required,
140 // handle warning report.
142 internal static void GenerateDocComment (MemberCore mc,
145 if (mc.DocComment != null) {
146 string name = mc.GetDocCommentName (ds);
148 XmlNode n = GetDocCommentNode (mc, name);
150 XmlElement el = n as XmlElement;
152 mc.OnGenerateDocComment (ds, el);
154 // FIXME: it could be done with XmlReader
155 foreach (XmlElement inc in n.SelectNodes (".//include"))
156 HandleInclude (mc, inc);
158 // FIXME: it could be done with XmlReader
159 DeclSpace dsTarget = mc as DeclSpace;
160 if (dsTarget == null)
163 foreach (XmlElement see in n.SelectNodes (".//see"))
164 HandleSee (mc, dsTarget, see);
165 foreach (XmlElement seealso in n.SelectNodes (".//seealso"))
166 HandleSeeAlso (mc, dsTarget, seealso);
167 foreach (XmlElement see in n.SelectNodes (".//exception"))
168 HandleException (mc, dsTarget, see);
171 n.WriteTo (RootContext.Documentation.XmlCommentOutput);
173 else if (mc.IsExposedFromAssembly (ds)) {
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 ());
182 // Processes "include" element. Check included file and
183 // embed the document content inside this documentation node.
185 private static void HandleInclude (MemberCore mc, XmlElement el)
187 string file = el.GetAttribute ("file");
188 string path = el.GetAttribute ("path");
190 Report.Warning (1590, 1, mc.Location, "Invalid XML `include' element. Missing `file' attribute");
191 el.ParentNode.InsertBefore (el.OwnerDocument.CreateComment (" Include tag is invalid "), el);
193 else if (path == "") {
194 Report.Warning (1590, 1, mc.Location, "Invalid XML `include' element. Missing `path' attribute");
195 el.ParentNode.InsertBefore (el.OwnerDocument.CreateComment (" Include tag is invalid "), el);
198 XmlDocument doc = RootContext.Documentation.StoredDocuments [file] as XmlDocument;
201 doc = new XmlDocument ();
203 RootContext.Documentation.StoredDocuments.Add (file, doc);
204 } catch (Exception) {
205 el.ParentNode.InsertBefore (el.OwnerDocument.CreateComment (String.Format (" Badly formed XML in at comment file `{0}': cannot be included ", file)), el);
206 Report.Warning (1592, 1, mc.Location, "Badly formed XML in included comments file -- `{0}'", file);
209 bool keepIncludeNode = false;
212 XmlNodeList nl = doc.SelectNodes (path);
214 el.ParentNode.InsertBefore (el.OwnerDocument.CreateComment (" No matching elements were found for the include tag embedded here. "), el);
216 keepIncludeNode = true;
218 foreach (XmlNode n in nl)
219 el.ParentNode.InsertBefore (el.OwnerDocument.ImportNode (n, true), el);
220 } catch (Exception ex) {
221 el.ParentNode.InsertBefore (el.OwnerDocument.CreateComment (" Failed to insert some or all of included XML "), el);
222 Report.Warning (1589, 1, mc.Location, "Unable to include XML fragment `{0}' of file `{1}' ({2})", path, file, ex.Message);
225 if (!keepIncludeNode)
226 el.ParentNode.RemoveChild (el);
231 // Handles <see> elements.
233 private static void HandleSee (MemberCore mc,
234 DeclSpace ds, XmlElement see)
236 HandleXrefCommon (mc, ds, see);
240 // Handles <seealso> elements.
242 private static void HandleSeeAlso (MemberCore mc,
243 DeclSpace ds, XmlElement seealso)
245 HandleXrefCommon (mc, ds, seealso);
249 // Handles <exception> elements.
251 private static void HandleException (MemberCore mc,
252 DeclSpace ds, XmlElement seealso)
254 HandleXrefCommon (mc, ds, seealso);
257 static readonly char [] wsChars =
258 new char [] {' ', '\t', '\n', '\r'};
261 // returns a full runtime type name from a name which might
262 // be C# specific type name.
264 private static Type FindDocumentedType (MemberCore mc, string name, DeclSpace ds, string cref)
266 bool isArray = false;
267 string identifier = name;
268 if (name [name.Length - 1] == ']') {
269 string tmp = name.Substring (0, name.Length - 1).Trim (wsChars);
270 if (tmp [tmp.Length - 1] == '[') {
271 identifier = tmp.Substring (0, tmp.Length - 1).Trim (wsChars);
275 Type t = FindDocumentedTypeNonArray (mc, identifier, ds, cref);
276 if (t != null && isArray)
277 t = Array.CreateInstance (t, 0).GetType ();
281 private static Type FindDocumentedTypeNonArray (MemberCore mc,
282 string identifier, DeclSpace ds, string cref)
284 switch (identifier) {
288 return typeof (uint);
290 return typeof (short);
292 return typeof (ushort);
294 return typeof (long);
296 return typeof (ulong);
298 return typeof (float);
300 return typeof (double);
302 return typeof (char);
304 return typeof (decimal);
306 return typeof (byte);
308 return typeof (sbyte);
310 return typeof (object);
312 return typeof (bool);
314 return typeof (string);
316 return typeof (void);
318 FullNamedExpression e = ds.LookupType (identifier, mc.Location, false);
320 if (!(e is TypeExpr))
324 int index = identifier.LastIndexOf ('.');
328 Type parent = FindDocumentedType (mc, identifier.Substring (0, index), ds, cref);
331 // no need to detect warning 419 here
332 return FindDocumentedMember (mc, parent,
333 identifier.Substring (index + 1),
334 null, ds, out warn, cref, false, null).Member as Type;
337 private static MemberInfo [] empty_member_infos =
340 private static MemberInfo [] FindMethodBase (Type type,
341 BindingFlags bindingFlags, MethodSignature signature)
343 MemberList ml = TypeManager.FindMembers (
345 MemberTypes.Constructor | MemberTypes.Method | MemberTypes.Property | MemberTypes.Custom,
347 MethodSignature.method_signature_filter,
350 return empty_member_infos;
352 return FilterOverridenMembersOut (type, (MemberInfo []) ml);
355 static bool IsOverride (PropertyInfo deriv_prop, PropertyInfo base_prop)
357 if (!Invocation.IsAncestralType (base_prop.DeclaringType, deriv_prop.DeclaringType))
360 Type [] deriv_pd = TypeManager.GetArgumentTypes (deriv_prop);
361 Type [] base_pd = TypeManager.GetArgumentTypes (base_prop);
363 if (deriv_pd.Length != base_pd.Length)
366 for (int j = 0; j < deriv_pd.Length; ++j) {
367 if (deriv_pd [j] != base_pd [j])
369 Type ct = TypeManager.TypeToCoreType (deriv_pd [j]);
370 Type bt = TypeManager.TypeToCoreType (base_pd [j]);
379 private static MemberInfo [] FilterOverridenMembersOut (
380 Type type, MemberInfo [] ml)
383 return empty_member_infos;
385 ArrayList al = new ArrayList (ml.Length);
386 for (int i = 0; i < ml.Length; i++) {
387 MethodBase mx = ml [i] as MethodBase;
388 PropertyInfo px = ml [i] as PropertyInfo;
389 if (mx != null || px != null) {
390 bool overriden = false;
391 for (int j = 0; j < ml.Length; j++) {
394 MethodBase my = ml [j] as MethodBase;
395 if (mx != null && my != null &&
396 Invocation.IsOverride (my, mx)) {
402 PropertyInfo py = ml [j] as PropertyInfo;
403 if (px != null && py != null &&
404 IsOverride (py, px)) {
414 return al.ToArray (typeof (MemberInfo)) as MemberInfo [];
419 public static FoundMember Empty = new FoundMember (true);
422 public readonly MemberInfo Member;
423 public readonly Type Type;
425 public FoundMember (bool regardlessOfThisValueItsEmpty)
432 public FoundMember (Type foundType, MemberInfo member)
441 // Returns a MemberInfo that is referenced in XML documentation
442 // (by "see" or "seealso" elements).
444 private static FoundMember FindDocumentedMember (MemberCore mc,
445 Type type, string memberName, Type [] paramList,
446 DeclSpace ds, out int warningType, string cref,
447 bool warn419, string nameForError)
449 for (; type != null; type = type.DeclaringType) {
450 MemberInfo mi = FindDocumentedMemberNoNest (
451 mc, type, memberName, paramList, ds,
452 out warningType, cref, warn419,
455 return new FoundMember (type, mi);
458 return FoundMember.Empty;
461 private static MemberInfo FindDocumentedMemberNoNest (
462 MemberCore mc, Type type, string memberName,
463 Type [] paramList, DeclSpace ds, out int warningType,
464 string cref, bool warn419, string nameForError)
469 if (paramList == null) {
470 // search for fields/events etc.
471 mis = TypeManager.MemberLookup (type, null,
472 type, MemberTypes.All,
473 BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance,
475 mis = FilterOverridenMembersOut (type, mis);
476 if (mis == null || mis.Length == 0)
478 if (warn419 && IsAmbiguous (mis))
479 Report419 (mc, nameForError, mis);
483 MethodSignature msig = new MethodSignature (memberName, null, paramList);
484 mis = FindMethodBase (type,
485 BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance,
488 if (warn419 && mis.Length > 0) {
489 if (IsAmbiguous (mis))
490 Report419 (mc, nameForError, mis);
494 // search for operators (whose parameters exactly
495 // matches with the list) and possibly report CS1581.
497 string returnTypeName = null;
498 if (memberName.StartsWith ("implicit operator ")) {
499 oper = "op_Implicit";
500 returnTypeName = memberName.Substring (18).Trim (wsChars);
502 else if (memberName.StartsWith ("explicit operator ")) {
503 oper = "op_Explicit";
504 returnTypeName = memberName.Substring (18).Trim (wsChars);
506 else if (memberName.StartsWith ("operator ")) {
507 oper = memberName.Substring (9).Trim (wsChars);
509 // either unary or binary
511 oper = paramList.Length == 2 ?
512 Binary.oper_names [(int) Binary.Operator.Addition] :
513 Unary.oper_names [(int) Unary.Operator.UnaryPlus];
516 oper = paramList.Length == 2 ?
517 Binary.oper_names [(int) Binary.Operator.Subtraction] :
518 Unary.oper_names [(int) Unary.Operator.UnaryNegation];
522 oper = Unary.oper_names [(int) Unary.Operator.LogicalNot]; break;
524 oper = Unary.oper_names [(int) Unary.Operator.OnesComplement]; break;
527 oper = "op_Increment"; break;
529 oper = "op_Decrement"; break;
531 oper = "op_True"; break;
533 oper = "op_False"; break;
536 oper = Binary.oper_names [(int) Binary.Operator.Multiply]; break;
538 oper = Binary.oper_names [(int) Binary.Operator.Division]; break;
540 oper = Binary.oper_names [(int) Binary.Operator.Modulus]; break;
542 oper = Binary.oper_names [(int) Binary.Operator.BitwiseAnd]; break;
544 oper = Binary.oper_names [(int) Binary.Operator.BitwiseOr]; break;
546 oper = Binary.oper_names [(int) Binary.Operator.ExclusiveOr]; break;
548 oper = Binary.oper_names [(int) Binary.Operator.LeftShift]; break;
550 oper = Binary.oper_names [(int) Binary.Operator.RightShift]; break;
552 oper = Binary.oper_names [(int) Binary.Operator.Equality]; break;
554 oper = Binary.oper_names [(int) Binary.Operator.Inequality]; break;
556 oper = Binary.oper_names [(int) Binary.Operator.LessThan]; break;
558 oper = Binary.oper_names [(int) Binary.Operator.GreaterThan]; break;
560 oper = Binary.oper_names [(int) Binary.Operator.LessThanOrEqual]; break;
562 oper = Binary.oper_names [(int) Binary.Operator.GreaterThanOrEqual]; break;
565 Report.Warning (1020, 1, mc.Location, "Overloadable {0} operator is expected", paramList.Length == 2 ? "binary" : "unary");
566 Report.Warning (1584, 1, mc.Location, "XML comment on `{0}' has syntactically incorrect cref attribute `{1}'",
567 mc.GetSignatureForError (), cref);
571 // here we still don't consider return type (to
572 // detect CS1581 or CS1002+CS1584).
573 msig = new MethodSignature (oper, null, paramList);
575 mis = FindMethodBase (type,
576 BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance,
579 return null; // CS1574
580 MemberInfo mi = mis [0];
581 Type expected = mi is MethodInfo ?
582 ((MethodInfo) mi).ReturnType :
584 ((PropertyInfo) mi).PropertyType :
586 if (returnTypeName != null) {
587 Type returnType = FindDocumentedType (mc, returnTypeName, ds, cref);
588 if (returnType == null || returnType != expected) {
590 Report.Warning (1581, 1, mc.Location, "Invalid return type in XML comment cref attribute `{0}'", cref);
597 private static bool IsAmbiguous (MemberInfo [] members)
599 if (members.Length < 2)
601 if (members.Length > 2)
603 if (members [0] is EventInfo && members [1] is FieldInfo)
605 if (members [1] is EventInfo && members [0] is FieldInfo)
611 // Processes "see" or "seealso" elements.
612 // Checks cref attribute.
614 private static void HandleXrefCommon (MemberCore mc,
615 DeclSpace ds, XmlElement xref)
617 string cref = xref.GetAttribute ("cref").Trim (wsChars);
618 // when, XmlReader, "if (cref == null)"
619 if (!xref.HasAttribute ("cref"))
621 if (cref.Length == 0)
622 Report.Warning (1001, 1, mc.Location, "Identifier expected");
623 // ... and continue until CS1584.
625 string signature; // "x:" are stripped
626 string name; // method invokation "(...)" are removed
627 string parameters; // method parameter list
629 // strip 'T:' 'M:' 'F:' 'P:' 'E:' etc.
630 // Here, MS ignores its member kind. No idea why.
631 if (cref.Length > 2 && cref [1] == ':')
632 signature = cref.Substring (2).Trim (wsChars);
636 int parensPos = signature.IndexOf ('(');
637 int bracePos = parensPos >= 0 ? -1 :
638 signature.IndexOf ('[');
639 if (parensPos > 0 && signature [signature.Length - 1] == ')') {
640 name = signature.Substring (0, parensPos).Trim (wsChars);
641 parameters = signature.Substring (parensPos + 1, signature.Length - parensPos - 2).Trim (wsChars);
643 else if (bracePos > 0 && signature [signature.Length - 1] == ']') {
644 name = signature.Substring (0, bracePos).Trim (wsChars);
645 parameters = signature.Substring (bracePos + 1, signature.Length - bracePos - 2).Trim (wsChars);
651 Normalize (mc, ref name);
653 string identifier = GetBodyIdentifierFromName (name);
655 // Check if identifier is valid.
656 // This check is not necessary to mark as error, but
657 // csc specially reports CS1584 for wrong identifiers.
658 string [] nameElems = identifier.Split ('.');
659 for (int i = 0; i < nameElems.Length; i++) {
660 string nameElem = GetBodyIdentifierFromName (nameElems [i]);
662 Normalize (mc, ref nameElem);
663 if (!Tokenizer.IsValidIdentifier (nameElem)
664 && nameElem.IndexOf ("operator") < 0) {
665 Report.Warning (1584, 1, mc.Location, "XML comment on `{0}' has syntactically incorrect cref attribute `{1}'",
666 mc.GetSignatureForError (), cref);
667 xref.SetAttribute ("cref", "!:" + signature);
672 // check if parameters are valid
673 Type [] parameterTypes;
674 if (parameters == null)
675 parameterTypes = null;
676 else if (parameters.Length == 0)
677 parameterTypes = Type.EmptyTypes;
679 string [] paramList = parameters.Split (',');
680 ArrayList plist = new ArrayList ();
681 for (int i = 0; i < paramList.Length; i++) {
682 string paramTypeName = paramList [i].Trim (wsChars);
683 Normalize (mc, ref paramTypeName);
684 Type paramType = FindDocumentedType (mc, paramTypeName, ds, cref);
685 if (paramType == null) {
686 Report.Warning (1580, 1, mc.Location, "Invalid type for parameter `{0}' in XML comment cref attribute `{1}'",
687 (i + 1).ToString (), cref);
690 plist.Add (paramType);
692 parameterTypes = plist.ToArray (typeof (Type)) as Type [];
695 Type type = FindDocumentedType (mc, name, ds, cref);
697 // delegate must not be referenced with args
698 && (!type.IsSubclassOf (typeof (System.Delegate))
699 || parameterTypes == null)) {
700 string result = type.FullName.Replace ("+", ".")
701 + (bracePos < 0 ? String.Empty : signature.Substring (bracePos));
702 xref.SetAttribute ("cref", "T:" + result);
706 int period = name.LastIndexOf ('.');
708 string typeName = name.Substring (0, period);
709 string memberName = name.Substring (period + 1);
710 Normalize (mc, ref memberName);
711 type = FindDocumentedType (mc, typeName, ds, cref);
714 FoundMember fm = FindDocumentedMember (mc, type, memberName, parameterTypes, ds, out warnResult, cref, true, name);
718 MemberInfo mi = fm.Member;
719 // we cannot use 'type' directly
720 // to get its name, since mi
721 // could be from DeclaringType
723 xref.SetAttribute ("cref", GetMemberDocHead (mi.MemberType) + fm.Type.FullName.Replace ("+", ".") + "." + memberName + GetParametersFormatted (mi));
724 return; // a member of a type
730 FoundMember fm = FindDocumentedMember (mc, ds.TypeBuilder, name, parameterTypes, ds, out warnResult, cref, true, name);
734 MemberInfo mi = fm.Member;
735 // we cannot use 'type' directly
736 // to get its name, since mi
737 // could be from DeclaringType
739 xref.SetAttribute ("cref", GetMemberDocHead (mi.MemberType) + fm.Type.FullName.Replace ("+", ".") + "." + name + GetParametersFormatted (mi));
740 return; // local member name
744 // It still might be part of namespace name.
745 Namespace ns = ds.NamespaceEntry.NS.GetNamespace (name, false);
747 xref.SetAttribute ("cref", "N:" + ns.FullName);
748 return; // a namespace
750 if (RootNamespace.Global.IsNamespace (name)) {
751 xref.SetAttribute ("cref", "N:" + name);
752 return; // a namespace
755 Report.Warning (1574, 1, mc.Location, "XML comment on `{0}' has cref attribute `{1}' that could not be resolved",
756 mc.GetSignatureForError (), cref);
758 xref.SetAttribute ("cref", "!:" + name);
761 static string GetParametersFormatted (MemberInfo mi)
763 MethodBase mb = mi as MethodBase;
764 bool isSetter = false;
765 PropertyInfo pi = mi as PropertyInfo;
767 mb = pi.GetGetMethod ();
770 mb = pi.GetSetMethod ();
776 ParameterData parameters = TypeManager.GetParameterData (mb);
777 if (parameters == null || parameters.Count == 0)
780 StringBuilder sb = new StringBuilder ();
782 for (int i = 0; i < parameters.Count; i++) {
783 if (isSetter && i + 1 == parameters.Count)
784 break; // skip "value".
787 Type t = parameters.ParameterType (i);
788 sb.Append (t.FullName.Replace ('+', '.').Replace ('&', '@'));
791 return sb.ToString ();
794 static string GetBodyIdentifierFromName (string name)
796 string identifier = name;
798 if (name.Length > 0 && name [name.Length - 1] == ']') {
799 string tmp = name.Substring (0, name.Length - 1).Trim (wsChars);
800 int last = tmp.LastIndexOf ('[');
802 identifier = tmp.Substring (0, last).Trim (wsChars);
808 static void Report419 (MemberCore mc, string memberName, MemberInfo [] mis)
810 Report.Warning (419, 3, mc.Location,
811 "Ambiguous reference in cref attribute `{0}'. Assuming `{1}' but other overloads including `{2}' have also matched",
813 TypeManager.GetFullNameSignature (mis [0]),
814 TypeManager.GetFullNameSignature (mis [1]));
818 // Get a prefix from member type for XML documentation (used
819 // to formalize cref target name).
821 static string GetMemberDocHead (MemberTypes type)
824 case MemberTypes.Constructor:
825 case MemberTypes.Method:
827 case MemberTypes.Event:
829 case MemberTypes.Field:
831 case MemberTypes.NestedType:
832 case MemberTypes.TypeInfo:
834 case MemberTypes.Property:
843 // Returns a string that represents the signature for this
844 // member which should be used in XML documentation.
846 public static string GetMethodDocCommentName (MethodCore mc, DeclSpace ds)
848 Parameter [] plist = mc.Parameters.FixedParameters;
849 string paramSpec = String.Empty;
851 StringBuilder psb = new StringBuilder ();
852 foreach (Parameter p in plist) {
853 psb.Append (psb.Length != 0 ? "," : "(");
854 psb.Append (p.ExternalType ().FullName.Replace ("+", ".").Replace ('&', '@'));
856 paramSpec = psb.ToString ();
859 if (paramSpec.Length > 0)
862 string name = mc is Constructor ? "#ctor" : mc.Name;
863 string suffix = String.Empty;
864 Operator op = mc as Operator;
866 switch (op.OperatorType) {
867 case Operator.OpType.Implicit:
868 case Operator.OpType.Explicit:
869 suffix = "~" + op.OperatorMethodBuilder.ReturnType.FullName.Replace ('+', '.');
873 return String.Concat (mc.DocCommentHeader, ds.Name, ".", name, paramSpec, suffix);
877 // Raised (and passed an XmlElement that contains the comment)
878 // when GenerateDocComment is writing documentation expectedly.
880 // FIXME: with a few effort, it could be done with XmlReader,
881 // that means removal of DOM use.
883 internal static void OnMethodGenerateDocComment (
884 MethodCore mc, DeclSpace ds, XmlElement el)
886 Hashtable paramTags = new Hashtable ();
887 foreach (XmlElement pelem in el.SelectNodes ("param")) {
889 string xname = pelem.GetAttribute ("name");
891 continue; // really? but MS looks doing so
892 if (xname != "" && mc.Parameters.GetParameterByName (xname, out i) == null)
893 Report.Warning (1572, 2, mc.Location, "XML comment on `{0}' has a param tag for `{1}', but there is no parameter by that name",
894 mc.GetSignatureForError (), xname);
895 else if (paramTags [xname] != null)
896 Report.Warning (1571, 2, mc.Location, "XML comment on `{0}' has a duplicate param tag for `{1}'",
897 mc.GetSignatureForError (), xname);
898 paramTags [xname] = xname;
900 Parameter [] plist = mc.Parameters.FixedParameters;
901 foreach (Parameter p in plist) {
902 if (paramTags.Count > 0 && paramTags [p.Name] == null)
903 Report.Warning (1573, 4, mc.Location, "Parameter `{0}' has no matching param tag in the XML comment for `{1}'",
904 p.Name, mc.GetSignatureForError ());
908 private static void Normalize (MemberCore mc, ref string name)
910 if (name.Length > 0 && name [0] == '@')
911 name = name.Substring (1);
912 else if (name == "this")
914 else if (Tokenizer.IsKeyword (name) && !IsTypeName (name))
915 Report.Warning (1041, 1, mc.Location, "Identifier expected. `{0}' is a keyword", name);
918 private static bool IsTypeName (string name)
944 // Implements XML documentation generation.
946 public class Documentation
948 public Documentation (string xml_output_filename)
950 docfilename = xml_output_filename;
951 XmlDocumentation = new XmlDocument ();
952 XmlDocumentation.PreserveWhitespace = false;
955 private string docfilename;
958 // Used to create element which helps well-formedness checking.
960 public XmlDocument XmlDocumentation;
963 // The output for XML documentation.
965 public XmlWriter XmlCommentOutput;
968 // Stores XmlDocuments that are included in XML documentation.
969 // Keys are included filenames, values are XmlDocuments.
971 public Hashtable StoredDocuments = new Hashtable ();
974 // Stores comments on partial types (should handle uniquely).
975 // Keys are PartialContainers, values are comment strings
976 // (didn't use StringBuilder; usually we have just 2 or more).
978 public IDictionary PartialComments = new ListDictionary ();
981 // Outputs XML documentation comment from tokenized comments.
983 public bool OutputDocComment (string asmfilename)
985 XmlTextWriter w = null;
987 w = new XmlTextWriter (docfilename, null);
989 w.Formatting = Formatting.Indented;
990 w.WriteStartDocument ();
991 w.WriteStartElement ("doc");
992 w.WriteStartElement ("assembly");
993 w.WriteStartElement ("name");
994 w.WriteString (Path.ChangeExtension (asmfilename, null));
995 w.WriteEndElement (); // name
996 w.WriteEndElement (); // assembly
997 w.WriteStartElement ("members");
998 XmlCommentOutput = w;
999 GenerateDocComment ();
1000 w.WriteFullEndElement (); // members
1001 w.WriteEndElement ();
1002 w.WriteWhitespace (Environment.NewLine);
1003 w.WriteEndDocument ();
1005 } catch (Exception ex) {
1006 Report.Error (1569, "Error generating XML documentation file `{0}' (`{1}')", docfilename, ex.Message);
1015 // Fixes full type name of each documented types/members up.
1017 public void GenerateDocComment ()
1019 TypeContainer root = RootContext.Tree.Types;
1020 if (root.Interfaces != null)
1021 foreach (Interface i in root.Interfaces)
1022 DocUtil.GenerateTypeDocComment (i, null);
1024 if (root.Types != null)
1025 foreach (TypeContainer tc in root.Types)
1026 DocUtil.GenerateTypeDocComment (tc, null);
1028 if (root.Parts != null) {
1029 IDictionary comments = PartialComments;
1030 foreach (ClassPart cp in root.Parts) {
1031 if (cp.DocComment == null)
1037 if (root.Delegates != null)
1038 foreach (Delegate d in root.Delegates)
1039 DocUtil.GenerateDocComment (d, null);
1041 if (root.Enums != null)
1042 foreach (Enum e in root.Enums)
1043 e.GenerateDocComment (null);
1045 IDictionary table = new ListDictionary ();
1046 foreach (ClassPart cp in PartialComments.Keys) {
1047 // FIXME: IDictionary does not guarantee that the keys will be
1048 // accessed in the order they were added.
1049 table [cp.PartialContainer] += cp.DocComment;
1051 foreach (PartialContainer pc in table.Keys) {
1052 pc.DocComment = table [pc] as string;
1053 DocUtil.GenerateDocComment (pc, null);