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.Delegates != null)
66 foreach (Delegate de in t.Delegates)
67 de.GenerateDocComment (t);
69 foreach (Enum en in t.Enums)
70 en.GenerateDocComment (t);
72 if (t.Constants != null)
73 foreach (Const c in t.Constants)
74 c.GenerateDocComment (t);
77 foreach (FieldBase f in t.Fields)
78 f.GenerateDocComment (t);
81 foreach (Event e in t.Events)
82 e.GenerateDocComment (t);
84 if (t.Indexers != null)
85 foreach (Indexer ix in t.Indexers)
86 ix.GenerateDocComment (t);
88 if (t.Properties != null)
89 foreach (Property p in t.Properties)
90 p.GenerateDocComment (t);
92 if (t.Methods != null)
93 foreach (Method m in t.Methods)
94 m.GenerateDocComment (t);
96 if (t.Operators != null)
97 foreach (Operator o in t.Operators)
98 o.GenerateDocComment (t);
102 private static readonly string lineHead =
103 Environment.NewLine + " ";
105 private static XmlNode GetDocCommentNode (MemberCore mc,
108 // FIXME: It could be even optimizable as not
109 // to use XmlDocument. But anyways the nodes
110 // are not kept in memory.
111 XmlDocument doc = RootContext.Documentation.XmlDocumentation;
113 XmlElement el = doc.CreateElement ("member");
114 el.SetAttribute ("name", name);
115 string normalized = mc.DocComment;
116 el.InnerXml = normalized;
117 // csc keeps lines as written in the sources
118 // and inserts formatting indentation (which
119 // is different from XmlTextWriter.Formatting
120 // one), but when a start tag contains an
121 // endline, it joins the next line. We don't
122 // have to follow such a hacky behavior.
124 normalized.Split ('\n');
126 for (int i = 0; i < split.Length; i++) {
127 string s = split [i].TrimEnd ();
131 el.InnerXml = lineHead + String.Join (
132 lineHead, split, 0, j);
134 } catch (XmlException ex) {
135 Report.Warning (1570, 1, mc.Location, "XML comment on `{0}' has non-well-formed XML ({1})", name, ex.Message);
136 XmlComment com = doc.CreateComment (String.Format ("FIXME: Invalid documentation markup was found for member {0}", name));
142 // Generates xml doc comments (if any), and if required,
143 // handle warning report.
145 internal static void GenerateDocComment (MemberCore mc,
148 if (mc.DocComment != null) {
149 string name = mc.GetDocCommentName (ds);
151 XmlNode n = GetDocCommentNode (mc, name);
153 XmlElement el = n as XmlElement;
155 mc.OnGenerateDocComment (el);
157 // FIXME: it could be done with XmlReader
158 XmlNodeList nl = n.SelectNodes (".//include");
160 // It could result in current node removal, so prepare another list to iterate.
161 ArrayList al = new ArrayList (nl.Count);
162 foreach (XmlNode inc in nl)
164 foreach (XmlElement inc in al)
165 if (!HandleInclude (mc, inc))
166 inc.ParentNode.RemoveChild (inc);
169 // FIXME: it could be done with XmlReader
170 DeclSpace dsTarget = mc as DeclSpace;
171 if (dsTarget == null)
174 foreach (XmlElement see in n.SelectNodes (".//see"))
175 HandleSee (mc, dsTarget, see);
176 foreach (XmlElement seealso in n.SelectNodes (".//seealso"))
177 HandleSeeAlso (mc, dsTarget, seealso);
178 foreach (XmlElement see in n.SelectNodes (".//exception"))
179 HandleException (mc, dsTarget, see);
182 n.WriteTo (RootContext.Documentation.XmlCommentOutput);
184 else if (mc.IsExposedFromAssembly ()) {
185 Constructor c = mc as Constructor;
186 if (c == null || !c.IsDefault ())
187 Report.Warning (1591, 4, mc.Location,
188 "Missing XML comment for publicly visible type or member `{0}'", mc.GetSignatureForError ());
193 // Processes "include" element. Check included file and
194 // embed the document content inside this documentation node.
196 private static bool HandleInclude (MemberCore mc, XmlElement el)
198 bool keepIncludeNode = false;
199 string file = el.GetAttribute ("file");
200 string path = el.GetAttribute ("path");
202 Report.Warning (1590, 1, mc.Location, "Invalid XML `include' element. Missing `file' attribute");
203 el.ParentNode.InsertBefore (el.OwnerDocument.CreateComment (" Include tag is invalid "), el);
204 keepIncludeNode = true;
206 else if (path.Length == 0) {
207 Report.Warning (1590, 1, mc.Location, "Invalid XML `include' element. Missing `path' attribute");
208 el.ParentNode.InsertBefore (el.OwnerDocument.CreateComment (" Include tag is invalid "), el);
209 keepIncludeNode = true;
212 XmlDocument doc = RootContext.Documentation.StoredDocuments [file] as XmlDocument;
215 doc = new XmlDocument ();
217 RootContext.Documentation.StoredDocuments.Add (file, doc);
218 } catch (Exception) {
219 el.ParentNode.InsertBefore (el.OwnerDocument.CreateComment (String.Format (" Badly formed XML in at comment file `{0}': cannot be included ", file)), el);
220 Report.Warning (1592, 1, mc.Location, "Badly formed XML in included comments file -- `{0}'", file);
225 XmlNodeList nl = doc.SelectNodes (path);
227 el.ParentNode.InsertBefore (el.OwnerDocument.CreateComment (" No matching elements were found for the include tag embedded here. "), el);
229 keepIncludeNode = true;
231 foreach (XmlNode n in nl)
232 el.ParentNode.InsertBefore (el.OwnerDocument.ImportNode (n, true), el);
233 } catch (Exception ex) {
234 el.ParentNode.InsertBefore (el.OwnerDocument.CreateComment (" Failed to insert some or all of included XML "), el);
235 Report.Warning (1589, 1, mc.Location, "Unable to include XML fragment `{0}' of file `{1}' ({2})", path, file, ex.Message);
239 return keepIncludeNode;
243 // Handles <see> elements.
245 private static void HandleSee (MemberCore mc,
246 DeclSpace ds, XmlElement see)
248 HandleXrefCommon (mc, ds, see);
252 // Handles <seealso> elements.
254 private static void HandleSeeAlso (MemberCore mc,
255 DeclSpace ds, XmlElement seealso)
257 HandleXrefCommon (mc, ds, seealso);
261 // Handles <exception> elements.
263 private static void HandleException (MemberCore mc,
264 DeclSpace ds, XmlElement seealso)
266 HandleXrefCommon (mc, ds, seealso);
269 static readonly char [] wsChars =
270 new char [] {' ', '\t', '\n', '\r'};
273 // returns a full runtime type name from a name which might
274 // be C# specific type name.
276 private static Type FindDocumentedType (MemberCore mc, string name, DeclSpace ds, string cref)
278 bool isArray = false;
279 string identifier = name;
280 if (name [name.Length - 1] == ']') {
281 string tmp = name.Substring (0, name.Length - 1).Trim (wsChars);
282 if (tmp [tmp.Length - 1] == '[') {
283 identifier = tmp.Substring (0, tmp.Length - 1).Trim (wsChars);
287 Type t = FindDocumentedTypeNonArray (mc, identifier, ds, cref);
288 if (t != null && isArray)
289 t = Array.CreateInstance (t, 0).GetType ();
293 private static Type FindDocumentedTypeNonArray (MemberCore mc,
294 string identifier, DeclSpace ds, string cref)
296 switch (identifier) {
300 return typeof (uint);
302 return typeof (short);
304 return typeof (ushort);
306 return typeof (long);
308 return typeof (ulong);
310 return typeof (float);
312 return typeof (double);
314 return typeof (char);
316 return typeof (decimal);
318 return typeof (byte);
320 return typeof (sbyte);
322 return typeof (object);
324 return typeof (bool);
326 return typeof (string);
328 return typeof (void);
330 FullNamedExpression e = ds.LookupType (identifier, mc.Location, false);
332 if (!(e is TypeExpr))
336 int index = identifier.LastIndexOf ('.');
340 Type parent = FindDocumentedType (mc, identifier.Substring (0, index), ds, cref);
343 // no need to detect warning 419 here
344 return FindDocumentedMember (mc, parent,
345 identifier.Substring (index + 1),
346 null, ds, out warn, cref, false, null).Member as Type;
349 private static MemberInfo [] empty_member_infos =
352 private static MemberInfo [] FindMethodBase (Type type,
353 BindingFlags bindingFlags, MethodSignature signature)
355 MemberList ml = TypeManager.FindMembers (
357 MemberTypes.Constructor | MemberTypes.Method | MemberTypes.Property | MemberTypes.Custom,
359 MethodSignature.method_signature_filter,
362 return empty_member_infos;
364 return FilterOverridenMembersOut ((MemberInfo []) ml);
367 static bool IsOverride (PropertyInfo deriv_prop, PropertyInfo base_prop)
369 if (!Invocation.IsAncestralType (base_prop.DeclaringType, deriv_prop.DeclaringType))
372 Type [] deriv_pd = TypeManager.GetArgumentTypes (deriv_prop);
373 Type [] base_pd = TypeManager.GetArgumentTypes (base_prop);
375 if (deriv_pd.Length != base_pd.Length)
378 for (int j = 0; j < deriv_pd.Length; ++j) {
379 if (deriv_pd [j] != base_pd [j])
381 Type ct = TypeManager.TypeToCoreType (deriv_pd [j]);
382 Type bt = TypeManager.TypeToCoreType (base_pd [j]);
391 private static MemberInfo [] FilterOverridenMembersOut (
395 return empty_member_infos;
397 ArrayList al = new ArrayList (ml.Length);
398 for (int i = 0; i < ml.Length; i++) {
399 MethodBase mx = ml [i] as MethodBase;
400 PropertyInfo px = ml [i] as PropertyInfo;
401 if (mx != null || px != null) {
402 bool overriden = false;
403 for (int j = 0; j < ml.Length; j++) {
406 MethodBase my = ml [j] as MethodBase;
407 if (mx != null && my != null &&
408 Invocation.IsOverride (my, mx)) {
414 PropertyInfo py = ml [j] as PropertyInfo;
415 if (px != null && py != null &&
416 IsOverride (py, px)) {
426 return al.ToArray (typeof (MemberInfo)) as MemberInfo [];
431 public static FoundMember Empty = new FoundMember (true);
434 public readonly MemberInfo Member;
435 public readonly Type Type;
437 public FoundMember (bool regardlessOfThisValueItsEmpty)
444 public FoundMember (Type foundType, MemberInfo member)
453 // Returns a MemberInfo that is referenced in XML documentation
454 // (by "see" or "seealso" elements).
456 private static FoundMember FindDocumentedMember (MemberCore mc,
457 Type type, string memberName, Type [] paramList,
458 DeclSpace ds, out int warningType, string cref,
459 bool warn419, string nameForError)
461 for (; type != null; type = type.DeclaringType) {
462 MemberInfo mi = FindDocumentedMemberNoNest (
463 mc, type, memberName, paramList, ds,
464 out warningType, cref, warn419,
467 return new FoundMember (type, mi);
470 return FoundMember.Empty;
473 private static MemberInfo FindDocumentedMemberNoNest (
474 MemberCore mc, Type type, string memberName,
475 Type [] paramList, DeclSpace ds, out int warningType,
476 string cref, bool warn419, string nameForError)
481 if (paramList == null) {
482 // search for fields/events etc.
483 mis = TypeManager.MemberLookup (type, null,
484 type, MemberTypes.All,
485 BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance,
487 mis = FilterOverridenMembersOut (mis);
488 if (mis == null || mis.Length == 0)
490 if (warn419 && IsAmbiguous (mis))
491 Report419 (mc, nameForError, mis);
495 MethodSignature msig = new MethodSignature (memberName, null, paramList);
496 mis = FindMethodBase (type,
497 BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance,
500 if (warn419 && mis.Length > 0) {
501 if (IsAmbiguous (mis))
502 Report419 (mc, nameForError, mis);
506 // search for operators (whose parameters exactly
507 // matches with the list) and possibly report CS1581.
509 string returnTypeName = null;
510 if (memberName.StartsWith ("implicit operator ")) {
511 oper = "op_Implicit";
512 returnTypeName = memberName.Substring (18).Trim (wsChars);
514 else if (memberName.StartsWith ("explicit operator ")) {
515 oper = "op_Explicit";
516 returnTypeName = memberName.Substring (18).Trim (wsChars);
518 else if (memberName.StartsWith ("operator ")) {
519 oper = memberName.Substring (9).Trim (wsChars);
521 // either unary or binary
523 oper = paramList.Length == 2 ?
524 Binary.oper_names [(int) Binary.Operator.Addition] :
525 Unary.oper_names [(int) Unary.Operator.UnaryPlus];
528 oper = paramList.Length == 2 ?
529 Binary.oper_names [(int) Binary.Operator.Subtraction] :
530 Unary.oper_names [(int) Unary.Operator.UnaryNegation];
534 oper = Unary.oper_names [(int) Unary.Operator.LogicalNot]; break;
536 oper = Unary.oper_names [(int) Unary.Operator.OnesComplement]; break;
539 oper = "op_Increment"; break;
541 oper = "op_Decrement"; break;
543 oper = "op_True"; break;
545 oper = "op_False"; break;
548 oper = Binary.oper_names [(int) Binary.Operator.Multiply]; break;
550 oper = Binary.oper_names [(int) Binary.Operator.Division]; break;
552 oper = Binary.oper_names [(int) Binary.Operator.Modulus]; break;
554 oper = Binary.oper_names [(int) Binary.Operator.BitwiseAnd]; break;
556 oper = Binary.oper_names [(int) Binary.Operator.BitwiseOr]; break;
558 oper = Binary.oper_names [(int) Binary.Operator.ExclusiveOr]; break;
560 oper = Binary.oper_names [(int) Binary.Operator.LeftShift]; break;
562 oper = Binary.oper_names [(int) Binary.Operator.RightShift]; break;
564 oper = Binary.oper_names [(int) Binary.Operator.Equality]; break;
566 oper = Binary.oper_names [(int) Binary.Operator.Inequality]; break;
568 oper = Binary.oper_names [(int) Binary.Operator.LessThan]; break;
570 oper = Binary.oper_names [(int) Binary.Operator.GreaterThan]; break;
572 oper = Binary.oper_names [(int) Binary.Operator.LessThanOrEqual]; break;
574 oper = Binary.oper_names [(int) Binary.Operator.GreaterThanOrEqual]; break;
577 Report.Warning (1020, 1, mc.Location, "Overloadable {0} operator is expected", paramList.Length == 2 ? "binary" : "unary");
578 Report.Warning (1584, 1, mc.Location, "XML comment on `{0}' has syntactically incorrect cref attribute `{1}'",
579 mc.GetSignatureForError (), cref);
583 // here we still don't consider return type (to
584 // detect CS1581 or CS1002+CS1584).
585 msig = new MethodSignature (oper, null, paramList);
587 mis = FindMethodBase (type,
588 BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance,
591 return null; // CS1574
592 MemberInfo mi = mis [0];
593 Type expected = mi is MethodInfo ?
594 ((MethodInfo) mi).ReturnType :
596 ((PropertyInfo) mi).PropertyType :
598 if (returnTypeName != null) {
599 Type returnType = FindDocumentedType (mc, returnTypeName, ds, cref);
600 if (returnType == null || returnType != expected) {
602 Report.Warning (1581, 1, mc.Location, "Invalid return type in XML comment cref attribute `{0}'", cref);
609 private static bool IsAmbiguous (MemberInfo [] members)
611 if (members.Length < 2)
613 if (members.Length > 2)
615 if (members [0] is EventInfo && members [1] is FieldInfo)
617 if (members [1] is EventInfo && members [0] is FieldInfo)
623 // Processes "see" or "seealso" elements.
624 // Checks cref attribute.
626 private static void HandleXrefCommon (MemberCore mc,
627 DeclSpace ds, XmlElement xref)
629 string cref = xref.GetAttribute ("cref").Trim (wsChars);
630 // when, XmlReader, "if (cref == null)"
631 if (!xref.HasAttribute ("cref"))
633 if (cref.Length == 0)
634 Report.Warning (1001, 1, mc.Location, "Identifier expected");
635 // ... and continue until CS1584.
637 string signature; // "x:" are stripped
638 string name; // method invokation "(...)" are removed
639 string parameters; // method parameter list
641 // strip 'T:' 'M:' 'F:' 'P:' 'E:' etc.
642 // Here, MS ignores its member kind. No idea why.
643 if (cref.Length > 2 && cref [1] == ':')
644 signature = cref.Substring (2).Trim (wsChars);
648 int parensPos = signature.IndexOf ('(');
649 int bracePos = parensPos >= 0 ? -1 :
650 signature.IndexOf ('[');
651 if (parensPos > 0 && signature [signature.Length - 1] == ')') {
652 name = signature.Substring (0, parensPos).Trim (wsChars);
653 parameters = signature.Substring (parensPos + 1, signature.Length - parensPos - 2).Trim (wsChars);
655 else if (bracePos > 0 && signature [signature.Length - 1] == ']') {
656 name = signature.Substring (0, bracePos).Trim (wsChars);
657 parameters = signature.Substring (bracePos + 1, signature.Length - bracePos - 2).Trim (wsChars);
663 Normalize (mc, ref name);
665 string identifier = GetBodyIdentifierFromName (name);
667 // Check if identifier is valid.
668 // This check is not necessary to mark as error, but
669 // csc specially reports CS1584 for wrong identifiers.
670 string [] nameElems = identifier.Split ('.');
671 for (int i = 0; i < nameElems.Length; i++) {
672 string nameElem = GetBodyIdentifierFromName (nameElems [i]);
674 Normalize (mc, ref nameElem);
675 if (!Tokenizer.IsValidIdentifier (nameElem)
676 && nameElem.IndexOf ("operator") < 0) {
677 Report.Warning (1584, 1, mc.Location, "XML comment on `{0}' has syntactically incorrect cref attribute `{1}'",
678 mc.GetSignatureForError (), cref);
679 xref.SetAttribute ("cref", "!:" + signature);
684 // check if parameters are valid
685 Type [] parameterTypes;
686 if (parameters == null)
687 parameterTypes = null;
688 else if (parameters.Length == 0)
689 parameterTypes = Type.EmptyTypes;
691 string [] paramList = parameters.Split (',');
692 ArrayList plist = new ArrayList ();
693 for (int i = 0; i < paramList.Length; i++) {
694 string paramTypeName = paramList [i].Trim (wsChars);
695 Normalize (mc, ref paramTypeName);
696 Type paramType = FindDocumentedType (mc, paramTypeName, ds, cref);
697 if (paramType == null) {
698 Report.Warning (1580, 1, mc.Location, "Invalid type for parameter `{0}' in XML comment cref attribute `{1}'",
699 (i + 1).ToString (), cref);
702 plist.Add (paramType);
704 parameterTypes = plist.ToArray (typeof (Type)) as Type [];
707 Type type = FindDocumentedType (mc, name, ds, cref);
709 // delegate must not be referenced with args
710 && (!type.IsSubclassOf (typeof (System.Delegate))
711 || parameterTypes == null)) {
712 string result = GetSignatureForDoc (type)
713 + (bracePos < 0 ? String.Empty : signature.Substring (bracePos));
714 xref.SetAttribute ("cref", "T:" + result);
718 int period = name.LastIndexOf ('.');
720 string typeName = name.Substring (0, period);
721 string memberName = name.Substring (period + 1);
722 Normalize (mc, ref memberName);
723 type = FindDocumentedType (mc, typeName, ds, cref);
726 FoundMember fm = FindDocumentedMember (mc, type, memberName, parameterTypes, ds, out warnResult, cref, true, name);
730 MemberInfo mi = fm.Member;
731 // we cannot use 'type' directly
732 // to get its name, since mi
733 // could be from DeclaringType
735 xref.SetAttribute ("cref", GetMemberDocHead (mi.MemberType) + GetSignatureForDoc (fm.Type) + "." + memberName + GetParametersFormatted (mi));
736 return; // a member of a type
742 FoundMember fm = FindDocumentedMember (mc, ds.TypeBuilder, name, parameterTypes, ds, out warnResult, cref, true, name);
746 MemberInfo mi = fm.Member;
747 // we cannot use 'type' directly
748 // to get its name, since mi
749 // could be from DeclaringType
751 xref.SetAttribute ("cref", GetMemberDocHead (mi.MemberType) + GetSignatureForDoc (fm.Type) + "." + name + GetParametersFormatted (mi));
752 return; // local member name
756 // It still might be part of namespace name.
757 Namespace ns = ds.NamespaceEntry.NS.GetNamespace (name, false);
759 xref.SetAttribute ("cref", "N:" + ns.FullName);
760 return; // a namespace
762 if (RootNamespace.Global.IsNamespace (name)) {
763 xref.SetAttribute ("cref", "N:" + name);
764 return; // a namespace
767 Report.Warning (1574, 1, mc.Location, "XML comment on `{0}' has cref attribute `{1}' that could not be resolved",
768 mc.GetSignatureForError (), cref);
770 xref.SetAttribute ("cref", "!:" + name);
773 static string GetParametersFormatted (MemberInfo mi)
775 MethodBase mb = mi as MethodBase;
776 bool isSetter = false;
777 PropertyInfo pi = mi as PropertyInfo;
779 mb = pi.GetGetMethod ();
782 mb = pi.GetSetMethod ();
788 ParameterData parameters = TypeManager.GetParameterData (mb);
789 if (parameters == null || parameters.Count == 0)
792 StringBuilder sb = new StringBuilder ();
794 for (int i = 0; i < parameters.Count; i++) {
795 if (isSetter && i + 1 == parameters.Count)
796 break; // skip "value".
799 Type t = parameters.ParameterType (i);
800 sb.Append (GetSignatureForDoc (t));
803 return sb.ToString ();
806 static string GetBodyIdentifierFromName (string name)
808 string identifier = name;
810 if (name.Length > 0 && name [name.Length - 1] == ']') {
811 string tmp = name.Substring (0, name.Length - 1).Trim (wsChars);
812 int last = tmp.LastIndexOf ('[');
814 identifier = tmp.Substring (0, last).Trim (wsChars);
820 static void Report419 (MemberCore mc, string memberName, MemberInfo [] mis)
822 Report.Warning (419, 3, mc.Location,
823 "Ambiguous reference in cref attribute `{0}'. Assuming `{1}' but other overloads including `{2}' have also matched",
825 TypeManager.GetFullNameSignature (mis [0]),
826 TypeManager.GetFullNameSignature (mis [1]));
830 // Get a prefix from member type for XML documentation (used
831 // to formalize cref target name).
833 static string GetMemberDocHead (MemberTypes type)
836 case MemberTypes.Constructor:
837 case MemberTypes.Method:
839 case MemberTypes.Event:
841 case MemberTypes.Field:
843 case MemberTypes.NestedType:
844 case MemberTypes.TypeInfo:
846 case MemberTypes.Property:
855 // Returns a string that represents the signature for this
856 // member which should be used in XML documentation.
858 public static string GetMethodDocCommentName (MethodCore mc, DeclSpace ds)
860 Parameter [] plist = mc.Parameters.FixedParameters;
861 string paramSpec = String.Empty;
863 StringBuilder psb = new StringBuilder ();
864 foreach (Parameter p in plist) {
865 psb.Append (psb.Length != 0 ? "," : "(");
866 psb.Append (GetSignatureForDoc (p.ExternalType ()));
868 paramSpec = psb.ToString ();
871 if (paramSpec.Length > 0)
874 string name = mc is Constructor ? "#ctor" : mc.Name;
875 string suffix = String.Empty;
876 Operator op = mc as Operator;
878 switch (op.OperatorType) {
879 case Operator.OpType.Implicit:
880 case Operator.OpType.Explicit:
881 suffix = "~" + GetSignatureForDoc (op.MethodBuilder.ReturnType);
885 return String.Concat (mc.DocCommentHeader, ds.Name, ".", name, paramSpec, suffix);
888 static string GetSignatureForDoc (Type type)
890 return TypeManager.IsGenericParameter (type) ?
891 "`" + TypeManager.GenericParameterPosition (type) :
892 type.FullName.Replace ("+", ".").Replace ('&', '@');
896 // Raised (and passed an XmlElement that contains the comment)
897 // when GenerateDocComment is writing documentation expectedly.
899 // FIXME: with a few effort, it could be done with XmlReader,
900 // that means removal of DOM use.
902 internal static void OnMethodGenerateDocComment (
903 MethodCore mc, XmlElement el)
905 Hashtable paramTags = new Hashtable ();
906 foreach (XmlElement pelem in el.SelectNodes ("param")) {
908 string xname = pelem.GetAttribute ("name");
909 if (xname.Length == 0)
910 continue; // really? but MS looks doing so
911 if (xname != "" && mc.Parameters.GetParameterByName (xname, out i) == null)
912 Report.Warning (1572, 2, mc.Location, "XML comment on `{0}' has a param tag for `{1}', but there is no parameter by that name",
913 mc.GetSignatureForError (), xname);
914 else if (paramTags [xname] != null)
915 Report.Warning (1571, 2, mc.Location, "XML comment on `{0}' has a duplicate param tag for `{1}'",
916 mc.GetSignatureForError (), xname);
917 paramTags [xname] = xname;
919 Parameter [] plist = mc.Parameters.FixedParameters;
920 foreach (Parameter p in plist) {
921 if (paramTags.Count > 0 && paramTags [p.Name] == null)
922 Report.Warning (1573, 4, mc.Location, "Parameter `{0}' has no matching param tag in the XML comment for `{1}'",
923 p.Name, mc.GetSignatureForError ());
927 private static void Normalize (MemberCore mc, ref string name)
929 if (name.Length > 0 && name [0] == '@')
930 name = name.Substring (1);
931 else if (name == "this")
933 else if (Tokenizer.IsKeyword (name) && !IsTypeName (name))
934 Report.Warning (1041, 1, mc.Location, "Identifier expected. `{0}' is a keyword", name);
937 private static bool IsTypeName (string name)
963 // Implements XML documentation generation.
965 public class Documentation
967 public Documentation (string xml_output_filename)
969 docfilename = xml_output_filename;
970 XmlDocumentation = new XmlDocument ();
971 XmlDocumentation.PreserveWhitespace = false;
974 private string docfilename;
977 // Used to create element which helps well-formedness checking.
979 public XmlDocument XmlDocumentation;
982 // The output for XML documentation.
984 public XmlWriter XmlCommentOutput;
987 // Stores XmlDocuments that are included in XML documentation.
988 // Keys are included filenames, values are XmlDocuments.
990 public Hashtable StoredDocuments = new Hashtable ();
993 // Outputs XML documentation comment from tokenized comments.
995 public bool OutputDocComment (string asmfilename)
997 XmlTextWriter w = null;
999 w = new XmlTextWriter (docfilename, null);
1001 w.Formatting = Formatting.Indented;
1002 w.WriteStartDocument ();
1003 w.WriteStartElement ("doc");
1004 w.WriteStartElement ("assembly");
1005 w.WriteStartElement ("name");
1006 w.WriteString (Path.ChangeExtension (asmfilename, null));
1007 w.WriteEndElement (); // name
1008 w.WriteEndElement (); // assembly
1009 w.WriteStartElement ("members");
1010 XmlCommentOutput = w;
1011 GenerateDocComment ();
1012 w.WriteFullEndElement (); // members
1013 w.WriteEndElement ();
1014 w.WriteWhitespace (Environment.NewLine);
1015 w.WriteEndDocument ();
1017 } catch (Exception ex) {
1018 Report.Error (1569, "Error generating XML documentation file `{0}' (`{1}')", docfilename, ex.Message);
1027 // Fixes full type name of each documented types/members up.
1029 public void GenerateDocComment ()
1031 TypeContainer root = RootContext.ToplevelTypes;
1032 if (root.Interfaces != null)
1033 foreach (Interface i in root.Interfaces)
1034 DocUtil.GenerateTypeDocComment (i, null);
1036 if (root.Types != null)
1037 foreach (TypeContainer tc in root.Types)
1038 DocUtil.GenerateTypeDocComment (tc, null);
1040 if (root.Delegates != null)
1041 foreach (Delegate d in root.Delegates)
1042 DocUtil.GenerateDocComment (d, null);
1044 if (root.Enums != null)
1045 foreach (Enum e in root.Enums)
1046 e.GenerateDocComment (null);