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
;
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
.Constants
!= null)
58 foreach (Const c
in t
.Constants
)
59 c
.GenerateDocComment (t
);
62 foreach (FieldBase f
in t
.Fields
)
63 f
.GenerateDocComment (t
);
66 foreach (Event e
in t
.Events
)
67 e
.GenerateDocComment (t
);
69 if (t
.Indexers
!= null)
70 foreach (Indexer ix
in t
.Indexers
)
71 ix
.GenerateDocComment (t
);
73 if (t
.Properties
!= null)
74 foreach (Property p
in t
.Properties
)
75 p
.GenerateDocComment (t
);
77 if (t
.Methods
!= null)
78 foreach (MethodOrOperator m
in t
.Methods
)
79 m
.GenerateDocComment (t
);
81 if (t
.Operators
!= null)
82 foreach (Operator o
in t
.Operators
)
83 o
.GenerateDocComment (t
);
87 private static readonly string line_head
=
88 Environment
.NewLine
+ " ";
90 private static XmlNode
GetDocCommentNode (MemberCore mc
,
91 string name
, Report Report
)
93 // FIXME: It could be even optimizable as not
94 // to use XmlDocument. But anyways the nodes
95 // are not kept in memory.
96 XmlDocument doc
= RootContext
.Documentation
.XmlDocumentation
;
98 XmlElement el
= doc
.CreateElement ("member");
99 el
.SetAttribute ("name", name
);
100 string normalized
= mc
.DocComment
;
101 el
.InnerXml
= normalized
;
102 // csc keeps lines as written in the sources
103 // and inserts formatting indentation (which
104 // is different from XmlTextWriter.Formatting
105 // one), but when a start tag contains an
106 // endline, it joins the next line. We don't
107 // have to follow such a hacky behavior.
109 normalized
.Split ('\n');
111 for (int i
= 0; i
< split
.Length
; i
++) {
112 string s
= split
[i
].TrimEnd ();
116 el
.InnerXml
= line_head
+ String
.Join (
117 line_head
, split
, 0, j
);
119 } catch (Exception ex
) {
120 Report
.Warning (1570, 1, mc
.Location
, "XML comment on `{0}' has non-well-formed XML ({1})", name
, ex
.Message
);
121 XmlComment com
= doc
.CreateComment (String
.Format ("FIXME: Invalid documentation markup was found for member {0}", name
));
127 // Generates xml doc comments (if any), and if required,
128 // handle warning report.
130 internal static void GenerateDocComment (MemberCore mc
,
131 DeclSpace ds
, Report Report
)
133 if (mc
.DocComment
!= null) {
134 string name
= mc
.GetDocCommentName (ds
);
136 XmlNode n
= GetDocCommentNode (mc
, name
, Report
);
138 XmlElement el
= n
as XmlElement
;
140 mc
.OnGenerateDocComment (el
);
142 // FIXME: it could be done with XmlReader
143 XmlNodeList nl
= n
.SelectNodes (".//include");
145 // It could result in current node removal, so prepare another list to iterate.
146 var al
= new List
<XmlNode
> (nl
.Count
);
147 foreach (XmlNode inc
in nl
)
149 foreach (XmlElement inc
in al
)
150 if (!HandleInclude (mc
, inc
, Report
))
151 inc
.ParentNode
.RemoveChild (inc
);
154 // FIXME: it could be done with XmlReader
155 DeclSpace ds_target
= mc
as DeclSpace
;
156 if (ds_target
== null)
159 foreach (XmlElement see
in n
.SelectNodes (".//see"))
160 HandleSee (mc
, ds_target
, see
, Report
);
161 foreach (XmlElement seealso
in n
.SelectNodes (".//seealso"))
162 HandleSeeAlso (mc
, ds_target
, seealso
,Report
);
163 foreach (XmlElement see
in n
.SelectNodes (".//exception"))
164 HandleException (mc
, ds_target
, see
, Report
);
167 n
.WriteTo (RootContext
.Documentation
.XmlCommentOutput
);
169 else if (mc
.IsExposedFromAssembly ()) {
170 Constructor c
= mc
as Constructor
;
171 if (c
== null || !c
.IsDefault ())
172 Report
.Warning (1591, 4, mc
.Location
,
173 "Missing XML comment for publicly visible type or member `{0}'", mc
.GetSignatureForError ());
178 // Processes "include" element. Check included file and
179 // embed the document content inside this documentation node.
181 private static bool HandleInclude (MemberCore mc
, XmlElement el
, Report Report
)
183 bool keep_include_node
= false;
184 string file
= el
.GetAttribute ("file");
185 string path
= el
.GetAttribute ("path");
187 Report
.Warning (1590, 1, mc
.Location
, "Invalid XML `include' element. Missing `file' attribute");
188 el
.ParentNode
.InsertBefore (el
.OwnerDocument
.CreateComment (" Include tag is invalid "), el
);
189 keep_include_node
= true;
191 else if (path
.Length
== 0) {
192 Report
.Warning (1590, 1, mc
.Location
, "Invalid XML `include' element. Missing `path' attribute");
193 el
.ParentNode
.InsertBefore (el
.OwnerDocument
.CreateComment (" Include tag is invalid "), el
);
194 keep_include_node
= true;
198 if (!RootContext
.Documentation
.StoredDocuments
.TryGetValue (file
, out doc
)) {
200 doc
= new XmlDocument ();
202 RootContext
.Documentation
.StoredDocuments
.Add (file
, doc
);
203 } catch (Exception
) {
204 el
.ParentNode
.InsertBefore (el
.OwnerDocument
.CreateComment (String
.Format (" Badly formed XML in at comment file `{0}': cannot be included ", file
)), el
);
205 Report
.Warning (1592, 1, mc
.Location
, "Badly formed XML in included comments file -- `{0}'", file
);
210 XmlNodeList nl
= doc
.SelectNodes (path
);
212 el
.ParentNode
.InsertBefore (el
.OwnerDocument
.CreateComment (" No matching elements were found for the include tag embedded here. "), el
);
214 keep_include_node
= true;
216 foreach (XmlNode n
in nl
)
217 el
.ParentNode
.InsertBefore (el
.OwnerDocument
.ImportNode (n
, true), el
);
218 } catch (Exception ex
) {
219 el
.ParentNode
.InsertBefore (el
.OwnerDocument
.CreateComment (" Failed to insert some or all of included XML "), el
);
220 Report
.Warning (1589, 1, mc
.Location
, "Unable to include XML fragment `{0}' of file `{1}' ({2})", path
, file
, ex
.Message
);
224 return keep_include_node
;
228 // Handles <see> elements.
230 private static void HandleSee (MemberCore mc
,
231 DeclSpace ds
, XmlElement see
, Report r
)
233 HandleXrefCommon (mc
, ds
, see
, r
);
237 // Handles <seealso> elements.
239 private static void HandleSeeAlso (MemberCore mc
,
240 DeclSpace ds
, XmlElement seealso
, Report r
)
242 HandleXrefCommon (mc
, ds
, seealso
, r
);
246 // Handles <exception> elements.
248 private static void HandleException (MemberCore mc
,
249 DeclSpace ds
, XmlElement seealso
, Report r
)
251 HandleXrefCommon (mc
, ds
, seealso
, r
);
254 static readonly char [] wsChars
=
255 new char [] {' ', '\t', '\n', '\r'}
;
258 // returns a full runtime type name from a name which might
259 // be C# specific type name.
261 private static TypeSpec
FindDocumentedType (MemberCore mc
, string name
, DeclSpace ds
, string cref
, Report r
)
263 bool is_array
= false;
264 string identifier
= name
;
265 if (name
[name
.Length
- 1] == ']') {
266 string tmp
= name
.Substring (0, name
.Length
- 1).Trim (wsChars
);
267 if (tmp
[tmp
.Length
- 1] == '[') {
268 identifier
= tmp
.Substring (0, tmp
.Length
- 1).Trim (wsChars
);
272 TypeSpec t
= FindDocumentedTypeNonArray (mc
, identifier
, ds
, cref
, r
);
273 if (t
!= null && is_array
)
274 t
= Import
.ImportType (Array
.CreateInstance (t
.GetMetaInfo (), 0).GetType ());
278 private static TypeSpec
FindDocumentedTypeNonArray (MemberCore mc
,
279 string identifier
, DeclSpace ds
, string cref
, Report r
)
281 switch (identifier
) {
283 return TypeManager
.int32_type
;
285 return TypeManager
.uint32_type
;
287 return TypeManager
.short_type
;;
289 return TypeManager
.ushort_type
;
291 return TypeManager
.int64_type
;
293 return TypeManager
.uint64_type
;;
295 return TypeManager
.float_type
;;
297 return TypeManager
.double_type
;
299 return TypeManager
.char_type
;;
301 return TypeManager
.decimal_type
;;
303 return TypeManager
.byte_type
;;
305 return TypeManager
.sbyte_type
;;
307 return TypeManager
.object_type
;;
309 return TypeManager
.bool_type
;;
311 return TypeManager
.string_type
;;
313 return TypeManager
.void_type
;;
315 FullNamedExpression e
= ds
.LookupNamespaceOrType (identifier
, 0, mc
.Location
, false);
317 if (!(e
is TypeExpr
))
321 int index
= identifier
.LastIndexOf ('.');
325 TypeSpec parent
= FindDocumentedType (mc
, identifier
.Substring (0, index
), ds
, cref
, r
);
328 // no need to detect warning 419 here
329 var ts
= FindDocumentedMember (mc
, parent
,
330 identifier
.Substring (index
+ 1),
331 null, ds
, out warn
, cref
, false, null, r
) as TypeSpec
;
338 // Returns a MemberInfo that is referenced in XML documentation
339 // (by "see" or "seealso" elements).
341 private static MemberSpec
FindDocumentedMember (MemberCore mc
,
342 TypeSpec type
, string member_name
, AParametersCollection param_list
,
343 DeclSpace ds
, out int warning_type
, string cref
,
344 bool warn419
, string name_for_error
, Report r
)
346 // for (; type != null; type = type.DeclaringType) {
347 var mi
= FindDocumentedMemberNoNest (
348 mc
, type
, member_name
, param_list
, ds
,
349 out warning_type
, cref
, warn419
,
352 return mi
; // new FoundMember (type, mi);
358 private static MemberSpec
FindDocumentedMemberNoNest (
359 MemberCore mc
, TypeSpec type
, string member_name
,
360 AParametersCollection param_list
, DeclSpace ds
, out int warning_type
,
361 string cref
, bool warn419
, string name_for_error
, Report Report
)
364 var filter
= new MemberFilter (member_name
, 0, MemberKind
.All
, param_list
, null);
365 IList
<MemberSpec
> found
= null;
366 while (type
!= null && found
== null) {
367 found
= MemberCache
.FindMembers (type
, filter
, BindingRestriction
.None
);
368 type
= type
.DeclaringType
;
374 if (warn419
&& found
.Count
> 1) {
375 Report419 (mc
, name_for_error
, found
.ToArray (), Report
);
381 if (param_list == null) {
382 // search for fields/events etc.
383 mis = TypeManager.MemberLookup (type, null,
384 type, MemberKind.All,
385 BindingRestriction.None,
387 mis = FilterOverridenMembersOut (mis);
388 if (mis == null || mis.Length == 0)
390 if (warn419 && IsAmbiguous (mis))
391 Report419 (mc, name_for_error, mis, Report);
395 MethodSignature msig = new MethodSignature (member_name, null, param_list);
396 mis = FindMethodBase (type,
397 BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance,
400 if (warn419 && mis.Length > 0) {
401 if (IsAmbiguous (mis))
402 Report419 (mc, name_for_error, mis, Report);
406 // search for operators (whose parameters exactly
407 // matches with the list) and possibly report CS1581.
409 string return_type_name = null;
410 if (member_name.StartsWith ("implicit operator ")) {
411 Operator.GetMetadataName (Operator.OpType.Implicit);
412 return_type_name = member_name.Substring (18).Trim (wsChars);
414 else if (member_name.StartsWith ("explicit operator ")) {
415 oper = Operator.GetMetadataName (Operator.OpType.Explicit);
416 return_type_name = member_name.Substring (18).Trim (wsChars);
418 else if (member_name.StartsWith ("operator ")) {
419 oper = member_name.Substring (9).Trim (wsChars);
421 // either unary or binary
423 oper = param_list.Length == 2 ?
424 Operator.GetMetadataName (Operator.OpType.Addition) :
425 Operator.GetMetadataName (Operator.OpType.UnaryPlus);
428 oper = param_list.Length == 2 ?
429 Operator.GetMetadataName (Operator.OpType.Subtraction) :
430 Operator.GetMetadataName (Operator.OpType.UnaryNegation);
433 oper = Operator.GetMetadataName (oper);
438 Report.Warning (1020, 1, mc.Location, "Overloadable {0} operator is expected", param_list.Length == 2 ? "binary" : "unary");
439 Report.Warning (1584, 1, mc.Location, "XML comment on `{0}' has syntactically incorrect cref attribute `{1}'",
440 mc.GetSignatureForError (), cref);
444 // here we still don't consider return type (to
445 // detect CS1581 or CS1002+CS1584).
446 msig = new MethodSignature (oper, null, param_list);
448 mis = FindMethodBase (type,
449 BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance,
452 return null; // CS1574
454 TypeSpec expected = mi is MethodSpec ?
455 ((MethodSpec) mi).ReturnType :
457 ((PropertySpec) mi).PropertyType :
459 if (return_type_name != null) {
460 TypeSpec returnType = FindDocumentedType (mc, return_type_name, ds, cref, Report);
461 if (returnType == null || returnType != expected) {
463 Report.Warning (1581, 1, mc.Location, "Invalid return type in XML comment cref attribute `{0}'", cref);
472 // Processes "see" or "seealso" elements.
473 // Checks cref attribute.
475 private static void HandleXrefCommon (MemberCore mc
,
476 DeclSpace ds
, XmlElement xref
, Report Report
)
478 string cref
= xref
.GetAttribute ("cref").Trim (wsChars
);
479 // when, XmlReader, "if (cref == null)"
480 if (!xref
.HasAttribute ("cref"))
482 if (cref
.Length
== 0)
483 Report
.Warning (1001, 1, mc
.Location
, "Identifier expected");
484 // ... and continue until CS1584.
486 string signature
; // "x:" are stripped
487 string name
; // method invokation "(...)" are removed
488 string parameters
; // method parameter list
490 // When it found '?:' ('T:' 'M:' 'F:' 'P:' 'E:' etc.),
491 // MS ignores not only its member kind, but also
492 // the entire syntax correctness. Nor it also does
493 // type fullname resolution i.e. "T:List(int)" is kept
494 // as T:List(int), not
495 // T:System.Collections.Generic.List<System.Int32>
496 if (cref
.Length
> 2 && cref
[1] == ':')
501 // Also note that without "T:" any generic type
504 int parens_pos
= signature
.IndexOf ('(');
505 int brace_pos
= parens_pos
>= 0 ? -1 :
506 signature
.IndexOf ('[');
507 if (parens_pos
> 0 && signature
[signature
.Length
- 1] == ')') {
508 name
= signature
.Substring (0, parens_pos
).Trim (wsChars
);
509 parameters
= signature
.Substring (parens_pos
+ 1, signature
.Length
- parens_pos
- 2).Trim (wsChars
);
511 else if (brace_pos
> 0 && signature
[signature
.Length
- 1] == ']') {
512 name
= signature
.Substring (0, brace_pos
).Trim (wsChars
);
513 parameters
= signature
.Substring (brace_pos
+ 1, signature
.Length
- brace_pos
- 2).Trim (wsChars
);
519 Normalize (mc
, ref name
, Report
);
521 string identifier
= GetBodyIdentifierFromName (name
);
523 // Check if identifier is valid.
524 // This check is not necessary to mark as error, but
525 // csc specially reports CS1584 for wrong identifiers.
526 string [] name_elems
= identifier
.Split ('.');
527 for (int i
= 0; i
< name_elems
.Length
; i
++) {
528 string nameElem
= GetBodyIdentifierFromName (name_elems
[i
]);
530 Normalize (mc
, ref nameElem
, Report
);
531 if (!Tokenizer
.IsValidIdentifier (nameElem
)
532 && nameElem
.IndexOf ("operator") < 0) {
533 Report
.Warning (1584, 1, mc
.Location
, "XML comment on `{0}' has syntactically incorrect cref attribute `{1}'",
534 mc
.GetSignatureForError (), cref
);
535 xref
.SetAttribute ("cref", "!:" + signature
);
540 // check if parameters are valid
541 AParametersCollection parameter_types
;
542 if (parameters
== null)
543 parameter_types
= null;
544 else if (parameters
.Length
== 0)
545 parameter_types
= ParametersCompiled
.EmptyReadOnlyParameters
;
547 string [] param_list
= parameters
.Split (',');
548 var plist
= new List
<TypeSpec
> ();
549 for (int i
= 0; i
< param_list
.Length
; i
++) {
550 string param_type_name
= param_list
[i
].Trim (wsChars
);
551 Normalize (mc
, ref param_type_name
, Report
);
552 TypeSpec param_type
= FindDocumentedType (mc
, param_type_name
, ds
, cref
, Report
);
553 if (param_type
== null) {
554 Report
.Warning (1580, 1, mc
.Location
, "Invalid type for parameter `{0}' in XML comment cref attribute `{1}'",
555 (i
+ 1).ToString (), cref
);
558 plist
.Add (param_type
);
561 parameter_types
= ParametersCompiled
.CreateFullyResolved (plist
.ToArray ());
564 TypeSpec type
= FindDocumentedType (mc
, name
, ds
, cref
, Report
);
566 // delegate must not be referenced with args
568 || parameter_types
== null)) {
569 string result
= GetSignatureForDoc (type
)
570 + (brace_pos
< 0 ? String
.Empty
: signature
.Substring (brace_pos
));
571 xref
.SetAttribute ("cref", "T:" + result
);
575 int period
= name
.LastIndexOf ('.');
577 string typeName
= name
.Substring (0, period
);
578 string member_name
= name
.Substring (period
+ 1);
579 string lookup_name
= member_name
== "this" ? MemberCache
.IndexerNameAlias
: member_name
;
580 Normalize (mc
, ref lookup_name
, Report
);
581 Normalize (mc
, ref member_name
, Report
);
582 type
= FindDocumentedType (mc
, typeName
, ds
, cref
, Report
);
585 var mi
= FindDocumentedMember (mc
, type
, lookup_name
, parameter_types
, ds
, out warn_result
, cref
, true, name
, Report
);
589 // we cannot use 'type' directly
590 // to get its name, since mi
591 // could be from DeclaringType
593 xref
.SetAttribute ("cref", GetMemberDocHead (mi
) + GetSignatureForDoc (mi
.DeclaringType
) + "." + member_name
+ GetParametersFormatted (mi
));
594 return; // a member of a type
599 var mi
= FindDocumentedMember (mc
, ds
.PartialContainer
.Definition
, name
, parameter_types
, ds
, out warn_result
, cref
, true, name
, Report
);
604 // we cannot use 'type' directly
605 // to get its name, since mi
606 // could be from DeclaringType
608 xref
.SetAttribute ("cref", GetMemberDocHead (mi
) + GetSignatureForDoc (mi
.DeclaringType
) + "." + name
+ GetParametersFormatted (mi
));
609 return; // local member name
613 // It still might be part of namespace name.
614 Namespace ns
= ds
.NamespaceEntry
.NS
.GetNamespace (name
, false);
616 xref
.SetAttribute ("cref", "N:" + ns
.GetSignatureForError ());
617 return; // a namespace
619 if (GlobalRootNamespace
.Instance
.IsNamespace (name
)) {
620 xref
.SetAttribute ("cref", "N:" + name
);
621 return; // a namespace
624 Report
.Warning (1574, 1, mc
.Location
, "XML comment on `{0}' has cref attribute `{1}' that could not be resolved",
625 mc
.GetSignatureForError (), cref
);
627 xref
.SetAttribute ("cref", "!:" + name
);
630 static string GetParametersFormatted (MemberSpec mi
)
632 var pm
= mi
as IParametersMember
;
633 if (pm
== null || pm
.Parameters
.IsEmpty
)
636 AParametersCollection parameters
= pm
.Parameters
;
638 if (parameters == null || parameters.Count == 0)
641 StringBuilder sb
= new StringBuilder ();
643 for (int i
= 0; i
< parameters
.Count
; i
++) {
644 // if (is_setter && i + 1 == parameters.Count)
645 // break; // skip "value".
648 TypeSpec t
= parameters
.Types
[i
];
649 sb
.Append (GetSignatureForDoc (t
));
652 return sb
.ToString ();
655 static string GetBodyIdentifierFromName (string name
)
657 string identifier
= name
;
659 if (name
.Length
> 0 && name
[name
.Length
- 1] == ']') {
660 string tmp
= name
.Substring (0, name
.Length
- 1).Trim (wsChars
);
661 int last
= tmp
.LastIndexOf ('[');
663 identifier
= tmp
.Substring (0, last
).Trim (wsChars
);
669 static void Report419 (MemberCore mc
, string member_name
, MemberSpec
[] mis
, Report Report
)
671 Report
.Warning (419, 3, mc
.Location
,
672 "Ambiguous reference in cref attribute `{0}'. Assuming `{1}' but other overloads including `{2}' have also matched",
674 TypeManager
.GetFullNameSignature (mis
[0]),
675 TypeManager
.GetFullNameSignature (mis
[1]));
679 // Get a prefix from member type for XML documentation (used
680 // to formalize cref target name).
682 static string GetMemberDocHead (MemberSpec type
)
684 if (type
is FieldSpec
)
686 if (type
is MethodSpec
)
688 if (type
is EventSpec
)
690 if (type
is PropertySpec
)
692 if (type
is TypeSpec
)
701 // Returns a string that represents the signature for this
702 // member which should be used in XML documentation.
704 public static string GetMethodDocCommentName (MemberCore mc
, ParametersCompiled parameters
, DeclSpace ds
)
706 IParameterData
[] plist
= parameters
.FixedParameters
;
707 string paramSpec
= String
.Empty
;
709 StringBuilder psb
= new StringBuilder ();
711 foreach (Parameter p
in plist
) {
712 psb
.Append (psb
.Length
!= 0 ? "," : "(");
713 psb
.Append (GetSignatureForDoc (parameters
.Types
[i
++]));
714 if ((p
.ModFlags
& Parameter
.Modifier
.ISBYREF
) != 0)
717 paramSpec
= psb
.ToString ();
720 if (paramSpec
.Length
> 0)
723 string name
= mc
is Constructor
? "#ctor" : mc
.Name
;
724 if (mc
.MemberName
.IsGeneric
)
725 name
+= "``" + mc
.MemberName
.CountTypeArguments
;
727 string suffix
= String
.Empty
;
728 Operator op
= mc
as Operator
;
730 switch (op
.OperatorType
) {
731 case Operator
.OpType
.Implicit
:
732 case Operator
.OpType
.Explicit
:
733 suffix
= "~" + GetSignatureForDoc (op
.ReturnType
);
737 return String
.Concat (mc
.DocCommentHeader
, ds
.Name
, ".", name
, paramSpec
, suffix
);
740 static string GetSignatureForDoc (TypeSpec type
)
742 var tp
= type
as TypeParameterSpec
;
744 var prefix
= tp
.IsMethodOwned
? "``" : "`";
745 return prefix
+ tp
.DeclaredPosition
;
748 if (TypeManager
.IsGenericType (type
)) {
749 string g
= type
.MemberDefinition
.Namespace
;
750 if (g
!= null && g
.Length
> 0)
752 int idx
= type
.Name
.LastIndexOf ('`');
753 g
+= (idx
< 0 ? type
.Name
: type
.Name
.Substring (0, idx
)) + '{';
755 foreach (TypeSpec t
in TypeManager
.GetTypeArguments (type
))
756 g
+= (argpos
++ > 0 ? "," : String
.Empty
) + GetSignatureForDoc (t
);
761 string name
= type
.GetMetaInfo ().FullName
!= null ? type
.GetMetaInfo ().FullName
: type
.Name
;
762 return name
.Replace ("+", ".").Replace ('&', '@');
766 // Raised (and passed an XmlElement that contains the comment)
767 // when GenerateDocComment is writing documentation expectedly.
769 // FIXME: with a few effort, it could be done with XmlReader,
770 // that means removal of DOM use.
772 internal static void OnMethodGenerateDocComment (
773 MethodCore mc
, XmlElement el
, Report Report
)
775 var paramTags
= new Dictionary
<string, string> ();
776 foreach (XmlElement pelem
in el
.SelectNodes ("param")) {
777 string xname
= pelem
.GetAttribute ("name");
778 if (xname
.Length
== 0)
779 continue; // really? but MS looks doing so
780 if (xname
!= "" && mc
.ParameterInfo
.GetParameterIndexByName (xname
) < 0)
781 Report
.Warning (1572, 2, mc
.Location
, "XML comment on `{0}' has a param tag for `{1}', but there is no parameter by that name",
782 mc
.GetSignatureForError (), xname
);
783 else if (paramTags
.ContainsKey (xname
))
784 Report
.Warning (1571, 2, mc
.Location
, "XML comment on `{0}' has a duplicate param tag for `{1}'",
785 mc
.GetSignatureForError (), xname
);
786 paramTags
[xname
] = xname
;
788 IParameterData
[] plist
= mc
.ParameterInfo
.FixedParameters
;
789 foreach (Parameter p
in plist
) {
790 if (paramTags
.Count
> 0 && !paramTags
.ContainsKey (p
.Name
))
791 Report
.Warning (1573, 4, mc
.Location
, "Parameter `{0}' has no matching param tag in the XML comment for `{1}'",
792 p
.Name
, mc
.GetSignatureForError ());
796 private static void Normalize (MemberCore mc
, ref string name
, Report Report
)
798 if (name
.Length
> 0 && name
[0] == '@')
799 name
= name
.Substring (1);
800 else if (name
== "this")
802 else if (Tokenizer
.IsKeyword (name
) && !IsTypeName (name
))
803 Report
.Warning (1041, 1, mc
.Location
, "Identifier expected. `{0}' is a keyword", name
);
806 private static bool IsTypeName (string name
)
832 // Implements XML documentation generation.
834 public class Documentation
836 public Documentation (string xml_output_filename
)
838 docfilename
= xml_output_filename
;
839 XmlDocumentation
= new XmlDocument ();
840 XmlDocumentation
.PreserveWhitespace
= false;
843 private string docfilename
;
846 // Used to create element which helps well-formedness checking.
848 public XmlDocument XmlDocumentation
;
851 // The output for XML documentation.
853 public XmlWriter XmlCommentOutput
;
856 // Stores XmlDocuments that are included in XML documentation.
857 // Keys are included filenames, values are XmlDocuments.
859 public Dictionary
<string, XmlDocument
> StoredDocuments
= new Dictionary
<string, XmlDocument
> ();
862 // Outputs XML documentation comment from tokenized comments.
864 public bool OutputDocComment (string asmfilename
, Report Report
)
866 XmlTextWriter w
= null;
868 w
= new XmlTextWriter (docfilename
, null);
870 w
.Formatting
= Formatting
.Indented
;
871 w
.WriteStartDocument ();
872 w
.WriteStartElement ("doc");
873 w
.WriteStartElement ("assembly");
874 w
.WriteStartElement ("name");
875 w
.WriteString (Path
.ChangeExtension (asmfilename
, null));
876 w
.WriteEndElement (); // name
877 w
.WriteEndElement (); // assembly
878 w
.WriteStartElement ("members");
879 XmlCommentOutput
= w
;
880 GenerateDocComment (Report
);
881 w
.WriteFullEndElement (); // members
882 w
.WriteEndElement ();
883 w
.WriteWhitespace (Environment
.NewLine
);
884 w
.WriteEndDocument ();
886 } catch (Exception ex
) {
887 Report
.Error (1569, "Error generating XML documentation file `{0}' (`{1}')", docfilename
, ex
.Message
);
896 // Fixes full type name of each documented types/members up.
898 public void GenerateDocComment (Report r
)
900 TypeContainer root
= RootContext
.ToplevelTypes
;
902 if (root
.Types
!= null)
903 foreach (TypeContainer tc
in root
.Types
)
904 DocUtil
.GenerateTypeDocComment (tc
, null, r
);