2
// doc.cs: Support for XML documentation comment.
5
// Atsushi Enomoto <atsushi@ximian.com>
6
// Marek Safar (marek.safar@gmail.com>
8
// Dual licensed under the terms of the MIT X11 or GNU GPL
10
// Copyright 2004 Novell, Inc.
11
// Copyright 2011 Xamarin Inc
16
using System.Collections.Generic;
25
// Implements XML documentation generation.
27
class DocumentationBuilder
30
// Used to create element which helps well-formedness checking.
32
readonly XmlDocument XmlDocumentation;
34
readonly ModuleContainer module;
35
readonly ModuleContainer doc_module;
38
// The output for XML documentation.
40
XmlWriter XmlCommentOutput;
42
static readonly string line_head = Environment.NewLine + " ";
45
// Stores XmlDocuments that are included in XML documentation.
46
// Keys are included filenames, values are XmlDocuments.
48
Dictionary<string, XmlDocument> StoredDocuments = new Dictionary<string, XmlDocument> ();
50
public DocumentationBuilder (ModuleContainer module)
52
doc_module = new ModuleContainer (module.Compiler);
53
doc_module.DocumentationBuilder = this;
56
XmlDocumentation = new XmlDocument ();
57
XmlDocumentation.PreserveWhitespace = false;
62
return module.Compiler.Report;
66
public MemberName ParsedName {
70
public List<DocumentationParameter> ParsedParameters {
74
public TypeExpression ParsedBuiltinType {
78
public Operator.OpType? ParsedOperator {
82
XmlNode GetDocCommentNode (MemberCore mc, string name)
84
// FIXME: It could be even optimizable as not
85
// to use XmlDocument. But anyways the nodes
86
// are not kept in memory.
87
XmlDocument doc = XmlDocumentation;
89
XmlElement el = doc.CreateElement ("member");
90
el.SetAttribute ("name", name);
91
string normalized = mc.DocComment;
92
el.InnerXml = normalized;
93
// csc keeps lines as written in the sources
94
// and inserts formatting indentation (which
95
// is different from XmlTextWriter.Formatting
96
// one), but when a start tag contains an
97
// endline, it joins the next line. We don't
98
// have to follow such a hacky behavior.
100
normalized.Split ('\n');
102
for (int i = 0; i < split.Length; i++) {
103
string s = split [i].TrimEnd ();
107
el.InnerXml = line_head + String.Join (
108
line_head, split, 0, j);
110
} catch (Exception ex) {
111
Report.Warning (1570, 1, mc.Location, "XML documentation comment on `{0}' is not well-formed XML markup ({1})",
112
mc.GetSignatureForError (), ex.Message);
114
return doc.CreateComment (String.Format ("FIXME: Invalid documentation markup was found for member {0}", name));
119
// Generates xml doc comments (if any), and if required,
120
// handle warning report.
122
internal void GenerateDocumentationForMember (MemberCore mc)
124
string name = mc.DocCommentHeader + mc.GetSignatureForDocumentation ();
126
XmlNode n = GetDocCommentNode (mc, name);
128
XmlElement el = n as XmlElement;
130
var pm = mc as IParametersMember;
132
CheckParametersComments (mc, pm, el);
135
// FIXME: it could be done with XmlReader
136
XmlNodeList nl = n.SelectNodes (".//include");
138
// It could result in current node removal, so prepare another list to iterate.
139
var al = new List<XmlNode> (nl.Count);
140
foreach (XmlNode inc in nl)
142
foreach (XmlElement inc in al)
143
if (!HandleInclude (mc, inc))
144
inc.ParentNode.RemoveChild (inc);
147
// FIXME: it could be done with XmlReader
148
var ds_target = mc as TypeContainer;
149
if (ds_target == null)
150
ds_target = mc.Parent;
152
foreach (XmlElement see in n.SelectNodes (".//see"))
153
HandleSee (mc, ds_target, see);
154
foreach (XmlElement seealso in n.SelectNodes (".//seealso"))
155
HandleSeeAlso (mc, ds_target, seealso);
156
foreach (XmlElement see in n.SelectNodes (".//exception"))
157
HandleException (mc, ds_target, see);
158
foreach (XmlElement node in n.SelectNodes (".//typeparam"))
159
HandleTypeParam (mc, node);
160
foreach (XmlElement node in n.SelectNodes (".//typeparamref"))
161
HandleTypeParamRef (mc, node);
164
n.WriteTo (XmlCommentOutput);
168
// Processes "include" element. Check included file and
169
// embed the document content inside this documentation node.
171
bool HandleInclude (MemberCore mc, XmlElement el)
173
bool keep_include_node = false;
174
string file = el.GetAttribute ("file");
175
string path = el.GetAttribute ("path");
177
Report.Warning (1590, 1, mc.Location, "Invalid XML `include' element. Missing `file' attribute");
178
el.ParentNode.InsertBefore (el.OwnerDocument.CreateComment (" Include tag is invalid "), el);
179
keep_include_node = true;
181
else if (path.Length == 0) {
182
Report.Warning (1590, 1, mc.Location, "Invalid XML `include' element. Missing `path' attribute");
183
el.ParentNode.InsertBefore (el.OwnerDocument.CreateComment (" Include tag is invalid "), el);
184
keep_include_node = true;
188
if (!StoredDocuments.TryGetValue (file, out doc)) {
190
doc = new XmlDocument ();
192
StoredDocuments.Add (file, doc);
193
} catch (Exception) {
194
el.ParentNode.InsertBefore (el.OwnerDocument.CreateComment (String.Format (" Badly formed XML in at comment file `{0}': cannot be included ", file)), el);
195
Report.Warning (1592, 1, mc.Location, "Badly formed XML in included comments file -- `{0}'", file);
200
XmlNodeList nl = doc.SelectNodes (path);
202
el.ParentNode.InsertBefore (el.OwnerDocument.CreateComment (" No matching elements were found for the include tag embedded here. "), el);
204
keep_include_node = true;
206
foreach (XmlNode n in nl)
207
el.ParentNode.InsertBefore (el.OwnerDocument.ImportNode (n, true), el);
208
} catch (Exception ex) {
209
el.ParentNode.InsertBefore (el.OwnerDocument.CreateComment (" Failed to insert some or all of included XML "), el);
210
Report.Warning (1589, 1, mc.Location, "Unable to include XML fragment `{0}' of file `{1}' ({2})", path, file, ex.Message);
214
return keep_include_node;
218
// Handles <see> elements.
220
void HandleSee (MemberCore mc, TypeContainer ds, XmlElement see)
222
HandleXrefCommon (mc, ds, see);
226
// Handles <seealso> elements.
228
void HandleSeeAlso (MemberCore mc, TypeContainer ds, XmlElement seealso)
230
HandleXrefCommon (mc, ds, seealso);
234
// Handles <exception> elements.
236
void HandleException (MemberCore mc, TypeContainer ds, XmlElement seealso)
238
HandleXrefCommon (mc, ds, seealso);
242
// Handles <typeparam /> node
244
static void HandleTypeParam (MemberCore mc, XmlElement node)
246
if (!node.HasAttribute ("name"))
249
string tp_name = node.GetAttribute ("name");
250
if (mc.CurrentTypeParameters != null) {
251
if (mc.CurrentTypeParameters.Find (tp_name) != null)
255
// TODO: CS1710, CS1712
257
mc.Compiler.Report.Warning (1711, 2, mc.Location,
258
"XML comment on `{0}' has a typeparam name `{1}' but there is no type parameter by that name",
259
mc.GetSignatureForError (), tp_name);
263
// Handles <typeparamref /> node
265
static void HandleTypeParamRef (MemberCore mc, XmlElement node)
267
if (!node.HasAttribute ("name"))
270
string tp_name = node.GetAttribute ("name");
273
if (member.CurrentTypeParameters != null) {
274
if (member.CurrentTypeParameters.Find (tp_name) != null)
278
member = member.Parent;
279
} while (member != null);
281
mc.Compiler.Report.Warning (1735, 2, mc.Location,
282
"XML comment on `{0}' has a typeparamref name `{1}' that could not be resolved",
283
mc.GetSignatureForError (), tp_name);
286
FullNamedExpression ResolveMemberName (IMemberContext context, MemberName mn)
289
return context.LookupNamespaceOrType (mn.Name, mn.Arity, LookupMode.Probing, Location.Null);
291
var left = ResolveMemberName (context, mn.Left);
292
var ns = left as Namespace;
294
return ns.LookupTypeOrNamespace (context, mn.Name, mn.Arity, LookupMode.Probing, Location.Null);
296
TypeExpr texpr = left as TypeExpr;
298
var found = MemberCache.FindNestedType (texpr.Type, ParsedName.Name, ParsedName.Arity);
300
return new TypeExpression (found, Location.Null);
309
// Processes "see" or "seealso" elements from cref attribute.
311
void HandleXrefCommon (MemberCore mc, TypeContainer ds, XmlElement xref)
313
string cref = xref.GetAttribute ("cref");
314
// when, XmlReader, "if (cref == null)"
315
if (!xref.HasAttribute ("cref"))
318
// Nothing to be resolved the reference is marked explicitly
319
if (cref.Length > 2 && cref [1] == ':')
322
// Additional symbols for < and > are allowed for easier XML typing
323
cref = cref.Replace ('{', '<').Replace ('}', '>');
325
var encoding = module.Compiler.Settings.Encoding;
326
var s = new MemoryStream (encoding.GetBytes (cref));
327
SeekableStreamReader seekable = new SeekableStreamReader (s, encoding);
329
var source_file = new CompilationSourceFile (doc_module);
330
var report = new Report (doc_module.Compiler, new NullReportPrinter ());
332
var parser = new CSharpParser (seekable, source_file, report);
333
ParsedParameters = null;
335
ParsedBuiltinType = null;
336
ParsedOperator = null;
337
parser.Lexer.putback_char = Tokenizer.DocumentationXref;
338
parser.Lexer.parsing_generic_declaration_doc = true;
340
if (report.Errors > 0) {
341
Report.Warning (1584, 1, mc.Location, "XML comment on `{0}' has syntactically incorrect cref attribute `{1}'",
342
mc.GetSignatureForError (), cref);
344
xref.SetAttribute ("cref", "!:" + cref);
349
string prefix = null;
350
FullNamedExpression fne = null;
353
// Try built-in type first because we are using ParsedName as identifier of
354
// member names on built-in types
356
if (ParsedBuiltinType != null && (ParsedParameters == null || ParsedName != null)) {
357
member = ParsedBuiltinType.Type;
362
if (ParsedName != null || ParsedOperator.HasValue) {
363
TypeSpec type = null;
364
string member_name = null;
366
if (member == null) {
367
if (ParsedOperator.HasValue) {
368
type = mc.CurrentType;
369
} else if (ParsedName.Left != null) {
370
fne = ResolveMemberName (mc, ParsedName.Left);
372
var ns = fne as Namespace;
374
fne = ns.LookupTypeOrNamespace (mc, ParsedName.Name, ParsedName.Arity, LookupMode.Probing, Location.Null);
383
fne = ResolveMemberName (mc, ParsedName);
385
type = mc.CurrentType;
386
} else if (ParsedParameters == null) {
388
} else if (fne.Type.MemberDefinition == mc.CurrentType.MemberDefinition) {
389
member_name = Constructor.ConstructorName;
394
type = (TypeSpec) member;
398
if (ParsedParameters != null) {
399
var old_printer = mc.Module.Compiler.Report.SetPrinter (new NullReportPrinter ());
401
var context = new DocumentationMemberContext (mc, ParsedName ?? MemberName.Null);
403
foreach (var pp in ParsedParameters) {
404
pp.Resolve (context);
407
mc.Module.Compiler.Report.SetPrinter (old_printer);
412
if (member_name == null)
413
member_name = ParsedOperator.HasValue ?
414
Operator.GetMetadataName (ParsedOperator.Value) : ParsedName.Name;
416
int parsed_param_count;
417
if (ParsedOperator == Operator.OpType.Explicit || ParsedOperator == Operator.OpType.Implicit) {
418
parsed_param_count = ParsedParameters.Count - 1;
419
} else if (ParsedParameters != null) {
420
parsed_param_count = ParsedParameters.Count;
422
parsed_param_count = 0;
425
int parameters_match = -1;
427
var members = MemberCache.FindMembers (type, member_name, true);
428
if (members != null) {
429
foreach (var m in members) {
430
if (ParsedName != null && m.Arity != ParsedName.Arity)
433
if (ParsedParameters != null) {
434
IParametersMember pm = m as IParametersMember;
438
if (m.Kind == MemberKind.Operator && !ParsedOperator.HasValue)
441
var pm_params = pm.Parameters;
444
for (i = 0; i < parsed_param_count; ++i) {
445
var pparam = ParsedParameters[i];
447
if (i >= pm_params.Count || pparam == null || pparam.TypeSpec == null ||
448
!TypeSpecComparer.Override.IsEqual (pparam.TypeSpec, pm_params.Types[i]) ||
449
(pparam.Modifier & Parameter.Modifier.RefOutMask) != (pm_params.FixedParameters[i].ModFlags & Parameter.Modifier.RefOutMask)) {
451
if (i > parameters_match) {
452
parameters_match = i;
463
if (ParsedOperator == Operator.OpType.Explicit || ParsedOperator == Operator.OpType.Implicit) {
464
if (pm.MemberType != ParsedParameters[parsed_param_count].TypeSpec) {
465
parameters_match = parsed_param_count + 1;
469
if (parsed_param_count != pm_params.Count)
474
if (member != null) {
475
Report.Warning (419, 3, mc.Location,
476
"Ambiguous reference in cref attribute `{0}'. Assuming `{1}' but other overloads including `{2}' have also matched",
477
cref, member.GetSignatureForError (), m.GetSignatureForError ());
486
// Continue with parent type for nested types
487
if (member == null) {
488
type = type.DeclaringType;
492
} while (type != null);
494
if (member == null && parameters_match >= 0) {
495
for (int i = parameters_match; i < parsed_param_count; ++i) {
496
Report.Warning (1580, 1, mc.Location, "Invalid type for parameter `{0}' in XML comment cref attribute `{1}'",
497
(i + 1).ToString (), cref);
500
if (parameters_match == parsed_param_count + 1) {
501
Report.Warning (1581, 1, mc.Location, "Invalid return type in XML comment cref attribute `{0}'", cref);
507
if (member == null) {
508
Report.Warning (1574, 1, mc.Location, "XML comment on `{0}' has cref attribute `{1}' that could not be resolved",
509
mc.GetSignatureForError (), cref);
511
} else if (member == InternalType.Namespace) {
512
cref = "N:" + fne.GetSignatureForError ();
514
prefix = GetMemberDocHead (member);
515
cref = prefix + member.GetSignatureForDocumentation ();
518
xref.SetAttribute ("cref", cref);
522
// Get a prefix from member type for XML documentation (used
523
// to formalize cref target name).
525
static string GetMemberDocHead (MemberSpec type)
527
if (type is FieldSpec)
529
if (type is MethodSpec)
531
if (type is EventSpec)
533
if (type is PropertySpec)
535
if (type is TypeSpec)
538
throw new NotImplementedException (type.GetType ().ToString ());
542
// Raised (and passed an XmlElement that contains the comment)
543
// when GenerateDocComment is writing documentation expectedly.
545
// FIXME: with a few effort, it could be done with XmlReader,
546
// that means removal of DOM use.
548
void CheckParametersComments (MemberCore member, IParametersMember paramMember, XmlElement el)
550
HashSet<string> found_tags = null;
551
foreach (XmlElement pelem in el.SelectNodes ("param")) {
552
string xname = pelem.GetAttribute ("name");
553
if (xname.Length == 0)
554
continue; // really? but MS looks doing so
556
if (found_tags == null) {
557
found_tags = new HashSet<string> ();
560
if (xname != "" && paramMember.Parameters.GetParameterIndexByName (xname) < 0) {
561
Report.Warning (1572, 2, member.Location,
562
"XML comment on `{0}' has a param tag for `{1}', but there is no parameter by that name",
563
member.GetSignatureForError (), xname);
567
if (found_tags.Contains (xname)) {
568
Report.Warning (1571, 2, member.Location,
569
"XML comment on `{0}' has a duplicate param tag for `{1}'",
570
member.GetSignatureForError (), xname);
574
found_tags.Add (xname);
577
if (found_tags != null) {
578
foreach (Parameter p in paramMember.Parameters.FixedParameters) {
579
if (!found_tags.Contains (p.Name) && !(p is ArglistParameter))
580
Report.Warning (1573, 4, member.Location,
581
"Parameter `{0}' has no matching param tag in the XML comment for `{1}'",
582
p.Name, member.GetSignatureForError ());
588
// Outputs XML documentation comment from tokenized comments.
590
public bool OutputDocComment (string asmfilename, string xmlFileName)
592
XmlTextWriter w = null;
594
w = new XmlTextWriter (xmlFileName, null);
596
w.Formatting = Formatting.Indented;
597
w.WriteStartDocument ();
598
w.WriteStartElement ("doc");
599
w.WriteStartElement ("assembly");
600
w.WriteStartElement ("name");
601
w.WriteString (Path.GetFileNameWithoutExtension (asmfilename));
602
w.WriteEndElement (); // name
603
w.WriteEndElement (); // assembly
604
w.WriteStartElement ("members");
605
XmlCommentOutput = w;
606
module.GenerateDocComment (this);
607
w.WriteFullEndElement (); // members
608
w.WriteEndElement ();
609
w.WriteWhitespace (Environment.NewLine);
610
w.WriteEndDocument ();
612
} catch (Exception ex) {
613
Report.Error (1569, "Error generating XML documentation file `{0}' (`{1}')", xmlFileName, ex.Message);
623
// Type lookup of documentation references uses context of type where
624
// the reference is used but type parameters from cref value
626
sealed class DocumentationMemberContext : IMemberContext
628
readonly MemberCore host;
629
MemberName contextName;
631
public DocumentationMemberContext (MemberCore host, MemberName contextName)
634
this.contextName = contextName;
637
public TypeSpec CurrentType {
639
return host.CurrentType;
643
public TypeParameters CurrentTypeParameters {
645
return contextName.TypeParameters;
649
public MemberCore CurrentMemberDefinition {
651
return host.CurrentMemberDefinition;
655
public bool IsObsolete {
661
public bool IsUnsafe {
663
return host.IsStatic;
667
public bool IsStatic {
669
return host.IsStatic;
673
public ModuleContainer Module {
679
public string GetSignatureForError ()
681
return host.GetSignatureForError ();
684
public ExtensionMethodCandidates LookupExtensionMethod (TypeSpec extensionType, string name, int arity)
689
public FullNamedExpression LookupNamespaceOrType (string name, int arity, LookupMode mode, Location loc)
692
var tp = CurrentTypeParameters;
694
for (int i = 0; i < tp.Count; ++i) {
696
if (t.Name == name) {
697
t.Type.DeclaredPosition = i;
698
return new TypeParameterExpr (t, loc);
704
return host.Parent.LookupNamespaceOrType (name, arity, mode, loc);
707
public FullNamedExpression LookupNamespaceAlias (string name)
709
throw new NotImplementedException ();
713
class DocumentationParameter
715
public readonly Parameter.Modifier Modifier;
716
public FullNamedExpression Type;
719
public DocumentationParameter (Parameter.Modifier modifier, FullNamedExpression type)
722
this.Modifier = modifier;
725
public DocumentationParameter (FullNamedExpression type)
730
public TypeSpec TypeSpec {
736
public void Resolve (IMemberContext context)
738
type = Type.ResolveAsType (context);