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.
13 #if ! BOOTSTRAP_WITH_OLDLIB
15 using System
.Collections
;
16 using System
.Collections
.Specialized
;
18 using System
.Reflection
;
19 using System
.Reflection
.Emit
;
20 using System
.Runtime
.CompilerServices
;
21 using System
.Runtime
.InteropServices
;
22 using System
.Security
;
23 using System
.Security
.Permissions
;
27 using Mono
.CompilerServices
.SymbolWriter
;
29 namespace Mono
.CSharp
{
32 // Support class for XML documentation.
39 // Generates xml doc comments (if any), and if required,
40 // handle warning report.
42 internal static void GenerateTypeDocComment (TypeContainer t
,
45 GenerateDocComment (t
, ds
);
47 if (t
.DefaultStaticConstructor
!= null)
48 t
.DefaultStaticConstructor
.GenerateDocComment (t
);
50 if (t
.InstanceConstructors
!= null)
51 foreach (Constructor c
in t
.InstanceConstructors
)
52 c
.GenerateDocComment (t
);
55 foreach (TypeContainer tc
in t
.Types
)
56 tc
.GenerateDocComment (t
);
58 if (t
.Parts
!= null) {
59 IDictionary comments
= RootContext
.Documentation
.PartialComments
;
60 foreach (ClassPart cp
in t
.Parts
) {
61 if (cp
.DocComment
== null)
68 foreach (Enum en
in t
.Enums
)
69 en
.GenerateDocComment (t
);
71 if (t
.Constants
!= null)
72 foreach (Const c
in t
.Constants
)
73 c
.GenerateDocComment (t
);
76 foreach (FieldBase f
in t
.Fields
)
77 f
.GenerateDocComment (t
);
80 foreach (Event e
in t
.Events
)
81 e
.GenerateDocComment (t
);
83 if (t
.Indexers
!= null)
84 foreach (Indexer ix
in t
.Indexers
)
85 ix
.GenerateDocComment (t
);
87 if (t
.Properties
!= null)
88 foreach (Property p
in t
.Properties
)
89 p
.GenerateDocComment (t
);
91 if (t
.Methods
!= null)
92 foreach (Method m
in t
.Methods
)
93 m
.GenerateDocComment (t
);
95 if (t
.Operators
!= null)
96 foreach (Operator o
in t
.Operators
)
97 o
.GenerateDocComment (t
);
101 private static readonly string lineHead
=
102 Environment
.NewLine
+ " ";
104 private static XmlNode
GetDocCommentNode (MemberCore mc
,
107 // FIXME: It could be even optimizable as not
108 // to use XmlDocument. But anyways the nodes
109 // are not kept in memory.
110 XmlDocument doc
= RootContext
.Documentation
.XmlDocumentation
;
112 XmlElement el
= doc
.CreateElement ("member");
113 el
.SetAttribute ("name", name
);
114 string normalized
= mc
.DocComment
;
115 el
.InnerXml
= normalized
;
116 // csc keeps lines as written in the sources
117 // and inserts formatting indentation (which
118 // is different from XmlTextWriter.Formatting
119 // one), but when a start tag contains an
120 // endline, it joins the next line. We don't
121 // have to follow such a hacky behavior.
123 normalized
.Split ('\n');
125 for (int i
= 0; i
< split
.Length
; i
++) {
126 string s
= split
[i
].TrimEnd ();
130 el
.InnerXml
= lineHead
+ String
.Join (
131 lineHead
, split
, 0, j
);
133 } catch (XmlException ex
) {
134 Report
.Warning (1570, 1, mc
.Location
, "XML comment on `{0}' has non-well-formed XML ({1})", name
, ex
.Message
);
135 XmlComment com
= doc
.CreateComment (String
.Format ("FIXME: Invalid documentation markup was found for member {0}", name
));
141 // Generates xml doc comments (if any), and if required,
142 // handle warning report.
144 internal static void GenerateDocComment (MemberCore mc
,
147 if (mc
.DocComment
!= null) {
148 string name
= mc
.GetDocCommentName (ds
);
150 XmlNode n
= GetDocCommentNode (mc
, name
);
152 XmlElement el
= n
as XmlElement
;
154 mc
.OnGenerateDocComment (ds
, el
);
156 // FIXME: it could be done with XmlReader
157 foreach (XmlElement inc
in n
.SelectNodes (".//include"))
158 HandleInclude (mc
, inc
);
160 // FIXME: it could be done with XmlReader
161 DeclSpace dsTarget
= mc
as DeclSpace
;
162 if (dsTarget
== null)
165 foreach (XmlElement see
in n
.SelectNodes (".//see"))
166 HandleSee (mc
, dsTarget
, see
);
167 foreach (XmlElement seealso
in n
.SelectNodes (".//seealso"))
168 HandleSeeAlso (mc
, dsTarget
, seealso
);
169 foreach (XmlElement see
in n
.SelectNodes (".//exception"))
170 HandleException (mc
, dsTarget
, see
);
173 n
.WriteTo (RootContext
.Documentation
.XmlCommentOutput
);
175 else if (mc
.IsExposedFromAssembly (ds
)) {
176 Constructor c
= mc
as Constructor
;
177 if (c
== null || !c
.IsDefault ())
178 Report
.Warning (1591, 4, mc
.Location
,
179 "Missing XML comment for publicly visible type or member `{0}'", mc
.GetSignatureForError ());
184 // Processes "include" element. Check included file and
185 // embed the document content inside this documentation node.
187 private static void HandleInclude (MemberCore mc
, XmlElement el
)
189 string file
= el
.GetAttribute ("file");
190 string path
= el
.GetAttribute ("path");
192 Report
.Warning (1590, 1, mc
.Location
, "Invalid XML `include' element. Missing `file' attribute");
193 el
.ParentNode
.InsertBefore (el
.OwnerDocument
.CreateComment (" Include tag is invalid "), el
);
195 else if (path
== "") {
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
);
200 XmlDocument doc
= RootContext
.Documentation
.StoredDocuments
[file
] as XmlDocument
;
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
);
211 bool keepIncludeNode
= false;
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 keepIncludeNode
= 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
);
227 if (!keepIncludeNode
)
228 el
.ParentNode
.RemoveChild (el
);
233 // Handles <see> elements.
235 private static void HandleSee (MemberCore mc
,
236 DeclSpace ds
, XmlElement see
)
238 HandleXrefCommon (mc
, ds
, see
);
242 // Handles <seealso> elements.
244 private static void HandleSeeAlso (MemberCore mc
,
245 DeclSpace ds
, XmlElement seealso
)
247 HandleXrefCommon (mc
, ds
, seealso
);
251 // Handles <exception> elements.
253 private static void HandleException (MemberCore mc
,
254 DeclSpace ds
, XmlElement seealso
)
256 HandleXrefCommon (mc
, ds
, seealso
);
259 static readonly char [] wsChars
=
260 new char [] {' ', '\t', '\n', '\r'}
;
263 // returns a full runtime type name from a name which might
264 // be C# specific type name.
266 private static Type
FindDocumentedType (MemberCore mc
, string name
, DeclSpace ds
, string cref
)
268 bool isArray
= false;
269 string identifier
= name
;
270 if (name
[name
.Length
- 1] == ']') {
271 string tmp
= name
.Substring (0, name
.Length
- 1).Trim (wsChars
);
272 if (tmp
[tmp
.Length
- 1] == '[') {
273 identifier
= tmp
.Substring (0, tmp
.Length
- 1).Trim (wsChars
);
277 Type t
= FindDocumentedTypeNonArray (mc
, identifier
, ds
, cref
);
278 if (t
!= null && isArray
)
279 t
= Array
.CreateInstance (t
, 0).GetType ();
283 private static Type
FindDocumentedTypeNonArray (MemberCore mc
,
284 string identifier
, DeclSpace ds
, string cref
)
286 switch (identifier
) {
290 return typeof (uint);
292 return typeof (short);
294 return typeof (ushort);
296 return typeof (long);
298 return typeof (ulong);
300 return typeof (float);
302 return typeof (double);
304 return typeof (char);
306 return typeof (decimal);
308 return typeof (byte);
310 return typeof (sbyte);
312 return typeof (object);
314 return typeof (bool);
316 return typeof (string);
318 return typeof (void);
320 FullNamedExpression e
= ds
.LookupType (identifier
, mc
.Location
, false);
322 if (!(e
is TypeExpr
))
326 int index
= identifier
.LastIndexOf ('.');
330 Type parent
= FindDocumentedType (mc
, identifier
.Substring (0, index
), ds
, cref
);
333 // no need to detect warning 419 here
334 return FindDocumentedMember (mc
, parent
,
335 identifier
.Substring (index
+ 1),
336 null, ds
, out warn
, cref
, false, null).Member
as Type
;
339 private static MemberInfo
[] empty_member_infos
=
342 private static MemberInfo
[] FindMethodBase (Type type
,
343 BindingFlags bindingFlags
, MethodSignature signature
)
345 MemberList ml
= TypeManager
.FindMembers (
347 MemberTypes
.Constructor
| MemberTypes
.Method
| MemberTypes
.Property
| MemberTypes
.Custom
,
349 MethodSignature
.method_signature_filter
,
352 return empty_member_infos
;
354 return FilterOverridenMembersOut (type
, (MemberInfo
[]) ml
);
357 static bool IsOverride (PropertyInfo deriv_prop
, PropertyInfo base_prop
)
359 if (!Invocation
.IsAncestralType (base_prop
.DeclaringType
, deriv_prop
.DeclaringType
))
362 Type
[] deriv_pd
= TypeManager
.GetArgumentTypes (deriv_prop
);
363 Type
[] base_pd
= TypeManager
.GetArgumentTypes (base_prop
);
365 if (deriv_pd
.Length
!= base_pd
.Length
)
368 for (int j
= 0; j
< deriv_pd
.Length
; ++j
) {
369 if (deriv_pd
[j
] != base_pd
[j
])
371 Type ct
= TypeManager
.TypeToCoreType (deriv_pd
[j
]);
372 Type bt
= TypeManager
.TypeToCoreType (base_pd
[j
]);
381 private static MemberInfo
[] FilterOverridenMembersOut (
382 Type type
, MemberInfo
[] ml
)
385 return empty_member_infos
;
387 ArrayList al
= new ArrayList (ml
.Length
);
388 for (int i
= 0; i
< ml
.Length
; i
++) {
389 MethodBase mx
= ml
[i
] as MethodBase
;
390 PropertyInfo px
= ml
[i
] as PropertyInfo
;
391 if (mx
!= null || px
!= null) {
392 bool overriden
= false;
393 for (int j
= 0; j
< ml
.Length
; j
++) {
396 MethodBase my
= ml
[j
] as MethodBase
;
397 if (mx
!= null && my
!= null &&
398 Invocation
.IsOverride (my
, mx
)) {
404 PropertyInfo py
= ml
[j
] as PropertyInfo
;
405 if (px
!= null && py
!= null &&
406 IsOverride (py
, px
)) {
416 return al
.ToArray (typeof (MemberInfo
)) as MemberInfo
[];
421 public static FoundMember Empty
= new FoundMember (true);
424 public readonly MemberInfo Member
;
425 public readonly Type Type
;
427 public FoundMember (bool regardlessOfThisValueItsEmpty
)
434 public FoundMember (Type foundType
, MemberInfo member
)
443 // Returns a MemberInfo that is referenced in XML documentation
444 // (by "see" or "seealso" elements).
446 private static FoundMember
FindDocumentedMember (MemberCore mc
,
447 Type type
, string memberName
, Type
[] paramList
,
448 DeclSpace ds
, out int warningType
, string cref
,
449 bool warn419
, string nameForError
)
451 for (; type
!= null; type
= type
.DeclaringType
) {
452 MemberInfo mi
= FindDocumentedMemberNoNest (
453 mc
, type
, memberName
, paramList
, ds
,
454 out warningType
, cref
, warn419
,
457 return new FoundMember (type
, mi
);
460 return FoundMember
.Empty
;
463 private static MemberInfo
FindDocumentedMemberNoNest (
464 MemberCore mc
, Type type
, string memberName
,
465 Type
[] paramList
, DeclSpace ds
, out int warningType
,
466 string cref
, bool warn419
, string nameForError
)
471 if (paramList
== null) {
472 // search for fields/events etc.
473 mis
= TypeManager
.MemberLookup (type
, null,
474 type
, MemberTypes
.All
,
475 BindingFlags
.Public
| BindingFlags
.NonPublic
| BindingFlags
.Static
| BindingFlags
.Instance
,
477 mis
= FilterOverridenMembersOut (type
, mis
);
478 if (mis
== null || mis
.Length
== 0)
480 if (warn419
&& IsAmbiguous (mis
))
481 Report419 (mc
, nameForError
, mis
);
485 MethodSignature msig
= new MethodSignature (memberName
, null, paramList
);
486 mis
= FindMethodBase (type
,
487 BindingFlags
.Public
| BindingFlags
.NonPublic
| BindingFlags
.Static
| BindingFlags
.Instance
,
490 if (warn419
&& mis
.Length
> 0) {
491 if (IsAmbiguous (mis
))
492 Report419 (mc
, nameForError
, mis
);
496 // search for operators (whose parameters exactly
497 // matches with the list) and possibly report CS1581.
499 string returnTypeName
= null;
500 if (memberName
.StartsWith ("implicit operator ")) {
501 oper
= "op_Implicit";
502 returnTypeName
= memberName
.Substring (18).Trim (wsChars
);
504 else if (memberName
.StartsWith ("explicit operator ")) {
505 oper
= "op_Explicit";
506 returnTypeName
= memberName
.Substring (18).Trim (wsChars
);
508 else if (memberName
.StartsWith ("operator ")) {
509 oper
= memberName
.Substring (9).Trim (wsChars
);
511 // either unary or binary
513 oper
= paramList
.Length
== 2 ?
514 Binary
.oper_names
[(int) Binary
.Operator
.Addition
] :
515 Unary
.oper_names
[(int) Unary
.Operator
.UnaryPlus
];
518 oper
= paramList
.Length
== 2 ?
519 Binary
.oper_names
[(int) Binary
.Operator
.Subtraction
] :
520 Unary
.oper_names
[(int) Unary
.Operator
.UnaryNegation
];
524 oper
= Unary
.oper_names
[(int) Unary
.Operator
.LogicalNot
]; break;
526 oper
= Unary
.oper_names
[(int) Unary
.Operator
.OnesComplement
]; break;
529 oper
= "op_Increment"; break;
531 oper
= "op_Decrement"; break;
533 oper
= "op_True"; break;
535 oper
= "op_False"; break;
538 oper
= Binary
.oper_names
[(int) Binary
.Operator
.Multiply
]; break;
540 oper
= Binary
.oper_names
[(int) Binary
.Operator
.Division
]; break;
542 oper
= Binary
.oper_names
[(int) Binary
.Operator
.Modulus
]; break;
544 oper
= Binary
.oper_names
[(int) Binary
.Operator
.BitwiseAnd
]; break;
546 oper
= Binary
.oper_names
[(int) Binary
.Operator
.BitwiseOr
]; break;
548 oper
= Binary
.oper_names
[(int) Binary
.Operator
.ExclusiveOr
]; break;
550 oper
= Binary
.oper_names
[(int) Binary
.Operator
.LeftShift
]; break;
552 oper
= Binary
.oper_names
[(int) Binary
.Operator
.RightShift
]; break;
554 oper
= Binary
.oper_names
[(int) Binary
.Operator
.Equality
]; break;
556 oper
= Binary
.oper_names
[(int) Binary
.Operator
.Inequality
]; break;
558 oper
= Binary
.oper_names
[(int) Binary
.Operator
.LessThan
]; break;
560 oper
= Binary
.oper_names
[(int) Binary
.Operator
.GreaterThan
]; break;
562 oper
= Binary
.oper_names
[(int) Binary
.Operator
.LessThanOrEqual
]; break;
564 oper
= Binary
.oper_names
[(int) Binary
.Operator
.GreaterThanOrEqual
]; break;
567 Report
.Warning (1020, 1, mc
.Location
, "Overloadable {0} operator is expected", paramList
.Length
== 2 ? "binary" : "unary");
568 Report
.Warning (1584, 1, mc
.Location
, "XML comment on `{0}' has syntactically incorrect cref attribute `{1}'",
569 mc
.GetSignatureForError (), cref
);
573 // here we still don't consider return type (to
574 // detect CS1581 or CS1002+CS1584).
575 msig
= new MethodSignature (oper
, null, paramList
);
577 mis
= FindMethodBase (type
,
578 BindingFlags
.Public
| BindingFlags
.NonPublic
| BindingFlags
.Static
| BindingFlags
.Instance
,
581 return null; // CS1574
582 MemberInfo mi
= mis
[0];
583 Type expected
= mi
is MethodInfo
?
584 ((MethodInfo
) mi
).ReturnType
:
586 ((PropertyInfo
) mi
).PropertyType
:
588 if (returnTypeName
!= null) {
589 Type returnType
= FindDocumentedType (mc
, returnTypeName
, ds
, cref
);
590 if (returnType
== null || returnType
!= expected
) {
592 Report
.Warning (1581, 1, mc
.Location
, "Invalid return type in XML comment cref attribute `{0}'", cref
);
599 private static bool IsAmbiguous (MemberInfo
[] members
)
601 if (members
.Length
< 2)
603 if (members
.Length
> 2)
605 if (members
[0] is EventInfo
&& members
[1] is FieldInfo
)
607 if (members
[1] is EventInfo
&& members
[0] is FieldInfo
)
613 // Processes "see" or "seealso" elements.
614 // Checks cref attribute.
616 private static void HandleXrefCommon (MemberCore mc
,
617 DeclSpace ds
, XmlElement xref
)
619 string cref
= xref
.GetAttribute ("cref").Trim (wsChars
);
620 // when, XmlReader, "if (cref == null)"
621 if (!xref
.HasAttribute ("cref"))
623 if (cref
.Length
== 0)
624 Report
.Warning (1001, 1, mc
.Location
, "Identifier expected");
625 // ... and continue until CS1584.
627 string signature
; // "x:" are stripped
628 string name
; // method invokation "(...)" are removed
629 string parameters
; // method parameter list
631 // strip 'T:' 'M:' 'F:' 'P:' 'E:' etc.
632 // Here, MS ignores its member kind. No idea why.
633 if (cref
.Length
> 2 && cref
[1] == ':')
634 signature
= cref
.Substring (2).Trim (wsChars
);
638 int parensPos
= signature
.IndexOf ('(');
639 int bracePos
= parensPos
>= 0 ? -1 :
640 signature
.IndexOf ('[');
641 if (parensPos
> 0 && signature
[signature
.Length
- 1] == ')') {
642 name
= signature
.Substring (0, parensPos
).Trim (wsChars
);
643 parameters
= signature
.Substring (parensPos
+ 1, signature
.Length
- parensPos
- 2).Trim (wsChars
);
645 else if (bracePos
> 0 && signature
[signature
.Length
- 1] == ']') {
646 name
= signature
.Substring (0, bracePos
).Trim (wsChars
);
647 parameters
= signature
.Substring (bracePos
+ 1, signature
.Length
- bracePos
- 2).Trim (wsChars
);
653 Normalize (mc
, ref name
);
655 string identifier
= GetBodyIdentifierFromName (name
);
657 // Check if identifier is valid.
658 // This check is not necessary to mark as error, but
659 // csc specially reports CS1584 for wrong identifiers.
660 string [] nameElems
= identifier
.Split ('.');
661 for (int i
= 0; i
< nameElems
.Length
; i
++) {
662 string nameElem
= GetBodyIdentifierFromName (nameElems
[i
]);
664 Normalize (mc
, ref nameElem
);
665 if (!Tokenizer
.IsValidIdentifier (nameElem
)
666 && nameElem
.IndexOf ("operator") < 0) {
667 Report
.Warning (1584, 1, mc
.Location
, "XML comment on `{0}' has syntactically incorrect cref attribute `{1}'",
668 mc
.GetSignatureForError (), cref
);
669 xref
.SetAttribute ("cref", "!:" + signature
);
674 // check if parameters are valid
675 Type
[] parameterTypes
;
676 if (parameters
== null)
677 parameterTypes
= null;
678 else if (parameters
.Length
== 0)
679 parameterTypes
= Type
.EmptyTypes
;
681 string [] paramList
= parameters
.Split (',');
682 ArrayList plist
= new ArrayList ();
683 for (int i
= 0; i
< paramList
.Length
; i
++) {
684 string paramTypeName
= paramList
[i
].Trim (wsChars
);
685 Normalize (mc
, ref paramTypeName
);
686 Type paramType
= FindDocumentedType (mc
, paramTypeName
, ds
, cref
);
687 if (paramType
== null) {
688 Report
.Warning (1580, 1, mc
.Location
, "Invalid type for parameter `{0}' in XML comment cref attribute `{1}'",
689 (i
+ 1).ToString (), cref
);
692 plist
.Add (paramType
);
694 parameterTypes
= plist
.ToArray (typeof (Type
)) as Type
[];
697 Type type
= FindDocumentedType (mc
, name
, ds
, cref
);
699 // delegate must not be referenced with args
700 && (!type
.IsSubclassOf (typeof (System
.Delegate
))
701 || parameterTypes
== null)) {
702 string result
= type
.FullName
.Replace ("+", ".")
703 + (bracePos
< 0 ? String
.Empty
: signature
.Substring (bracePos
));
704 xref
.SetAttribute ("cref", "T:" + result
);
708 int period
= name
.LastIndexOf ('.');
710 string typeName
= name
.Substring (0, period
);
711 string memberName
= name
.Substring (period
+ 1);
712 Normalize (mc
, ref memberName
);
713 type
= FindDocumentedType (mc
, typeName
, ds
, cref
);
716 FoundMember fm
= FindDocumentedMember (mc
, type
, memberName
, parameterTypes
, ds
, out warnResult
, cref
, true, name
);
720 MemberInfo mi
= fm
.Member
;
721 // we cannot use 'type' directly
722 // to get its name, since mi
723 // could be from DeclaringType
725 xref
.SetAttribute ("cref", GetMemberDocHead (mi
.MemberType
) + fm
.Type
.FullName
.Replace ("+", ".") + "." + memberName
+ GetParametersFormatted (mi
));
726 return; // a member of a type
732 FoundMember fm
= FindDocumentedMember (mc
, ds
.TypeBuilder
, name
, parameterTypes
, ds
, out warnResult
, cref
, true, name
);
736 MemberInfo mi
= fm
.Member
;
737 // we cannot use 'type' directly
738 // to get its name, since mi
739 // could be from DeclaringType
741 xref
.SetAttribute ("cref", GetMemberDocHead (mi
.MemberType
) + fm
.Type
.FullName
.Replace ("+", ".") + "." + name
+ GetParametersFormatted (mi
));
742 return; // local member name
746 // It still might be part of namespace name.
747 Namespace ns
= ds
.NamespaceEntry
.NS
.GetNamespace (name
, false);
749 xref
.SetAttribute ("cref", "N:" + ns
.FullName
);
750 return; // a namespace
752 if (RootNamespace
.Global
.IsNamespace (name
)) {
753 xref
.SetAttribute ("cref", "N:" + name
);
754 return; // a namespace
757 Report
.Warning (1574, 1, mc
.Location
, "XML comment on `{0}' has cref attribute `{1}' that could not be resolved",
758 mc
.GetSignatureForError (), cref
);
760 xref
.SetAttribute ("cref", "!:" + name
);
763 static string GetParametersFormatted (MemberInfo mi
)
765 MethodBase mb
= mi
as MethodBase
;
766 bool isSetter
= false;
767 PropertyInfo pi
= mi
as PropertyInfo
;
769 mb
= pi
.GetGetMethod ();
772 mb
= pi
.GetSetMethod ();
778 ParameterData parameters
= TypeManager
.GetParameterData (mb
);
779 if (parameters
== null || parameters
.Count
== 0)
782 StringBuilder sb
= new StringBuilder ();
784 for (int i
= 0; i
< parameters
.Count
; i
++) {
785 if (isSetter
&& i
+ 1 == parameters
.Count
)
786 break; // skip "value".
789 Type t
= parameters
.ParameterType (i
);
790 sb
.Append (t
.FullName
.Replace ('+', '.').Replace ('&', '@'));
793 return sb
.ToString ();
796 static string GetBodyIdentifierFromName (string name
)
798 string identifier
= name
;
800 if (name
.Length
> 0 && name
[name
.Length
- 1] == ']') {
801 string tmp
= name
.Substring (0, name
.Length
- 1).Trim (wsChars
);
802 int last
= tmp
.LastIndexOf ('[');
804 identifier
= tmp
.Substring (0, last
).Trim (wsChars
);
810 static void Report419 (MemberCore mc
, string memberName
, MemberInfo
[] mis
)
812 Report
.Warning (419, 3, mc
.Location
,
813 "Ambiguous reference in cref attribute `{0}'. Assuming `{1}' but other overloads including `{2}' have also matched",
815 TypeManager
.GetFullNameSignature (mis
[0]),
816 TypeManager
.GetFullNameSignature (mis
[1]));
820 // Get a prefix from member type for XML documentation (used
821 // to formalize cref target name).
823 static string GetMemberDocHead (MemberTypes type
)
826 case MemberTypes
.Constructor
:
827 case MemberTypes
.Method
:
829 case MemberTypes
.Event
:
831 case MemberTypes
.Field
:
833 case MemberTypes
.NestedType
:
834 case MemberTypes
.TypeInfo
:
836 case MemberTypes
.Property
:
845 // Returns a string that represents the signature for this
846 // member which should be used in XML documentation.
848 public static string GetMethodDocCommentName (MethodCore mc
, DeclSpace ds
)
850 Parameter
[] plist
= mc
.Parameters
.FixedParameters
;
851 string paramSpec
= String
.Empty
;
853 StringBuilder psb
= new StringBuilder ();
854 foreach (Parameter p
in plist
) {
855 psb
.Append (psb
.Length
!= 0 ? "," : "(");
856 psb
.Append (p
.ExternalType ().FullName
.Replace ("+", ".").Replace ('&', '@'));
858 paramSpec
= psb
.ToString ();
861 if (paramSpec
.Length
> 0)
864 string name
= mc
is Constructor
? "#ctor" : mc
.Name
;
865 string suffix
= String
.Empty
;
866 Operator op
= mc
as Operator
;
868 switch (op
.OperatorType
) {
869 case Operator
.OpType
.Implicit
:
870 case Operator
.OpType
.Explicit
:
871 suffix
= "~" + op
.OperatorMethodBuilder
.ReturnType
.FullName
.Replace ('+', '.');
875 return String
.Concat (mc
.DocCommentHeader
, ds
.Name
, ".", name
, paramSpec
, suffix
);
879 // Raised (and passed an XmlElement that contains the comment)
880 // when GenerateDocComment is writing documentation expectedly.
882 // FIXME: with a few effort, it could be done with XmlReader,
883 // that means removal of DOM use.
885 internal static void OnMethodGenerateDocComment (
886 MethodCore mc
, DeclSpace ds
, XmlElement el
)
888 Hashtable paramTags
= new Hashtable ();
889 foreach (XmlElement pelem
in el
.SelectNodes ("param")) {
891 string xname
= pelem
.GetAttribute ("name");
893 continue; // really? but MS looks doing so
894 if (xname
!= "" && mc
.Parameters
.GetParameterByName (xname
, out i
) == null)
895 Report
.Warning (1572, 2, mc
.Location
, "XML comment on `{0}' has a param tag for `{1}', but there is no parameter by that name",
896 mc
.GetSignatureForError (), xname
);
897 else if (paramTags
[xname
] != null)
898 Report
.Warning (1571, 2, mc
.Location
, "XML comment on `{0}' has a duplicate param tag for `{1}'",
899 mc
.GetSignatureForError (), xname
);
900 paramTags
[xname
] = xname
;
902 Parameter
[] plist
= mc
.Parameters
.FixedParameters
;
903 foreach (Parameter p
in plist
) {
904 if (paramTags
.Count
> 0 && paramTags
[p
.Name
] == null)
905 Report
.Warning (1573, 4, mc
.Location
, "Parameter `{0}' has no matching param tag in the XML comment for `{1}'",
906 p
.Name
, mc
.GetSignatureForError ());
910 private static void Normalize (MemberCore mc
, ref string name
)
912 if (name
.Length
> 0 && name
[0] == '@')
913 name
= name
.Substring (1);
914 else if (name
== "this")
916 else if (Tokenizer
.IsKeyword (name
) && !IsTypeName (name
))
917 Report
.Warning (1041, 1, mc
.Location
, "Identifier expected. `{0}' is a keyword", name
);
920 private static bool IsTypeName (string name
)
946 // Implements XML documentation generation.
948 public class Documentation
950 public Documentation (string xml_output_filename
)
952 docfilename
= xml_output_filename
;
953 XmlDocumentation
= new XmlDocument ();
954 XmlDocumentation
.PreserveWhitespace
= false;
957 private string docfilename
;
960 // Used to create element which helps well-formedness checking.
962 public XmlDocument XmlDocumentation
;
965 // The output for XML documentation.
967 public XmlWriter XmlCommentOutput
;
970 // Stores XmlDocuments that are included in XML documentation.
971 // Keys are included filenames, values are XmlDocuments.
973 public Hashtable StoredDocuments
= new Hashtable ();
976 // Stores comments on partial types (should handle uniquely).
977 // Keys are PartialContainers, values are comment strings
978 // (didn't use StringBuilder; usually we have just 2 or more).
980 public IDictionary PartialComments
= new ListDictionary ();
983 // Outputs XML documentation comment from tokenized comments.
985 public bool OutputDocComment (string asmfilename
)
987 XmlTextWriter w
= null;
989 w
= new XmlTextWriter (docfilename
, null);
991 w
.Formatting
= Formatting
.Indented
;
992 w
.WriteStartDocument ();
993 w
.WriteStartElement ("doc");
994 w
.WriteStartElement ("assembly");
995 w
.WriteStartElement ("name");
996 w
.WriteString (Path
.ChangeExtension (asmfilename
, null));
997 w
.WriteEndElement (); // name
998 w
.WriteEndElement (); // assembly
999 w
.WriteStartElement ("members");
1000 XmlCommentOutput
= w
;
1001 GenerateDocComment ();
1002 w
.WriteFullEndElement (); // members
1003 w
.WriteEndElement ();
1004 w
.WriteWhitespace (Environment
.NewLine
);
1005 w
.WriteEndDocument ();
1007 } catch (Exception ex
) {
1008 Report
.Error (1569, "Error generating XML documentation file `{0}' (`{1}')", docfilename
, ex
.Message
);
1017 // Fixes full type name of each documented types/members up.
1019 public void GenerateDocComment ()
1021 TypeContainer root
= RootContext
.Tree
.Types
;
1022 if (root
.Interfaces
!= null)
1023 foreach (Interface i
in root
.Interfaces
)
1024 DocUtil
.GenerateTypeDocComment (i
, null);
1026 if (root
.Types
!= null)
1027 foreach (TypeContainer tc
in root
.Types
)
1028 DocUtil
.GenerateTypeDocComment (tc
, null);
1030 if (root
.Parts
!= null) {
1031 IDictionary comments
= PartialComments
;
1032 foreach (ClassPart cp
in root
.Parts
) {
1033 if (cp
.DocComment
== null)
1039 if (root
.Delegates
!= null)
1040 foreach (Delegate d
in root
.Delegates
)
1041 DocUtil
.GenerateDocComment (d
, null);
1043 if (root
.Enums
!= null)
1044 foreach (Enum e
in root
.Enums
)
1045 e
.GenerateDocComment (null);
1047 IDictionary table
= new ListDictionary ();
1048 foreach (ClassPart cp
in PartialComments
.Keys
) {
1049 // FIXME: IDictionary does not guarantee that the keys will be
1050 // accessed in the order they were added.
1051 table
[cp
.PartialContainer
] += cp
.DocComment
;
1053 foreach (PartialContainer pc
in table
.Keys
) {
1054 pc
.DocComment
= table
[pc
] as string;
1055 DocUtil
.GenerateDocComment (pc
, null);