[xbuild] Actually delete common files (CopyLocal) during Clean.
[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                         return i - 1;
432                 }
433
434                 public static string EtcKindToCaption (char etc)
435                 {
436                         switch (etc) {
437                         case 'M':
438                                 return "Methods";
439                         case 'P':
440                                 return "Properties";
441                         case 'C':
442                                 return "Constructors";
443                         case 'F':
444                                 return "Fields";
445                         case 'E':
446                                 return "Events";
447                         case 'O':
448                                 return "Operators";
449                         case '*':
450                                 return "Members";
451                         default:
452                                 return null;
453                         }
454                 }
455
456                 public static string MemberKindToCaption (EcmaDesc.Kind kind)
457                 {
458                         switch (kind) {
459                         case EcmaDesc.Kind.Method:
460                                 return "Methods";
461                         case EcmaDesc.Kind.Property:
462                                 return "Properties";
463                         case EcmaDesc.Kind.Constructor:
464                                 return "Constructors";
465                         case EcmaDesc.Kind.Field:
466                                 return "Fields";
467                         case EcmaDesc.Kind.Event:
468                                 return "Events";
469                         case EcmaDesc.Kind.Operator:
470                                 return "Operators";
471                         default:
472                                 return null;
473                         }
474                 }
475
476                 public static Node FindNodeForCaption (IList<Node> nodes, string caption)
477                 {
478                         foreach (var node in nodes)
479                                 if (node.Caption.Equals (caption, StringComparison.OrdinalIgnoreCase))
480                                         return node;
481                         return null;
482                 }
483
484                 public static int CountTypeGenericArguments (string typeDefinition, int startIndex = 0)
485                 {
486                         int nestedLevel = 0;
487                         int count = 0;
488                         bool started = false;
489
490                         foreach (char c in typeDefinition.Skip (startIndex)) {
491                                 switch (c) {
492                                 case '<':
493                                         if (!started)
494                                                 count = 1;
495                                         started = true;
496                                         nestedLevel++;
497                                         break;
498                                 case ',':
499                                         if (started && nestedLevel == 1)
500                                                 count++;
501                                         break;
502                                 case '>':
503                                         nestedLevel--;
504                                         break;
505                                 }
506                         }
507
508                         return count;
509                 }
510
511                 internal static string MakeOperatorSignature (XElement member, out string memberSignature)
512                 {
513                         string name = (string)member.Attribute ("MemberName");
514                         var nicename = name.Substring(3);
515                         memberSignature = null;
516
517                         switch (name) {
518                         // unary operators: no overloading possible     [ECMA-335 §10.3.1]
519                         case "op_UnaryPlus":                    // static     R operator+       (T)
520                         case "op_UnaryNegation":                // static     R operator-       (T)
521                         case "op_LogicalNot":                   // static     R operator!       (T)
522                         case "op_OnesComplement":               // static     R operator~       (T)
523                         case "op_Increment":                    // static     R operator++      (T)
524                         case "op_Decrement":                    // static     R operator--      (T)
525                         case "op_True":                         // static  bool operator true   (T)
526                         case "op_False":                        // static  bool operator false  (T)
527                         case "op_AddressOf":                    // static     R operator&       (T)
528                         case "op_PointerDereference":           // static     R operator*       (T)
529                                 memberSignature = nicename;
530                                 break;
531                         // conversion operators: overloading based on parameter and return type [ECMA-335 §10.3.3]
532                         case "op_Implicit":                    // static implicit operator R (T)
533                         case "op_Explicit":                    // static explicit operator R (T)
534                                 nicename = name.EndsWith ("Implicit") ? "ImplicitConversion" : "ExplicitConversion";
535                                 string arg = (string)member.Element ("Parameters").Element ("Parameter").Attribute ("Type");
536                                 string ret = (string)member.Element ("ReturnValue").Element ("ReturnType");
537                                 memberSignature = arg + " to " + ret;
538                                 break;
539                         // binary operators: overloading is possible [ECMA-335 §10.3.2]
540                         default:
541                                 memberSignature =
542                                         nicename + "("
543                                         + string.Join (",", member.Element ("Parameters").Elements ("Parameter").Select (p => (string)p.Attribute ("Type")))
544                                         + ")";
545                                 break;
546                         }
547
548                         return nicename;
549                 }
550
551                 static XElement ExtractClassSummary (string typeFilePath)
552                 {
553                         using (var reader = XmlReader.Create (typeFilePath)) {
554                                 reader.ReadToFollowing ("Type");
555                                 var name = reader.GetAttribute ("Name");
556                                 var fullName = reader.GetAttribute ("FullName");
557                                 reader.ReadToFollowing ("AssemblyName");
558                                 var assemblyName = reader.ReadElementString ();
559                                 var summary = reader.ReadToFollowing ("summary") ? XElement.Load (reader.ReadSubtree ()) : new XElement ("summary");
560                                 var remarks = reader.ReadToFollowing ("remarks") ? XElement.Load (reader.ReadSubtree ()) : new XElement ("remarks");
561
562                                 return new XElement ("class",
563                                                      new XAttribute ("name", name ?? string.Empty),
564                                                      new XAttribute ("fullname", fullName ?? string.Empty),
565                                                      new XAttribute ("assembly", assemblyName ?? string.Empty),
566                                                      summary,
567                                                      remarks);
568                         }
569                 }
570         }
571 }