2010-05-19 Jb Evain <jbevain@novell.com>
[mcs.git] / mcs / doc.cs
blob8b8dfeecc8b370a395d21887919c7685440ce554
1 //
2 // doc.cs: Support for XML documentation comment.
3 //
4 // Author:
5 // Atsushi Enomoto <atsushi@ximian.com>
6 //
7 // Dual licensed under the terms of the MIT X11 or GNU GPL
8 //
9 // Copyright 2004 Novell, Inc.
13 using System;
14 using System.Collections.Generic;
15 using System.IO;
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;
22 using System.Text;
23 using System.Xml;
24 using System.Linq;
26 using Mono.CompilerServices.SymbolWriter;
28 namespace Mono.CSharp {
31 // Support class for XML documentation.
33 static class DocUtil
35 // TypeContainer
38 // Generates xml doc comments (if any), and if required,
39 // handle warning report.
41 internal static void GenerateTypeDocComment (TypeContainer t,
42 DeclSpace ds, Report Report)
44 GenerateDocComment (t, ds, Report);
46 if (t.DefaultStaticConstructor != null)
47 t.DefaultStaticConstructor.GenerateDocComment (t);
49 if (t.InstanceConstructors != null)
50 foreach (Constructor c in t.InstanceConstructors)
51 c.GenerateDocComment (t);
53 if (t.Types != null)
54 foreach (TypeContainer tc in t.Types)
55 tc.GenerateDocComment (t);
57 if (t.Constants != null)
58 foreach (Const c in t.Constants)
59 c.GenerateDocComment (t);
61 if (t.Fields != null)
62 foreach (FieldBase f in t.Fields)
63 f.GenerateDocComment (t);
65 if (t.Events != null)
66 foreach (Event e in t.Events)
67 e.GenerateDocComment (t);
69 if (t.Indexers != null)
70 foreach (Indexer ix in t.Indexers)
71 ix.GenerateDocComment (t);
73 if (t.Properties != null)
74 foreach (Property p in t.Properties)
75 p.GenerateDocComment (t);
77 if (t.Methods != null)
78 foreach (MethodOrOperator m in t.Methods)
79 m.GenerateDocComment (t);
81 if (t.Operators != null)
82 foreach (Operator o in t.Operators)
83 o.GenerateDocComment (t);
86 // MemberCore
87 private static readonly string line_head =
88 Environment.NewLine + " ";
90 private static XmlNode GetDocCommentNode (MemberCore mc,
91 string name, Report Report)
93 // FIXME: It could be even optimizable as not
94 // to use XmlDocument. But anyways the nodes
95 // are not kept in memory.
96 XmlDocument doc = RootContext.Documentation.XmlDocumentation;
97 try {
98 XmlElement el = doc.CreateElement ("member");
99 el.SetAttribute ("name", name);
100 string normalized = mc.DocComment;
101 el.InnerXml = normalized;
102 // csc keeps lines as written in the sources
103 // and inserts formatting indentation (which
104 // is different from XmlTextWriter.Formatting
105 // one), but when a start tag contains an
106 // endline, it joins the next line. We don't
107 // have to follow such a hacky behavior.
108 string [] split =
109 normalized.Split ('\n');
110 int j = 0;
111 for (int i = 0; i < split.Length; i++) {
112 string s = split [i].TrimEnd ();
113 if (s.Length > 0)
114 split [j++] = s;
116 el.InnerXml = line_head + String.Join (
117 line_head, split, 0, j);
118 return el;
119 } catch (Exception ex) {
120 Report.Warning (1570, 1, mc.Location, "XML comment on `{0}' has non-well-formed XML ({1})", name, ex.Message);
121 XmlComment com = doc.CreateComment (String.Format ("FIXME: Invalid documentation markup was found for member {0}", name));
122 return com;
127 // Generates xml doc comments (if any), and if required,
128 // handle warning report.
130 internal static void GenerateDocComment (MemberCore mc,
131 DeclSpace ds, Report Report)
133 if (mc.DocComment != null) {
134 string name = mc.GetDocCommentName (ds);
136 XmlNode n = GetDocCommentNode (mc, name, Report);
138 XmlElement el = n as XmlElement;
139 if (el != null) {
140 mc.OnGenerateDocComment (el);
142 // FIXME: it could be done with XmlReader
143 XmlNodeList nl = n.SelectNodes (".//include");
144 if (nl.Count > 0) {
145 // It could result in current node removal, so prepare another list to iterate.
146 var al = new List<XmlNode> (nl.Count);
147 foreach (XmlNode inc in nl)
148 al.Add (inc);
149 foreach (XmlElement inc in al)
150 if (!HandleInclude (mc, inc, Report))
151 inc.ParentNode.RemoveChild (inc);
154 // FIXME: it could be done with XmlReader
155 DeclSpace ds_target = mc as DeclSpace;
156 if (ds_target == null)
157 ds_target = ds;
159 foreach (XmlElement see in n.SelectNodes (".//see"))
160 HandleSee (mc, ds_target, see, Report);
161 foreach (XmlElement seealso in n.SelectNodes (".//seealso"))
162 HandleSeeAlso (mc, ds_target, seealso ,Report);
163 foreach (XmlElement see in n.SelectNodes (".//exception"))
164 HandleException (mc, ds_target, see, Report);
167 n.WriteTo (RootContext.Documentation.XmlCommentOutput);
169 else if (mc.IsExposedFromAssembly ()) {
170 Constructor c = mc as Constructor;
171 if (c == null || !c.IsDefault ())
172 Report.Warning (1591, 4, mc.Location,
173 "Missing XML comment for publicly visible type or member `{0}'", mc.GetSignatureForError ());
178 // Processes "include" element. Check included file and
179 // embed the document content inside this documentation node.
181 private static bool HandleInclude (MemberCore mc, XmlElement el, Report Report)
183 bool keep_include_node = false;
184 string file = el.GetAttribute ("file");
185 string path = el.GetAttribute ("path");
186 if (file == "") {
187 Report.Warning (1590, 1, mc.Location, "Invalid XML `include' element. Missing `file' attribute");
188 el.ParentNode.InsertBefore (el.OwnerDocument.CreateComment (" Include tag is invalid "), el);
189 keep_include_node = true;
191 else if (path.Length == 0) {
192 Report.Warning (1590, 1, mc.Location, "Invalid XML `include' element. Missing `path' attribute");
193 el.ParentNode.InsertBefore (el.OwnerDocument.CreateComment (" Include tag is invalid "), el);
194 keep_include_node = true;
196 else {
197 XmlDocument doc;
198 if (!RootContext.Documentation.StoredDocuments.TryGetValue (file, out doc)) {
199 try {
200 doc = new XmlDocument ();
201 doc.Load (file);
202 RootContext.Documentation.StoredDocuments.Add (file, doc);
203 } catch (Exception) {
204 el.ParentNode.InsertBefore (el.OwnerDocument.CreateComment (String.Format (" Badly formed XML in at comment file `{0}': cannot be included ", file)), el);
205 Report.Warning (1592, 1, mc.Location, "Badly formed XML in included comments file -- `{0}'", file);
208 if (doc != null) {
209 try {
210 XmlNodeList nl = doc.SelectNodes (path);
211 if (nl.Count == 0) {
212 el.ParentNode.InsertBefore (el.OwnerDocument.CreateComment (" No matching elements were found for the include tag embedded here. "), el);
214 keep_include_node = true;
216 foreach (XmlNode n in nl)
217 el.ParentNode.InsertBefore (el.OwnerDocument.ImportNode (n, true), el);
218 } catch (Exception ex) {
219 el.ParentNode.InsertBefore (el.OwnerDocument.CreateComment (" Failed to insert some or all of included XML "), el);
220 Report.Warning (1589, 1, mc.Location, "Unable to include XML fragment `{0}' of file `{1}' ({2})", path, file, ex.Message);
224 return keep_include_node;
228 // Handles <see> elements.
230 private static void HandleSee (MemberCore mc,
231 DeclSpace ds, XmlElement see, Report r)
233 HandleXrefCommon (mc, ds, see, r);
237 // Handles <seealso> elements.
239 private static void HandleSeeAlso (MemberCore mc,
240 DeclSpace ds, XmlElement seealso, Report r)
242 HandleXrefCommon (mc, ds, seealso, r);
246 // Handles <exception> elements.
248 private static void HandleException (MemberCore mc,
249 DeclSpace ds, XmlElement seealso, Report r)
251 HandleXrefCommon (mc, ds, seealso, r);
254 static readonly char [] wsChars =
255 new char [] {' ', '\t', '\n', '\r'};
258 // returns a full runtime type name from a name which might
259 // be C# specific type name.
261 private static TypeSpec FindDocumentedType (MemberCore mc, string name, DeclSpace ds, string cref, Report r)
263 bool is_array = false;
264 string identifier = name;
265 if (name [name.Length - 1] == ']') {
266 string tmp = name.Substring (0, name.Length - 1).Trim (wsChars);
267 if (tmp [tmp.Length - 1] == '[') {
268 identifier = tmp.Substring (0, tmp.Length - 1).Trim (wsChars);
269 is_array = true;
272 TypeSpec t = FindDocumentedTypeNonArray (mc, identifier, ds, cref, r);
273 if (t != null && is_array)
274 t = Import.ImportType (Array.CreateInstance (t.GetMetaInfo (), 0).GetType ());
275 return t;
278 private static TypeSpec FindDocumentedTypeNonArray (MemberCore mc,
279 string identifier, DeclSpace ds, string cref, Report r)
281 switch (identifier) {
282 case "int":
283 return TypeManager.int32_type;
284 case "uint":
285 return TypeManager.uint32_type;
286 case "short":
287 return TypeManager.short_type;;
288 case "ushort":
289 return TypeManager.ushort_type;
290 case "long":
291 return TypeManager.int64_type;
292 case "ulong":
293 return TypeManager.uint64_type;;
294 case "float":
295 return TypeManager.float_type;;
296 case "double":
297 return TypeManager.double_type;
298 case "char":
299 return TypeManager.char_type;;
300 case "decimal":
301 return TypeManager.decimal_type;;
302 case "byte":
303 return TypeManager.byte_type;;
304 case "sbyte":
305 return TypeManager.sbyte_type;;
306 case "object":
307 return TypeManager.object_type;;
308 case "bool":
309 return TypeManager.bool_type;;
310 case "string":
311 return TypeManager.string_type;;
312 case "void":
313 return TypeManager.void_type;;
315 FullNamedExpression e = ds.LookupNamespaceOrType (identifier, 0, mc.Location, false);
316 if (e != null) {
317 if (!(e is TypeExpr))
318 return null;
319 return e.Type;
321 int index = identifier.LastIndexOf ('.');
322 if (index < 0)
323 return null;
324 int warn;
325 TypeSpec parent = FindDocumentedType (mc, identifier.Substring (0, index), ds, cref, r);
326 if (parent == null)
327 return null;
328 // no need to detect warning 419 here
329 var ts = FindDocumentedMember (mc, parent,
330 identifier.Substring (index + 1),
331 null, ds, out warn, cref, false, null, r) as TypeSpec;
332 if (ts != null)
333 return ts;
334 return null;
338 // Returns a MemberInfo that is referenced in XML documentation
339 // (by "see" or "seealso" elements).
341 private static MemberSpec FindDocumentedMember (MemberCore mc,
342 TypeSpec type, string member_name, AParametersCollection param_list,
343 DeclSpace ds, out int warning_type, string cref,
344 bool warn419, string name_for_error, Report r)
346 // for (; type != null; type = type.DeclaringType) {
347 var mi = FindDocumentedMemberNoNest (
348 mc, type, member_name, param_list, ds,
349 out warning_type, cref, warn419,
350 name_for_error, r);
351 if (mi != null)
352 return mi; // new FoundMember (type, mi);
353 // }
354 warning_type = 0;
355 return null;
358 private static MemberSpec FindDocumentedMemberNoNest (
359 MemberCore mc, TypeSpec type, string member_name,
360 AParametersCollection param_list, DeclSpace ds, out int warning_type,
361 string cref, bool warn419, string name_for_error, Report Report)
363 warning_type = 0;
364 var filter = new MemberFilter (member_name, 0, MemberKind.All, param_list, null);
365 IList<MemberSpec> found = null;
366 while (type != null && found == null) {
367 found = MemberCache.FindMembers (type, filter, BindingRestriction.None);
368 type = type.DeclaringType;
371 if (found == null)
372 return null;
374 if (warn419 && found.Count > 1) {
375 Report419 (mc, name_for_error, found.ToArray (), Report);
378 return found [0];
381 if (param_list == null) {
382 // search for fields/events etc.
383 mis = TypeManager.MemberLookup (type, null,
384 type, MemberKind.All,
385 BindingRestriction.None,
386 member_name, null);
387 mis = FilterOverridenMembersOut (mis);
388 if (mis == null || mis.Length == 0)
389 return null;
390 if (warn419 && IsAmbiguous (mis))
391 Report419 (mc, name_for_error, mis, Report);
392 return mis [0];
395 MethodSignature msig = new MethodSignature (member_name, null, param_list);
396 mis = FindMethodBase (type,
397 BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance,
398 msig);
400 if (warn419 && mis.Length > 0) {
401 if (IsAmbiguous (mis))
402 Report419 (mc, name_for_error, mis, Report);
403 return mis [0];
406 // search for operators (whose parameters exactly
407 // matches with the list) and possibly report CS1581.
408 string oper = null;
409 string return_type_name = null;
410 if (member_name.StartsWith ("implicit operator ")) {
411 Operator.GetMetadataName (Operator.OpType.Implicit);
412 return_type_name = member_name.Substring (18).Trim (wsChars);
414 else if (member_name.StartsWith ("explicit operator ")) {
415 oper = Operator.GetMetadataName (Operator.OpType.Explicit);
416 return_type_name = member_name.Substring (18).Trim (wsChars);
418 else if (member_name.StartsWith ("operator ")) {
419 oper = member_name.Substring (9).Trim (wsChars);
420 switch (oper) {
421 // either unary or binary
422 case "+":
423 oper = param_list.Length == 2 ?
424 Operator.GetMetadataName (Operator.OpType.Addition) :
425 Operator.GetMetadataName (Operator.OpType.UnaryPlus);
426 break;
427 case "-":
428 oper = param_list.Length == 2 ?
429 Operator.GetMetadataName (Operator.OpType.Subtraction) :
430 Operator.GetMetadataName (Operator.OpType.UnaryNegation);
431 break;
432 default:
433 oper = Operator.GetMetadataName (oper);
434 if (oper != null)
435 break;
437 warning_type = 1584;
438 Report.Warning (1020, 1, mc.Location, "Overloadable {0} operator is expected", param_list.Length == 2 ? "binary" : "unary");
439 Report.Warning (1584, 1, mc.Location, "XML comment on `{0}' has syntactically incorrect cref attribute `{1}'",
440 mc.GetSignatureForError (), cref);
441 return null;
444 // here we still don't consider return type (to
445 // detect CS1581 or CS1002+CS1584).
446 msig = new MethodSignature (oper, null, param_list);
448 mis = FindMethodBase (type,
449 BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance,
450 msig);
451 if (mis.Length == 0)
452 return null; // CS1574
453 var mi = mis [0];
454 TypeSpec expected = mi is MethodSpec ?
455 ((MethodSpec) mi).ReturnType :
456 mi is PropertySpec ?
457 ((PropertySpec) mi).PropertyType :
458 null;
459 if (return_type_name != null) {
460 TypeSpec returnType = FindDocumentedType (mc, return_type_name, ds, cref, Report);
461 if (returnType == null || returnType != expected) {
462 warning_type = 1581;
463 Report.Warning (1581, 1, mc.Location, "Invalid return type in XML comment cref attribute `{0}'", cref);
464 return null;
467 return mis [0];
472 // Processes "see" or "seealso" elements.
473 // Checks cref attribute.
475 private static void HandleXrefCommon (MemberCore mc,
476 DeclSpace ds, XmlElement xref, Report Report)
478 string cref = xref.GetAttribute ("cref").Trim (wsChars);
479 // when, XmlReader, "if (cref == null)"
480 if (!xref.HasAttribute ("cref"))
481 return;
482 if (cref.Length == 0)
483 Report.Warning (1001, 1, mc.Location, "Identifier expected");
484 // ... and continue until CS1584.
486 string signature; // "x:" are stripped
487 string name; // method invokation "(...)" are removed
488 string parameters; // method parameter list
490 // When it found '?:' ('T:' 'M:' 'F:' 'P:' 'E:' etc.),
491 // MS ignores not only its member kind, but also
492 // the entire syntax correctness. Nor it also does
493 // type fullname resolution i.e. "T:List(int)" is kept
494 // as T:List(int), not
495 // T:System.Collections.Generic.List&lt;System.Int32&gt;
496 if (cref.Length > 2 && cref [1] == ':')
497 return;
498 else
499 signature = cref;
501 // Also note that without "T:" any generic type
502 // indication fails.
504 int parens_pos = signature.IndexOf ('(');
505 int brace_pos = parens_pos >= 0 ? -1 :
506 signature.IndexOf ('[');
507 if (parens_pos > 0 && signature [signature.Length - 1] == ')') {
508 name = signature.Substring (0, parens_pos).Trim (wsChars);
509 parameters = signature.Substring (parens_pos + 1, signature.Length - parens_pos - 2).Trim (wsChars);
511 else if (brace_pos > 0 && signature [signature.Length - 1] == ']') {
512 name = signature.Substring (0, brace_pos).Trim (wsChars);
513 parameters = signature.Substring (brace_pos + 1, signature.Length - brace_pos - 2).Trim (wsChars);
515 else {
516 name = signature;
517 parameters = null;
519 Normalize (mc, ref name, Report);
521 string identifier = GetBodyIdentifierFromName (name);
523 // Check if identifier is valid.
524 // This check is not necessary to mark as error, but
525 // csc specially reports CS1584 for wrong identifiers.
526 string [] name_elems = identifier.Split ('.');
527 for (int i = 0; i < name_elems.Length; i++) {
528 string nameElem = GetBodyIdentifierFromName (name_elems [i]);
529 if (i > 0)
530 Normalize (mc, ref nameElem, Report);
531 if (!Tokenizer.IsValidIdentifier (nameElem)
532 && nameElem.IndexOf ("operator") < 0) {
533 Report.Warning (1584, 1, mc.Location, "XML comment on `{0}' has syntactically incorrect cref attribute `{1}'",
534 mc.GetSignatureForError (), cref);
535 xref.SetAttribute ("cref", "!:" + signature);
536 return;
540 // check if parameters are valid
541 AParametersCollection parameter_types;
542 if (parameters == null)
543 parameter_types = null;
544 else if (parameters.Length == 0)
545 parameter_types = ParametersCompiled.EmptyReadOnlyParameters;
546 else {
547 string [] param_list = parameters.Split (',');
548 var plist = new List<TypeSpec> ();
549 for (int i = 0; i < param_list.Length; i++) {
550 string param_type_name = param_list [i].Trim (wsChars);
551 Normalize (mc, ref param_type_name, Report);
552 TypeSpec param_type = FindDocumentedType (mc, param_type_name, ds, cref, Report);
553 if (param_type == null) {
554 Report.Warning (1580, 1, mc.Location, "Invalid type for parameter `{0}' in XML comment cref attribute `{1}'",
555 (i + 1).ToString (), cref);
556 return;
558 plist.Add (param_type);
561 parameter_types = ParametersCompiled.CreateFullyResolved (plist.ToArray ());
564 TypeSpec type = FindDocumentedType (mc, name, ds, cref, Report);
565 if (type != null
566 // delegate must not be referenced with args
567 && (!type.IsDelegate
568 || parameter_types == null)) {
569 string result = GetSignatureForDoc (type)
570 + (brace_pos < 0 ? String.Empty : signature.Substring (brace_pos));
571 xref.SetAttribute ("cref", "T:" + result);
572 return; // a type
575 int period = name.LastIndexOf ('.');
576 if (period > 0) {
577 string typeName = name.Substring (0, period);
578 string member_name = name.Substring (period + 1);
579 string lookup_name = member_name == "this" ? MemberCache.IndexerNameAlias : member_name;
580 Normalize (mc, ref lookup_name, Report);
581 Normalize (mc, ref member_name, Report);
582 type = FindDocumentedType (mc, typeName, ds, cref, Report);
583 int warn_result;
584 if (type != null) {
585 var mi = FindDocumentedMember (mc, type, lookup_name, parameter_types, ds, out warn_result, cref, true, name, Report);
586 if (warn_result > 0)
587 return;
588 if (mi != null) {
589 // we cannot use 'type' directly
590 // to get its name, since mi
591 // could be from DeclaringType
592 // for nested types.
593 xref.SetAttribute ("cref", GetMemberDocHead (mi) + GetSignatureForDoc (mi.DeclaringType) + "." + member_name + GetParametersFormatted (mi));
594 return; // a member of a type
597 } else {
598 int warn_result;
599 var mi = FindDocumentedMember (mc, ds.PartialContainer.Definition, name, parameter_types, ds, out warn_result, cref, true, name, Report);
601 if (warn_result > 0)
602 return;
603 if (mi != null) {
604 // we cannot use 'type' directly
605 // to get its name, since mi
606 // could be from DeclaringType
607 // for nested types.
608 xref.SetAttribute ("cref", GetMemberDocHead (mi) + GetSignatureForDoc (mi.DeclaringType) + "." + name + GetParametersFormatted (mi));
609 return; // local member name
613 // It still might be part of namespace name.
614 Namespace ns = ds.NamespaceEntry.NS.GetNamespace (name, false);
615 if (ns != null) {
616 xref.SetAttribute ("cref", "N:" + ns.GetSignatureForError ());
617 return; // a namespace
619 if (GlobalRootNamespace.Instance.IsNamespace (name)) {
620 xref.SetAttribute ("cref", "N:" + name);
621 return; // a namespace
624 Report.Warning (1574, 1, mc.Location, "XML comment on `{0}' has cref attribute `{1}' that could not be resolved",
625 mc.GetSignatureForError (), cref);
627 xref.SetAttribute ("cref", "!:" + name);
630 static string GetParametersFormatted (MemberSpec mi)
632 var pm = mi as IParametersMember;
633 if (pm == null || pm.Parameters.IsEmpty)
634 return string.Empty;
636 AParametersCollection parameters = pm.Parameters;
638 if (parameters == null || parameters.Count == 0)
639 return String.Empty;
641 StringBuilder sb = new StringBuilder ();
642 sb.Append ('(');
643 for (int i = 0; i < parameters.Count; i++) {
644 // if (is_setter && i + 1 == parameters.Count)
645 // break; // skip "value".
646 if (i > 0)
647 sb.Append (',');
648 TypeSpec t = parameters.Types [i];
649 sb.Append (GetSignatureForDoc (t));
651 sb.Append (')');
652 return sb.ToString ();
655 static string GetBodyIdentifierFromName (string name)
657 string identifier = name;
659 if (name.Length > 0 && name [name.Length - 1] == ']') {
660 string tmp = name.Substring (0, name.Length - 1).Trim (wsChars);
661 int last = tmp.LastIndexOf ('[');
662 if (last > 0)
663 identifier = tmp.Substring (0, last).Trim (wsChars);
666 return identifier;
669 static void Report419 (MemberCore mc, string member_name, MemberSpec [] mis, Report Report)
671 Report.Warning (419, 3, mc.Location,
672 "Ambiguous reference in cref attribute `{0}'. Assuming `{1}' but other overloads including `{2}' have also matched",
673 member_name,
674 TypeManager.GetFullNameSignature (mis [0]),
675 TypeManager.GetFullNameSignature (mis [1]));
679 // Get a prefix from member type for XML documentation (used
680 // to formalize cref target name).
682 static string GetMemberDocHead (MemberSpec type)
684 if (type is FieldSpec)
685 return "F:";
686 if (type is MethodSpec)
687 return "M:";
688 if (type is EventSpec)
689 return "E:";
690 if (type is PropertySpec)
691 return "P:";
692 if (type is TypeSpec)
693 return "T:";
695 return "!:";
698 // MethodCore
701 // Returns a string that represents the signature for this
702 // member which should be used in XML documentation.
704 public static string GetMethodDocCommentName (MemberCore mc, ParametersCompiled parameters, DeclSpace ds)
706 IParameterData [] plist = parameters.FixedParameters;
707 string paramSpec = String.Empty;
708 if (plist != null) {
709 StringBuilder psb = new StringBuilder ();
710 int i = 0;
711 foreach (Parameter p in plist) {
712 psb.Append (psb.Length != 0 ? "," : "(");
713 psb.Append (GetSignatureForDoc (parameters.Types [i++]));
714 if ((p.ModFlags & Parameter.Modifier.ISBYREF) != 0)
715 psb.Append ('@');
717 paramSpec = psb.ToString ();
720 if (paramSpec.Length > 0)
721 paramSpec += ")";
723 string name = mc is Constructor ? "#ctor" : mc.Name;
724 if (mc.MemberName.IsGeneric)
725 name += "``" + mc.MemberName.CountTypeArguments;
727 string suffix = String.Empty;
728 Operator op = mc as Operator;
729 if (op != null) {
730 switch (op.OperatorType) {
731 case Operator.OpType.Implicit:
732 case Operator.OpType.Explicit:
733 suffix = "~" + GetSignatureForDoc (op.ReturnType);
734 break;
737 return String.Concat (mc.DocCommentHeader, ds.Name, ".", name, paramSpec, suffix);
740 static string GetSignatureForDoc (TypeSpec type)
742 var tp = type as TypeParameterSpec;
743 if (tp != null) {
744 var prefix = tp.IsMethodOwned ? "``" : "`";
745 return prefix + tp.DeclaredPosition;
748 if (TypeManager.IsGenericType (type)) {
749 string g = type.MemberDefinition.Namespace;
750 if (g != null && g.Length > 0)
751 g += '.';
752 int idx = type.Name.LastIndexOf ('`');
753 g += (idx < 0 ? type.Name : type.Name.Substring (0, idx)) + '{';
754 int argpos = 0;
755 foreach (TypeSpec t in TypeManager.GetTypeArguments (type))
756 g += (argpos++ > 0 ? "," : String.Empty) + GetSignatureForDoc (t);
757 g += '}';
758 return g;
761 string name = type.GetMetaInfo ().FullName != null ? type.GetMetaInfo ().FullName : type.Name;
762 return name.Replace ("+", ".").Replace ('&', '@');
766 // Raised (and passed an XmlElement that contains the comment)
767 // when GenerateDocComment is writing documentation expectedly.
769 // FIXME: with a few effort, it could be done with XmlReader,
770 // that means removal of DOM use.
772 internal static void OnMethodGenerateDocComment (
773 MethodCore mc, XmlElement el, Report Report)
775 var paramTags = new Dictionary<string, string> ();
776 foreach (XmlElement pelem in el.SelectNodes ("param")) {
777 string xname = pelem.GetAttribute ("name");
778 if (xname.Length == 0)
779 continue; // really? but MS looks doing so
780 if (xname != "" && mc.ParameterInfo.GetParameterIndexByName (xname) < 0)
781 Report.Warning (1572, 2, mc.Location, "XML comment on `{0}' has a param tag for `{1}', but there is no parameter by that name",
782 mc.GetSignatureForError (), xname);
783 else if (paramTags.ContainsKey (xname))
784 Report.Warning (1571, 2, mc.Location, "XML comment on `{0}' has a duplicate param tag for `{1}'",
785 mc.GetSignatureForError (), xname);
786 paramTags [xname] = xname;
788 IParameterData [] plist = mc.ParameterInfo.FixedParameters;
789 foreach (Parameter p in plist) {
790 if (paramTags.Count > 0 && !paramTags.ContainsKey (p.Name))
791 Report.Warning (1573, 4, mc.Location, "Parameter `{0}' has no matching param tag in the XML comment for `{1}'",
792 p.Name, mc.GetSignatureForError ());
796 private static void Normalize (MemberCore mc, ref string name, Report Report)
798 if (name.Length > 0 && name [0] == '@')
799 name = name.Substring (1);
800 else if (name == "this")
801 name = "Item";
802 else if (Tokenizer.IsKeyword (name) && !IsTypeName (name))
803 Report.Warning (1041, 1, mc.Location, "Identifier expected. `{0}' is a keyword", name);
806 private static bool IsTypeName (string name)
808 switch (name) {
809 case "bool":
810 case "byte":
811 case "char":
812 case "decimal":
813 case "double":
814 case "float":
815 case "int":
816 case "long":
817 case "object":
818 case "sbyte":
819 case "short":
820 case "string":
821 case "uint":
822 case "ulong":
823 case "ushort":
824 case "void":
825 return true;
827 return false;
832 // Implements XML documentation generation.
834 public class Documentation
836 public Documentation (string xml_output_filename)
838 docfilename = xml_output_filename;
839 XmlDocumentation = new XmlDocument ();
840 XmlDocumentation.PreserveWhitespace = false;
843 private string docfilename;
846 // Used to create element which helps well-formedness checking.
848 public XmlDocument XmlDocumentation;
851 // The output for XML documentation.
853 public XmlWriter XmlCommentOutput;
856 // Stores XmlDocuments that are included in XML documentation.
857 // Keys are included filenames, values are XmlDocuments.
859 public Dictionary<string, XmlDocument> StoredDocuments = new Dictionary<string, XmlDocument> ();
862 // Outputs XML documentation comment from tokenized comments.
864 public bool OutputDocComment (string asmfilename, Report Report)
866 XmlTextWriter w = null;
867 try {
868 w = new XmlTextWriter (docfilename, null);
869 w.Indentation = 4;
870 w.Formatting = Formatting.Indented;
871 w.WriteStartDocument ();
872 w.WriteStartElement ("doc");
873 w.WriteStartElement ("assembly");
874 w.WriteStartElement ("name");
875 w.WriteString (Path.ChangeExtension (asmfilename, null));
876 w.WriteEndElement (); // name
877 w.WriteEndElement (); // assembly
878 w.WriteStartElement ("members");
879 XmlCommentOutput = w;
880 GenerateDocComment (Report);
881 w.WriteFullEndElement (); // members
882 w.WriteEndElement ();
883 w.WriteWhitespace (Environment.NewLine);
884 w.WriteEndDocument ();
885 return true;
886 } catch (Exception ex) {
887 Report.Error (1569, "Error generating XML documentation file `{0}' (`{1}')", docfilename, ex.Message);
888 return false;
889 } finally {
890 if (w != null)
891 w.Close ();
896 // Fixes full type name of each documented types/members up.
898 public void GenerateDocComment (Report r)
900 TypeContainer root = RootContext.ToplevelTypes;
902 if (root.Types != null)
903 foreach (TypeContainer tc in root.Types)
904 DocUtil.GenerateTypeDocComment (tc, null, r);