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
;
16 using System
.Reflection
;
17 using System
.Reflection
.Emit
;
18 using System
.Runtime
.CompilerServices
;
19 using System
.Runtime
.InteropServices
;
20 using System
.Security
;
21 using System
.Security
.Permissions
;
25 using Mono
.CompilerServices
.SymbolWriter
;
27 namespace Mono
.CSharp
{
30 // Support class for XML documentation.
37 // Generates xml doc comments (if any), and if required,
38 // handle warning report.
40 internal static void GenerateTypeDocComment (TypeContainer t
,
41 DeclSpace ds
, Report Report
)
43 GenerateDocComment (t
, ds
, Report
);
45 if (t
.DefaultStaticConstructor
!= null)
46 t
.DefaultStaticConstructor
.GenerateDocComment (t
);
48 if (t
.InstanceConstructors
!= null)
49 foreach (Constructor c
in t
.InstanceConstructors
)
50 c
.GenerateDocComment (t
);
53 foreach (TypeContainer tc
in t
.Types
)
54 tc
.GenerateDocComment (t
);
56 if (t
.Delegates
!= null)
57 foreach (Delegate de
in t
.Delegates
)
58 de
.GenerateDocComment (t
);
60 if (t
.Constants
!= null)
61 foreach (Const c
in t
.Constants
)
62 c
.GenerateDocComment (t
);
65 foreach (FieldBase f
in t
.Fields
)
66 f
.GenerateDocComment (t
);
69 foreach (Event e
in t
.Events
)
70 e
.GenerateDocComment (t
);
72 if (t
.Indexers
!= null)
73 foreach (Indexer ix
in t
.Indexers
)
74 ix
.GenerateDocComment (t
);
76 if (t
.Properties
!= null)
77 foreach (Property p
in t
.Properties
)
78 p
.GenerateDocComment (t
);
80 if (t
.Methods
!= null)
81 foreach (MethodOrOperator m
in t
.Methods
)
82 m
.GenerateDocComment (t
);
84 if (t
.Operators
!= null)
85 foreach (Operator o
in t
.Operators
)
86 o
.GenerateDocComment (t
);
90 private static readonly string line_head
=
91 Environment
.NewLine
+ " ";
93 private static XmlNode
GetDocCommentNode (MemberCore mc
,
94 string name
, Report Report
)
96 // FIXME: It could be even optimizable as not
97 // to use XmlDocument. But anyways the nodes
98 // are not kept in memory.
99 XmlDocument doc
= RootContext
.Documentation
.XmlDocumentation
;
101 XmlElement el
= doc
.CreateElement ("member");
102 el
.SetAttribute ("name", name
);
103 string normalized
= mc
.DocComment
;
104 el
.InnerXml
= normalized
;
105 // csc keeps lines as written in the sources
106 // and inserts formatting indentation (which
107 // is different from XmlTextWriter.Formatting
108 // one), but when a start tag contains an
109 // endline, it joins the next line. We don't
110 // have to follow such a hacky behavior.
112 normalized
.Split ('\n');
114 for (int i
= 0; i
< split
.Length
; i
++) {
115 string s
= split
[i
].TrimEnd ();
119 el
.InnerXml
= line_head
+ String
.Join (
120 line_head
, split
, 0, j
);
122 } catch (Exception ex
) {
123 Report
.Warning (1570, 1, mc
.Location
, "XML comment on `{0}' has non-well-formed XML ({1})", name
, ex
.Message
);
124 XmlComment com
= doc
.CreateComment (String
.Format ("FIXME: Invalid documentation markup was found for member {0}", name
));
130 // Generates xml doc comments (if any), and if required,
131 // handle warning report.
133 internal static void GenerateDocComment (MemberCore mc
,
134 DeclSpace ds
, Report Report
)
136 if (mc
.DocComment
!= null) {
137 string name
= mc
.GetDocCommentName (ds
);
139 XmlNode n
= GetDocCommentNode (mc
, name
, Report
);
141 XmlElement el
= n
as XmlElement
;
143 mc
.OnGenerateDocComment (el
);
145 // FIXME: it could be done with XmlReader
146 XmlNodeList nl
= n
.SelectNodes (".//include");
148 // It could result in current node removal, so prepare another list to iterate.
149 var al
= new List
<XmlNode
> (nl
.Count
);
150 foreach (XmlNode inc
in nl
)
152 foreach (XmlElement inc
in al
)
153 if (!HandleInclude (mc
, inc
, Report
))
154 inc
.ParentNode
.RemoveChild (inc
);
157 // FIXME: it could be done with XmlReader
158 DeclSpace ds_target
= mc
as DeclSpace
;
159 if (ds_target
== null)
162 foreach (XmlElement see
in n
.SelectNodes (".//see"))
163 HandleSee (mc
, ds_target
, see
, Report
);
164 foreach (XmlElement seealso
in n
.SelectNodes (".//seealso"))
165 HandleSeeAlso (mc
, ds_target
, seealso
,Report
);
166 foreach (XmlElement see
in n
.SelectNodes (".//exception"))
167 HandleException (mc
, ds_target
, see
, Report
);
170 n
.WriteTo (RootContext
.Documentation
.XmlCommentOutput
);
172 else if (mc
.IsExposedFromAssembly ()) {
173 Constructor c
= mc
as Constructor
;
174 if (c
== null || !c
.IsDefault ())
175 Report
.Warning (1591, 4, mc
.Location
,
176 "Missing XML comment for publicly visible type or member `{0}'", mc
.GetSignatureForError ());
181 // Processes "include" element. Check included file and
182 // embed the document content inside this documentation node.
184 private static bool HandleInclude (MemberCore mc
, XmlElement el
, Report Report
)
186 bool keep_include_node
= false;
187 string file
= el
.GetAttribute ("file");
188 string path
= el
.GetAttribute ("path");
190 Report
.Warning (1590, 1, mc
.Location
, "Invalid XML `include' element. Missing `file' attribute");
191 el
.ParentNode
.InsertBefore (el
.OwnerDocument
.CreateComment (" Include tag is invalid "), el
);
192 keep_include_node
= true;
194 else if (path
.Length
== 0) {
195 Report
.Warning (1590, 1, mc
.Location
, "Invalid XML `include' element. Missing `path' attribute");
196 el
.ParentNode
.InsertBefore (el
.OwnerDocument
.CreateComment (" Include tag is invalid "), el
);
197 keep_include_node
= true;
201 if (!RootContext
.Documentation
.StoredDocuments
.TryGetValue (file
, out doc
)) {
203 doc
= new XmlDocument ();
205 RootContext
.Documentation
.StoredDocuments
.Add (file
, doc
);
206 } catch (Exception
) {
207 el
.ParentNode
.InsertBefore (el
.OwnerDocument
.CreateComment (String
.Format (" Badly formed XML in at comment file `{0}': cannot be included ", file
)), el
);
208 Report
.Warning (1592, 1, mc
.Location
, "Badly formed XML in included comments file -- `{0}'", file
);
213 XmlNodeList nl
= doc
.SelectNodes (path
);
215 el
.ParentNode
.InsertBefore (el
.OwnerDocument
.CreateComment (" No matching elements were found for the include tag embedded here. "), el
);
217 keep_include_node
= true;
219 foreach (XmlNode n
in nl
)
220 el
.ParentNode
.InsertBefore (el
.OwnerDocument
.ImportNode (n
, true), el
);
221 } catch (Exception ex
) {
222 el
.ParentNode
.InsertBefore (el
.OwnerDocument
.CreateComment (" Failed to insert some or all of included XML "), el
);
223 Report
.Warning (1589, 1, mc
.Location
, "Unable to include XML fragment `{0}' of file `{1}' ({2})", path
, file
, ex
.Message
);
227 return keep_include_node
;
231 // Handles <see> elements.
233 private static void HandleSee (MemberCore mc
,
234 DeclSpace ds
, XmlElement see
, Report r
)
236 HandleXrefCommon (mc
, ds
, see
, r
);
240 // Handles <seealso> elements.
242 private static void HandleSeeAlso (MemberCore mc
,
243 DeclSpace ds
, XmlElement seealso
, Report r
)
245 HandleXrefCommon (mc
, ds
, seealso
, r
);
249 // Handles <exception> elements.
251 private static void HandleException (MemberCore mc
,
252 DeclSpace ds
, XmlElement seealso
, Report r
)
254 HandleXrefCommon (mc
, ds
, seealso
, r
);
257 static readonly char [] wsChars
=
258 new char [] {' ', '\t', '\n', '\r'}
;
261 // returns a full runtime type name from a name which might
262 // be C# specific type name.
264 private static Type
FindDocumentedType (MemberCore mc
, string name
, DeclSpace ds
, string cref
, Report r
)
266 bool is_array
= false;
267 string identifier
= name
;
268 if (name
[name
.Length
- 1] == ']') {
269 string tmp
= name
.Substring (0, name
.Length
- 1).Trim (wsChars
);
270 if (tmp
[tmp
.Length
- 1] == '[') {
271 identifier
= tmp
.Substring (0, tmp
.Length
- 1).Trim (wsChars
);
275 Type t
= FindDocumentedTypeNonArray (mc
, identifier
, ds
, cref
, r
);
276 if (t
!= null && is_array
)
277 t
= Array
.CreateInstance (t
, 0).GetType ();
281 private static Type
FindDocumentedTypeNonArray (MemberCore mc
,
282 string identifier
, DeclSpace ds
, string cref
, Report r
)
284 switch (identifier
) {
286 return TypeManager
.int32_type
;
288 return TypeManager
.uint32_type
;
290 return TypeManager
.short_type
;;
292 return TypeManager
.ushort_type
;
294 return TypeManager
.int64_type
;
296 return TypeManager
.uint64_type
;;
298 return TypeManager
.float_type
;;
300 return TypeManager
.double_type
;
302 return TypeManager
.char_type
;;
304 return TypeManager
.decimal_type
;;
306 return TypeManager
.byte_type
;;
308 return TypeManager
.sbyte_type
;;
310 return TypeManager
.object_type
;;
312 return TypeManager
.bool_type
;;
314 return TypeManager
.string_type
;;
316 return TypeManager
.void_type
;;
318 FullNamedExpression e
= ds
.LookupNamespaceOrType (identifier
, mc
.Location
, false);
320 if (!(e
is TypeExpr
))
324 int index
= identifier
.LastIndexOf ('.');
328 Type parent
= FindDocumentedType (mc
, identifier
.Substring (0, index
), ds
, cref
, r
);
331 // no need to detect warning 419 here
332 return FindDocumentedMember (mc
, parent
,
333 identifier
.Substring (index
+ 1),
334 null, ds
, out warn
, cref
, false, null, r
).Member
as Type
;
337 private static MemberInfo
[] empty_member_infos
=
340 private static MemberInfo
[] FindMethodBase (Type type
,
341 BindingFlags binding_flags
, MethodSignature signature
)
343 MemberList ml
= TypeManager
.FindMembers (
345 MemberTypes
.Constructor
| MemberTypes
.Method
| MemberTypes
.Property
| MemberTypes
.Custom
,
347 MethodSignature
.method_signature_filter
,
350 return empty_member_infos
;
352 return FilterOverridenMembersOut ((MemberInfo
[]) ml
);
355 static bool IsOverride (PropertyInfo deriv_prop
, PropertyInfo base_prop
)
357 if (!MethodGroupExpr
.IsAncestralType (base_prop
.DeclaringType
, deriv_prop
.DeclaringType
))
360 Type
[] deriv_pd
= TypeManager
.GetParameterData (deriv_prop
).Types
;
361 Type
[] base_pd
= TypeManager
.GetParameterData (base_prop
).Types
;
363 if (deriv_pd
.Length
!= base_pd
.Length
)
366 for (int j
= 0; j
< deriv_pd
.Length
; ++j
) {
367 if (deriv_pd
[j
] != base_pd
[j
])
369 Type ct
= TypeManager
.TypeToCoreType (deriv_pd
[j
]);
370 Type bt
= TypeManager
.TypeToCoreType (base_pd
[j
]);
379 private static MemberInfo
[] FilterOverridenMembersOut (
383 return empty_member_infos
;
385 var al
= new List
<MemberInfo
> (ml
.Length
);
386 for (int i
= 0; i
< ml
.Length
; i
++) {
387 MethodBase mx
= ml
[i
] as MethodBase
;
388 PropertyInfo px
= ml
[i
] as PropertyInfo
;
389 if (mx
!= null || px
!= null) {
390 bool overriden
= false;
391 for (int j
= 0; j
< ml
.Length
; j
++) {
394 MethodBase my
= ml
[j
] as MethodBase
;
395 if (mx
!= null && my
!= null &&
396 MethodGroupExpr
.IsOverride (my
, mx
)) {
402 PropertyInfo py
= ml
[j
] as PropertyInfo
;
403 if (px
!= null && py
!= null &&
404 IsOverride (py
, px
)) {
414 return al
.ToArray ();
419 public static FoundMember Empty
= new FoundMember (true);
422 public readonly MemberInfo Member
;
423 public readonly Type Type
;
425 public FoundMember (bool regardless_of_this_value_its_empty
)
432 public FoundMember (Type found_type
, MemberInfo member
)
441 // Returns a MemberInfo that is referenced in XML documentation
442 // (by "see" or "seealso" elements).
444 private static FoundMember
FindDocumentedMember (MemberCore mc
,
445 Type type
, string member_name
, Type
[] param_list
,
446 DeclSpace ds
, out int warning_type
, string cref
,
447 bool warn419
, string name_for_error
, Report r
)
449 for (; type
!= null; type
= type
.DeclaringType
) {
450 MemberInfo mi
= FindDocumentedMemberNoNest (
451 mc
, type
, member_name
, param_list
, ds
,
452 out warning_type
, cref
, warn419
,
455 return new FoundMember (type
, mi
);
458 return FoundMember
.Empty
;
461 private static MemberInfo
FindDocumentedMemberNoNest (
462 MemberCore mc
, Type type
, string member_name
,
463 Type
[] param_list
, DeclSpace ds
, out int warning_type
,
464 string cref
, bool warn419
, string name_for_error
, Report Report
)
469 if (param_list
== null) {
470 // search for fields/events etc.
471 mis
= TypeManager
.MemberLookup (type
, null,
472 type
, MemberTypes
.All
,
473 BindingFlags
.Public
| BindingFlags
.NonPublic
| BindingFlags
.Static
| BindingFlags
.Instance
,
475 mis
= FilterOverridenMembersOut (mis
);
476 if (mis
== null || mis
.Length
== 0)
478 if (warn419
&& IsAmbiguous (mis
))
479 Report419 (mc
, name_for_error
, mis
, Report
);
483 MethodSignature msig
= new MethodSignature (member_name
, null, param_list
);
484 mis
= FindMethodBase (type
,
485 BindingFlags
.Public
| BindingFlags
.NonPublic
| BindingFlags
.Static
| BindingFlags
.Instance
,
488 if (warn419
&& mis
.Length
> 0) {
489 if (IsAmbiguous (mis
))
490 Report419 (mc
, name_for_error
, mis
, Report
);
494 // search for operators (whose parameters exactly
495 // matches with the list) and possibly report CS1581.
497 string return_type_name
= null;
498 if (member_name
.StartsWith ("implicit operator ")) {
499 Operator
.GetMetadataName (Operator
.OpType
.Implicit
);
500 return_type_name
= member_name
.Substring (18).Trim (wsChars
);
502 else if (member_name
.StartsWith ("explicit operator ")) {
503 oper
= Operator
.GetMetadataName (Operator
.OpType
.Explicit
);
504 return_type_name
= member_name
.Substring (18).Trim (wsChars
);
506 else if (member_name
.StartsWith ("operator ")) {
507 oper
= member_name
.Substring (9).Trim (wsChars
);
509 // either unary or binary
511 oper
= param_list
.Length
== 2 ?
512 Operator
.GetMetadataName (Operator
.OpType
.Addition
) :
513 Operator
.GetMetadataName (Operator
.OpType
.UnaryPlus
);
516 oper
= param_list
.Length
== 2 ?
517 Operator
.GetMetadataName (Operator
.OpType
.Subtraction
) :
518 Operator
.GetMetadataName (Operator
.OpType
.UnaryNegation
);
521 oper
= Operator
.GetMetadataName (oper
);
526 Report
.Warning (1020, 1, mc
.Location
, "Overloadable {0} operator is expected", param_list
.Length
== 2 ? "binary" : "unary");
527 Report
.Warning (1584, 1, mc
.Location
, "XML comment on `{0}' has syntactically incorrect cref attribute `{1}'",
528 mc
.GetSignatureForError (), cref
);
532 // here we still don't consider return type (to
533 // detect CS1581 or CS1002+CS1584).
534 msig
= new MethodSignature (oper
, null, param_list
);
536 mis
= FindMethodBase (type
,
537 BindingFlags
.Public
| BindingFlags
.NonPublic
| BindingFlags
.Static
| BindingFlags
.Instance
,
540 return null; // CS1574
541 MemberInfo mi
= mis
[0];
542 Type expected
= mi
is MethodInfo
?
543 ((MethodInfo
) mi
).ReturnType
:
545 ((PropertyInfo
) mi
).PropertyType
:
547 if (return_type_name
!= null) {
548 Type returnType
= FindDocumentedType (mc
, return_type_name
, ds
, cref
, Report
);
549 if (returnType
== null || returnType
!= expected
) {
551 Report
.Warning (1581, 1, mc
.Location
, "Invalid return type in XML comment cref attribute `{0}'", cref
);
558 private static bool IsAmbiguous (MemberInfo
[] members
)
560 if (members
.Length
< 2)
562 if (members
.Length
> 2)
564 if (members
[0] is EventInfo
&& members
[1] is FieldInfo
)
566 if (members
[1] is EventInfo
&& members
[0] is FieldInfo
)
572 // Processes "see" or "seealso" elements.
573 // Checks cref attribute.
575 private static void HandleXrefCommon (MemberCore mc
,
576 DeclSpace ds
, XmlElement xref
, Report Report
)
578 string cref
= xref
.GetAttribute ("cref").Trim (wsChars
);
579 // when, XmlReader, "if (cref == null)"
580 if (!xref
.HasAttribute ("cref"))
582 if (cref
.Length
== 0)
583 Report
.Warning (1001, 1, mc
.Location
, "Identifier expected");
584 // ... and continue until CS1584.
586 string signature
; // "x:" are stripped
587 string name
; // method invokation "(...)" are removed
588 string parameters
; // method parameter list
590 // When it found '?:' ('T:' 'M:' 'F:' 'P:' 'E:' etc.),
591 // MS ignores not only its member kind, but also
592 // the entire syntax correctness. Nor it also does
593 // type fullname resolution i.e. "T:List(int)" is kept
594 // as T:List(int), not
595 // T:System.Collections.Generic.List<System.Int32>
596 if (cref
.Length
> 2 && cref
[1] == ':')
601 // Also note that without "T:" any generic type
604 int parens_pos
= signature
.IndexOf ('(');
605 int brace_pos
= parens_pos
>= 0 ? -1 :
606 signature
.IndexOf ('[');
607 if (parens_pos
> 0 && signature
[signature
.Length
- 1] == ')') {
608 name
= signature
.Substring (0, parens_pos
).Trim (wsChars
);
609 parameters
= signature
.Substring (parens_pos
+ 1, signature
.Length
- parens_pos
- 2).Trim (wsChars
);
611 else if (brace_pos
> 0 && signature
[signature
.Length
- 1] == ']') {
612 name
= signature
.Substring (0, brace_pos
).Trim (wsChars
);
613 parameters
= signature
.Substring (brace_pos
+ 1, signature
.Length
- brace_pos
- 2).Trim (wsChars
);
619 Normalize (mc
, ref name
, Report
);
621 string identifier
= GetBodyIdentifierFromName (name
);
623 // Check if identifier is valid.
624 // This check is not necessary to mark as error, but
625 // csc specially reports CS1584 for wrong identifiers.
626 string [] name_elems
= identifier
.Split ('.');
627 for (int i
= 0; i
< name_elems
.Length
; i
++) {
628 string nameElem
= GetBodyIdentifierFromName (name_elems
[i
]);
630 Normalize (mc
, ref nameElem
, Report
);
631 if (!Tokenizer
.IsValidIdentifier (nameElem
)
632 && nameElem
.IndexOf ("operator") < 0) {
633 Report
.Warning (1584, 1, mc
.Location
, "XML comment on `{0}' has syntactically incorrect cref attribute `{1}'",
634 mc
.GetSignatureForError (), cref
);
635 xref
.SetAttribute ("cref", "!:" + signature
);
640 // check if parameters are valid
641 Type
[] parameter_types
;
642 if (parameters
== null)
643 parameter_types
= null;
644 else if (parameters
.Length
== 0)
645 parameter_types
= Type
.EmptyTypes
;
647 string [] param_list
= parameters
.Split (',');
648 var plist
= new List
<Type
> ();
649 for (int i
= 0; i
< param_list
.Length
; i
++) {
650 string param_type_name
= param_list
[i
].Trim (wsChars
);
651 Normalize (mc
, ref param_type_name
, Report
);
652 Type param_type
= FindDocumentedType (mc
, param_type_name
, ds
, cref
, Report
);
653 if (param_type
== null) {
654 Report
.Warning (1580, 1, mc
.Location
, "Invalid type for parameter `{0}' in XML comment cref attribute `{1}'",
655 (i
+ 1).ToString (), cref
);
658 plist
.Add (param_type
);
660 parameter_types
= plist
.ToArray ();
663 Type type
= FindDocumentedType (mc
, name
, ds
, cref
, Report
);
665 // delegate must not be referenced with args
666 && (!TypeManager
.IsDelegateType (type
)
667 || parameter_types
== null)) {
668 string result
= GetSignatureForDoc (type
)
669 + (brace_pos
< 0 ? String
.Empty
: signature
.Substring (brace_pos
));
670 xref
.SetAttribute ("cref", "T:" + result
);
674 int period
= name
.LastIndexOf ('.');
676 string typeName
= name
.Substring (0, period
);
677 string member_name
= name
.Substring (period
+ 1);
678 Normalize (mc
, ref member_name
, Report
);
679 type
= FindDocumentedType (mc
, typeName
, ds
, cref
, Report
);
682 FoundMember fm
= FindDocumentedMember (mc
, type
, member_name
, parameter_types
, ds
, out warn_result
, cref
, true, name
, Report
);
686 MemberInfo mi
= fm
.Member
;
687 // we cannot use 'type' directly
688 // to get its name, since mi
689 // could be from DeclaringType
691 xref
.SetAttribute ("cref", GetMemberDocHead (mi
.MemberType
) + GetSignatureForDoc (fm
.Type
) + "." + member_name
+ GetParametersFormatted (mi
));
692 return; // a member of a type
698 FoundMember fm
= FindDocumentedMember (mc
, ds
.TypeBuilder
, name
, parameter_types
, ds
, out warn_result
, cref
, true, name
, Report
);
702 MemberInfo mi
= fm
.Member
;
703 // we cannot use 'type' directly
704 // to get its name, since mi
705 // could be from DeclaringType
707 xref
.SetAttribute ("cref", GetMemberDocHead (mi
.MemberType
) + GetSignatureForDoc (fm
.Type
) + "." + name
+ GetParametersFormatted (mi
));
708 return; // local member name
712 // It still might be part of namespace name.
713 Namespace ns
= ds
.NamespaceEntry
.NS
.GetNamespace (name
, false);
715 xref
.SetAttribute ("cref", "N:" + ns
.GetSignatureForError ());
716 return; // a namespace
718 if (GlobalRootNamespace
.Instance
.IsNamespace (name
)) {
719 xref
.SetAttribute ("cref", "N:" + name
);
720 return; // a namespace
723 Report
.Warning (1574, 1, mc
.Location
, "XML comment on `{0}' has cref attribute `{1}' that could not be resolved",
724 mc
.GetSignatureForError (), cref
);
726 xref
.SetAttribute ("cref", "!:" + name
);
729 static string GetParametersFormatted (MemberInfo mi
)
731 MethodBase mb
= mi
as MethodBase
;
732 bool is_setter
= false;
733 PropertyInfo pi
= mi
as PropertyInfo
;
735 mb
= pi
.GetGetMethod ();
738 mb
= pi
.GetSetMethod ();
744 AParametersCollection parameters
= TypeManager
.GetParameterData (mb
);
745 if (parameters
== null || parameters
.Count
== 0)
748 StringBuilder sb
= new StringBuilder ();
750 for (int i
= 0; i
< parameters
.Count
; i
++) {
751 if (is_setter
&& i
+ 1 == parameters
.Count
)
752 break; // skip "value".
755 Type t
= parameters
.Types
[i
];
756 sb
.Append (GetSignatureForDoc (t
));
759 return sb
.ToString ();
762 static string GetBodyIdentifierFromName (string name
)
764 string identifier
= name
;
766 if (name
.Length
> 0 && name
[name
.Length
- 1] == ']') {
767 string tmp
= name
.Substring (0, name
.Length
- 1).Trim (wsChars
);
768 int last
= tmp
.LastIndexOf ('[');
770 identifier
= tmp
.Substring (0, last
).Trim (wsChars
);
776 static void Report419 (MemberCore mc
, string member_name
, MemberInfo
[] mis
, Report Report
)
778 Report
.Warning (419, 3, mc
.Location
,
779 "Ambiguous reference in cref attribute `{0}'. Assuming `{1}' but other overloads including `{2}' have also matched",
781 TypeManager
.GetFullNameSignature (mis
[0]),
782 TypeManager
.GetFullNameSignature (mis
[1]));
786 // Get a prefix from member type for XML documentation (used
787 // to formalize cref target name).
789 static string GetMemberDocHead (MemberTypes type
)
792 case MemberTypes
.Constructor
:
793 case MemberTypes
.Method
:
795 case MemberTypes
.Event
:
797 case MemberTypes
.Field
:
799 case MemberTypes
.NestedType
:
800 case MemberTypes
.TypeInfo
:
802 case MemberTypes
.Property
:
811 // Returns a string that represents the signature for this
812 // member which should be used in XML documentation.
814 public static string GetMethodDocCommentName (MemberCore mc
, ParametersCompiled parameters
, DeclSpace ds
)
816 IParameterData
[] plist
= parameters
.FixedParameters
;
817 string paramSpec
= String
.Empty
;
819 StringBuilder psb
= new StringBuilder ();
821 foreach (Parameter p
in plist
) {
822 psb
.Append (psb
.Length
!= 0 ? "," : "(");
823 psb
.Append (GetSignatureForDoc (parameters
.Types
[i
++]));
824 if ((p
.ModFlags
& Parameter
.Modifier
.ISBYREF
) != 0)
827 paramSpec
= psb
.ToString ();
830 if (paramSpec
.Length
> 0)
833 string name
= mc
is Constructor
? "#ctor" : mc
.Name
;
834 if (mc
.MemberName
.IsGeneric
)
835 name
+= "``" + mc
.MemberName
.CountTypeArguments
;
837 string suffix
= String
.Empty
;
838 Operator op
= mc
as Operator
;
840 switch (op
.OperatorType
) {
841 case Operator
.OpType
.Implicit
:
842 case Operator
.OpType
.Explicit
:
843 suffix
= "~" + GetSignatureForDoc (op
.MethodBuilder
.ReturnType
);
847 return String
.Concat (mc
.DocCommentHeader
, ds
.Name
, ".", name
, paramSpec
, suffix
);
850 static string GetSignatureForDoc (Type type
)
852 if (TypeManager
.IsGenericParameter (type
))
853 return (type
.DeclaringMethod
!= null ? "``" : "`") + TypeManager
.GenericParameterPosition (type
);
855 if (TypeManager
.IsGenericType (type
)) {
856 string g
= type
.Namespace
;
857 if (g
!= null && g
.Length
> 0)
859 int idx
= type
.Name
.LastIndexOf ('`');
860 g
+= (idx
< 0 ? type
.Name
: type
.Name
.Substring (0, idx
)) + '{';
862 foreach (Type t
in type
.GetGenericArguments ())
863 g
+= (argpos
++ > 0 ? "," : String
.Empty
) + GetSignatureForDoc (t
);
868 string name
= type
.FullName
!= null ? type
.FullName
: type
.Name
;
869 return name
.Replace ("+", ".").Replace ('&', '@');
873 // Raised (and passed an XmlElement that contains the comment)
874 // when GenerateDocComment is writing documentation expectedly.
876 // FIXME: with a few effort, it could be done with XmlReader,
877 // that means removal of DOM use.
879 internal static void OnMethodGenerateDocComment (
880 MethodCore mc
, XmlElement el
, Report Report
)
882 var paramTags
= new Dictionary
<string, string> ();
883 foreach (XmlElement pelem
in el
.SelectNodes ("param")) {
884 string xname
= pelem
.GetAttribute ("name");
885 if (xname
.Length
== 0)
886 continue; // really? but MS looks doing so
887 if (xname
!= "" && mc
.Parameters
.GetParameterIndexByName (xname
) < 0)
888 Report
.Warning (1572, 2, mc
.Location
, "XML comment on `{0}' has a param tag for `{1}', but there is no parameter by that name",
889 mc
.GetSignatureForError (), xname
);
890 else if (paramTags
.ContainsKey (xname
))
891 Report
.Warning (1571, 2, mc
.Location
, "XML comment on `{0}' has a duplicate param tag for `{1}'",
892 mc
.GetSignatureForError (), xname
);
893 paramTags
[xname
] = xname
;
895 IParameterData
[] plist
= mc
.Parameters
.FixedParameters
;
896 foreach (Parameter p
in plist
) {
897 if (paramTags
.Count
> 0 && !paramTags
.ContainsKey (p
.Name
))
898 Report
.Warning (1573, 4, mc
.Location
, "Parameter `{0}' has no matching param tag in the XML comment for `{1}'",
899 p
.Name
, mc
.GetSignatureForError ());
903 private static void Normalize (MemberCore mc
, ref string name
, Report Report
)
905 if (name
.Length
> 0 && name
[0] == '@')
906 name
= name
.Substring (1);
907 else if (name
== "this")
909 else if (Tokenizer
.IsKeyword (name
) && !IsTypeName (name
))
910 Report
.Warning (1041, 1, mc
.Location
, "Identifier expected. `{0}' is a keyword", name
);
913 private static bool IsTypeName (string name
)
939 // Implements XML documentation generation.
941 public class Documentation
943 public Documentation (string xml_output_filename
)
945 docfilename
= xml_output_filename
;
946 XmlDocumentation
= new XmlDocument ();
947 XmlDocumentation
.PreserveWhitespace
= false;
950 private string docfilename
;
953 // Used to create element which helps well-formedness checking.
955 public XmlDocument XmlDocumentation
;
958 // The output for XML documentation.
960 public XmlWriter XmlCommentOutput
;
963 // Stores XmlDocuments that are included in XML documentation.
964 // Keys are included filenames, values are XmlDocuments.
966 public Dictionary
<string, XmlDocument
> StoredDocuments
= new Dictionary
<string, XmlDocument
> ();
969 // Outputs XML documentation comment from tokenized comments.
971 public bool OutputDocComment (string asmfilename
, Report Report
)
973 XmlTextWriter w
= null;
975 w
= new XmlTextWriter (docfilename
, null);
977 w
.Formatting
= Formatting
.Indented
;
978 w
.WriteStartDocument ();
979 w
.WriteStartElement ("doc");
980 w
.WriteStartElement ("assembly");
981 w
.WriteStartElement ("name");
982 w
.WriteString (Path
.ChangeExtension (asmfilename
, null));
983 w
.WriteEndElement (); // name
984 w
.WriteEndElement (); // assembly
985 w
.WriteStartElement ("members");
986 XmlCommentOutput
= w
;
987 GenerateDocComment (Report
);
988 w
.WriteFullEndElement (); // members
989 w
.WriteEndElement ();
990 w
.WriteWhitespace (Environment
.NewLine
);
991 w
.WriteEndDocument ();
993 } catch (Exception ex
) {
994 Report
.Error (1569, "Error generating XML documentation file `{0}' (`{1}')", docfilename
, ex
.Message
);
1003 // Fixes full type name of each documented types/members up.
1005 public void GenerateDocComment (Report r
)
1007 TypeContainer root
= RootContext
.ToplevelTypes
;
1009 if (root
.Types
!= null)
1010 foreach (TypeContainer tc
in root
.Types
)
1011 DocUtil
.GenerateTypeDocComment (tc
, null, r
);
1013 if (root
.Delegates
!= null)
1014 foreach (Delegate d
in root
.Delegates
)
1015 DocUtil
.GenerateDocComment (d
, null, r
);