2 // doc.cs: Support for XML documentation comment.
5 // Atsushi Enomoto <atsushi@ximian.com>
7 // Licensed under the terms of the GNU GPL
9 // (C) 2004 Novell, Inc.
12 #if ! BOOTSTRAP_WITH_OLDLIB
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.
46 // Generates xml doc comments (if any), and if required,
47 // handle warning report.
49 internal static void GenerateTypeDocComment (TypeContainer t
,
52 GenerateDocComment (t
, ds
);
54 if (t
.DefaultStaticConstructor
!= null)
55 t
.DefaultStaticConstructor
.GenerateDocComment (t
);
57 if (t
.InstanceConstructors
!= null)
58 foreach (Constructor c
in t
.InstanceConstructors
)
59 c
.GenerateDocComment (t
);
62 foreach (TypeContainer tc
in t
.Types
)
63 tc
.GenerateDocComment (t
);
65 if (t
.Delegates
!= null)
66 foreach (Delegate de
in t
.Delegates
)
67 de
.GenerateDocComment (t
);
69 if (t
.Constants
!= null)
70 foreach (Const c
in t
.Constants
)
71 c
.GenerateDocComment (t
);
74 foreach (FieldBase f
in t
.Fields
)
75 f
.GenerateDocComment (t
);
78 foreach (Event e
in t
.Events
)
79 e
.GenerateDocComment (t
);
81 if (t
.Indexers
!= null)
82 foreach (Indexer ix
in t
.Indexers
)
83 ix
.GenerateDocComment (t
);
85 if (t
.Properties
!= null)
86 foreach (Property p
in t
.Properties
)
87 p
.GenerateDocComment (t
);
89 if (t
.Methods
!= null)
90 foreach (Method m
in t
.Methods
)
91 m
.GenerateDocComment (t
);
93 if (t
.Operators
!= null)
94 foreach (Operator o
in t
.Operators
)
95 o
.GenerateDocComment (t
);
99 private static readonly string line_head
=
100 Environment
.NewLine
+ " ";
102 private static XmlNode
GetDocCommentNode (MemberCore mc
,
105 // FIXME: It could be even optimizable as not
106 // to use XmlDocument. But anyways the nodes
107 // are not kept in memory.
108 XmlDocument doc
= RootContext
.Documentation
.XmlDocumentation
;
110 XmlElement el
= doc
.CreateElement ("member");
111 el
.SetAttribute ("name", name
);
112 string normalized
= mc
.DocComment
;
113 el
.InnerXml
= normalized
;
114 // csc keeps lines as written in the sources
115 // and inserts formatting indentation (which
116 // is different from XmlTextWriter.Formatting
117 // one), but when a start tag contains an
118 // endline, it joins the next line. We don't
119 // have to follow such a hacky behavior.
121 normalized
.Split ('\n');
123 for (int i
= 0; i
< split
.Length
; i
++) {
124 string s
= split
[i
].TrimEnd ();
128 el
.InnerXml
= line_head
+ String
.Join (
129 line_head
, split
, 0, j
);
131 } catch (XmlException ex
) {
132 Report
.Warning (1570, 1, mc
.Location
, "XML comment on `{0}' has non-well-formed XML ({1})", name
, ex
.Message
);
133 XmlComment com
= doc
.CreateComment (String
.Format ("FIXME: Invalid documentation markup was found for member {0}", name
));
139 // Generates xml doc comments (if any), and if required,
140 // handle warning report.
142 internal static void GenerateDocComment (MemberCore mc
,
145 if (mc
.DocComment
!= null) {
146 string name
= mc
.GetDocCommentName (ds
);
148 XmlNode n
= GetDocCommentNode (mc
, name
);
150 XmlElement el
= n
as XmlElement
;
152 mc
.OnGenerateDocComment (el
);
154 // FIXME: it could be done with XmlReader
155 XmlNodeList nl
= n
.SelectNodes (".//include");
157 // It could result in current node removal, so prepare another list to iterate.
158 ArrayList al
= new ArrayList (nl
.Count
);
159 foreach (XmlNode inc
in nl
)
161 foreach (XmlElement inc
in al
)
162 if (!HandleInclude (mc
, inc
))
163 inc
.ParentNode
.RemoveChild (inc
);
166 // FIXME: it could be done with XmlReader
167 DeclSpace ds_target
= mc
as DeclSpace
;
168 if (ds_target
== null)
171 foreach (XmlElement see
in n
.SelectNodes (".//see"))
172 HandleSee (mc
, ds_target
, see
);
173 foreach (XmlElement seealso
in n
.SelectNodes (".//seealso"))
174 HandleSeeAlso (mc
, ds_target
, seealso
);
175 foreach (XmlElement see
in n
.SelectNodes (".//exception"))
176 HandleException (mc
, ds_target
, see
);
179 n
.WriteTo (RootContext
.Documentation
.XmlCommentOutput
);
181 else if (mc
.IsExposedFromAssembly ()) {
182 Constructor c
= mc
as Constructor
;
183 if (c
== null || !c
.IsDefault ())
184 Report
.Warning (1591, 4, mc
.Location
,
185 "Missing XML comment for publicly visible type or member `{0}'", mc
.GetSignatureForError ());
190 // Processes "include" element. Check included file and
191 // embed the document content inside this documentation node.
193 private static bool HandleInclude (MemberCore mc
, XmlElement el
)
195 bool keep_include_node
= false;
196 string file
= el
.GetAttribute ("file");
197 string path
= el
.GetAttribute ("path");
199 Report
.Warning (1590, 1, mc
.Location
, "Invalid XML `include' element. Missing `file' attribute");
200 el
.ParentNode
.InsertBefore (el
.OwnerDocument
.CreateComment (" Include tag is invalid "), el
);
201 keep_include_node
= true;
203 else if (path
.Length
== 0) {
204 Report
.Warning (1590, 1, mc
.Location
, "Invalid XML `include' element. Missing `path' attribute");
205 el
.ParentNode
.InsertBefore (el
.OwnerDocument
.CreateComment (" Include tag is invalid "), el
);
206 keep_include_node
= true;
209 XmlDocument doc
= RootContext
.Documentation
.StoredDocuments
[file
] as XmlDocument
;
212 doc
= new XmlDocument ();
214 RootContext
.Documentation
.StoredDocuments
.Add (file
, doc
);
215 } catch (Exception
) {
216 el
.ParentNode
.InsertBefore (el
.OwnerDocument
.CreateComment (String
.Format (" Badly formed XML in at comment file `{0}': cannot be included ", file
)), el
);
217 Report
.Warning (1592, 1, mc
.Location
, "Badly formed XML in included comments file -- `{0}'", file
);
222 XmlNodeList nl
= doc
.SelectNodes (path
);
224 el
.ParentNode
.InsertBefore (el
.OwnerDocument
.CreateComment (" No matching elements were found for the include tag embedded here. "), el
);
226 keep_include_node
= true;
228 foreach (XmlNode n
in nl
)
229 el
.ParentNode
.InsertBefore (el
.OwnerDocument
.ImportNode (n
, true), el
);
230 } catch (Exception ex
) {
231 el
.ParentNode
.InsertBefore (el
.OwnerDocument
.CreateComment (" Failed to insert some or all of included XML "), el
);
232 Report
.Warning (1589, 1, mc
.Location
, "Unable to include XML fragment `{0}' of file `{1}' ({2})", path
, file
, ex
.Message
);
236 return keep_include_node
;
240 // Handles <see> elements.
242 private static void HandleSee (MemberCore mc
,
243 DeclSpace ds
, XmlElement see
)
245 HandleXrefCommon (mc
, ds
, see
);
249 // Handles <seealso> elements.
251 private static void HandleSeeAlso (MemberCore mc
,
252 DeclSpace ds
, XmlElement seealso
)
254 HandleXrefCommon (mc
, ds
, seealso
);
258 // Handles <exception> elements.
260 private static void HandleException (MemberCore mc
,
261 DeclSpace ds
, XmlElement seealso
)
263 HandleXrefCommon (mc
, ds
, seealso
);
266 static readonly char [] wsChars
=
267 new char [] {' ', '\t', '\n', '\r'}
;
270 // returns a full runtime type name from a name which might
271 // be C# specific type name.
273 private static Type
FindDocumentedType (MemberCore mc
, string name
, DeclSpace ds
, string cref
)
275 bool is_array
= false;
276 string identifier
= name
;
277 if (name
[name
.Length
- 1] == ']') {
278 string tmp
= name
.Substring (0, name
.Length
- 1).Trim (wsChars
);
279 if (tmp
[tmp
.Length
- 1] == '[') {
280 identifier
= tmp
.Substring (0, tmp
.Length
- 1).Trim (wsChars
);
284 Type t
= FindDocumentedTypeNonArray (mc
, identifier
, ds
, cref
);
285 if (t
!= null && is_array
)
286 t
= Array
.CreateInstance (t
, 0).GetType ();
290 private static Type
FindDocumentedTypeNonArray (MemberCore mc
,
291 string identifier
, DeclSpace ds
, string cref
)
293 switch (identifier
) {
297 return typeof (uint);
299 return typeof (short);
301 return typeof (ushort);
303 return typeof (long);
305 return typeof (ulong);
307 return typeof (float);
309 return typeof (double);
311 return typeof (char);
313 return typeof (decimal);
315 return typeof (byte);
317 return typeof (sbyte);
319 return typeof (object);
321 return typeof (bool);
323 return typeof (string);
325 return typeof (void);
327 FullNamedExpression e
= ds
.LookupNamespaceOrType (identifier
, mc
.Location
, false);
329 if (!(e
is TypeExpr
))
333 int index
= identifier
.LastIndexOf ('.');
337 Type parent
= FindDocumentedType (mc
, identifier
.Substring (0, index
), ds
, cref
);
340 // no need to detect warning 419 here
341 return FindDocumentedMember (mc
, parent
,
342 identifier
.Substring (index
+ 1),
343 null, ds
, out warn
, cref
, false, null).Member
as Type
;
346 private static MemberInfo
[] empty_member_infos
=
349 private static MemberInfo
[] FindMethodBase (Type type
,
350 BindingFlags binding_flags
, MethodSignature signature
)
352 MemberList ml
= TypeManager
.FindMembers (
354 MemberTypes
.Constructor
| MemberTypes
.Method
| MemberTypes
.Property
| MemberTypes
.Custom
,
356 MethodSignature
.method_signature_filter
,
359 return empty_member_infos
;
361 return FilterOverridenMembersOut ((MemberInfo
[]) ml
);
364 static bool IsOverride (PropertyInfo deriv_prop
, PropertyInfo base_prop
)
366 if (!MethodGroupExpr
.IsAncestralType (base_prop
.DeclaringType
, deriv_prop
.DeclaringType
))
369 Type
[] deriv_pd
= TypeManager
.GetArgumentTypes (deriv_prop
);
370 Type
[] base_pd
= TypeManager
.GetArgumentTypes (base_prop
);
372 if (deriv_pd
.Length
!= base_pd
.Length
)
375 for (int j
= 0; j
< deriv_pd
.Length
; ++j
) {
376 if (deriv_pd
[j
] != base_pd
[j
])
378 Type ct
= TypeManager
.TypeToCoreType (deriv_pd
[j
]);
379 Type bt
= TypeManager
.TypeToCoreType (base_pd
[j
]);
388 private static MemberInfo
[] FilterOverridenMembersOut (
392 return empty_member_infos
;
394 ArrayList al
= new ArrayList (ml
.Length
);
395 for (int i
= 0; i
< ml
.Length
; i
++) {
396 MethodBase mx
= ml
[i
] as MethodBase
;
397 PropertyInfo px
= ml
[i
] as PropertyInfo
;
398 if (mx
!= null || px
!= null) {
399 bool overriden
= false;
400 for (int j
= 0; j
< ml
.Length
; j
++) {
403 MethodBase my
= ml
[j
] as MethodBase
;
404 if (mx
!= null && my
!= null &&
405 MethodGroupExpr
.IsOverride (my
, mx
)) {
411 PropertyInfo py
= ml
[j
] as PropertyInfo
;
412 if (px
!= null && py
!= null &&
413 IsOverride (py
, px
)) {
423 return al
.ToArray (typeof (MemberInfo
)) as MemberInfo
[];
428 public static FoundMember Empty
= new FoundMember (true);
431 public readonly MemberInfo Member
;
432 public readonly Type Type
;
434 public FoundMember (bool regardless_of_this_value_its_empty
)
441 public FoundMember (Type found_type
, MemberInfo member
)
450 // Returns a MemberInfo that is referenced in XML documentation
451 // (by "see" or "seealso" elements).
453 private static FoundMember
FindDocumentedMember (MemberCore mc
,
454 Type type
, string member_name
, Type
[] param_list
,
455 DeclSpace ds
, out int warning_type
, string cref
,
456 bool warn419
, string name_for_error
)
458 for (; type
!= null; type
= type
.DeclaringType
) {
459 MemberInfo mi
= FindDocumentedMemberNoNest (
460 mc
, type
, member_name
, param_list
, ds
,
461 out warning_type
, cref
, warn419
,
464 return new FoundMember (type
, mi
);
467 return FoundMember
.Empty
;
470 private static MemberInfo
FindDocumentedMemberNoNest (
471 MemberCore mc
, Type type
, string member_name
,
472 Type
[] param_list
, DeclSpace ds
, out int warning_type
,
473 string cref
, bool warn419
, string name_for_error
)
478 if (param_list
== null) {
479 // search for fields/events etc.
480 mis
= TypeManager
.MemberLookup (type
, null,
481 type
, MemberTypes
.All
,
482 BindingFlags
.Public
| BindingFlags
.NonPublic
| BindingFlags
.Static
| BindingFlags
.Instance
,
484 mis
= FilterOverridenMembersOut (mis
);
485 if (mis
== null || mis
.Length
== 0)
487 if (warn419
&& IsAmbiguous (mis
))
488 Report419 (mc
, name_for_error
, mis
);
492 MethodSignature msig
= new MethodSignature (member_name
, null, param_list
);
493 mis
= FindMethodBase (type
,
494 BindingFlags
.Public
| BindingFlags
.NonPublic
| BindingFlags
.Static
| BindingFlags
.Instance
,
497 if (warn419
&& mis
.Length
> 0) {
498 if (IsAmbiguous (mis
))
499 Report419 (mc
, name_for_error
, mis
);
503 // search for operators (whose parameters exactly
504 // matches with the list) and possibly report CS1581.
506 string return_type_name
= null;
507 if (member_name
.StartsWith ("implicit operator ")) {
508 oper
= "op_Implicit";
509 return_type_name
= member_name
.Substring (18).Trim (wsChars
);
511 else if (member_name
.StartsWith ("explicit operator ")) {
512 oper
= "op_Explicit";
513 return_type_name
= member_name
.Substring (18).Trim (wsChars
);
515 else if (member_name
.StartsWith ("operator ")) {
516 oper
= member_name
.Substring (9).Trim (wsChars
);
518 // either unary or binary
520 oper
= param_list
.Length
== 2 ?
521 Binary
.oper_names
[(int) Binary
.Operator
.Addition
] :
522 Unary
.oper_names
[(int) Unary
.Operator
.UnaryPlus
];
525 oper
= param_list
.Length
== 2 ?
526 Binary
.oper_names
[(int) Binary
.Operator
.Subtraction
] :
527 Unary
.oper_names
[(int) Unary
.Operator
.UnaryNegation
];
531 oper
= Unary
.oper_names
[(int) Unary
.Operator
.LogicalNot
]; break;
533 oper
= Unary
.oper_names
[(int) Unary
.Operator
.OnesComplement
]; break;
536 oper
= "op_Increment"; break;
538 oper
= "op_Decrement"; break;
540 oper
= "op_True"; break;
542 oper
= "op_False"; break;
545 oper
= Binary
.oper_names
[(int) Binary
.Operator
.Multiply
]; break;
547 oper
= Binary
.oper_names
[(int) Binary
.Operator
.Division
]; break;
549 oper
= Binary
.oper_names
[(int) Binary
.Operator
.Modulus
]; break;
551 oper
= Binary
.oper_names
[(int) Binary
.Operator
.BitwiseAnd
]; break;
553 oper
= Binary
.oper_names
[(int) Binary
.Operator
.BitwiseOr
]; break;
555 oper
= Binary
.oper_names
[(int) Binary
.Operator
.ExclusiveOr
]; break;
557 oper
= Binary
.oper_names
[(int) Binary
.Operator
.LeftShift
]; break;
559 oper
= Binary
.oper_names
[(int) Binary
.Operator
.RightShift
]; break;
561 oper
= Binary
.oper_names
[(int) Binary
.Operator
.Equality
]; break;
563 oper
= Binary
.oper_names
[(int) Binary
.Operator
.Inequality
]; break;
565 oper
= Binary
.oper_names
[(int) Binary
.Operator
.LessThan
]; break;
567 oper
= Binary
.oper_names
[(int) Binary
.Operator
.GreaterThan
]; break;
569 oper
= Binary
.oper_names
[(int) Binary
.Operator
.LessThanOrEqual
]; break;
571 oper
= Binary
.oper_names
[(int) Binary
.Operator
.GreaterThanOrEqual
]; break;
574 Report
.Warning (1020, 1, mc
.Location
, "Overloadable {0} operator is expected", param_list
.Length
== 2 ? "binary" : "unary");
575 Report
.Warning (1584, 1, mc
.Location
, "XML comment on `{0}' has syntactically incorrect cref attribute `{1}'",
576 mc
.GetSignatureForError (), cref
);
580 // here we still don't consider return type (to
581 // detect CS1581 or CS1002+CS1584).
582 msig
= new MethodSignature (oper
, null, param_list
);
584 mis
= FindMethodBase (type
,
585 BindingFlags
.Public
| BindingFlags
.NonPublic
| BindingFlags
.Static
| BindingFlags
.Instance
,
588 return null; // CS1574
589 MemberInfo mi
= mis
[0];
590 Type expected
= mi
is MethodInfo
?
591 ((MethodInfo
) mi
).ReturnType
:
593 ((PropertyInfo
) mi
).PropertyType
:
595 if (return_type_name
!= null) {
596 Type returnType
= FindDocumentedType (mc
, return_type_name
, ds
, cref
);
597 if (returnType
== null || returnType
!= expected
) {
599 Report
.Warning (1581, 1, mc
.Location
, "Invalid return type in XML comment cref attribute `{0}'", cref
);
606 private static bool IsAmbiguous (MemberInfo
[] members
)
608 if (members
.Length
< 2)
610 if (members
.Length
> 2)
612 if (members
[0] is EventInfo
&& members
[1] is FieldInfo
)
614 if (members
[1] is EventInfo
&& members
[0] is FieldInfo
)
620 // Processes "see" or "seealso" elements.
621 // Checks cref attribute.
623 private static void HandleXrefCommon (MemberCore mc
,
624 DeclSpace ds
, XmlElement xref
)
626 string cref
= xref
.GetAttribute ("cref").Trim (wsChars
);
627 // when, XmlReader, "if (cref == null)"
628 if (!xref
.HasAttribute ("cref"))
630 if (cref
.Length
== 0)
631 Report
.Warning (1001, 1, mc
.Location
, "Identifier expected");
632 // ... and continue until CS1584.
634 string signature
; // "x:" are stripped
635 string name
; // method invokation "(...)" are removed
636 string parameters
; // method parameter list
638 // strip 'T:' 'M:' 'F:' 'P:' 'E:' etc.
639 // Here, MS ignores its member kind. No idea why.
640 if (cref
.Length
> 2 && cref
[1] == ':')
641 signature
= cref
.Substring (2).Trim (wsChars
);
645 int parens_pos
= signature
.IndexOf ('(');
646 int brace_pos
= parens_pos
>= 0 ? -1 :
647 signature
.IndexOf ('[');
648 if (parens_pos
> 0 && signature
[signature
.Length
- 1] == ')') {
649 name
= signature
.Substring (0, parens_pos
).Trim (wsChars
);
650 parameters
= signature
.Substring (parens_pos
+ 1, signature
.Length
- parens_pos
- 2).Trim (wsChars
);
652 else if (brace_pos
> 0 && signature
[signature
.Length
- 1] == ']') {
653 name
= signature
.Substring (0, brace_pos
).Trim (wsChars
);
654 parameters
= signature
.Substring (brace_pos
+ 1, signature
.Length
- brace_pos
- 2).Trim (wsChars
);
660 Normalize (mc
, ref name
);
662 string identifier
= GetBodyIdentifierFromName (name
);
664 // Check if identifier is valid.
665 // This check is not necessary to mark as error, but
666 // csc specially reports CS1584 for wrong identifiers.
667 string [] name_elems
= identifier
.Split ('.');
668 for (int i
= 0; i
< name_elems
.Length
; i
++) {
669 string nameElem
= GetBodyIdentifierFromName (name_elems
[i
]);
671 Normalize (mc
, ref nameElem
);
672 if (!Tokenizer
.IsValidIdentifier (nameElem
)
673 && nameElem
.IndexOf ("operator") < 0) {
674 Report
.Warning (1584, 1, mc
.Location
, "XML comment on `{0}' has syntactically incorrect cref attribute `{1}'",
675 mc
.GetSignatureForError (), cref
);
676 xref
.SetAttribute ("cref", "!:" + signature
);
681 // check if parameters are valid
682 Type
[] parameter_types
;
683 if (parameters
== null)
684 parameter_types
= null;
685 else if (parameters
.Length
== 0)
686 parameter_types
= Type
.EmptyTypes
;
688 string [] param_list
= parameters
.Split (',');
689 ArrayList plist
= new ArrayList ();
690 for (int i
= 0; i
< param_list
.Length
; i
++) {
691 string param_type_name
= param_list
[i
].Trim (wsChars
);
692 Normalize (mc
, ref param_type_name
);
693 Type param_type
= FindDocumentedType (mc
, param_type_name
, ds
, cref
);
694 if (param_type
== null) {
695 Report
.Warning (1580, 1, mc
.Location
, "Invalid type for parameter `{0}' in XML comment cref attribute `{1}'",
696 (i
+ 1).ToString (), cref
);
699 plist
.Add (param_type
);
701 parameter_types
= plist
.ToArray (typeof (Type
)) as Type
[];
704 Type type
= FindDocumentedType (mc
, name
, ds
, cref
);
706 // delegate must not be referenced with args
707 && (!type
.IsSubclassOf (typeof (System
.Delegate
))
708 || parameter_types
== null)) {
709 string result
= GetSignatureForDoc (type
)
710 + (brace_pos
< 0 ? String
.Empty
: signature
.Substring (brace_pos
));
711 xref
.SetAttribute ("cref", "T:" + result
);
715 int period
= name
.LastIndexOf ('.');
717 string typeName
= name
.Substring (0, period
);
718 string member_name
= name
.Substring (period
+ 1);
719 Normalize (mc
, ref member_name
);
720 type
= FindDocumentedType (mc
, typeName
, ds
, cref
);
723 FoundMember fm
= FindDocumentedMember (mc
, type
, member_name
, parameter_types
, ds
, out warn_result
, cref
, true, name
);
727 MemberInfo mi
= fm
.Member
;
728 // we cannot use 'type' directly
729 // to get its name, since mi
730 // could be from DeclaringType
732 xref
.SetAttribute ("cref", GetMemberDocHead (mi
.MemberType
) + GetSignatureForDoc (fm
.Type
) + "." + member_name
+ GetParametersFormatted (mi
));
733 return; // a member of a type
739 FoundMember fm
= FindDocumentedMember (mc
, ds
.TypeBuilder
, name
, parameter_types
, ds
, out warn_result
, cref
, true, name
);
743 MemberInfo mi
= fm
.Member
;
744 // we cannot use 'type' directly
745 // to get its name, since mi
746 // could be from DeclaringType
748 xref
.SetAttribute ("cref", GetMemberDocHead (mi
.MemberType
) + GetSignatureForDoc (fm
.Type
) + "." + name
+ GetParametersFormatted (mi
));
749 return; // local member name
753 // It still might be part of namespace name.
754 Namespace ns
= ds
.NamespaceEntry
.NS
.GetNamespace (name
, false);
756 xref
.SetAttribute ("cref", "N:" + ns
.FullName
);
757 return; // a namespace
759 if (RootNamespace
.Global
.IsNamespace (name
)) {
760 xref
.SetAttribute ("cref", "N:" + name
);
761 return; // a namespace
764 Report
.Warning (1574, 1, mc
.Location
, "XML comment on `{0}' has cref attribute `{1}' that could not be resolved",
765 mc
.GetSignatureForError (), cref
);
767 xref
.SetAttribute ("cref", "!:" + name
);
770 static string GetParametersFormatted (MemberInfo mi
)
772 MethodBase mb
= mi
as MethodBase
;
773 bool is_setter
= false;
774 PropertyInfo pi
= mi
as PropertyInfo
;
776 mb
= pi
.GetGetMethod ();
779 mb
= pi
.GetSetMethod ();
785 ParameterData parameters
= TypeManager
.GetParameterData (mb
);
786 if (parameters
== null || parameters
.Count
== 0)
789 StringBuilder sb
= new StringBuilder ();
791 for (int i
= 0; i
< parameters
.Count
; i
++) {
792 if (is_setter
&& i
+ 1 == parameters
.Count
)
793 break; // skip "value".
796 Type t
= parameters
.ParameterType (i
);
797 sb
.Append (GetSignatureForDoc (t
));
800 return sb
.ToString ();
803 static string GetBodyIdentifierFromName (string name
)
805 string identifier
= name
;
807 if (name
.Length
> 0 && name
[name
.Length
- 1] == ']') {
808 string tmp
= name
.Substring (0, name
.Length
- 1).Trim (wsChars
);
809 int last
= tmp
.LastIndexOf ('[');
811 identifier
= tmp
.Substring (0, last
).Trim (wsChars
);
817 static void Report419 (MemberCore mc
, string member_name
, MemberInfo
[] mis
)
819 Report
.Warning (419, 3, mc
.Location
,
820 "Ambiguous reference in cref attribute `{0}'. Assuming `{1}' but other overloads including `{2}' have also matched",
822 TypeManager
.GetFullNameSignature (mis
[0]),
823 TypeManager
.GetFullNameSignature (mis
[1]));
827 // Get a prefix from member type for XML documentation (used
828 // to formalize cref target name).
830 static string GetMemberDocHead (MemberTypes type
)
833 case MemberTypes
.Constructor
:
834 case MemberTypes
.Method
:
836 case MemberTypes
.Event
:
838 case MemberTypes
.Field
:
840 case MemberTypes
.NestedType
:
841 case MemberTypes
.TypeInfo
:
843 case MemberTypes
.Property
:
852 // Returns a string that represents the signature for this
853 // member which should be used in XML documentation.
855 public static string GetMethodDocCommentName (MemberCore mc
, Parameters parameters
, DeclSpace ds
)
857 Parameter
[] plist
= parameters
.FixedParameters
;
858 string paramSpec
= String
.Empty
;
860 StringBuilder psb
= new StringBuilder ();
861 foreach (Parameter p
in plist
) {
862 psb
.Append (psb
.Length
!= 0 ? "," : "(");
863 psb
.Append (GetSignatureForDoc (p
.ExternalType ()));
865 paramSpec
= psb
.ToString ();
868 if (paramSpec
.Length
> 0)
871 string name
= mc
is Constructor
? "#ctor" : mc
.Name
;
872 string suffix
= String
.Empty
;
873 Operator op
= mc
as Operator
;
875 switch (op
.OperatorType
) {
876 case Operator
.OpType
.Implicit
:
877 case Operator
.OpType
.Explicit
:
878 suffix
= "~" + GetSignatureForDoc (op
.MethodBuilder
.ReturnType
);
882 return String
.Concat (mc
.DocCommentHeader
, ds
.Name
, ".", name
, paramSpec
, suffix
);
885 static string GetSignatureForDoc (Type type
)
887 if (TypeManager
.IsGenericParameter (type
))
888 return "`" + TypeManager
.GenericParameterPosition (type
);
890 string name
= type
.FullName
!= null ? type
.FullName
: type
.Name
;
891 return name
.Replace ("+", ".").Replace ('&', '@');
895 // Raised (and passed an XmlElement that contains the comment)
896 // when GenerateDocComment is writing documentation expectedly.
898 // FIXME: with a few effort, it could be done with XmlReader,
899 // that means removal of DOM use.
901 internal static void OnMethodGenerateDocComment (
902 MethodCore mc
, XmlElement el
)
904 Hashtable paramTags
= new Hashtable ();
905 foreach (XmlElement pelem
in el
.SelectNodes ("param")) {
907 string xname
= pelem
.GetAttribute ("name");
908 if (xname
.Length
== 0)
909 continue; // really? but MS looks doing so
910 if (xname
!= "" && mc
.Parameters
.GetParameterByName (xname
, out i
) == null)
911 Report
.Warning (1572, 2, mc
.Location
, "XML comment on `{0}' has a param tag for `{1}', but there is no parameter by that name",
912 mc
.GetSignatureForError (), xname
);
913 else if (paramTags
[xname
] != null)
914 Report
.Warning (1571, 2, mc
.Location
, "XML comment on `{0}' has a duplicate param tag for `{1}'",
915 mc
.GetSignatureForError (), xname
);
916 paramTags
[xname
] = xname
;
918 Parameter
[] plist
= mc
.Parameters
.FixedParameters
;
919 foreach (Parameter p
in plist
) {
920 if (paramTags
.Count
> 0 && paramTags
[p
.Name
] == null)
921 Report
.Warning (1573, 4, mc
.Location
, "Parameter `{0}' has no matching param tag in the XML comment for `{1}'",
922 p
.Name
, mc
.GetSignatureForError ());
926 private static void Normalize (MemberCore mc
, ref string name
)
928 if (name
.Length
> 0 && name
[0] == '@')
929 name
= name
.Substring (1);
930 else if (name
== "this")
932 else if (Tokenizer
.IsKeyword (name
) && !IsTypeName (name
))
933 Report
.Warning (1041, 1, mc
.Location
, "Identifier expected. `{0}' is a keyword", name
);
936 private static bool IsTypeName (string name
)
962 // Implements XML documentation generation.
964 public class Documentation
966 public Documentation (string xml_output_filename
)
968 docfilename
= xml_output_filename
;
969 XmlDocumentation
= new XmlDocument ();
970 XmlDocumentation
.PreserveWhitespace
= false;
973 private string docfilename
;
976 // Used to create element which helps well-formedness checking.
978 public XmlDocument XmlDocumentation
;
981 // The output for XML documentation.
983 public XmlWriter XmlCommentOutput
;
986 // Stores XmlDocuments that are included in XML documentation.
987 // Keys are included filenames, values are XmlDocuments.
989 public Hashtable StoredDocuments
= new Hashtable ();
992 // Outputs XML documentation comment from tokenized comments.
994 public bool OutputDocComment (string asmfilename
)
996 XmlTextWriter w
= null;
998 w
= new XmlTextWriter (docfilename
, null);
1000 w
.Formatting
= Formatting
.Indented
;
1001 w
.WriteStartDocument ();
1002 w
.WriteStartElement ("doc");
1003 w
.WriteStartElement ("assembly");
1004 w
.WriteStartElement ("name");
1005 w
.WriteString (Path
.ChangeExtension (asmfilename
, null));
1006 w
.WriteEndElement (); // name
1007 w
.WriteEndElement (); // assembly
1008 w
.WriteStartElement ("members");
1009 XmlCommentOutput
= w
;
1010 GenerateDocComment ();
1011 w
.WriteFullEndElement (); // members
1012 w
.WriteEndElement ();
1013 w
.WriteWhitespace (Environment
.NewLine
);
1014 w
.WriteEndDocument ();
1016 } catch (Exception ex
) {
1017 Report
.Error (1569, "Error generating XML documentation file `{0}' (`{1}')", docfilename
, ex
.Message
);
1026 // Fixes full type name of each documented types/members up.
1028 public void GenerateDocComment ()
1030 TypeContainer root
= RootContext
.ToplevelTypes
;
1032 if (root
.Types
!= null)
1033 foreach (TypeContainer tc
in root
.Types
)
1034 DocUtil
.GenerateTypeDocComment (tc
, null);
1036 if (root
.Delegates
!= null)
1037 foreach (Delegate d
in root
.Delegates
)
1038 DocUtil
.GenerateDocComment (d
, null);