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
;
15 using System
.Collections
.Specialized
;
17 using System
.Reflection
;
18 using System
.Reflection
.Emit
;
19 using System
.Runtime
.CompilerServices
;
20 using System
.Runtime
.InteropServices
;
21 using System
.Security
;
22 using System
.Security
.Permissions
;
26 using Mono
.CompilerServices
.SymbolWriter
;
28 namespace Mono
.CSharp
{
31 // Support class for XML documentation.
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
);
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
);
66 foreach (FieldBase f
in t
.Fields
)
67 f
.GenerateDocComment (t
);
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
);
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
;
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.
113 normalized
.Split ('\n');
115 for (int i
= 0; i
< split
.Length
; i
++) {
116 string s
= split
[i
].TrimEnd ();
120 el
.InnerXml
= line_head
+ String
.Join (
121 line_head
, split
, 0, j
);
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
));
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
;
144 mc
.OnGenerateDocComment (el
);
146 // FIXME: it could be done with XmlReader
147 XmlNodeList nl
= n
.SelectNodes (".//include");
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
)
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)
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");
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;
201 XmlDocument doc
= RootContext
.Documentation
.StoredDocuments
[file
] as XmlDocument
;
204 doc
= new XmlDocument ();
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
);
214 XmlNodeList nl
= doc
.SelectNodes (path
);
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
);
276 Type t
= FindDocumentedTypeNonArray (mc
, identifier
, ds
, cref
, r
);
277 if (t
!= null && is_array
)
278 t
= Array
.CreateInstance (t
, 0).GetType ();
282 private static Type
FindDocumentedTypeNonArray (MemberCore mc
,
283 string identifier
, DeclSpace ds
, string cref
, Report r
)
285 switch (identifier
) {
287 return TypeManager
.int32_type
;
289 return TypeManager
.uint32_type
;
291 return TypeManager
.short_type
;;
293 return TypeManager
.ushort_type
;
295 return TypeManager
.int64_type
;
297 return TypeManager
.uint64_type
;;
299 return TypeManager
.float_type
;;
301 return TypeManager
.double_type
;
303 return TypeManager
.char_type
;;
305 return TypeManager
.decimal_type
;;
307 return TypeManager
.byte_type
;;
309 return TypeManager
.sbyte_type
;;
311 return TypeManager
.object_type
;;
313 return TypeManager
.bool_type
;;
315 return TypeManager
.string_type
;;
317 return TypeManager
.void_type
;;
319 FullNamedExpression e
= ds
.LookupNamespaceOrType (identifier
, mc
.Location
, false);
321 if (!(e
is TypeExpr
))
325 int index
= identifier
.LastIndexOf ('.');
329 Type parent
= FindDocumentedType (mc
, identifier
.Substring (0, index
), ds
, cref
, r
);
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
=
341 private static MemberInfo
[] FindMethodBase (Type type
,
342 BindingFlags binding_flags
, MethodSignature signature
)
344 MemberList ml
= TypeManager
.FindMembers (
346 MemberTypes
.Constructor
| MemberTypes
.Method
| MemberTypes
.Property
| MemberTypes
.Custom
,
348 MethodSignature
.method_signature_filter
,
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
))
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
)
367 for (int j
= 0; j
< deriv_pd
.Length
; ++j
) {
368 if (deriv_pd
[j
] != base_pd
[j
])
370 Type ct
= TypeManager
.TypeToCoreType (deriv_pd
[j
]);
371 Type bt
= TypeManager
.TypeToCoreType (base_pd
[j
]);
380 private static MemberInfo
[] FilterOverridenMembersOut (
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
++) {
395 MethodBase my
= ml
[j
] as MethodBase
;
396 if (mx
!= null && my
!= null &&
397 MethodGroupExpr
.IsOverride (my
, mx
)) {
403 PropertyInfo py
= ml
[j
] as PropertyInfo
;
404 if (px
!= null && py
!= null &&
405 IsOverride (py
, px
)) {
415 return al
.ToArray (typeof (MemberInfo
)) as MemberInfo
[];
420 public static FoundMember Empty
= new FoundMember (true);
423 public readonly MemberInfo Member
;
424 public readonly Type Type
;
426 public FoundMember (bool regardless_of_this_value_its_empty
)
433 public FoundMember (Type found_type
, MemberInfo 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
,
456 return new FoundMember (type
, mi
);
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
)
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
,
476 mis
= FilterOverridenMembersOut (mis
);
477 if (mis
== null || mis
.Length
== 0)
479 if (warn419
&& IsAmbiguous (mis
))
480 Report419 (mc
, name_for_error
, mis
, Report
);
484 MethodSignature msig
= new MethodSignature (member_name
, null, param_list
);
485 mis
= FindMethodBase (type
,
486 BindingFlags
.Public
| BindingFlags
.NonPublic
| BindingFlags
.Static
| BindingFlags
.Instance
,
489 if (warn419
&& mis
.Length
> 0) {
490 if (IsAmbiguous (mis
))
491 Report419 (mc
, name_for_error
, mis
, Report
);
495 // search for operators (whose parameters exactly
496 // matches with the list) and possibly report CS1581.
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
);
510 // either unary or binary
512 oper
= param_list
.Length
== 2 ?
513 Operator
.GetMetadataName (Operator
.OpType
.Addition
) :
514 Operator
.GetMetadataName (Operator
.OpType
.UnaryPlus
);
517 oper
= param_list
.Length
== 2 ?
518 Operator
.GetMetadataName (Operator
.OpType
.Subtraction
) :
519 Operator
.GetMetadataName (Operator
.OpType
.UnaryNegation
);
522 oper
= Operator
.GetMetadataName (oper
);
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
);
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
,
541 return null; // CS1574
542 MemberInfo mi
= mis
[0];
543 Type expected
= mi
is MethodInfo
?
544 ((MethodInfo
) mi
).ReturnType
:
546 ((PropertyInfo
) mi
).PropertyType
:
548 if (return_type_name
!= null) {
549 Type returnType
= FindDocumentedType (mc
, return_type_name
, ds
, cref
, Report
);
550 if (returnType
== null || returnType
!= expected
) {
552 Report
.Warning (1581, 1, mc
.Location
, "Invalid return type in XML comment cref attribute `{0}'", cref
);
559 private static bool IsAmbiguous (MemberInfo
[] members
)
561 if (members
.Length
< 2)
563 if (members
.Length
> 2)
565 if (members
[0] is EventInfo
&& members
[1] is FieldInfo
)
567 if (members
[1] is EventInfo
&& members
[0] is FieldInfo
)
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"))
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<System.Int32>
597 if (cref
.Length
> 2 && cref
[1] == ':')
602 // Also note that without "T:" any generic type
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
);
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
]);
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
);
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
;
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
);
659 plist
.Add (param_type
);
661 parameter_types
= plist
.ToArray (typeof (Type
)) as Type
[];
664 Type type
= FindDocumentedType (mc
, name
, ds
, cref
, Report
);
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
);
675 int period
= name
.LastIndexOf ('.');
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
);
683 FoundMember fm
= FindDocumentedMember (mc
, type
, member_name
, parameter_types
, ds
, out warn_result
, cref
, true, name
, Report
);
687 MemberInfo mi
= fm
.Member
;
688 // we cannot use 'type' directly
689 // to get its name, since mi
690 // could be from DeclaringType
692 xref
.SetAttribute ("cref", GetMemberDocHead (mi
.MemberType
) + GetSignatureForDoc (fm
.Type
) + "." + member_name
+ GetParametersFormatted (mi
));
693 return; // a member of a type
699 FoundMember fm
= FindDocumentedMember (mc
, ds
.TypeBuilder
, name
, parameter_types
, ds
, out warn_result
, cref
, true, name
, Report
);
703 MemberInfo mi
= fm
.Member
;
704 // we cannot use 'type' directly
705 // to get its name, since mi
706 // could be from DeclaringType
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);
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
;
736 mb
= pi
.GetGetMethod ();
739 mb
= pi
.GetSetMethod ();
745 AParametersCollection parameters
= TypeManager
.GetParameterData (mb
);
746 if (parameters
== null || parameters
.Count
== 0)
749 StringBuilder sb
= new StringBuilder ();
751 for (int i
= 0; i
< parameters
.Count
; i
++) {
752 if (is_setter
&& i
+ 1 == parameters
.Count
)
753 break; // skip "value".
756 Type t
= parameters
.Types
[i
];
757 sb
.Append (GetSignatureForDoc (t
));
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 ('[');
771 identifier
= tmp
.Substring (0, last
).Trim (wsChars
);
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",
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
)
793 case MemberTypes
.Constructor
:
794 case MemberTypes
.Method
:
796 case MemberTypes
.Event
:
798 case MemberTypes
.Field
:
800 case MemberTypes
.NestedType
:
801 case MemberTypes
.TypeInfo
:
803 case MemberTypes
.Property
:
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
;
820 StringBuilder psb
= new StringBuilder ();
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)
828 paramSpec
= psb
.ToString ();
831 if (paramSpec
.Length
> 0)
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
;
841 switch (op
.OperatorType
) {
842 case Operator
.OpType
.Implicit
:
843 case Operator
.OpType
.Explicit
:
844 suffix
= "~" + GetSignatureForDoc (op
.MethodBuilder
.ReturnType
);
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)
860 int idx
= type
.Name
.LastIndexOf ('`');
861 g
+= (idx
< 0 ? type
.Name
: type
.Name
.Substring (0, idx
)) + '{';
863 foreach (Type t
in type
.GetGenericArguments ())
864 g
+= (argpos
++ > 0 ? "," : String
.Empty
) + GetSignatureForDoc (t
);
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")
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
)
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;
976 w
= new XmlTextWriter (docfilename
, null);
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 ();
994 } catch (Exception ex
) {
995 Report
.Error (1569, "Error generating XML documentation file `{0}' (`{1}')", docfilename
, ex
.Message
);
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
);