2009-11-24 Jb Evain <jbevain@novell.com>
[mcs.git] / mcs / doc.cs
blob986990c423245e571515557bc2355ba21592d651
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;
15 using System.Collections.Specialized;
16 using System.IO;
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;
23 using System.Text;
24 using System.Xml;
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.Delegates != null)
58 foreach (Delegate de in t.Delegates)
59 de.GenerateDocComment (t);
61 if (t.Constants != null)
62 foreach (Const c in t.Constants)
63 c.GenerateDocComment (t);
65 if (t.Fields != null)
66 foreach (FieldBase f in t.Fields)
67 f.GenerateDocComment (t);
69 if (t.Events != null)
70 foreach (Event e in t.Events)
71 e.GenerateDocComment (t);
73 if (t.Indexers != null)
74 foreach (Indexer ix in t.Indexers)
75 ix.GenerateDocComment (t);
77 if (t.Properties != null)
78 foreach (Property p in t.Properties)
79 p.GenerateDocComment (t);
81 if (t.Methods != null)
82 foreach (MethodOrOperator m in t.Methods)
83 m.GenerateDocComment (t);
85 if (t.Operators != null)
86 foreach (Operator o in t.Operators)
87 o.GenerateDocComment (t);
90 // MemberCore
91 private static readonly string line_head =
92 Environment.NewLine + " ";
94 private static XmlNode GetDocCommentNode (MemberCore mc,
95 string name, Report Report)
97 // FIXME: It could be even optimizable as not
98 // to use XmlDocument. But anyways the nodes
99 // are not kept in memory.
100 XmlDocument doc = RootContext.Documentation.XmlDocumentation;
101 try {
102 XmlElement el = doc.CreateElement ("member");
103 el.SetAttribute ("name", name);
104 string normalized = mc.DocComment;
105 el.InnerXml = normalized;
106 // csc keeps lines as written in the sources
107 // and inserts formatting indentation (which
108 // is different from XmlTextWriter.Formatting
109 // one), but when a start tag contains an
110 // endline, it joins the next line. We don't
111 // have to follow such a hacky behavior.
112 string [] split =
113 normalized.Split ('\n');
114 int j = 0;
115 for (int i = 0; i < split.Length; i++) {
116 string s = split [i].TrimEnd ();
117 if (s.Length > 0)
118 split [j++] = s;
120 el.InnerXml = line_head + String.Join (
121 line_head, split, 0, j);
122 return el;
123 } catch (Exception ex) {
124 Report.Warning (1570, 1, mc.Location, "XML comment on `{0}' has non-well-formed XML ({1})", name, ex.Message);
125 XmlComment com = doc.CreateComment (String.Format ("FIXME: Invalid documentation markup was found for member {0}", name));
126 return com;
131 // Generates xml doc comments (if any), and if required,
132 // handle warning report.
134 internal static void GenerateDocComment (MemberCore mc,
135 DeclSpace ds, Report Report)
137 if (mc.DocComment != null) {
138 string name = mc.GetDocCommentName (ds);
140 XmlNode n = GetDocCommentNode (mc, name, Report);
142 XmlElement el = n as XmlElement;
143 if (el != null) {
144 mc.OnGenerateDocComment (el);
146 // FIXME: it could be done with XmlReader
147 XmlNodeList nl = n.SelectNodes (".//include");
148 if (nl.Count > 0) {
149 // It could result in current node removal, so prepare another list to iterate.
150 ArrayList al = new ArrayList (nl.Count);
151 foreach (XmlNode inc in nl)
152 al.Add (inc);
153 foreach (XmlElement inc in al)
154 if (!HandleInclude (mc, inc, Report))
155 inc.ParentNode.RemoveChild (inc);
158 // FIXME: it could be done with XmlReader
159 DeclSpace ds_target = mc as DeclSpace;
160 if (ds_target == null)
161 ds_target = ds;
163 foreach (XmlElement see in n.SelectNodes (".//see"))
164 HandleSee (mc, ds_target, see, Report);
165 foreach (XmlElement seealso in n.SelectNodes (".//seealso"))
166 HandleSeeAlso (mc, ds_target, seealso ,Report);
167 foreach (XmlElement see in n.SelectNodes (".//exception"))
168 HandleException (mc, ds_target, see, Report);
171 n.WriteTo (RootContext.Documentation.XmlCommentOutput);
173 else if (mc.IsExposedFromAssembly ()) {
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 bool HandleInclude (MemberCore mc, XmlElement el, Report Report)
187 bool keep_include_node = false;
188 string file = el.GetAttribute ("file");
189 string path = el.GetAttribute ("path");
190 if (file == "") {
191 Report.Warning (1590, 1, mc.Location, "Invalid XML `include' element. Missing `file' attribute");
192 el.ParentNode.InsertBefore (el.OwnerDocument.CreateComment (" Include tag is invalid "), el);
193 keep_include_node = true;
195 else if (path.Length == 0) {
196 Report.Warning (1590, 1, mc.Location, "Invalid XML `include' element. Missing `path' attribute");
197 el.ParentNode.InsertBefore (el.OwnerDocument.CreateComment (" Include tag is invalid "), el);
198 keep_include_node = true;
200 else {
201 XmlDocument doc = RootContext.Documentation.StoredDocuments [file] as XmlDocument;
202 if (doc == null) {
203 try {
204 doc = new XmlDocument ();
205 doc.Load (file);
206 RootContext.Documentation.StoredDocuments.Add (file, doc);
207 } catch (Exception) {
208 el.ParentNode.InsertBefore (el.OwnerDocument.CreateComment (String.Format (" Badly formed XML in at comment file `{0}': cannot be included ", file)), el);
209 Report.Warning (1592, 1, mc.Location, "Badly formed XML in included comments file -- `{0}'", file);
212 if (doc != null) {
213 try {
214 XmlNodeList nl = doc.SelectNodes (path);
215 if (nl.Count == 0) {
216 el.ParentNode.InsertBefore (el.OwnerDocument.CreateComment (" No matching elements were found for the include tag embedded here. "), el);
218 keep_include_node = true;
220 foreach (XmlNode n in nl)
221 el.ParentNode.InsertBefore (el.OwnerDocument.ImportNode (n, true), el);
222 } catch (Exception ex) {
223 el.ParentNode.InsertBefore (el.OwnerDocument.CreateComment (" Failed to insert some or all of included XML "), el);
224 Report.Warning (1589, 1, mc.Location, "Unable to include XML fragment `{0}' of file `{1}' ({2})", path, file, ex.Message);
228 return keep_include_node;
232 // Handles <see> elements.
234 private static void HandleSee (MemberCore mc,
235 DeclSpace ds, XmlElement see, Report r)
237 HandleXrefCommon (mc, ds, see, r);
241 // Handles <seealso> elements.
243 private static void HandleSeeAlso (MemberCore mc,
244 DeclSpace ds, XmlElement seealso, Report r)
246 HandleXrefCommon (mc, ds, seealso, r);
250 // Handles <exception> elements.
252 private static void HandleException (MemberCore mc,
253 DeclSpace ds, XmlElement seealso, Report r)
255 HandleXrefCommon (mc, ds, seealso, r);
258 static readonly char [] wsChars =
259 new char [] {' ', '\t', '\n', '\r'};
262 // returns a full runtime type name from a name which might
263 // be C# specific type name.
265 private static Type FindDocumentedType (MemberCore mc, string name, DeclSpace ds, string cref, Report r)
267 bool is_array = false;
268 string identifier = name;
269 if (name [name.Length - 1] == ']') {
270 string tmp = name.Substring (0, name.Length - 1).Trim (wsChars);
271 if (tmp [tmp.Length - 1] == '[') {
272 identifier = tmp.Substring (0, tmp.Length - 1).Trim (wsChars);
273 is_array = true;
276 Type t = FindDocumentedTypeNonArray (mc, identifier, ds, cref, r);
277 if (t != null && is_array)
278 t = Array.CreateInstance (t, 0).GetType ();
279 return t;
282 private static Type FindDocumentedTypeNonArray (MemberCore mc,
283 string identifier, DeclSpace ds, string cref, Report r)
285 switch (identifier) {
286 case "int":
287 return TypeManager.int32_type;
288 case "uint":
289 return TypeManager.uint32_type;
290 case "short":
291 return TypeManager.short_type;;
292 case "ushort":
293 return TypeManager.ushort_type;
294 case "long":
295 return TypeManager.int64_type;
296 case "ulong":
297 return TypeManager.uint64_type;;
298 case "float":
299 return TypeManager.float_type;;
300 case "double":
301 return TypeManager.double_type;
302 case "char":
303 return TypeManager.char_type;;
304 case "decimal":
305 return TypeManager.decimal_type;;
306 case "byte":
307 return TypeManager.byte_type;;
308 case "sbyte":
309 return TypeManager.sbyte_type;;
310 case "object":
311 return TypeManager.object_type;;
312 case "bool":
313 return TypeManager.bool_type;;
314 case "string":
315 return TypeManager.string_type;;
316 case "void":
317 return TypeManager.void_type;;
319 FullNamedExpression e = ds.LookupNamespaceOrType (identifier, mc.Location, false);
320 if (e != null) {
321 if (!(e is TypeExpr))
322 return null;
323 return e.Type;
325 int index = identifier.LastIndexOf ('.');
326 if (index < 0)
327 return null;
328 int warn;
329 Type parent = FindDocumentedType (mc, identifier.Substring (0, index), ds, cref, r);
330 if (parent == null)
331 return null;
332 // no need to detect warning 419 here
333 return FindDocumentedMember (mc, parent,
334 identifier.Substring (index + 1),
335 null, ds, out warn, cref, false, null, r).Member as Type;
338 private static MemberInfo [] empty_member_infos =
339 new MemberInfo [0];
341 private static MemberInfo [] FindMethodBase (Type type,
342 BindingFlags binding_flags, MethodSignature signature)
344 MemberList ml = TypeManager.FindMembers (
345 type,
346 MemberTypes.Constructor | MemberTypes.Method | MemberTypes.Property | MemberTypes.Custom,
347 binding_flags,
348 MethodSignature.method_signature_filter,
349 signature);
350 if (ml == null)
351 return empty_member_infos;
353 return FilterOverridenMembersOut ((MemberInfo []) ml);
356 static bool IsOverride (PropertyInfo deriv_prop, PropertyInfo base_prop)
358 if (!MethodGroupExpr.IsAncestralType (base_prop.DeclaringType, deriv_prop.DeclaringType))
359 return false;
361 Type [] deriv_pd = TypeManager.GetParameterData (deriv_prop).Types;
362 Type [] base_pd = TypeManager.GetParameterData (base_prop).Types;
364 if (deriv_pd.Length != base_pd.Length)
365 return false;
367 for (int j = 0; j < deriv_pd.Length; ++j) {
368 if (deriv_pd [j] != base_pd [j])
369 return false;
370 Type ct = TypeManager.TypeToCoreType (deriv_pd [j]);
371 Type bt = TypeManager.TypeToCoreType (base_pd [j]);
373 if (ct != bt)
374 return false;
377 return true;
380 private static MemberInfo [] FilterOverridenMembersOut (
381 MemberInfo [] ml)
383 if (ml == null)
384 return empty_member_infos;
386 ArrayList al = new ArrayList (ml.Length);
387 for (int i = 0; i < ml.Length; i++) {
388 MethodBase mx = ml [i] as MethodBase;
389 PropertyInfo px = ml [i] as PropertyInfo;
390 if (mx != null || px != null) {
391 bool overriden = false;
392 for (int j = 0; j < ml.Length; j++) {
393 if (j == i)
394 continue;
395 MethodBase my = ml [j] as MethodBase;
396 if (mx != null && my != null &&
397 MethodGroupExpr.IsOverride (my, mx)) {
398 overriden = true;
399 break;
401 else if (mx != null)
402 continue;
403 PropertyInfo py = ml [j] as PropertyInfo;
404 if (px != null && py != null &&
405 IsOverride (py, px)) {
406 overriden = true;
407 break;
410 if (overriden)
411 continue;
413 al.Add (ml [i]);
415 return al.ToArray (typeof (MemberInfo)) as MemberInfo [];
418 struct FoundMember
420 public static FoundMember Empty = new FoundMember (true);
422 public bool IsEmpty;
423 public readonly MemberInfo Member;
424 public readonly Type Type;
426 public FoundMember (bool regardless_of_this_value_its_empty)
428 IsEmpty = true;
429 Member = null;
430 Type = null;
433 public FoundMember (Type found_type, MemberInfo member)
435 IsEmpty = false;
436 Type = found_type;
437 Member = member;
442 // Returns a MemberInfo that is referenced in XML documentation
443 // (by "see" or "seealso" elements).
445 private static FoundMember FindDocumentedMember (MemberCore mc,
446 Type type, string member_name, Type [] param_list,
447 DeclSpace ds, out int warning_type, string cref,
448 bool warn419, string name_for_error, Report r)
450 for (; type != null; type = type.DeclaringType) {
451 MemberInfo mi = FindDocumentedMemberNoNest (
452 mc, type, member_name, param_list, ds,
453 out warning_type, cref, warn419,
454 name_for_error, r);
455 if (mi != null)
456 return new FoundMember (type, mi);
458 warning_type = 0;
459 return FoundMember.Empty;
462 private static MemberInfo FindDocumentedMemberNoNest (
463 MemberCore mc, Type type, string member_name,
464 Type [] param_list, DeclSpace ds, out int warning_type,
465 string cref, bool warn419, string name_for_error, Report Report)
467 warning_type = 0;
468 MemberInfo [] mis;
470 if (param_list == null) {
471 // search for fields/events etc.
472 mis = TypeManager.MemberLookup (type, null,
473 type, MemberTypes.All,
474 BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance,
475 member_name, null);
476 mis = FilterOverridenMembersOut (mis);
477 if (mis == null || mis.Length == 0)
478 return null;
479 if (warn419 && IsAmbiguous (mis))
480 Report419 (mc, name_for_error, mis, Report);
481 return mis [0];
484 MethodSignature msig = new MethodSignature (member_name, null, param_list);
485 mis = FindMethodBase (type,
486 BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance,
487 msig);
489 if (warn419 && mis.Length > 0) {
490 if (IsAmbiguous (mis))
491 Report419 (mc, name_for_error, mis, Report);
492 return mis [0];
495 // search for operators (whose parameters exactly
496 // matches with the list) and possibly report CS1581.
497 string oper = null;
498 string return_type_name = null;
499 if (member_name.StartsWith ("implicit operator ")) {
500 Operator.GetMetadataName (Operator.OpType.Implicit);
501 return_type_name = member_name.Substring (18).Trim (wsChars);
503 else if (member_name.StartsWith ("explicit operator ")) {
504 oper = Operator.GetMetadataName (Operator.OpType.Explicit);
505 return_type_name = member_name.Substring (18).Trim (wsChars);
507 else if (member_name.StartsWith ("operator ")) {
508 oper = member_name.Substring (9).Trim (wsChars);
509 switch (oper) {
510 // either unary or binary
511 case "+":
512 oper = param_list.Length == 2 ?
513 Operator.GetMetadataName (Operator.OpType.Addition) :
514 Operator.GetMetadataName (Operator.OpType.UnaryPlus);
515 break;
516 case "-":
517 oper = param_list.Length == 2 ?
518 Operator.GetMetadataName (Operator.OpType.Subtraction) :
519 Operator.GetMetadataName (Operator.OpType.UnaryNegation);
520 break;
521 default:
522 oper = Operator.GetMetadataName (oper);
523 if (oper != null)
524 break;
526 warning_type = 1584;
527 Report.Warning (1020, 1, mc.Location, "Overloadable {0} operator is expected", param_list.Length == 2 ? "binary" : "unary");
528 Report.Warning (1584, 1, mc.Location, "XML comment on `{0}' has syntactically incorrect cref attribute `{1}'",
529 mc.GetSignatureForError (), cref);
530 return null;
533 // here we still don't consider return type (to
534 // detect CS1581 or CS1002+CS1584).
535 msig = new MethodSignature (oper, null, param_list);
537 mis = FindMethodBase (type,
538 BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance,
539 msig);
540 if (mis.Length == 0)
541 return null; // CS1574
542 MemberInfo mi = mis [0];
543 Type expected = mi is MethodInfo ?
544 ((MethodInfo) mi).ReturnType :
545 mi is PropertyInfo ?
546 ((PropertyInfo) mi).PropertyType :
547 null;
548 if (return_type_name != null) {
549 Type returnType = FindDocumentedType (mc, return_type_name, ds, cref, Report);
550 if (returnType == null || returnType != expected) {
551 warning_type = 1581;
552 Report.Warning (1581, 1, mc.Location, "Invalid return type in XML comment cref attribute `{0}'", cref);
553 return null;
556 return mis [0];
559 private static bool IsAmbiguous (MemberInfo [] members)
561 if (members.Length < 2)
562 return false;
563 if (members.Length > 2)
564 return true;
565 if (members [0] is EventInfo && members [1] is FieldInfo)
566 return false;
567 if (members [1] is EventInfo && members [0] is FieldInfo)
568 return false;
569 return true;
573 // Processes "see" or "seealso" elements.
574 // Checks cref attribute.
576 private static void HandleXrefCommon (MemberCore mc,
577 DeclSpace ds, XmlElement xref, Report Report)
579 string cref = xref.GetAttribute ("cref").Trim (wsChars);
580 // when, XmlReader, "if (cref == null)"
581 if (!xref.HasAttribute ("cref"))
582 return;
583 if (cref.Length == 0)
584 Report.Warning (1001, 1, mc.Location, "Identifier expected");
585 // ... and continue until CS1584.
587 string signature; // "x:" are stripped
588 string name; // method invokation "(...)" are removed
589 string parameters; // method parameter list
591 // When it found '?:' ('T:' 'M:' 'F:' 'P:' 'E:' etc.),
592 // MS ignores not only its member kind, but also
593 // the entire syntax correctness. Nor it also does
594 // type fullname resolution i.e. "T:List(int)" is kept
595 // as T:List(int), not
596 // T:System.Collections.Generic.List&lt;System.Int32&gt;
597 if (cref.Length > 2 && cref [1] == ':')
598 return;
599 else
600 signature = cref;
602 // Also note that without "T:" any generic type
603 // indication fails.
605 int parens_pos = signature.IndexOf ('(');
606 int brace_pos = parens_pos >= 0 ? -1 :
607 signature.IndexOf ('[');
608 if (parens_pos > 0 && signature [signature.Length - 1] == ')') {
609 name = signature.Substring (0, parens_pos).Trim (wsChars);
610 parameters = signature.Substring (parens_pos + 1, signature.Length - parens_pos - 2).Trim (wsChars);
612 else if (brace_pos > 0 && signature [signature.Length - 1] == ']') {
613 name = signature.Substring (0, brace_pos).Trim (wsChars);
614 parameters = signature.Substring (brace_pos + 1, signature.Length - brace_pos - 2).Trim (wsChars);
616 else {
617 name = signature;
618 parameters = null;
620 Normalize (mc, ref name, Report);
622 string identifier = GetBodyIdentifierFromName (name);
624 // Check if identifier is valid.
625 // This check is not necessary to mark as error, but
626 // csc specially reports CS1584 for wrong identifiers.
627 string [] name_elems = identifier.Split ('.');
628 for (int i = 0; i < name_elems.Length; i++) {
629 string nameElem = GetBodyIdentifierFromName (name_elems [i]);
630 if (i > 0)
631 Normalize (mc, ref nameElem, Report);
632 if (!Tokenizer.IsValidIdentifier (nameElem)
633 && nameElem.IndexOf ("operator") < 0) {
634 Report.Warning (1584, 1, mc.Location, "XML comment on `{0}' has syntactically incorrect cref attribute `{1}'",
635 mc.GetSignatureForError (), cref);
636 xref.SetAttribute ("cref", "!:" + signature);
637 return;
641 // check if parameters are valid
642 Type [] parameter_types;
643 if (parameters == null)
644 parameter_types = null;
645 else if (parameters.Length == 0)
646 parameter_types = Type.EmptyTypes;
647 else {
648 string [] param_list = parameters.Split (',');
649 ArrayList plist = new ArrayList ();
650 for (int i = 0; i < param_list.Length; i++) {
651 string param_type_name = param_list [i].Trim (wsChars);
652 Normalize (mc, ref param_type_name, Report);
653 Type param_type = FindDocumentedType (mc, param_type_name, ds, cref, Report);
654 if (param_type == null) {
655 Report.Warning (1580, 1, mc.Location, "Invalid type for parameter `{0}' in XML comment cref attribute `{1}'",
656 (i + 1).ToString (), cref);
657 return;
659 plist.Add (param_type);
661 parameter_types = plist.ToArray (typeof (Type)) as Type [];
664 Type type = FindDocumentedType (mc, name, ds, cref, Report);
665 if (type != null
666 // delegate must not be referenced with args
667 && (!TypeManager.IsDelegateType (type)
668 || parameter_types == null)) {
669 string result = GetSignatureForDoc (type)
670 + (brace_pos < 0 ? String.Empty : signature.Substring (brace_pos));
671 xref.SetAttribute ("cref", "T:" + result);
672 return; // a type
675 int period = name.LastIndexOf ('.');
676 if (period > 0) {
677 string typeName = name.Substring (0, period);
678 string member_name = name.Substring (period + 1);
679 Normalize (mc, ref member_name, Report);
680 type = FindDocumentedType (mc, typeName, ds, cref, Report);
681 int warn_result;
682 if (type != null) {
683 FoundMember fm = FindDocumentedMember (mc, type, member_name, parameter_types, ds, out warn_result, cref, true, name, Report);
684 if (warn_result > 0)
685 return;
686 if (!fm.IsEmpty) {
687 MemberInfo mi = fm.Member;
688 // we cannot use 'type' directly
689 // to get its name, since mi
690 // could be from DeclaringType
691 // for nested types.
692 xref.SetAttribute ("cref", GetMemberDocHead (mi.MemberType) + GetSignatureForDoc (fm.Type) + "." + member_name + GetParametersFormatted (mi));
693 return; // a member of a type
697 else {
698 int warn_result;
699 FoundMember fm = FindDocumentedMember (mc, ds.TypeBuilder, name, parameter_types, ds, out warn_result, cref, true, name, Report);
700 if (warn_result > 0)
701 return;
702 if (!fm.IsEmpty) {
703 MemberInfo mi = fm.Member;
704 // we cannot use 'type' directly
705 // to get its name, since mi
706 // could be from DeclaringType
707 // for nested types.
708 xref.SetAttribute ("cref", GetMemberDocHead (mi.MemberType) + GetSignatureForDoc (fm.Type) + "." + name + GetParametersFormatted (mi));
709 return; // local member name
713 // It still might be part of namespace name.
714 Namespace ns = ds.NamespaceEntry.NS.GetNamespace (name, false);
715 if (ns != null) {
716 xref.SetAttribute ("cref", "N:" + ns.GetSignatureForError ());
717 return; // a namespace
719 if (GlobalRootNamespace.Instance.IsNamespace (name)) {
720 xref.SetAttribute ("cref", "N:" + name);
721 return; // a namespace
724 Report.Warning (1574, 1, mc.Location, "XML comment on `{0}' has cref attribute `{1}' that could not be resolved",
725 mc.GetSignatureForError (), cref);
727 xref.SetAttribute ("cref", "!:" + name);
730 static string GetParametersFormatted (MemberInfo mi)
732 MethodBase mb = mi as MethodBase;
733 bool is_setter = false;
734 PropertyInfo pi = mi as PropertyInfo;
735 if (pi != null) {
736 mb = pi.GetGetMethod ();
737 if (mb == null) {
738 is_setter = true;
739 mb = pi.GetSetMethod ();
742 if (mb == null)
743 return String.Empty;
745 AParametersCollection parameters = TypeManager.GetParameterData (mb);
746 if (parameters == null || parameters.Count == 0)
747 return String.Empty;
749 StringBuilder sb = new StringBuilder ();
750 sb.Append ('(');
751 for (int i = 0; i < parameters.Count; i++) {
752 if (is_setter && i + 1 == parameters.Count)
753 break; // skip "value".
754 if (i > 0)
755 sb.Append (',');
756 Type t = parameters.Types [i];
757 sb.Append (GetSignatureForDoc (t));
759 sb.Append (')');
760 return sb.ToString ();
763 static string GetBodyIdentifierFromName (string name)
765 string identifier = name;
767 if (name.Length > 0 && name [name.Length - 1] == ']') {
768 string tmp = name.Substring (0, name.Length - 1).Trim (wsChars);
769 int last = tmp.LastIndexOf ('[');
770 if (last > 0)
771 identifier = tmp.Substring (0, last).Trim (wsChars);
774 return identifier;
777 static void Report419 (MemberCore mc, string member_name, MemberInfo [] mis, Report Report)
779 Report.Warning (419, 3, mc.Location,
780 "Ambiguous reference in cref attribute `{0}'. Assuming `{1}' but other overloads including `{2}' have also matched",
781 member_name,
782 TypeManager.GetFullNameSignature (mis [0]),
783 TypeManager.GetFullNameSignature (mis [1]));
787 // Get a prefix from member type for XML documentation (used
788 // to formalize cref target name).
790 static string GetMemberDocHead (MemberTypes type)
792 switch (type) {
793 case MemberTypes.Constructor:
794 case MemberTypes.Method:
795 return "M:";
796 case MemberTypes.Event:
797 return "E:";
798 case MemberTypes.Field:
799 return "F:";
800 case MemberTypes.NestedType:
801 case MemberTypes.TypeInfo:
802 return "T:";
803 case MemberTypes.Property:
804 return "P:";
806 return "!:";
809 // MethodCore
812 // Returns a string that represents the signature for this
813 // member which should be used in XML documentation.
815 public static string GetMethodDocCommentName (MemberCore mc, ParametersCompiled parameters, DeclSpace ds)
817 IParameterData [] plist = parameters.FixedParameters;
818 string paramSpec = String.Empty;
819 if (plist != null) {
820 StringBuilder psb = new StringBuilder ();
821 int i = 0;
822 foreach (Parameter p in plist) {
823 psb.Append (psb.Length != 0 ? "," : "(");
824 psb.Append (GetSignatureForDoc (parameters.Types [i++]));
825 if ((p.ModFlags & Parameter.Modifier.ISBYREF) != 0)
826 psb.Append ('@');
828 paramSpec = psb.ToString ();
831 if (paramSpec.Length > 0)
832 paramSpec += ")";
834 string name = mc is Constructor ? "#ctor" : mc.Name;
835 if (mc.MemberName.IsGeneric)
836 name += "``" + mc.MemberName.CountTypeArguments;
838 string suffix = String.Empty;
839 Operator op = mc as Operator;
840 if (op != null) {
841 switch (op.OperatorType) {
842 case Operator.OpType.Implicit:
843 case Operator.OpType.Explicit:
844 suffix = "~" + GetSignatureForDoc (op.MethodBuilder.ReturnType);
845 break;
848 return String.Concat (mc.DocCommentHeader, ds.Name, ".", name, paramSpec, suffix);
851 static string GetSignatureForDoc (Type type)
853 if (TypeManager.IsGenericParameter (type))
854 return (type.DeclaringMethod != null ? "``" : "`") + TypeManager.GenericParameterPosition (type);
856 if (TypeManager.IsGenericType (type)) {
857 string g = type.Namespace;
858 if (g != null && g.Length > 0)
859 g += '.';
860 int idx = type.Name.LastIndexOf ('`');
861 g += (idx < 0 ? type.Name : type.Name.Substring (0, idx)) + '{';
862 int argpos = 0;
863 foreach (Type t in type.GetGenericArguments ())
864 g += (argpos++ > 0 ? "," : String.Empty) + GetSignatureForDoc (t);
865 g += '}';
866 return g;
869 string name = type.FullName != null ? type.FullName : type.Name;
870 return name.Replace ("+", ".").Replace ('&', '@');
874 // Raised (and passed an XmlElement that contains the comment)
875 // when GenerateDocComment is writing documentation expectedly.
877 // FIXME: with a few effort, it could be done with XmlReader,
878 // that means removal of DOM use.
880 internal static void OnMethodGenerateDocComment (
881 MethodCore mc, XmlElement el, Report Report)
883 Hashtable paramTags = new Hashtable ();
884 foreach (XmlElement pelem in el.SelectNodes ("param")) {
885 string xname = pelem.GetAttribute ("name");
886 if (xname.Length == 0)
887 continue; // really? but MS looks doing so
888 if (xname != "" && mc.Parameters.GetParameterIndexByName (xname) < 0)
889 Report.Warning (1572, 2, mc.Location, "XML comment on `{0}' has a param tag for `{1}', but there is no parameter by that name",
890 mc.GetSignatureForError (), xname);
891 else if (paramTags [xname] != null)
892 Report.Warning (1571, 2, mc.Location, "XML comment on `{0}' has a duplicate param tag for `{1}'",
893 mc.GetSignatureForError (), xname);
894 paramTags [xname] = xname;
896 IParameterData [] plist = mc.Parameters.FixedParameters;
897 foreach (Parameter p in plist) {
898 if (paramTags.Count > 0 && paramTags [p.Name] == null)
899 Report.Warning (1573, 4, mc.Location, "Parameter `{0}' has no matching param tag in the XML comment for `{1}'",
900 p.Name, mc.GetSignatureForError ());
904 private static void Normalize (MemberCore mc, ref string name, Report Report)
906 if (name.Length > 0 && name [0] == '@')
907 name = name.Substring (1);
908 else if (name == "this")
909 name = "Item";
910 else if (Tokenizer.IsKeyword (name) && !IsTypeName (name))
911 Report.Warning (1041, 1, mc.Location, "Identifier expected. `{0}' is a keyword", name);
914 private static bool IsTypeName (string name)
916 switch (name) {
917 case "bool":
918 case "byte":
919 case "char":
920 case "decimal":
921 case "double":
922 case "float":
923 case "int":
924 case "long":
925 case "object":
926 case "sbyte":
927 case "short":
928 case "string":
929 case "uint":
930 case "ulong":
931 case "ushort":
932 case "void":
933 return true;
935 return false;
940 // Implements XML documentation generation.
942 public class Documentation
944 public Documentation (string xml_output_filename)
946 docfilename = xml_output_filename;
947 XmlDocumentation = new XmlDocument ();
948 XmlDocumentation.PreserveWhitespace = false;
951 private string docfilename;
954 // Used to create element which helps well-formedness checking.
956 public XmlDocument XmlDocumentation;
959 // The output for XML documentation.
961 public XmlWriter XmlCommentOutput;
964 // Stores XmlDocuments that are included in XML documentation.
965 // Keys are included filenames, values are XmlDocuments.
967 public Hashtable StoredDocuments = new Hashtable ();
970 // Outputs XML documentation comment from tokenized comments.
972 public bool OutputDocComment (string asmfilename, Report Report)
974 XmlTextWriter w = null;
975 try {
976 w = new XmlTextWriter (docfilename, null);
977 w.Indentation = 4;
978 w.Formatting = Formatting.Indented;
979 w.WriteStartDocument ();
980 w.WriteStartElement ("doc");
981 w.WriteStartElement ("assembly");
982 w.WriteStartElement ("name");
983 w.WriteString (Path.ChangeExtension (asmfilename, null));
984 w.WriteEndElement (); // name
985 w.WriteEndElement (); // assembly
986 w.WriteStartElement ("members");
987 XmlCommentOutput = w;
988 GenerateDocComment (Report);
989 w.WriteFullEndElement (); // members
990 w.WriteEndElement ();
991 w.WriteWhitespace (Environment.NewLine);
992 w.WriteEndDocument ();
993 return true;
994 } catch (Exception ex) {
995 Report.Error (1569, "Error generating XML documentation file `{0}' (`{1}')", docfilename, ex.Message);
996 return false;
997 } finally {
998 if (w != null)
999 w.Close ();
1004 // Fixes full type name of each documented types/members up.
1006 public void GenerateDocComment (Report r)
1008 TypeContainer root = RootContext.ToplevelTypes;
1010 if (root.Types != null)
1011 foreach (TypeContainer tc in root.Types)
1012 DocUtil.GenerateTypeDocComment (tc, null, r);
1014 if (root.Delegates != null)
1015 foreach (Delegate d in root.Delegates)
1016 DocUtil.GenerateDocComment (d, null, r);