mcs/error recovery: handle errors caused by closing braces after a statement expressi...
[mono.git] / mcs / mcs / doc.cs
1 //
2 // doc.cs: Support for XML documentation comment.
3 //
4 // Authors:
5 //      Atsushi Enomoto <atsushi@ximian.com>
6 //  Marek Safar (marek.safar@gmail.com>
7 //
8 // Dual licensed under the terms of the MIT X11 or GNU GPL
9 //
10 // Copyright 2004 Novell, Inc.
11 // Copyright 2011 Xamarin Inc
12 //
13 //
14
15 using System;
16 using System.Collections.Generic;
17 using System.IO;
18 using System.Text;
19 using System.Xml;
20 using System.Linq;
21
22 namespace Mono.CSharp
23 {
24         //
25         // Implements XML documentation generation.
26         //
27         class DocumentationBuilder
28         {
29                 //
30                 // Used to create element which helps well-formedness checking.
31                 //
32                 readonly XmlDocument XmlDocumentation;
33
34                 readonly ModuleContainer module;
35
36                 //
37                 // The output for XML documentation.
38                 //
39                 XmlWriter XmlCommentOutput;
40
41                 static readonly string line_head = Environment.NewLine + "            ";
42
43                 //
44                 // Stores XmlDocuments that are included in XML documentation.
45                 // Keys are included filenames, values are XmlDocuments.
46                 //
47                 Dictionary<string, XmlDocument> StoredDocuments = new Dictionary<string, XmlDocument> ();
48
49                 public DocumentationBuilder (ModuleContainer module)
50                 {
51                         this.module = module;
52                         XmlDocumentation = new XmlDocument ();
53                         XmlDocumentation.PreserveWhitespace = false;
54                 }
55
56                 Report Report {
57                         get {
58                                 return module.Compiler.Report;
59                         }
60                 }
61
62                 public MemberName ParsedName {
63                         get; set;
64                 }
65
66                 public List<DocumentationParameter> ParsedParameters {
67                         get; set;
68                 }
69
70                 public TypeExpression ParsedBuiltinType {
71                         get; set;
72                 }
73
74                 public Operator.OpType? ParsedOperator {
75                         get; set;
76                 }
77
78                 XmlNode GetDocCommentNode (MemberCore mc, string name)
79                 {
80                         // FIXME: It could be even optimizable as not
81                         // to use XmlDocument. But anyways the nodes
82                         // are not kept in memory.
83                         XmlDocument doc = XmlDocumentation;
84                         try {
85                                 XmlElement el = doc.CreateElement ("member");
86                                 el.SetAttribute ("name", name);
87                                 string normalized = mc.DocComment;
88                                 el.InnerXml = normalized;
89                                 // csc keeps lines as written in the sources
90                                 // and inserts formatting indentation (which 
91                                 // is different from XmlTextWriter.Formatting
92                                 // one), but when a start tag contains an 
93                                 // endline, it joins the next line. We don't
94                                 // have to follow such a hacky behavior.
95                                 string [] split =
96                                         normalized.Split ('\n');
97                                 int j = 0;
98                                 for (int i = 0; i < split.Length; i++) {
99                                         string s = split [i].TrimEnd ();
100                                         if (s.Length > 0)
101                                                 split [j++] = s;
102                                 }
103                                 el.InnerXml = line_head + String.Join (
104                                         line_head, split, 0, j);
105                                 return el;
106                         } catch (Exception ex) {
107                                 Report.Warning (1570, 1, mc.Location, "XML documentation comment on `{0}' is not well-formed XML markup ({1})",
108                                         mc.GetSignatureForError (), ex.Message);
109
110                                 return doc.CreateComment (String.Format ("FIXME: Invalid documentation markup was found for member {0}", name));
111                         }
112                 }
113
114                 //
115                 // Generates xml doc comments (if any), and if required,
116                 // handle warning report.
117                 //
118                 internal void GenerateDocumentationForMember (MemberCore mc)
119                 {
120                         string name = mc.DocCommentHeader + mc.GetSignatureForDocumentation ();
121
122                         XmlNode n = GetDocCommentNode (mc, name);
123
124                         XmlElement el = n as XmlElement;
125                         if (el != null) {
126                                 var pm = mc as IParametersMember;
127                                 if (pm != null) {
128                                         CheckParametersComments (mc, pm, el);
129                                 }
130
131                                 // FIXME: it could be done with XmlReader
132                                 XmlNodeList nl = n.SelectNodes (".//include");
133                                 if (nl.Count > 0) {
134                                         // It could result in current node removal, so prepare another list to iterate.
135                                         var al = new List<XmlNode> (nl.Count);
136                                         foreach (XmlNode inc in nl)
137                                                 al.Add (inc);
138                                         foreach (XmlElement inc in al)
139                                                 if (!HandleInclude (mc, inc))
140                                                         inc.ParentNode.RemoveChild (inc);
141                                 }
142
143                                 // FIXME: it could be done with XmlReader
144                                 var ds_target = mc as TypeContainer;
145                                 if (ds_target == null)
146                                         ds_target = mc.Parent;
147
148                                 foreach (XmlElement see in n.SelectNodes (".//see"))
149                                         HandleSee (mc, ds_target, see);
150                                 foreach (XmlElement seealso in n.SelectNodes (".//seealso"))
151                                         HandleSeeAlso (mc, ds_target, seealso);
152                                 foreach (XmlElement see in n.SelectNodes (".//exception"))
153                                         HandleException (mc, ds_target, see);
154                                 foreach (XmlElement node in n.SelectNodes (".//typeparam"))
155                                         HandleTypeParam (mc, node);
156                                 foreach (XmlElement node in n.SelectNodes (".//typeparamref"))
157                                         HandleTypeParamRef (mc, node);
158                         }
159
160                         n.WriteTo (XmlCommentOutput);
161                 }
162
163                 //
164                 // Processes "include" element. Check included file and
165                 // embed the document content inside this documentation node.
166                 //
167                 bool HandleInclude (MemberCore mc, XmlElement el)
168                 {
169                         bool keep_include_node = false;
170                         string file = el.GetAttribute ("file");
171                         string path = el.GetAttribute ("path");
172                         if (file == "") {
173                                 Report.Warning (1590, 1, mc.Location, "Invalid XML `include' element. Missing `file' attribute");
174                                 el.ParentNode.InsertBefore (el.OwnerDocument.CreateComment (" Include tag is invalid "), el);
175                                 keep_include_node = true;
176                         }
177                         else if (path.Length == 0) {
178                                 Report.Warning (1590, 1, mc.Location, "Invalid XML `include' element. Missing `path' attribute");
179                                 el.ParentNode.InsertBefore (el.OwnerDocument.CreateComment (" Include tag is invalid "), el);
180                                 keep_include_node = true;
181                         }
182                         else {
183                                 XmlDocument doc;
184                                 if (!StoredDocuments.TryGetValue (file, out doc)) {
185                                         try {
186                                                 doc = new XmlDocument ();
187                                                 doc.Load (file);
188                                                 StoredDocuments.Add (file, doc);
189                                         } catch (Exception) {
190                                                 el.ParentNode.InsertBefore (el.OwnerDocument.CreateComment (String.Format (" Badly formed XML in at comment file `{0}': cannot be included ", file)), el);
191                                                 Report.Warning (1592, 1, mc.Location, "Badly formed XML in included comments file -- `{0}'", file);
192                                         }
193                                 }
194                                 if (doc != null) {
195                                         try {
196                                                 XmlNodeList nl = doc.SelectNodes (path);
197                                                 if (nl.Count == 0) {
198                                                         el.ParentNode.InsertBefore (el.OwnerDocument.CreateComment (" No matching elements were found for the include tag embedded here. "), el);
199                                         
200                                                         keep_include_node = true;
201                                                 }
202                                                 foreach (XmlNode n in nl)
203                                                         el.ParentNode.InsertBefore (el.OwnerDocument.ImportNode (n, true), el);
204                                         } catch (Exception ex) {
205                                                 el.ParentNode.InsertBefore (el.OwnerDocument.CreateComment (" Failed to insert some or all of included XML "), el);
206                                                 Report.Warning (1589, 1, mc.Location, "Unable to include XML fragment `{0}' of file `{1}' ({2})", path, file, ex.Message);
207                                         }
208                                 }
209                         }
210                         return keep_include_node;
211                 }
212
213                 //
214                 // Handles <see> elements.
215                 //
216                 void HandleSee (MemberCore mc, TypeContainer ds, XmlElement see)
217                 {
218                         HandleXrefCommon (mc, ds, see);
219                 }
220
221                 //
222                 // Handles <seealso> elements.
223                 //
224                 void HandleSeeAlso (MemberCore mc, TypeContainer ds, XmlElement seealso)
225                 {
226                         HandleXrefCommon (mc, ds, seealso);
227                 }
228
229                 //
230                 // Handles <exception> elements.
231                 //
232                 void HandleException (MemberCore mc, TypeContainer ds, XmlElement seealso)
233                 {
234                         HandleXrefCommon (mc, ds, seealso);
235                 }
236
237                 //
238                 // Handles <typeparam /> node
239                 //
240                 void HandleTypeParam (MemberCore mc, XmlElement node)
241                 {
242                         if (!node.HasAttribute ("name"))
243                                 return;
244
245                         string tp_name = node.GetAttribute ("name");
246                         if (mc.CurrentTypeParameters != null) {
247                                 if (mc.CurrentTypeParameters.Find (tp_name) != null)
248                                         return;
249                         }
250                         
251                         // TODO: CS1710, CS1712
252                         
253                         mc.Compiler.Report.Warning (1711, 2, mc.Location,
254                                 "XML comment on `{0}' has a typeparam name `{1}' but there is no type parameter by that name",
255                                 mc.GetSignatureForError (), tp_name);
256                 }
257
258                 //
259                 // Handles <typeparamref /> node
260                 //
261                 void HandleTypeParamRef (MemberCore mc, XmlElement node)
262                 {
263                         if (!node.HasAttribute ("name"))
264                                 return;
265
266                         string tp_name = node.GetAttribute ("name");
267                         var member = mc;
268                         do {
269                                 if (member.CurrentTypeParameters != null) {
270                                         if (member.CurrentTypeParameters.Find (tp_name) != null)
271                                                 return;
272                                 }
273
274                                 member = member.Parent;
275                         } while (member != null);
276
277                         mc.Compiler.Report.Warning (1735, 2, mc.Location,
278                                 "XML comment on `{0}' has a typeparamref name `{1}' that could not be resolved",
279                                 mc.GetSignatureForError (), tp_name);
280                 }
281
282                 FullNamedExpression ResolveMemberName (IMemberContext context, MemberName mn)
283                 {
284                         if (mn.Left == null)
285                                 return context.LookupNamespaceOrType (mn.Name, mn.Arity, LookupMode.Probing, Location.Null);
286
287                         var left = ResolveMemberName (context, mn.Left);
288                         var ns = left as Namespace;
289                         if (ns != null)
290                                 return ns.LookupTypeOrNamespace (context, mn.Name, mn.Arity, LookupMode.Probing, Location.Null);
291
292                         TypeExpr texpr = left as TypeExpr;
293                         if (texpr != null) {
294                                 var found = MemberCache.FindNestedType (texpr.Type, ParsedName.Name, ParsedName.Arity);
295                                 if (found != null)
296                                         return new TypeExpression (found, Location.Null);
297
298                                 return null;
299                         }
300
301                         return left;
302                 }
303
304                 //
305                 // Processes "see" or "seealso" elements from cref attribute.
306                 //
307                 void HandleXrefCommon (MemberCore mc, TypeContainer ds, XmlElement xref)
308                 {
309                         string cref = xref.GetAttribute ("cref");
310                         // when, XmlReader, "if (cref == null)"
311                         if (!xref.HasAttribute ("cref"))
312                                 return;
313
314                         // Nothing to be resolved the reference is marked explicitly
315                         if (cref.Length > 2 && cref [1] == ':')
316                                 return;
317
318                         // Additional symbols for < and > are allowed for easier XML typing
319                         cref = cref.Replace ('{', '<').Replace ('}', '>');
320
321                         var encoding = module.Compiler.Settings.Encoding;
322                         var s = new MemoryStream (encoding.GetBytes (cref));
323                         SeekableStreamReader seekable = new SeekableStreamReader (s, encoding);
324
325                         var source_file = new CompilationSourceFile ("{documentation}", "", 1);
326                         var doc_module = new ModuleContainer (module.Compiler);
327                         doc_module.DocumentationBuilder = this;
328                         source_file.NamespaceContainer = new NamespaceContainer (null, doc_module, null, source_file);
329
330                         Report parse_report = new Report (new NullReportPrinter ());
331                         var parser = new CSharpParser (seekable, source_file, parse_report);
332                         ParsedParameters = null;
333                         ParsedName = null;
334                         ParsedBuiltinType = null;
335                         ParsedOperator = null;
336                         parser.Lexer.putback_char = Tokenizer.DocumentationXref;
337                         parser.Lexer.parsing_generic_declaration_doc = true;
338                         parser.parse ();
339                         if (parse_report.Errors > 0) {
340                                 Report.Warning (1584, 1, mc.Location, "XML comment on `{0}' has syntactically incorrect cref attribute `{1}'",
341                                         mc.GetSignatureForError (), cref);
342
343                                 xref.SetAttribute ("cref", "!:" + cref);
344                                 return;
345                         }
346
347                         MemberSpec member;
348                         string prefix = null;
349                         FullNamedExpression fne = null;
350
351                         //
352                         // Try built-in type first because we are using ParsedName as identifier of
353                         // member names on built-in types
354                         //
355                         if (ParsedBuiltinType != null && (ParsedParameters == null || ParsedName != null)) {
356                                 member = ParsedBuiltinType.Type;
357                         } else {
358                                 member = null;
359                         }
360
361                         if (ParsedName != null || ParsedOperator.HasValue) {
362                                 TypeSpec type = null;
363                                 string member_name = null;
364
365                                 if (member == null) {
366                                         if (ParsedOperator.HasValue) {
367                                                 type = mc.CurrentType;
368                                         } else if (ParsedName.Left != null) {
369                                                 fne = ResolveMemberName (mc, ParsedName.Left);
370                                                 if (fne != null) {
371                                                         var ns = fne as Namespace;
372                                                         if (ns != null) {
373                                                                 fne = ns.LookupTypeOrNamespace (mc, ParsedName.Name, ParsedName.Arity, LookupMode.Probing, Location.Null);
374                                                                 if (fne != null) {
375                                                                         member = fne.Type;
376                                                                 }
377                                                         } else {
378                                                                 type = fne.Type;
379                                                         }
380                                                 }
381                                         } else {
382                                                 fne = ResolveMemberName (mc, ParsedName);
383                                                 if (fne == null) {
384                                                         type = mc.CurrentType;
385                                                 } else if (ParsedParameters == null) {
386                                                         member = fne.Type;
387                                                 } else if (fne.Type.MemberDefinition == mc.CurrentType.MemberDefinition) {
388                                                         member_name = Constructor.ConstructorName;
389                                                         type = fne.Type;
390                                                 }
391                                         }
392                                 } else {
393                                         type = (TypeSpec) member;
394                                         member = null;
395                                 }
396
397                                 if (ParsedParameters != null) {
398                                         var old_printer = mc.Module.Compiler.Report.SetPrinter (new NullReportPrinter ());
399                                         foreach (var pp in ParsedParameters) {
400                                                 pp.Resolve (mc);
401                                         }
402                                         mc.Module.Compiler.Report.SetPrinter (old_printer);
403                                 }
404
405                                 if (type != null) {
406                                         if (member_name == null)
407                                                 member_name = ParsedOperator.HasValue ?
408                                                         Operator.GetMetadataName (ParsedOperator.Value) : ParsedName.Name;
409
410                                         int parsed_param_count;
411                                         if (ParsedOperator == Operator.OpType.Explicit || ParsedOperator == Operator.OpType.Implicit) {
412                                                 parsed_param_count = ParsedParameters.Count - 1;
413                                         } else if (ParsedParameters != null) {
414                                                 parsed_param_count = ParsedParameters.Count;
415                                         } else {
416                                                 parsed_param_count = 0;
417                                         }
418
419                                         int parameters_match = -1;
420                                         do {
421                                                 var members = MemberCache.FindMembers (type, member_name, true);
422                                                 if (members != null) {
423                                                         foreach (var m in members) {
424                                                                 if (ParsedName != null && m.Arity != ParsedName.Arity)
425                                                                         continue;
426
427                                                                 if (ParsedParameters != null) {
428                                                                         IParametersMember pm = m as IParametersMember;
429                                                                         if (pm == null)
430                                                                                 continue;
431
432                                                                         if (m.Kind == MemberKind.Operator && !ParsedOperator.HasValue)
433                                                                                 continue;
434
435                                                                         int i;
436                                                                         for (i = 0; i < parsed_param_count; ++i) {
437                                                                                 var pparam = ParsedParameters[i];
438
439                                                                                 if (i >= pm.Parameters.Count || pparam == null ||
440                                                                                         pparam.TypeSpec != pm.Parameters.Types[i] ||
441                                                                                         (pparam.Modifier & Parameter.Modifier.SignatureMask) != (pm.Parameters.FixedParameters[i].ModFlags & Parameter.Modifier.SignatureMask)) {
442
443                                                                                         if (i > parameters_match) {
444                                                                                                 parameters_match = i;
445                                                                                         }
446
447                                                                                         i = -1;
448                                                                                         break;
449                                                                                 }
450                                                                         }
451
452                                                                         if (i < 0)
453                                                                                 continue;
454
455                                                                         if (ParsedOperator == Operator.OpType.Explicit || ParsedOperator == Operator.OpType.Implicit) {
456                                                                                 if (pm.MemberType != ParsedParameters[parsed_param_count].TypeSpec) {
457                                                                                         parameters_match = parsed_param_count + 1;
458                                                                                         continue;
459                                                                                 }
460                                                                         } else {
461                                                                                 if (parsed_param_count != pm.Parameters.Count)
462                                                                                         continue;
463                                                                         }
464                                                                 }
465
466                                                                 if (member != null) {
467                                                                         Report.Warning (419, 3, mc.Location,
468                                                                                 "Ambiguous reference in cref attribute `{0}'. Assuming `{1}' but other overloads including `{2}' have also matched",
469                                                                                 cref, member.GetSignatureForError (), m.GetSignatureForError ());
470
471                                                                         break;
472                                                                 }
473
474                                                                 member = m;
475                                                         }
476                                                 }
477
478                                                 // Continue with parent type for nested types
479                                                 if (member == null) {
480                                                         type = type.DeclaringType;
481                                                 } else {
482                                                         type = null;
483                                                 }
484                                         } while (type != null);
485
486                                         if (member == null && parameters_match >= 0) {
487                                                 for (int i = parameters_match; i < parsed_param_count; ++i) {
488                                                         Report.Warning (1580, 1, mc.Location, "Invalid type for parameter `{0}' in XML comment cref attribute `{1}'",
489                                                                         (i + 1).ToString (), cref);
490                                                 }
491
492                                                 if (parameters_match == parsed_param_count + 1) {
493                                                         Report.Warning (1581, 1, mc.Location, "Invalid return type in XML comment cref attribute `{0}'", cref);
494                                                 }
495                                         }
496                                 }
497                         }
498
499                         if (member == null) {
500                                 Report.Warning (1574, 1, mc.Location, "XML comment on `{0}' has cref attribute `{1}' that could not be resolved",
501                                         mc.GetSignatureForError (), cref);
502                                 cref = "!:" + cref;
503                         } else if (member == InternalType.Namespace) {
504                                 cref = "N:" + fne.GetSignatureForError ();
505                         } else {
506                                 prefix = GetMemberDocHead (member);
507                                 cref = prefix + member.GetSignatureForDocumentation ();
508                         }
509
510                         xref.SetAttribute ("cref", cref);
511                 }
512
513                 //
514                 // Get a prefix from member type for XML documentation (used
515                 // to formalize cref target name).
516                 //
517                 static string GetMemberDocHead (MemberSpec type)
518                 {
519                         if (type is FieldSpec)
520                                 return "F:";
521                         if (type is MethodSpec)
522                                 return "M:";
523                         if (type is EventSpec)
524                                 return "E:";
525                         if (type is PropertySpec)
526                                 return "P:";
527                         if (type is TypeSpec)
528                                 return "T:";
529
530                         throw new NotImplementedException (type.GetType ().ToString ());
531                 }
532
533                 //
534                 // Raised (and passed an XmlElement that contains the comment)
535                 // when GenerateDocComment is writing documentation expectedly.
536                 //
537                 // FIXME: with a few effort, it could be done with XmlReader,
538                 // that means removal of DOM use.
539                 //
540                 void CheckParametersComments (MemberCore member, IParametersMember paramMember, XmlElement el)
541                 {
542                         HashSet<string> found_tags = null;
543                         foreach (XmlElement pelem in el.SelectNodes ("param")) {
544                                 string xname = pelem.GetAttribute ("name");
545                                 if (xname.Length == 0)
546                                         continue; // really? but MS looks doing so
547
548                                 if (found_tags == null) {
549                                         found_tags = new HashSet<string> ();
550                                 }
551
552                                 if (xname != "" && paramMember.Parameters.GetParameterIndexByName (xname) < 0) {
553                                         Report.Warning (1572, 2, member.Location,
554                                                 "XML comment on `{0}' has a param tag for `{1}', but there is no parameter by that name",
555                                                 member.GetSignatureForError (), xname);
556                                         continue;
557                                 }
558
559                                 if (found_tags.Contains (xname)) {
560                                         Report.Warning (1571, 2, member.Location,
561                                                 "XML comment on `{0}' has a duplicate param tag for `{1}'",
562                                                 member.GetSignatureForError (), xname);
563                                         continue;
564                                 }
565
566                                 found_tags.Add (xname);
567                         }
568
569                         if (found_tags != null) {
570                                 foreach (Parameter p in paramMember.Parameters.FixedParameters) {
571                                         if (!found_tags.Contains (p.Name) && !(p is ArglistParameter))
572                                                 Report.Warning (1573, 4, member.Location,
573                                                         "Parameter `{0}' has no matching param tag in the XML comment for `{1}'",
574                                                         p.Name, member.GetSignatureForError ());
575                                 }
576                         }
577                 }
578
579                 //
580                 // Outputs XML documentation comment from tokenized comments.
581                 //
582                 public bool OutputDocComment (string asmfilename, string xmlFileName)
583                 {
584                         XmlTextWriter w = null;
585                         try {
586                                 w = new XmlTextWriter (xmlFileName, null);
587                                 w.Indentation = 4;
588                                 w.Formatting = Formatting.Indented;
589                                 w.WriteStartDocument ();
590                                 w.WriteStartElement ("doc");
591                                 w.WriteStartElement ("assembly");
592                                 w.WriteStartElement ("name");
593                                 w.WriteString (Path.GetFileNameWithoutExtension (asmfilename));
594                                 w.WriteEndElement (); // name
595                                 w.WriteEndElement (); // assembly
596                                 w.WriteStartElement ("members");
597                                 XmlCommentOutput = w;
598                                 module.GenerateDocComment (this);
599                                 w.WriteFullEndElement (); // members
600                                 w.WriteEndElement ();
601                                 w.WriteWhitespace (Environment.NewLine);
602                                 w.WriteEndDocument ();
603                                 return true;
604                         } catch (Exception ex) {
605                                 Report.Error (1569, "Error generating XML documentation file `{0}' (`{1}')", xmlFileName, ex.Message);
606                                 return false;
607                         } finally {
608                                 if (w != null)
609                                         w.Close ();
610                         }
611                 }
612         }
613
614         class DocumentationParameter
615         {
616                 public readonly Parameter.Modifier Modifier;
617                 public FullNamedExpression Type;
618                 TypeSpec type;
619
620                 public DocumentationParameter (Parameter.Modifier modifier, FullNamedExpression type)
621                         : this (type)
622                 {
623                         this.Modifier = modifier;
624                 }
625
626                 public DocumentationParameter (FullNamedExpression type)
627                 {
628                         this.Type = type;
629                 }
630
631                 public TypeSpec TypeSpec {
632                         get {
633                                 return type;
634                         }
635                 }
636
637                 public void Resolve (IMemberContext context)
638                 {
639                         type = Type.ResolveAsType (context);
640                 }
641         }
642 }