[monodoc] Fix ecma traversal with unattached tree.
[mono.git] / mcs / class / monodoc / Monodoc / providers / EcmaDoc.cs
1 using System;
2 using System.Linq;
3 using System.IO;
4 using System.Text;
5 using System.Xml;
6 using System.Xml.Linq;
7 using System.Collections.Generic;
8
9 using Monodoc.Ecma;
10
11 namespace Monodoc.Providers
12 {
13         public enum EcmaNodeType {
14                 Invalid,
15                 Namespace,
16                 Type,
17                 Member,
18                 Meta, // A node that's here to serve as a header for other node
19         }
20
21         // Common functionality between ecma-provider and ecmauncompiled-provider
22         internal class EcmaDoc
23         {
24                 static EcmaUrlParser parser = new EcmaUrlParser ();
25
26                 public static void PopulateTreeFromIndexFile (string indexFilePath,
27                                                               string idPrefix,
28                                                               Tree tree,
29                                                               IDocStorage storage,
30                                                               Dictionary<string, XElement> nsSummaries,
31                                                               Func<XElement, string> indexGenerator = null)
32                 {
33                         var root = tree.RootNode;
34                         int resID = 0;
35                         var asm = Path.GetDirectoryName (indexFilePath);
36
37                         storage = storage ?? new Storage.NullStorage ();
38                         // nsSummaries is allowed to be null if the user doesn't care about it
39                         nsSummaries = nsSummaries ?? new Dictionary<string, XElement> ();
40                         // default index generator uses a counter
41                         indexGenerator = indexGenerator ?? (_ => resID++.ToString ());
42
43                         using (var reader = XmlReader.Create (File.OpenRead (indexFilePath))) {
44                                 reader.ReadToFollowing ("Types");
45                                 var types = XElement.Load (reader.ReadSubtree ());
46
47                                 foreach (var ns in types.Elements ("Namespace")) {
48                                         var nsName = (string)ns.Attribute ("Name");
49                                         nsName = !string.IsNullOrEmpty (nsName) ? nsName : "global";
50                                         var nsNode = root.GetOrCreateNode (nsName, "N:" + nsName);
51
52                                         XElement nsElements;
53                                         if (!nsSummaries.TryGetValue (nsName, out nsElements))
54                                                 nsSummaries[nsName] = nsElements = new XElement ("elements",
55                                                                                                  new XElement ("summary"),
56                                                                                                  new XElement ("remarks"));
57
58                                         foreach (var type in ns.Elements ("Type")) {
59                                                 // Add the XML file corresponding to the type to our storage
60                                                 var id = indexGenerator (type);
61                                                 string typeFilePath;
62                                                 var typeDocument = EcmaDoc.LoadTypeDocument (asm, nsName, type.Attribute ("Name").Value, out typeFilePath);
63                                                 if (typeDocument == null)
64                                                         continue;
65                                                 using (var file = File.OpenRead (typeFilePath))
66                                                         storage.Store (id, file);
67                                                 nsElements.Add (ExtractClassSummary (typeFilePath));
68
69                                                 var typeCaption = EcmaDoc.GetTypeCaptionFromIndex (type);
70                                                 var url = idPrefix + id + '#' + typeCaption + '/';
71                                                 typeCaption = EcmaDoc.GetTypeCaptionFromIndex (type, true);
72                                                 var typeNode = nsNode.CreateNode (typeCaption, url);
73
74                                                 // Add meta "Members" node
75                                                 typeNode.CreateNode ("Members", "*");
76                                                 var membersNode = typeDocument.Root.Element ("Members");
77                                                 if (membersNode == null || !membersNode.Elements ().Any ())
78                                                         continue;
79                                                 var members = membersNode
80                                                         .Elements ("Member")
81                                                         .ToLookup (EcmaDoc.GetMemberType);
82
83                                                 foreach (var memberType in members) {
84                                                         // We pluralize the member type to get the caption and take the first letter as URL
85                                                         var node = typeNode.CreateNode (EcmaDoc.PluralizeMemberType (memberType.Key), memberType.Key[0].ToString ());
86                                                         var memberIndex = 0;
87
88                                                         var isCtors = memberType.Key[0] == 'C';
89
90                                                         // We do not escape much member name here
91                                                         foreach (var memberGroup in memberType.GroupBy (m => MakeMemberCaption (m, isCtors))) {
92                                                                 if (memberGroup.Count () > 1) {
93                                                                         // Generate overload
94                                                                         var overloadCaption = MakeMemberCaption (memberGroup.First (), false);
95                                                                         var overloadNode = node.CreateNode (overloadCaption, overloadCaption);
96                                                                         foreach (var member in memberGroup)
97                                                                                 overloadNode.CreateNode (MakeMemberCaption (member, true), (memberIndex++).ToString ());
98                                                                         overloadNode.Sort ();
99                                                                 } else {
100                                                                         // We treat constructor differently by showing their argument list in all cases
101                                                                         node.CreateNode (MakeMemberCaption (memberGroup.First (), isCtors), (memberIndex++).ToString ());
102                                                                 }
103                                                         }
104                                                         node.Sort ();
105                                                 }
106                                         }
107
108                                         nsNode.Sort ();
109                                 }
110                                 root.Sort ();
111                         }
112                 }
113
114                 // Utility methods
115
116                 public static XDocument LoadTypeDocument (string basePath, string nsName, string typeName)
117                 {
118                         string dummy;
119                         return LoadTypeDocument (basePath, nsName, typeName, out dummy);
120                 }
121
122                 public static XDocument LoadTypeDocument (string basePath, string nsName, string typeName, out string finalPath)
123                 {
124                         finalPath = Path.Combine (basePath, nsName, Path.ChangeExtension (typeName, ".xml"));
125                         if (!File.Exists (finalPath)) {
126                                 Console.Error.WriteLine ("Warning: couldn't process type file `{0}' as it doesn't exist", finalPath);
127                                 return null;
128                         }
129
130                         XDocument doc = null;
131                         try {
132                                 doc = XDocument.Load (finalPath);
133                         } catch (Exception e) {
134                                 Console.WriteLine ("Document `{0}' is unparsable, {1}", finalPath, e.ToString ());
135                         }
136
137                         return doc;
138                 }
139
140                 public static string GetTypeCaptionFromIndex (XElement typeNodeFromIndex, bool full = false)
141                 {
142                         var t = typeNodeFromIndex;
143                         var c = ((string)(t.Attribute ("DisplayName") ?? t.Attribute ("Name"))).Replace ('+', '.');
144                         if (full)
145                                 c += " " + (string)t.Attribute ("Kind");
146                         return c;
147                 }
148
149                 public static string PluralizeMemberType (string memberType)
150                 {
151                         switch (memberType) {
152                         case "Property":
153                                 return "Properties";
154                         default:
155                                 return memberType + "s";
156                         }
157                 }
158
159                 public static string GetMemberType (XElement m)
160                 {
161                         return m.Attribute ("MemberName").Value.StartsWith ("op_") ? "Operator" : m.Element ("MemberType").Value;
162                 }
163
164                 public static string MakeMemberCaption (XElement member, bool withArguments)
165                 {
166                         var caption = (string)member.Attribute ("MemberName");
167                         // Use type name instead of .ctor for cosmetic sake
168                         if (caption == ".ctor") {
169                                 caption = (string)member.Ancestors ("Type").First ().Attribute ("Name");
170                                 // If this is an inner type ctor, strip the parent type reference
171                                 var plusIndex = caption.LastIndexOf ('+');
172                                 if (plusIndex != -1)
173                                         caption = caption.Substring (plusIndex + 1);
174                         }
175                         if (caption.StartsWith ("op_")) {
176                                 string sig;
177                                 caption = MakeOperatorSignature (member, out sig);
178                                 caption = withArguments ? sig : caption;
179                                 return caption;
180                         }
181                         if (withArguments) {
182                                 var args = member.Element ("Parameters");
183                                 caption += '(';
184                                 if (args != null && args.Elements ("Parameter").Any ()) {
185                                         caption += args.Elements ("Parameter")
186                                                 .Select (p => (string)p.Attribute ("Type"))
187                                                 .Aggregate ((p1, p2) => p1 + "," + p2);
188                                 }
189                                 caption += ')';
190                         }
191                         
192                         return caption;
193                 }
194
195                 public static Node MatchNodeWithEcmaUrl (string url, Tree tree)
196                 {
197                         Node result = null;
198                         EcmaDesc desc;
199                         if (!parser.TryParse (url, out desc))
200                                 return null;
201
202                         // Namespace search
203                         Node currentNode = tree.RootNode;
204                         Node searchNode = new Node () { Caption = desc.Namespace };
205                         int index = currentNode.ChildNodes.BinarySearch (searchNode, EcmaGenericNodeComparer.Instance);
206                         if (index >= 0)
207                                 result = currentNode.ChildNodes[index];
208                         if (desc.DescKind == EcmaDesc.Kind.Namespace || index < 0)
209                                 return result;
210
211                         // Type search
212                         currentNode = result;
213                         result = null;
214                         searchNode.Caption = desc.ToCompleteTypeName ();
215                         if (!desc.GenericTypeArgumentsIsNumeric)
216                                 index = currentNode.ChildNodes.BinarySearch (searchNode, EcmaTypeNodeComparer.Instance);
217                         else
218                                 index = GenericTypeBacktickSearch (currentNode.ChildNodes, desc);
219                         if (index >= 0)
220                                 result = currentNode.ChildNodes[index];
221                         if ((desc.DescKind == EcmaDesc.Kind.Type && !desc.IsEtc) || index < 0)
222                                 return result;
223
224                         // Member selection
225                         currentNode = result;
226                         result = null;
227                         var caption = desc.IsEtc ? EtcKindToCaption (desc.Etc) : MemberKindToCaption (desc.DescKind);
228                         currentNode = FindNodeForCaption (currentNode.ChildNodes, caption);
229                         if (currentNode == null
230                             || (desc.IsEtc && desc.DescKind == EcmaDesc.Kind.Type && string.IsNullOrEmpty (desc.EtcFilter)))
231                                 return currentNode;
232
233                         // Member search
234                         result = null;
235                         var format = desc.DescKind == EcmaDesc.Kind.Constructor ? EcmaDesc.Format.WithArgs : EcmaDesc.Format.WithoutArgs;
236                         searchNode.Caption = desc.ToCompleteMemberName (format);
237                         index = currentNode.ChildNodes.BinarySearch (searchNode, EcmaGenericNodeComparer.Instance);
238                         if (index < 0)
239                                 return null;
240                         result = currentNode.ChildNodes[index];
241                         if (result.ChildNodes.Count == 0 || desc.IsEtc)
242                                 return result;
243
244                         // Overloads search
245                         currentNode = result;
246                         searchNode.Caption = desc.ToCompleteMemberName (EcmaDesc.Format.WithArgs);
247                         index = currentNode.ChildNodes.BinarySearch (searchNode, EcmaGenericNodeComparer.Instance);
248                         if (index < 0)
249                                 return result;
250                         result = result.ChildNodes[index];
251
252                         return result;
253                 }
254
255                 static int GenericTypeBacktickSearch (IList<Node> childNodes, EcmaDesc desc)
256                 {
257                         /* Our strategy is to search for the non-generic variant of the type
258                          * (which in most case should fail) and then use the closest index
259                          * to linearily search for the generic variant with the right generic arg number
260                          */
261                         var searchNode = new Node () { Caption = desc.TypeName };
262                         int index = childNodes.BinarySearch (searchNode, EcmaTypeNodeComparer.Instance);
263                         // Place the index in the right start position
264                         if (index < 0)
265                                 index = ~index;
266
267                         for (int i = index; i < childNodes.Count; i++) {
268                                 var currentNode = childNodes[i];
269                                 // Find the index of the generic argument list
270                                 int genericIndex = currentNode.Caption.IndexOf ('<');
271                                 // If we are not on the same base type name anymore, there is no point
272                                 int captionSlice = genericIndex != -1 ? genericIndex : currentNode.Caption.LastIndexOf (' ');
273                                 if (string.Compare (searchNode.Caption, 0,
274                                                     currentNode.Caption, 0,
275                                                     Math.Max (captionSlice, searchNode.Caption.Length),
276                                                     StringComparison.Ordinal) != 0)
277                                         break;
278
279                                 var numGenerics = CountTypeGenericArguments (currentNode.Caption, genericIndex);
280                                 if (numGenerics == desc.GenericTypeArguments.Count) {
281                                         // Simple comparison if we are not looking for an inner type
282                                         if (desc.NestedType == null)
283                                                 return i;
284                                         // If more complicated, we fallback to using EcmaUrlParser
285                                         var caption = currentNode.Caption;
286                                         caption = "T:" + caption.Substring (0, caption.LastIndexOf (' ')).Replace ('.', '+');
287                                         EcmaDesc otherDesc;
288                                         var parser = new EcmaUrlParser ();
289                                         if (parser.TryParse (caption, out otherDesc) && desc.NestedType.Equals (otherDesc.NestedType))
290                                                 return i;
291                                 }
292                         }
293
294                         return -1;
295                 }
296
297                 // This comparer returns the answer straight from caption comparison
298                 class EcmaGenericNodeComparer : IComparer<Node>
299                 {
300                         public static readonly EcmaGenericNodeComparer Instance = new EcmaGenericNodeComparer ();
301
302                         public int Compare (Node n1, Node n2)
303                         {
304                                 return string.Compare (n1.Caption, n2.Caption, StringComparison.Ordinal);
305                         }
306                 }
307
308                 // This comparer take into account the space in the caption
309                 class EcmaTypeNodeComparer : IComparer<Node>
310                 {
311                         public static readonly EcmaTypeNodeComparer Instance = new EcmaTypeNodeComparer ();
312
313                         public int Compare (Node n1, Node n2)
314                         {
315                                 int length1 = CaptionLength (n1.Caption);
316                                 int length2 = CaptionLength (n2.Caption);
317
318                                 return string.Compare (n1.Caption, 0, n2.Caption, 0, Math.Max (length1, length2), StringComparison.Ordinal);
319                         }
320
321                         int CaptionLength (string caption)
322                         {
323                                 var length = caption.LastIndexOf (' ');
324                                 return length == -1 ? caption.Length : length;
325                         }
326                 }
327
328                 public static Dictionary<string, string> GetContextForEcmaNode (string hash, string sourceID, Node node)
329                 {
330                         var args = new Dictionary<string, string> ();
331
332                         args["source-id"] = sourceID;
333
334                         if (node != null) {
335                                 var nodeType = GetNodeType (node);
336                                 switch (nodeType) {
337                                 case EcmaNodeType.Namespace:
338                                         args["show"] = "namespace";
339                                         args["namespace"] = node.Element.Substring ("N:".Length);
340                                         break;
341                                 case EcmaNodeType.Type:
342                                         args["show"] = "typeoverview";
343                                         break;
344                                 case EcmaNodeType.Member:
345                                 case EcmaNodeType.Meta:
346                                         switch (GetNodeMemberTypeChar (node)){
347                                         case 'C':
348                                                 args["membertype"] = "Constructor";
349                                                 break;
350                                         case 'M':
351                                                 args["membertype"] = "Method";
352                                                 break;
353                                         case 'P':
354                                                 args["membertype"] = "Property";
355                                                 break;
356                                         case 'F':
357                                                 args["membertype"] = "Field";
358                                                 break;
359                                         case 'E':
360                                                 args["membertype"] = "Event";
361                                                 break;
362                                         case 'O':
363                                                 args["membertype"] = "Operator";
364                                                 break;
365                                         case 'X':
366                                                 args["membertype"] = "ExtensionMethod";
367                                                 break;
368                                         case '*':
369                                                 args["membertype"] = "All";
370                                                 break;
371                                         }
372
373                                         if (nodeType == EcmaNodeType.Meta) {
374                                                 args["show"] = "members";
375                                                 args["index"] = "all";
376                                         } else {
377                                                 args["show"] = "member";
378                                                 args["index"] = node.Element;
379                                         }
380                                         break;
381                                 }
382                         }
383
384                         if (!string.IsNullOrEmpty (hash))
385                                 args["hash"] = hash;
386
387                         return args;
388                 }
389
390                 public static EcmaNodeType GetNodeType (Node node)
391                 {
392                         // We guess the node type by checking the depth level it's at in the tree
393                         int level = GetNodeLevel (node);
394                         switch (level) {
395                         case 0:
396                                 return EcmaNodeType.Namespace;
397                         case 1:
398                                 return EcmaNodeType.Type;
399                         case 2:
400                                 return EcmaNodeType.Meta;
401                         case 3: // Here it's either a member or, in case of overload, a meta
402                                 return node.IsLeaf ? EcmaNodeType.Member : EcmaNodeType.Meta;
403                         case 4: // At this level, everything is necessarily a member
404                                 return EcmaNodeType.Member;
405                         default:
406                                 return EcmaNodeType.Invalid;
407                         }
408                 }
409
410                 public static char GetNodeMemberTypeChar (Node node)
411                 {
412                         int level = GetNodeLevel (node);
413                         // We try to reach the member group node depending on node nested level
414                         switch (level) {
415                         case 2:
416                                 return node.Element[0];
417                         case 3:
418                                 return node.Parent.Element[0];
419                         case 4:
420                                 return node.Parent.Parent.Element[0];
421                         default:
422                                 throw new ArgumentException ("node", "Couldn't determine member type of node `" + node.Caption + "'");
423                         }
424                 }
425
426                 public static int GetNodeLevel (Node node)
427                 {
428                         int i = 0;
429                         for (; !node.Element.StartsWith ("root:/", StringComparison.OrdinalIgnoreCase); i++) {
430                                 node = node.Parent;
431                                 if (node == null)
432                                         return i - 1;
433                         }
434                         return i - 1;
435                 }
436
437                 public static string EtcKindToCaption (char etc)
438                 {
439                         switch (etc) {
440                         case 'M':
441                                 return "Methods";
442                         case 'P':
443                                 return "Properties";
444                         case 'C':
445                                 return "Constructors";
446                         case 'F':
447                                 return "Fields";
448                         case 'E':
449                                 return "Events";
450                         case 'O':
451                                 return "Operators";
452                         case '*':
453                                 return "Members";
454                         default:
455                                 return null;
456                         }
457                 }
458
459                 public static string MemberKindToCaption (EcmaDesc.Kind kind)
460                 {
461                         switch (kind) {
462                         case EcmaDesc.Kind.Method:
463                                 return "Methods";
464                         case EcmaDesc.Kind.Property:
465                                 return "Properties";
466                         case EcmaDesc.Kind.Constructor:
467                                 return "Constructors";
468                         case EcmaDesc.Kind.Field:
469                                 return "Fields";
470                         case EcmaDesc.Kind.Event:
471                                 return "Events";
472                         case EcmaDesc.Kind.Operator:
473                                 return "Operators";
474                         default:
475                                 return null;
476                         }
477                 }
478
479                 public static Node FindNodeForCaption (IList<Node> nodes, string caption)
480                 {
481                         foreach (var node in nodes)
482                                 if (node.Caption.Equals (caption, StringComparison.OrdinalIgnoreCase))
483                                         return node;
484                         return null;
485                 }
486
487                 public static int CountTypeGenericArguments (string typeDefinition, int startIndex = 0)
488                 {
489                         int nestedLevel = 0;
490                         int count = 0;
491                         bool started = false;
492
493                         foreach (char c in typeDefinition.Skip (startIndex)) {
494                                 switch (c) {
495                                 case '<':
496                                         if (!started)
497                                                 count = 1;
498                                         started = true;
499                                         nestedLevel++;
500                                         break;
501                                 case ',':
502                                         if (started && nestedLevel == 1)
503                                                 count++;
504                                         break;
505                                 case '>':
506                                         nestedLevel--;
507                                         break;
508                                 }
509                         }
510
511                         return count;
512                 }
513
514                 internal static string MakeOperatorSignature (XElement member, out string memberSignature)
515                 {
516                         string name = (string)member.Attribute ("MemberName");
517                         var nicename = name.Substring(3);
518                         memberSignature = null;
519
520                         switch (name) {
521                         // unary operators: no overloading possible     [ECMA-335 §10.3.1]
522                         case "op_UnaryPlus":                    // static     R operator+       (T)
523                         case "op_UnaryNegation":                // static     R operator-       (T)
524                         case "op_LogicalNot":                   // static     R operator!       (T)
525                         case "op_OnesComplement":               // static     R operator~       (T)
526                         case "op_Increment":                    // static     R operator++      (T)
527                         case "op_Decrement":                    // static     R operator--      (T)
528                         case "op_True":                         // static  bool operator true   (T)
529                         case "op_False":                        // static  bool operator false  (T)
530                         case "op_AddressOf":                    // static     R operator&       (T)
531                         case "op_PointerDereference":           // static     R operator*       (T)
532                                 memberSignature = nicename;
533                                 break;
534                         // conversion operators: overloading based on parameter and return type [ECMA-335 §10.3.3]
535                         case "op_Implicit":                    // static implicit operator R (T)
536                         case "op_Explicit":                    // static explicit operator R (T)
537                                 nicename = name.EndsWith ("Implicit") ? "ImplicitConversion" : "ExplicitConversion";
538                                 string arg = (string)member.Element ("Parameters").Element ("Parameter").Attribute ("Type");
539                                 string ret = (string)member.Element ("ReturnValue").Element ("ReturnType");
540                                 memberSignature = arg + " to " + ret;
541                                 break;
542                         // binary operators: overloading is possible [ECMA-335 §10.3.2]
543                         default:
544                                 memberSignature =
545                                         nicename + "("
546                                         + string.Join (",", member.Element ("Parameters").Elements ("Parameter").Select (p => (string)p.Attribute ("Type")))
547                                         + ")";
548                                 break;
549                         }
550
551                         return nicename;
552                 }
553
554                 static XElement ExtractClassSummary (string typeFilePath)
555                 {
556                         using (var reader = XmlReader.Create (typeFilePath)) {
557                                 reader.ReadToFollowing ("Type");
558                                 var name = reader.GetAttribute ("Name");
559                                 var fullName = reader.GetAttribute ("FullName");
560                                 reader.ReadToFollowing ("AssemblyName");
561                                 var assemblyName = reader.ReadElementString ();
562                                 var summary = reader.ReadToFollowing ("summary") ? XElement.Load (reader.ReadSubtree ()) : new XElement ("summary");
563                                 var remarks = reader.ReadToFollowing ("remarks") ? XElement.Load (reader.ReadSubtree ()) : new XElement ("remarks");
564
565                                 return new XElement ("class",
566                                                      new XAttribute ("name", name ?? string.Empty),
567                                                      new XAttribute ("fullname", fullName ?? string.Empty),
568                                                      new XAttribute ("assembly", assemblyName ?? string.Empty),
569                                                      summary,
570                                                      remarks);
571                         }
572                 }
573         }
574 }