2 // doc.cs: Support for XML documentation comment.
5 // Atsushi Enomoto <atsushi@ximian.com>
7 // Dual licensed under the terms of the MIT X11 or GNU GPL
9 // Copyright 2004 Novell, Inc.
14 using System.Collections.Generic;
21 namespace Mono.CSharp {
24 // Support class for XML documentation.
31 // Generates xml doc comments (if any), and if required,
32 // handle warning report.
34 internal static void GenerateTypeDocComment (TypeContainer t,
35 DeclSpace ds, Report Report)
37 GenerateDocComment (t, ds, Report);
39 if (t.DefaultStaticConstructor != null)
40 t.DefaultStaticConstructor.GenerateDocComment (t);
42 if (t.InstanceConstructors != null)
43 foreach (Constructor c in t.InstanceConstructors)
44 c.GenerateDocComment (t);
47 foreach (TypeContainer tc in t.Types)
48 tc.GenerateDocComment (t);
50 if (t.Constants != null)
51 foreach (Const c in t.Constants)
52 c.GenerateDocComment (t);
55 foreach (FieldBase f in t.Fields)
56 f.GenerateDocComment (t);
59 foreach (Event e in t.Events)
60 e.GenerateDocComment (t);
62 if (t.Indexers != null)
63 foreach (Indexer ix in t.Indexers)
64 ix.GenerateDocComment (t);
66 if (t.Properties != null)
67 foreach (Property p in t.Properties)
68 p.GenerateDocComment (t);
70 if (t.Methods != null)
71 foreach (MethodOrOperator m in t.Methods)
72 m.GenerateDocComment (t);
74 if (t.Operators != null)
75 foreach (Operator o in t.Operators)
76 o.GenerateDocComment (t);
80 private static readonly string line_head =
81 Environment.NewLine + " ";
83 private static XmlNode GetDocCommentNode (MemberCore mc,
84 string name, Report Report)
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;
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.
102 normalized.Split ('\n');
104 for (int i = 0; i < split.Length; i++) {
105 string s = split [i].TrimEnd ();
109 el.InnerXml = line_head + String.Join (
110 line_head, split, 0, j);
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));
120 // Generates xml doc comments (if any), and if required,
121 // handle warning report.
123 internal static void GenerateDocComment (MemberCore mc,
124 DeclSpace ds, Report Report)
126 if (mc.DocComment != null) {
127 string name = mc.GetDocCommentName (ds);
129 XmlNode n = GetDocCommentNode (mc, name, Report);
131 XmlElement el = n as XmlElement;
133 mc.OnGenerateDocComment (el);
135 // FIXME: it could be done with XmlReader
136 XmlNodeList nl = n.SelectNodes (".//include");
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)
142 foreach (XmlElement inc in al)
143 if (!HandleInclude (mc, inc, Report))
144 inc.ParentNode.RemoveChild (inc);
147 // FIXME: it could be done with XmlReader
148 DeclSpace ds_target = mc as DeclSpace;
149 if (ds_target == null)
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);
160 n.WriteTo (mc.Compiler.Settings.Documentation.XmlCommentOutput);
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 ());
171 // Processes "include" element. Check included file and
172 // embed the document content inside this documentation node.
174 private static bool HandleInclude (MemberCore mc, XmlElement el, Report Report)
176 bool keep_include_node = false;
177 string file = el.GetAttribute ("file");
178 string path = el.GetAttribute ("path");
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;
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;
191 if (!mc.Compiler.Settings.Documentation.StoredDocuments.TryGetValue (file, out doc)) {
193 doc = new XmlDocument ();
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);
203 XmlNodeList nl = doc.SelectNodes (path);
205 el.ParentNode.InsertBefore (el.OwnerDocument.CreateComment (" No matching elements were found for the include tag embedded here. "), el);
207 keep_include_node = true;
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);
217 return keep_include_node;
221 // Handles <see> elements.
223 private static void HandleSee (MemberCore mc,
224 DeclSpace ds, XmlElement see, Report r)
226 HandleXrefCommon (mc, ds, see, r);
230 // Handles <seealso> elements.
232 private static void HandleSeeAlso (MemberCore mc,
233 DeclSpace ds, XmlElement seealso, Report r)
235 HandleXrefCommon (mc, ds, seealso, r);
239 // Handles <exception> elements.
241 private static void HandleException (MemberCore mc,
242 DeclSpace ds, XmlElement seealso, Report r)
244 HandleXrefCommon (mc, ds, seealso, r);
247 static readonly char [] wsChars =
248 new char [] {' ', '\t', '\n', '\r'};
251 // returns a full runtime type name from a name which might
252 // be C# specific type name.
254 private static TypeSpec FindDocumentedType (MemberCore mc, string name, DeclSpace ds, string cref, Report r)
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);
265 TypeSpec t = FindDocumentedTypeNonArray (mc, identifier, ds, cref, r);
266 if (t != null && is_array)
267 t = ArrayContainer.MakeType (mc.Module, t);
271 private static TypeSpec FindDocumentedTypeNonArray (MemberCore mc,
272 string identifier, DeclSpace ds, string cref, Report r)
274 switch (identifier) {
276 return TypeManager.int32_type;
278 return TypeManager.uint32_type;
280 return TypeManager.short_type;;
282 return TypeManager.ushort_type;
284 return TypeManager.int64_type;
286 return TypeManager.uint64_type;;
288 return TypeManager.float_type;;
290 return TypeManager.double_type;
292 return TypeManager.char_type;;
294 return TypeManager.decimal_type;;
296 return TypeManager.byte_type;;
298 return TypeManager.sbyte_type;;
300 return TypeManager.object_type;;
302 return TypeManager.bool_type;;
304 return TypeManager.string_type;;
306 return TypeManager.void_type;;
308 FullNamedExpression e = ds.LookupNamespaceOrType (identifier, 0, mc.Location, false);
310 if (!(e is TypeExpr))
314 int index = identifier.LastIndexOf ('.');
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);
323 var te = ns.LookupType(mc, typeName, 0, true, mc.Location);
329 TypeSpec parent = FindDocumentedType (mc, identifier.Substring (0, index), ds, cref, r);
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;
342 // Returns a MemberInfo that is referenced in XML documentation
343 // (by "see" or "seealso" elements).
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)
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,
356 return mi; // new FoundMember (type, mi);
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)
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;
378 if (warn419 && found.Count > 1) {
379 Report419 (mc, name_for_error, found.ToArray (), Report);
385 if (param_list == null) {
386 // search for fields/events etc.
387 mis = TypeManager.MemberLookup (type, null,
388 type, MemberKind.All,
389 BindingRestriction.None,
391 mis = FilterOverridenMembersOut (mis);
392 if (mis == null || mis.Length == 0)
394 if (warn419 && IsAmbiguous (mis))
395 Report419 (mc, name_for_error, mis, Report);
399 MethodSignature msig = new MethodSignature (member_name, null, param_list);
400 mis = FindMethodBase (type,
401 BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance,
404 if (warn419 && mis.Length > 0) {
405 if (IsAmbiguous (mis))
406 Report419 (mc, name_for_error, mis, Report);
410 // search for operators (whose parameters exactly
411 // matches with the list) and possibly report CS1581.
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);
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);
422 else if (member_name.StartsWith ("operator ")) {
423 oper = member_name.Substring (9).Trim (wsChars);
425 // either unary or binary
427 oper = param_list.Length == 2 ?
428 Operator.GetMetadataName (Operator.OpType.Addition) :
429 Operator.GetMetadataName (Operator.OpType.UnaryPlus);
432 oper = param_list.Length == 2 ?
433 Operator.GetMetadataName (Operator.OpType.Subtraction) :
434 Operator.GetMetadataName (Operator.OpType.UnaryNegation);
437 oper = Operator.GetMetadataName (oper);
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);
448 // here we still don't consider return type (to
449 // detect CS1581 or CS1002+CS1584).
450 msig = new MethodSignature (oper, null, param_list);
452 mis = FindMethodBase (type,
453 BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance,
456 return null; // CS1574
458 TypeSpec expected = mi is MethodSpec ?
459 ((MethodSpec) mi).ReturnType :
461 ((PropertySpec) mi).PropertyType :
463 if (return_type_name != null) {
464 TypeSpec returnType = FindDocumentedType (mc, return_type_name, ds, cref, Report);
465 if (returnType == null || returnType != expected) {
467 Report.Warning (1581, 1, mc.Location, "Invalid return type in XML comment cref attribute `{0}'", cref);
476 // Processes "see" or "seealso" elements.
477 // Checks cref attribute.
479 private static void HandleXrefCommon (MemberCore mc,
480 DeclSpace ds, XmlElement xref, Report Report)
482 string cref = xref.GetAttribute ("cref").Trim (wsChars);
483 // when, XmlReader, "if (cref == null)"
484 if (!xref.HasAttribute ("cref"))
486 if (cref.Length == 0)
487 Report.Warning (1001, 1, mc.Location, "Identifier expected");
488 // ... and continue until CS1584.
490 string signature; // "x:" are stripped
491 string name; // method invokation "(...)" are removed
492 string parameters; // method parameter list
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<System.Int32>
500 if (cref.Length > 2 && cref [1] == ':')
505 // Also note that without "T:" any generic type
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);
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);
523 Normalize (mc, ref name, Report);
525 string identifier = GetBodyIdentifierFromName (name);
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]);
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);
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;
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);
562 plist.Add (param_type);
565 parameter_types = ParametersCompiled.CreateFullyResolved (plist.ToArray ());
568 TypeSpec type = FindDocumentedType (mc, name, ds, cref, Report);
570 // delegate must not be referenced with args
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);
579 int period = name.LastIndexOf ('.');
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);
589 var mi = FindDocumentedMember (mc, type, lookup_name, parameter_types, ds, out warn_result, cref, true, name, Report);
593 // we cannot use 'type' directly
594 // to get its name, since mi
595 // could be from DeclaringType
597 xref.SetAttribute ("cref", GetMemberDocHead (mi) + GetSignatureForDoc (mi.DeclaringType) + "." + member_name + GetParametersFormatted (mi));
598 return; // a member of a type
603 var mi = FindDocumentedMember (mc, ds.PartialContainer.Definition, name, parameter_types, ds, out warn_result, cref, true, name, Report);
608 // we cannot use 'type' directly
609 // to get its name, since mi
610 // could be from DeclaringType
612 xref.SetAttribute ("cref", GetMemberDocHead (mi) + GetSignatureForDoc (mi.DeclaringType) + "." + name + GetParametersFormatted (mi));
613 return; // local member name
617 // It still might be part of namespace name.
618 Namespace ns = ds.NamespaceEntry.NS.GetNamespace (name, false);
620 xref.SetAttribute ("cref", "N:" + ns.GetSignatureForError ());
621 return; // a namespace
623 if (mc.Module.GlobalRootNamespace.IsNamespace (name)) {
624 xref.SetAttribute ("cref", "N:" + name);
625 return; // a namespace
628 Report.Warning (1574, 1, mc.Location, "XML comment on `{0}' has cref attribute `{1}' that could not be resolved",
629 mc.GetSignatureForError (), cref);
631 xref.SetAttribute ("cref", "!:" + name);
634 static string GetParametersFormatted (MemberSpec mi)
636 var pm = mi as IParametersMember;
637 if (pm == null || pm.Parameters.IsEmpty)
640 AParametersCollection parameters = pm.Parameters;
642 if (parameters == null || parameters.Count == 0)
645 StringBuilder sb = new StringBuilder ();
647 for (int i = 0; i < parameters.Count; i++) {
648 // if (is_setter && i + 1 == parameters.Count)
649 // break; // skip "value".
652 TypeSpec t = parameters.Types [i];
653 sb.Append (GetSignatureForDoc (t));
656 return sb.ToString ();
659 static string GetBodyIdentifierFromName (string name)
661 string identifier = name;
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 ('[');
667 identifier = tmp.Substring (0, last).Trim (wsChars);
673 static void Report419 (MemberCore mc, string member_name, MemberSpec [] mis, Report Report)
675 Report.Warning (419, 3, mc.Location,
676 "Ambiguous reference in cref attribute `{0}'. Assuming `{1}' but other overloads including `{2}' have also matched",
678 TypeManager.GetFullNameSignature (mis [0]),
679 TypeManager.GetFullNameSignature (mis [1]));
683 // Get a prefix from member type for XML documentation (used
684 // to formalize cref target name).
686 static string GetMemberDocHead (MemberSpec type)
688 if (type is FieldSpec)
690 if (type is MethodSpec)
692 if (type is EventSpec)
694 if (type is PropertySpec)
696 if (type is TypeSpec)
705 // Returns a string that represents the signature for this
706 // member which should be used in XML documentation.
708 public static string GetMethodDocCommentName (MemberCore mc, ParametersCompiled parameters, DeclSpace ds)
710 IParameterData [] plist = parameters.FixedParameters;
711 string paramSpec = String.Empty;
713 StringBuilder psb = new StringBuilder ();
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)
721 paramSpec = psb.ToString ();
724 if (paramSpec.Length > 0)
727 string name = mc.Name;
728 if (mc is Constructor)
730 else if (mc is InterfaceMemberBase) {
731 var imb = (InterfaceMemberBase) mc;
732 name = imb.GetFullName (imb.ShortName);
734 name = name.Replace ('.', '#');
736 if (mc.MemberName.TypeArguments != null && mc.MemberName.TypeArguments.Count > 0)
737 name += "``" + mc.MemberName.CountTypeArguments;
739 string suffix = String.Empty;
740 Operator op = mc as Operator;
742 switch (op.OperatorType) {
743 case Operator.OpType.Implicit:
744 case Operator.OpType.Explicit:
745 suffix = "~" + GetSignatureForDoc (op.ReturnType);
749 return String.Concat (mc.DocCommentHeader, ds.Name, ".", name, paramSpec, suffix);
752 static string GetSignatureForDoc (TypeSpec type)
754 var tp = type as TypeParameterSpec;
757 type = type.DeclaringType;
758 while (type != null && type.DeclaringType != null) {
759 type = type.DeclaringType;
760 c += type.MemberDefinition.TypeParametersCount;
762 var prefix = tp.IsMethodOwned ? "``" : "`";
763 return prefix + (c + tp.DeclaredPosition);
766 var pp = type as PointerContainer;
768 return GetSignatureForDoc (pp.Element) + "*";
770 ArrayContainer ap = type as ArrayContainer;
772 return GetSignatureForDoc (ap.Element) +
773 ArrayContainer.GetPostfixSignature (ap.Rank);
775 if (TypeManager.IsGenericType (type)) {
776 string g = type.MemberDefinition.Namespace;
777 if (g != null && g.Length > 0)
779 int idx = type.Name.LastIndexOf ('`');
780 g += (idx < 0 ? type.Name : type.Name.Substring (0, idx)) + '{';
782 foreach (TypeSpec t in TypeManager.GetTypeArguments (type))
783 g += (argpos++ > 0 ? "," : String.Empty) + GetSignatureForDoc (t);
788 string name = type.GetMetaInfo ().FullName != null ? type.GetMetaInfo ().FullName : type.Name;
789 return name.Replace ("+", ".").Replace ('&', '@');
793 // Raised (and passed an XmlElement that contains the comment)
794 // when GenerateDocComment is writing documentation expectedly.
796 // FIXME: with a few effort, it could be done with XmlReader,
797 // that means removal of DOM use.
799 internal static void OnMethodGenerateDocComment (
800 MethodCore mc, XmlElement el, Report Report)
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;
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 ());
823 private static void Normalize (MemberCore mc, ref string name, Report Report)
825 if (name.Length > 0 && name [0] == '@')
826 name = name.Substring (1);
827 else if (name == "this")
829 else if (Tokenizer.IsKeyword (name) && !IsTypeName (name))
830 Report.Warning (1041, 1, mc.Location, "Identifier expected. `{0}' is a keyword", name);
833 private static bool IsTypeName (string name)
859 // Implements XML documentation generation.
861 public class Documentation
863 public Documentation (string xml_output_filename)
865 docfilename = xml_output_filename;
866 XmlDocumentation = new XmlDocument ();
867 XmlDocumentation.PreserveWhitespace = false;
870 private string docfilename;
873 // Used to create element which helps well-formedness checking.
875 public XmlDocument XmlDocumentation;
878 // The output for XML documentation.
880 public XmlWriter XmlCommentOutput;
883 // Stores XmlDocuments that are included in XML documentation.
884 // Keys are included filenames, values are XmlDocuments.
886 public Dictionary<string, XmlDocument> StoredDocuments = new Dictionary<string, XmlDocument> ();
889 // Outputs XML documentation comment from tokenized comments.
891 public bool OutputDocComment (string asmfilename, Report Report)
893 XmlTextWriter w = null;
895 w = new XmlTextWriter (docfilename, null);
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 ();
913 } catch (Exception ex) {
914 Report.Error (1569, "Error generating XML documentation file `{0}' (`{1}')", docfilename, ex.Message);
923 // Fixes full type name of each documented types/members up.
925 public void GenerateDocComment (Report r)
927 TypeContainer root = RootContext.ToplevelTypes;
929 if (root.Types != null)
930 foreach (TypeContainer tc in root.Types)
931 DocUtil.GenerateTypeDocComment (tc, null, r);