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.
15
using System.Collections.Generic;
24
// Implements XML documentation generation.
26
class DocumentationBuilder
29
// Used to create element which helps well-formedness checking.
31
readonly XmlDocument XmlDocumentation;
33
readonly ModuleContainer module;
36
// The output for XML documentation.
38
XmlWriter XmlCommentOutput;
40
static readonly string line_head = Environment.NewLine + " ";
43
// Stores XmlDocuments that are included in XML documentation.
44
// Keys are included filenames, values are XmlDocuments.
46
Dictionary<string, XmlDocument> StoredDocuments = new Dictionary<string, XmlDocument> ();
48
public DocumentationBuilder (ModuleContainer module)
51
XmlDocumentation = new XmlDocument ();
52
XmlDocumentation.PreserveWhitespace = false;
57
return module.Compiler.Report;
61
public MemberName ParsedName {
65
public List<DocumentationParameter> ParsedParameters {
69
public TypeExpression ParsedBuiltinType {
73
public Operator.OpType? ParsedOperator {
77
XmlNode GetDocCommentNode (MemberCore mc, string name)
79
// FIXME: It could be even optimizable as not
80
// to use XmlDocument. But anyways the nodes
81
// are not kept in memory.
82
XmlDocument doc = XmlDocumentation;
84
XmlElement el = doc.CreateElement ("member");
85
el.SetAttribute ("name", name);
86
string normalized = mc.DocComment;
87
el.InnerXml = normalized;
88
// csc keeps lines as written in the sources
89
// and inserts formatting indentation (which
90
// is different from XmlTextWriter.Formatting
91
// one), but when a start tag contains an
92
// endline, it joins the next line. We don't
93
// have to follow such a hacky behavior.
95
normalized.Split ('\n');
97
for (int i = 0; i < split.Length; i++) {
98
string s = split [i].TrimEnd ();
102
el.InnerXml = line_head + String.Join (
103
line_head, split, 0, j);
105
} catch (Exception ex) {
106
Report.Warning (1570, 1, mc.Location, "XML documentation comment on `{0}' is not well-formed XML markup ({1})",
107
mc.GetSignatureForError (), ex.Message);
109
return doc.CreateComment (String.Format ("FIXME: Invalid documentation markup was found for member {0}", name));
114
// Generates xml doc comments (if any), and if required,
115
// handle warning report.
117
internal void GenerateDocumentationForMember (MemberCore mc)
119
string name = mc.DocCommentHeader + mc.GetSignatureForDocumentation ();
121
XmlNode n = GetDocCommentNode (mc, name);
123
XmlElement el = n as XmlElement;
125
var pm = mc as IParametersMember;
127
CheckParametersComments (mc, pm, el);
130
// FIXME: it could be done with XmlReader
131
XmlNodeList nl = n.SelectNodes (".//include");
133
// It could result in current node removal, so prepare another list to iterate.
134
var al = new List<XmlNode> (nl.Count);
135
foreach (XmlNode inc in nl)
137
foreach (XmlElement inc in al)
138
if (!HandleInclude (mc, inc))
139
inc.ParentNode.RemoveChild (inc);
142
// FIXME: it could be done with XmlReader
143
DeclSpace ds_target = mc as DeclSpace;
144
if (ds_target == null)
145
ds_target = mc.Parent;
147
foreach (XmlElement see in n.SelectNodes (".//see"))
148
HandleSee (mc, ds_target, see);
149
foreach (XmlElement seealso in n.SelectNodes (".//seealso"))
150
HandleSeeAlso (mc, ds_target, seealso);
151
foreach (XmlElement see in n.SelectNodes (".//exception"))
152
HandleException (mc, ds_target, see);
155
n.WriteTo (XmlCommentOutput);
159
// Processes "include" element. Check included file and
160
// embed the document content inside this documentation node.
162
bool HandleInclude (MemberCore mc, XmlElement el)
164
bool keep_include_node = false;
165
string file = el.GetAttribute ("file");
166
string path = el.GetAttribute ("path");
168
Report.Warning (1590, 1, mc.Location, "Invalid XML `include' element. Missing `file' attribute");
169
el.ParentNode.InsertBefore (el.OwnerDocument.CreateComment (" Include tag is invalid "), el);
170
keep_include_node = true;
172
else if (path.Length == 0) {
173
Report.Warning (1590, 1, mc.Location, "Invalid XML `include' element. Missing `path' attribute");
174
el.ParentNode.InsertBefore (el.OwnerDocument.CreateComment (" Include tag is invalid "), el);
175
keep_include_node = true;
179
if (!StoredDocuments.TryGetValue (file, out doc)) {
181
doc = new XmlDocument ();
183
StoredDocuments.Add (file, doc);
184
} catch (Exception) {
185
el.ParentNode.InsertBefore (el.OwnerDocument.CreateComment (String.Format (" Badly formed XML in at comment file `{0}': cannot be included ", file)), el);
186
Report.Warning (1592, 1, mc.Location, "Badly formed XML in included comments file -- `{0}'", file);
191
XmlNodeList nl = doc.SelectNodes (path);
193
el.ParentNode.InsertBefore (el.OwnerDocument.CreateComment (" No matching elements were found for the include tag embedded here. "), el);
195
keep_include_node = true;
197
foreach (XmlNode n in nl)
198
el.ParentNode.InsertBefore (el.OwnerDocument.ImportNode (n, true), el);
199
} catch (Exception ex) {
200
el.ParentNode.InsertBefore (el.OwnerDocument.CreateComment (" Failed to insert some or all of included XML "), el);
201
Report.Warning (1589, 1, mc.Location, "Unable to include XML fragment `{0}' of file `{1}' ({2})", path, file, ex.Message);
205
return keep_include_node;
209
// Handles <see> elements.
211
void HandleSee (MemberCore mc, DeclSpace ds, XmlElement see)
213
HandleXrefCommon (mc, ds, see);
217
// Handles <seealso> elements.
219
void HandleSeeAlso (MemberCore mc, DeclSpace ds, XmlElement seealso)
221
HandleXrefCommon (mc, ds, seealso);
225
// Handles <exception> elements.
227
void HandleException (MemberCore mc, DeclSpace ds, XmlElement seealso)
229
HandleXrefCommon (mc, ds, seealso);
232
FullNamedExpression ResolveMemberName (IMemberContext context, MemberName mn)
235
return context.LookupNamespaceOrType (mn.Name, mn.Arity, LookupMode.Probing, Location.Null);
237
var left = ResolveMemberName (context, mn.Left);
238
var ns = left as Namespace;
240
return ns.LookupTypeOrNamespace (context, mn.Name, mn.Arity, LookupMode.Probing, Location.Null);
242
TypeExpr texpr = left as TypeExpr;
244
var found = MemberCache.FindNestedType (texpr.Type, ParsedName.Name, ParsedName.Arity);
246
return new TypeExpression (found, Location.Null);
255
// Processes "see" or "seealso" elements from cref attribute.
257
void HandleXrefCommon (MemberCore mc, DeclSpace ds, XmlElement xref)
259
string cref = xref.GetAttribute ("cref");
260
// when, XmlReader, "if (cref == null)"
261
if (!xref.HasAttribute ("cref"))
264
// Nothing to be resolved the reference is marked explicitly
265
if (cref.Length > 2 && cref [1] == ':')
268
// Additional symbols for < and > are allowed for easier XML typing
269
cref = cref.Replace ('{', '<').Replace ('}', '>');
271
var encoding = module.Compiler.Settings.Encoding;
272
var s = new MemoryStream (encoding.GetBytes (cref));
273
SeekableStreamReader seekable = new SeekableStreamReader (s, encoding);
275
var source_file = new CompilationSourceFile ("{documentation}", "", 1);
276
var doc_module = new ModuleContainer (module.Compiler);
277
doc_module.DocumentationBuilder = this;
278
source_file.NamespaceContainer = new NamespaceContainer (null, doc_module, null, source_file);
280
Report parse_report = new Report (new NullReportPrinter ());
281
var parser = new CSharpParser (seekable, source_file, parse_report);
282
ParsedParameters = null;
284
ParsedBuiltinType = null;
285
ParsedOperator = null;
286
parser.Lexer.putback_char = Tokenizer.DocumentationXref;
287
parser.Lexer.parsing_generic_declaration_doc = true;
289
if (parse_report.Errors > 0) {
290
Report.Warning (1584, 1, mc.Location, "XML comment on `{0}' has syntactically incorrect cref attribute `{1}'",
291
mc.GetSignatureForError (), cref);
293
xref.SetAttribute ("cref", "!:" + cref);
298
string prefix = null;
299
FullNamedExpression fne = null;
302
// Try built-in type first because we are using ParsedName as identifier of
303
// member names on built-in types
305
if (ParsedBuiltinType != null && (ParsedParameters == null || ParsedName != null)) {
306
member = ParsedBuiltinType.Type;
311
if (ParsedName != null || ParsedOperator.HasValue) {
312
TypeSpec type = null;
313
string member_name = null;
315
if (member == null) {
316
if (ParsedOperator.HasValue) {
317
type = mc.CurrentType;
318
} else if (ParsedName.Left != null) {
319
fne = ResolveMemberName (mc, ParsedName.Left);
321
var ns = fne as Namespace;
323
fne = ns.LookupTypeOrNamespace (mc, ParsedName.Name, ParsedName.Arity, LookupMode.Probing, Location.Null);
332
fne = ResolveMemberName (mc, ParsedName);
334
type = mc.CurrentType;
335
} else if (ParsedParameters == null) {
337
} else if (fne.Type.MemberDefinition == mc.CurrentType.MemberDefinition) {
338
member_name = Constructor.ConstructorName;
343
type = (TypeSpec) member;
347
if (ParsedParameters != null) {
348
var old_printer = mc.Module.Compiler.Report.SetPrinter (new NullReportPrinter ());
349
foreach (var pp in ParsedParameters) {
352
mc.Module.Compiler.Report.SetPrinter (old_printer);
356
if (member_name == null)
357
member_name = ParsedOperator.HasValue ?
358
Operator.GetMetadataName (ParsedOperator.Value) : ParsedName.Name;
360
int parsed_param_count;
361
if (ParsedOperator == Operator.OpType.Explicit || ParsedOperator == Operator.OpType.Implicit) {
362
parsed_param_count = ParsedParameters.Count - 1;
363
} else if (ParsedParameters != null) {
364
parsed_param_count = ParsedParameters.Count;
366
parsed_param_count = 0;
369
int parameters_match = -1;
371
var members = MemberCache.FindMembers (type, member_name, true);
372
if (members != null) {
373
foreach (var m in members) {
374
if (ParsedName != null && m.Arity != ParsedName.Arity)
377
if (ParsedParameters != null) {
378
IParametersMember pm = m as IParametersMember;
382
if (m.Kind == MemberKind.Operator && !ParsedOperator.HasValue)
386
for (i = 0; i < parsed_param_count; ++i) {
387
var pparam = ParsedParameters[i];
389
if (i >= pm.Parameters.Count || pparam == null ||
390
pparam.TypeSpec != pm.Parameters.Types[i] ||
391
(pparam.Modifier & Parameter.Modifier.SignatureMask) != (pm.Parameters.FixedParameters[i].ModFlags & Parameter.Modifier.SignatureMask)) {
393
if (i > parameters_match) {
394
parameters_match = i;
405
if (ParsedOperator == Operator.OpType.Explicit || ParsedOperator == Operator.OpType.Implicit) {
406
if (pm.MemberType != ParsedParameters[parsed_param_count].TypeSpec) {
407
parameters_match = parsed_param_count + 1;
411
if (parsed_param_count != pm.Parameters.Count)
416
if (member != null) {
417
Report.Warning (419, 3, mc.Location,
418
"Ambiguous reference in cref attribute `{0}'. Assuming `{1}' but other overloads including `{2}' have also matched",
419
cref, member.GetSignatureForError (), m.GetSignatureForError ());
428
// Continue with parent type for nested types
429
if (member == null) {
430
type = type.DeclaringType;
434
} while (type != null);
436
if (member == null && parameters_match >= 0) {
437
for (int i = parameters_match; i < parsed_param_count; ++i) {
438
Report.Warning (1580, 1, mc.Location, "Invalid type for parameter `{0}' in XML comment cref attribute `{1}'",
439
(i + 1).ToString (), cref);
442
if (parameters_match == parsed_param_count + 1) {
443
Report.Warning (1581, 1, mc.Location, "Invalid return type in XML comment cref attribute `{0}'", cref);
449
if (member == null) {
450
Report.Warning (1574, 1, mc.Location, "XML comment on `{0}' has cref attribute `{1}' that could not be resolved",
451
mc.GetSignatureForError (), cref);
453
} else if (member == InternalType.Namespace) {
454
cref = "N:" + fne.GetSignatureForError ();
456
prefix = GetMemberDocHead (member);
457
cref = prefix + member.GetSignatureForDocumentation ();
460
xref.SetAttribute ("cref", cref);
464
// Get a prefix from member type for XML documentation (used
465
// to formalize cref target name).
467
static string GetMemberDocHead (MemberSpec type)
469
if (type is FieldSpec)
471
if (type is MethodSpec)
473
if (type is EventSpec)
475
if (type is PropertySpec)
477
if (type is TypeSpec)
480
throw new NotImplementedException (type.GetType ().ToString ());
484
// Raised (and passed an XmlElement that contains the comment)
485
// when GenerateDocComment is writing documentation expectedly.
487
// FIXME: with a few effort, it could be done with XmlReader,
488
// that means removal of DOM use.
490
void CheckParametersComments (MemberCore member, IParametersMember paramMember, XmlElement el)
492
HashSet<string> found_tags = null;
493
foreach (XmlElement pelem in el.SelectNodes ("param")) {
494
string xname = pelem.GetAttribute ("name");
495
if (xname.Length == 0)
496
continue; // really? but MS looks doing so
498
if (found_tags == null) {
499
found_tags = new HashSet<string> ();
502
if (xname != "" && paramMember.Parameters.GetParameterIndexByName (xname) < 0) {
503
Report.Warning (1572, 2, member.Location,
504
"XML comment on `{0}' has a param tag for `{1}', but there is no parameter by that name",
505
member.GetSignatureForError (), xname);
509
if (found_tags.Contains (xname)) {
510
Report.Warning (1571, 2, member.Location,
511
"XML comment on `{0}' has a duplicate param tag for `{1}'",
512
member.GetSignatureForError (), xname);
516
found_tags.Add (xname);
519
if (found_tags != null) {
520
foreach (Parameter p in paramMember.Parameters.FixedParameters) {
521
if (!found_tags.Contains (p.Name) && !(p is ArglistParameter))
522
Report.Warning (1573, 4, member.Location,
523
"Parameter `{0}' has no matching param tag in the XML comment for `{1}'",
524
p.Name, member.GetSignatureForError ());
530
// Outputs XML documentation comment from tokenized comments.
532
public bool OutputDocComment (string asmfilename, string xmlFileName)
534
XmlTextWriter w = null;
536
w = new XmlTextWriter (xmlFileName, null);
538
w.Formatting = Formatting.Indented;
539
w.WriteStartDocument ();
540
w.WriteStartElement ("doc");
541
w.WriteStartElement ("assembly");
542
w.WriteStartElement ("name");
543
w.WriteString (Path.GetFileNameWithoutExtension (asmfilename));
544
w.WriteEndElement (); // name
545
w.WriteEndElement (); // assembly
546
w.WriteStartElement ("members");
547
XmlCommentOutput = w;
548
module.GenerateDocComment (this);
549
w.WriteFullEndElement (); // members
550
w.WriteEndElement ();
551
w.WriteWhitespace (Environment.NewLine);
552
w.WriteEndDocument ();
554
} catch (Exception ex) {
555
Report.Error (1569, "Error generating XML documentation file `{0}' (`{1}')", xmlFileName, ex.Message);
564
class DocumentationParameter
566
public readonly Parameter.Modifier Modifier;
567
public FullNamedExpression Type;
570
public DocumentationParameter (Parameter.Modifier modifier, FullNamedExpression type)
573
this.Modifier = modifier;
576
public DocumentationParameter (FullNamedExpression type)
581
public TypeSpec TypeSpec {
587
public void Resolve (IMemberContext context)
589
type = Type.ResolveAsType (context);