Merge pull request #5714 from alexischr/update_bockbuild
[mono.git] / mcs / tools / mono-api-html / MemberComparer.cs
1 // 
2 // Authors
3 //    Sebastien Pouliot  <sebastien@xamarin.com>
4 //
5 // Copyright 2013-2014 Xamarin Inc. http://www.xamarin.com
6 // 
7 // Permission is hereby granted, free of charge, to any person obtaining
8 // a copy of this software and associated documentation files (the
9 // "Software"), to deal in the Software without restriction, including
10 // without limitation the rights to use, copy, modify, merge, publish,
11 // distribute, sublicense, and/or sell copies of the Software, and to
12 // permit persons to whom the Software is furnished to do so, subject to
13 // the following conditions:
14 //
15 // The above copyright notice and this permission notice shall be
16 // included in all copies or substantial portions of the Software.
17 //
18 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
22 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
23 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
24 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
25 //
26
27 using System;
28 using System.Collections.Generic;
29 using System.Linq;
30 using System.Reflection;
31 using System.Text;
32 using System.Xml.Linq;
33
34 namespace Xamarin.ApiDiff {
35
36         public abstract class MemberComparer : Comparer {
37
38                 // true if this is the first element being added or removed in the group being rendered
39                 protected bool first;
40
41                 public abstract string GroupName { get; }
42                 public abstract string ElementName { get; }
43
44                 protected virtual bool IsBreakingRemoval (XElement e)
45                 {
46                         return true;
47                 }
48
49                 public void Compare (XElement source, XElement target)
50                 {
51                         var s = source.Element (GroupName);
52                         var t = target.Element (GroupName);
53                         if (XNode.DeepEquals (s, t))
54                                 return;
55
56                         if (s == null) {
57                                 Add (t.Elements (ElementName));
58                         } else if (t == null) {
59                                 Remove (s.Elements (ElementName));
60                         } else {
61                                 Compare (s.Elements (ElementName), t.Elements (ElementName));
62                         }
63                 }
64
65                 public override void SetContext (XElement current)
66                 {
67                 }
68
69                 string GetContainingType (XElement el)
70                 {
71                         return el.Ancestors ("class").First ().Attribute ("type").Value;
72                 }
73
74                 bool IsInInterface (XElement el)
75                 {
76                         return GetContainingType (el) == "interface";
77                 }
78
79                 public XElement Source { get; set; }
80
81                 public virtual bool Find (XElement e)
82                 {
83                         return e.GetAttribute ("name") == Source.GetAttribute ("name");
84                 }
85
86                 XElement Find (IEnumerable<XElement> target)
87                 {
88                         return State.Lax ? target.FirstOrDefault (Find) : target.SingleOrDefault (Find);
89                 }
90
91                 public override void Compare (IEnumerable<XElement> source, IEnumerable<XElement> target)
92                 {
93                         removed.Clear ();
94                         modified.Clear ();
95
96                         foreach (var s in source) {
97                                 SetContext (s);
98                                 Source = s;
99                                 var t = Find (target);
100                                 if (t == null) {
101                                         // not in target, it was removed
102                                         removed.Add (s);
103                                 } else {
104                                         t.Remove ();
105                                         // possibly modified
106                                         if (Equals (s, t, modified))
107                                                 continue;
108
109                                         Modified (s, t, modified);
110                                 }
111                         }
112                         // delayed, that way we show "Modified", "Added" and then "Removed"
113                         Remove (removed);
114
115                         Modify (modified);
116
117                         // remaining == newly added in target
118                         Add (target);
119                 }
120
121                 void Add (IEnumerable<XElement> elements)
122                 {
123                         bool a = false;
124                         foreach (var item in elements) {
125                                 SetContext (item);
126                                 if (State.IgnoreAdded.Any (re => re.IsMatch (GetDescription (item))))
127                                         continue;
128                                 if (!a) {
129                                         BeforeAdding (elements);
130                                         a = true;
131                                 }
132                                 Added (item, false);
133                         }
134                         if (a)
135                                 AfterAdding ();
136                 }
137
138                 void Modify (ApiChanges modified)
139                 {
140                         foreach (var changes in modified) {
141                                 if (State.IgnoreNonbreaking && changes.Value.All (c => !c.Breaking))
142                                         continue;
143                                 Output.WriteLine ("<p>{0}:</p>", changes.Key);
144                                 Output.WriteLine ("<pre>");
145                                 foreach (var element in changes.Value) {
146                                         if (State.IgnoreNonbreaking && !element.Breaking)
147                                                 continue;
148                                         Output.Write ("<div {0}>", element.Breaking ? "data-is-breaking" : "data-is-non-breaking");
149                                         foreach (var line in element.Member.ToString ().Split ('\n'))
150                                                 Output.WriteLine ("\t{0}", line);
151                                         Output.Write ("</div>");
152
153                                 }
154                                 Output.WriteLine ("</pre>");
155                         }
156                 }
157
158                 void Remove (IEnumerable<XElement> elements)
159                 {
160                         bool r = false;
161                         foreach (var item in elements) {
162                                 if (State.IgnoreRemoved.Any (re => re.IsMatch (GetDescription (item))))
163                                         continue;
164                                 SetContext (item);
165                                 if (State.IgnoreNonbreaking && !IsBreakingRemoval (item))
166                                         continue;
167                                 if (!r) {
168                                         BeforeRemoving (elements);
169                                         r = true;
170                                 }
171                                 Removed (item);
172                         }
173                         if (r)
174                                 AfterRemoving ();
175                 }
176                         
177                 public abstract string GetDescription (XElement e);
178
179                 protected StringBuilder GetObsoleteMessage (XElement e)
180                 {
181                         var sb = new StringBuilder ();
182                         string o = e.GetObsoleteMessage ();
183                         if (o != null) {
184                                 sb.Append ("[Obsolete");
185                                 if (o.Length > 0)
186                                         sb.Append (" (\"").Append (o).Append ("\")");
187                                 sb.AppendLine ("]");
188                                 for (int i = 0; i < State.Indent + 1; i++)
189                                         sb.Append ('\t');
190                         }
191                         return sb;
192                 }
193
194                 public override bool Equals (XElement source, XElement target, ApiChanges changes)
195                 {
196                         RenderAttributes (source, target, changes);
197
198                         // We don't want to compare attributes.
199                         RemoveAttributes (source);
200                         RemoveAttributes (target);
201
202                         return base.Equals (source, target, changes);
203                 }
204
205                 public virtual void BeforeAdding (IEnumerable<XElement> list)
206                 {
207                         first = true;
208                         Output.WriteLine ("<div>");
209                         Output.WriteLine ("<p>Added {0}:</p>", list.Count () > 1 ? GroupName : ElementName);
210                         Output.WriteLine ("<pre>");
211                 }
212
213                 public override void Added (XElement target, bool wasParentAdded)
214                 {
215                         var o = GetObsoleteMessage (target);
216                         if (!first && (o.Length > 0))
217                                 Output.WriteLine ();
218                         Indent ();
219                         bool isInterfaceBreakingChange = !wasParentAdded && IsInInterface (target);
220                         Output.Write ("\t<span class='added added-{0} {1}' {2}>", ElementName, isInterfaceBreakingChange ? "breaking" : string.Empty, isInterfaceBreakingChange ? "data-is-breaking" : "data-is-non-breaking");
221                         Output.Write ("{0}{1}", o, GetDescription (target));
222                         Output.WriteLine ("</span>");
223                         first = false;
224                 }
225
226                 public virtual void AfterAdding ()
227                 {
228                         Output.WriteLine ("</pre>");
229                         Output.WriteLine ("</div>");
230                 }
231
232                 public override void Modified (XElement source, XElement target, ApiChanges change)
233                 {
234                 }
235
236                 public virtual void BeforeRemoving (IEnumerable<XElement> list)
237                 {
238                         first = true;
239                         Output.WriteLine ("<p>Removed {0}:</p>\n", list.Count () > 1 ? GroupName : ElementName);
240                         Output.WriteLine ("<pre>");
241                 }
242
243                 public override void Removed (XElement source)
244                 {
245                         var o = GetObsoleteMessage (source);
246                         if (!first && (o.Length > 0))
247                                 Output.WriteLine ();
248
249                         bool is_breaking = IsBreakingRemoval (source);
250
251                         Indent ();
252                         Output.Write ("\t<span class='removed removed-{0} {2}' {1}>", ElementName, is_breaking ? "data-is-breaking" : "data-is-non-breaking", is_breaking ? "breaking" : string.Empty);
253                         Output.Write ("{0}{1}", o, GetDescription (source));
254                         Output.WriteLine ("</span>");
255                         first = false;
256                 }
257
258                 public virtual void AfterRemoving ()
259                 {
260                         Output.WriteLine ("</pre>");;
261                 }
262
263                 string RenderGenericParameter (XElement gp)
264                 {
265                         var sb = new StringBuilder ();
266                         sb.Append (gp.GetTypeName ("name"));
267
268                         var constraints = gp.DescendantList ("generic-parameter-constraints", "generic-parameter-constraint");
269                         if (constraints != null && constraints.Count > 0) {
270                                 sb.Append (" : ");
271                                 for (int i = 0; i < constraints.Count; i++) {
272                                         if (i > 0)
273                                                 sb.Append (", ");
274                                         sb.Append (constraints [i].GetTypeName ("name"));
275                                 }
276                         }
277                         return sb.ToString ();
278                 }
279
280                 protected void RenderGenericParameters (XElement source, XElement target, ApiChange change)
281                 {
282                         var src = source.DescendantList ("generic-parameters", "generic-parameter");
283                         var tgt = target.DescendantList ("generic-parameters", "generic-parameter");
284                         var srcCount = src == null ? 0 : src.Count;
285                         var tgtCount = tgt == null ? 0 : tgt.Count;
286
287                         if (srcCount == 0 && tgtCount == 0)
288                                 return;
289
290                         change.Append ("&lt;");
291                         for (int i = 0; i < Math.Max (srcCount, tgtCount); i++) {
292                                 if (i > 0)
293                                         change.Append (", ");
294                                 if (i >= srcCount) {
295                                         change.AppendAdded (RenderGenericParameter (tgt [i]), true);
296                                 } else if (i >= tgtCount) {
297                                         change.AppendRemoved (RenderGenericParameter (src [i]), true);
298                                 } else {
299                                         var srcName = RenderGenericParameter (src [i]);
300                                         var tgtName = RenderGenericParameter (tgt [i]);
301
302                                         if (srcName != tgtName) {
303                                                 change.AppendModified (srcName, tgtName, true);
304                                         } else {
305                                                 change.Append (srcName);
306                                         }
307                                         }
308                                 }
309                         change.Append ("&gt;");
310                 }
311
312                 protected string FormatValue (string type, string value)
313                 {
314                         if (value == null)
315                                 return "null";
316
317                         if (type == "string")
318                                 return "\"" + value + "\"";
319                         else if (type == "bool") {
320                                 switch (value) {
321                                 case "True":
322                                         return "true";
323                                 case "False":
324                                         return "false";
325                                 default:
326                                         return value;
327                                 }
328                         }
329
330                         return value;
331                 }
332
333                 protected void RenderParameters (XElement source, XElement target, ApiChange change)
334                 {
335                         var src = source.DescendantList ("parameters", "parameter");
336                         var tgt = target.DescendantList ("parameters", "parameter");
337                         var srcCount = src == null ? 0 : src.Count;
338                         var tgtCount = tgt == null ? 0 : tgt.Count;
339
340                         change.Append (" (");
341                         for (int i = 0; i < Math.Max (srcCount, tgtCount); i++) {
342                                 if (i > 0)
343                                         change.Append (", ");
344
345                                 string mods_tgt = tgt [i].GetAttribute ("direction") ?? "";
346                                 string mods_src = src [i].GetAttribute ("direction") ?? "";
347
348                                 if (mods_tgt.Length > 0)
349                                         mods_tgt = mods_tgt + " ";
350
351                                 if (mods_src.Length > 0)
352                                         mods_src = mods_src + " ";
353
354                                 if (i >= srcCount) {
355                                         change.AppendAdded (mods_tgt + tgt [i].GetTypeName ("type") + " " + tgt [i].GetAttribute ("name"), true);
356                                 } else if (i >= tgtCount) {
357                                         change.AppendRemoved (mods_src + src [i].GetTypeName ("type") + " " + src [i].GetAttribute ("name"), true);
358                                 } else {
359                                         var paramSourceType = src [i].GetTypeName ("type");
360                                         var paramTargetType = tgt [i].GetTypeName ("type");
361
362                                         var paramSourceName = src [i].GetAttribute ("name");
363                                         var paramTargetName = tgt [i].GetAttribute ("name");
364
365                                         if (mods_src != mods_tgt) {
366                                                 change.AppendModified (mods_src, mods_tgt, true);
367                                         } else {
368                                                 change.Append (mods_src);
369                                         }
370
371                                         if (paramSourceType != paramTargetType) {
372                                                 change.AppendModified (paramSourceType, paramTargetType, true);
373                                         } else {
374                                                 change.Append (paramSourceType);
375                                         }
376                                         change.Append (" ");
377                                         if (!State.IgnoreParameterNameChanges && paramSourceName != paramTargetName) {
378                                                 change.AppendModified (paramSourceName, paramTargetName, true);
379                                         } else {
380                                                 change.Append (paramSourceName);
381                                         }
382
383                                         var optSource = src [i].Attribute ("optional");
384                                         var optTarget = tgt [i].Attribute ("optional");
385                                         var srcValue = FormatValue (paramSourceType, src [i].GetAttribute ("defaultValue"));
386                                         var tgtValue = FormatValue (paramTargetType, tgt [i].GetAttribute ("defaultValue"));
387
388                                         if (optSource != null) {
389                                                 if (optTarget != null) {
390                                                         change.Append (" = ");
391                                                         if (srcValue != tgtValue) {
392                                                                 change.AppendModified (srcValue, tgtValue, false);
393                                                         } else {
394                                                                 change.Append (tgtValue);
395                                                         }
396                                                 } else {
397                                                         change.AppendRemoved (" = " + srcValue);
398                                                 }
399                                         } else {
400                                                 if (optTarget != null)
401                                                         change.AppendAdded (" = " + tgtValue);
402                                         }
403                                 }
404                         }
405
406                         change.Append (")");
407                 }
408
409                 void RenderVTable (MethodAttributes source, MethodAttributes target, ApiChange change)
410                 {
411                         var srcAbstract = (source & MethodAttributes.Abstract) == MethodAttributes.Abstract;
412                         var tgtAbstract = (target & MethodAttributes.Abstract) == MethodAttributes.Abstract;
413                         var srcFinal = (source & MethodAttributes.Final) == MethodAttributes.Final;
414                         var tgtFinal = (target & MethodAttributes.Final) == MethodAttributes.Final;
415                         var srcVirtual = (source & MethodAttributes.Virtual) == MethodAttributes.Virtual;
416                         var tgtVirtual = (target & MethodAttributes.Virtual) == MethodAttributes.Virtual;
417                         var srcOverride = (source & MethodAttributes.VtableLayoutMask) != MethodAttributes.NewSlot;
418                         var tgtOverride = (target & MethodAttributes.VtableLayoutMask) != MethodAttributes.NewSlot;
419
420                         var srcWord = srcVirtual ? (srcOverride ? "override" : "virtual") : string.Empty;
421                         var tgtWord = tgtVirtual ? (tgtOverride ? "override" : "virtual") : string.Empty;
422                         var breaking = srcWord.Length > 0 && tgtWord.Length == 0;
423
424                         if (srcAbstract) {
425                                 if (tgtAbstract) {
426                                         change.Append ("abstract ");
427                                 } else if (tgtVirtual) {
428                                         change.AppendModified ("abstract", tgtWord, false).Append (" ");
429                                 } else {
430                                         change.AppendRemoved ("abstract").Append (" ");
431                                 }
432                         } else {
433                                 if (tgtAbstract) {
434                                         change.AppendAdded ("abstract", true).Append (" ");
435                                 } else if (srcWord != tgtWord) {
436                                         if (!tgtFinal) {
437                                                 if (State.IgnoreVirtualChanges) {
438                                                         change.HasIgnoredChanges = true;
439                                                 } else {
440                                                         change.AppendModified (srcWord, tgtWord, breaking).Append (" ");
441                                                 }
442                                         }
443                                 } else if (tgtWord.Length > 0) {
444                                         change.Append (tgtWord).Append (" ");
445                                 } else if (srcWord.Length > 0) {
446                                         change.AppendRemoved (srcWord, breaking).Append (" ");
447                                 }
448                         }
449
450                         if (srcFinal) {
451                                 if (tgtFinal) {
452                                         change.Append ("final ");
453                                 } else {
454                                         if (srcVirtual && !tgtVirtual && State.IgnoreVirtualChanges) {
455                                                 change.HasIgnoredChanges = true;
456                                         } else {
457                                                 change.AppendRemoved ("final", false).Append (" "); // removing 'final' is not a breaking change.
458                                         }
459                                 }
460                         } else {
461                                 if (tgtFinal && srcVirtual) {
462                                         change.AppendModified ("virtual", "final", true).Append (" "); // adding 'final' is a breaking change if the member was virtual
463                                 }
464                         }
465
466                         if (!srcVirtual && !srcFinal && tgtVirtual && tgtFinal) {
467                                 // existing member implements a member from a new interface
468                                 // this would show up as 'virtual final', which is redundant, so show nothing at all.
469                                 change.HasIgnoredChanges = true;
470                         }
471
472                         // Ignore non-breaking virtual changes.
473                         if (State.IgnoreVirtualChanges && !change.Breaking) {
474                                 change.AnyChange = false;
475                                 change.HasIgnoredChanges = true;
476                         }
477
478                         var tgtSecurity = (source & MethodAttributes.HasSecurity) == MethodAttributes.HasSecurity;
479                         var srcSecurity = (target & MethodAttributes.HasSecurity) == MethodAttributes.HasSecurity;
480
481                         if (tgtSecurity != srcSecurity)
482                                 change.HasIgnoredChanges = true;
483
484                         var srcPInvoke = (source & MethodAttributes.PinvokeImpl) == MethodAttributes.PinvokeImpl;
485                         var tgtPInvoke = (target & MethodAttributes.PinvokeImpl) == MethodAttributes.PinvokeImpl;
486                         if (srcPInvoke != tgtPInvoke)
487                                 change.HasIgnoredChanges = true;
488                 }
489
490                 protected string GetVisibility (MethodAttributes attr)
491                 {
492                         switch (attr) {
493                         case MethodAttributes.Private:
494                         case MethodAttributes.PrivateScope:
495                                 return "private";
496                         case MethodAttributes.Assembly:
497                                 return "internal";
498                         case MethodAttributes.FamANDAssem:
499                                 return "private internal";
500                         case MethodAttributes.FamORAssem:
501                                 return "protected"; // customers don't care about 'internal';
502                         case MethodAttributes.Family:
503                                 return "protected";
504                         case MethodAttributes.Public:
505                                 return "public";
506                         default:
507                                 throw new NotImplementedException ();
508                         }
509                 }
510
511                 protected void RenderVisibility (MethodAttributes source, MethodAttributes target, ApiChange diff)
512                 {
513                         source = source & MethodAttributes.MemberAccessMask;
514                         target = target & MethodAttributes.MemberAccessMask;
515
516                         if (source == target) {
517                                 diff.Append (GetVisibility (target));
518                         } else {
519                                 var breaking = false;
520                                 switch (source) {
521                                 case MethodAttributes.Private:
522                                 case MethodAttributes.Assembly:
523                                 case MethodAttributes.FamANDAssem:
524                                         break; // these are not publicly visible, thus not breaking
525                                 case MethodAttributes.FamORAssem:
526                                 case MethodAttributes.Family:
527                                         switch (target) {
528                                         case MethodAttributes.Public:
529                                                 // to public is not a breaking change
530                                                 break;
531                                         case MethodAttributes.Family:
532                                         case MethodAttributes.FamORAssem:
533                                                 // not a breaking change, but should still show up in diff
534                                                 break;
535                                         default:
536                                                 // anything else is a breaking change
537                                                 breaking = true;
538                                                 break;
539                                         }
540                                         break;
541                                 case MethodAttributes.Public:
542                                 default:
543                                         // any change from public is breaking.
544                                         breaking = true;
545                                         break;
546                                 }
547
548                                 diff.AppendModified (GetVisibility (source), GetVisibility (target), breaking);
549                         }
550                         diff.Append (" ");
551                 }
552
553                 protected void RenderStatic (MethodAttributes src, MethodAttributes tgt, ApiChange diff)
554                 {
555                         var srcStatic = (src & MethodAttributes.Static) == MethodAttributes.Static;
556                         var tgtStatic = (tgt & MethodAttributes.Static) == MethodAttributes.Static;
557
558                         if (srcStatic != tgtStatic) {
559                                 if (srcStatic) {
560                                         diff.AppendRemoved ("static", true).Append (" ");
561                                 } else {
562                                         diff.AppendAdded ("static", true).Append (" ");
563                                 }
564                         }
565                 }
566
567                 protected void RenderMethodAttributes (MethodAttributes src, MethodAttributes tgt, ApiChange diff)
568                 {
569                         RenderStatic (src, tgt, diff);
570                         RenderVisibility (src & MethodAttributes.MemberAccessMask, tgt & MethodAttributes.MemberAccessMask, diff);
571                         RenderVTable (src, tgt, diff);
572                 }
573
574                 protected void RenderMethodAttributes (XElement source, XElement target, ApiChange diff)
575                 {
576                         RenderMethodAttributes (source.GetMethodAttributes (), target.GetMethodAttributes (), diff);
577                 }
578
579                 protected void RemoveAttributes (XElement element)
580                 {
581                         var srcAttributes = element.Element ("attributes");
582                         if (srcAttributes != null)
583                                 srcAttributes.Remove ();
584
585                         foreach (var el in element.Elements ())
586                                 RemoveAttributes (el);
587                 }
588
589                 protected void RenderAttributes (XElement source, XElement target, ApiChanges changes)
590                 {
591                         var srcObsolete = source.GetObsoleteMessage ();
592                         var tgtObsolete = target.GetObsoleteMessage ();
593
594                         if (srcObsolete == tgtObsolete)
595                                 return; // nothing changed
596
597                         if (srcObsolete == null) {
598                                 if (tgtObsolete == null)
599                                         return; // neither is obsolete
600                                 var change = new ApiChange ();
601                                 change.Header = "Obsoleted " + GroupName;
602                                 change.Append (string.Format ("<span class='obsolete obsolete-{0}' data-is-non-breaking>", ElementName));
603                                 change.Append ("[Obsolete (");
604                                 if (tgtObsolete != string.Empty)
605                                         change.Append ("\"").Append (tgtObsolete).Append ("\"");
606                                 change.Append (")]\n");
607                                 change.Append (GetDescription (target));
608                                 change.Append ("</span>");
609                                 change.AnyChange = true;
610                                 changes.Add (source, target, change);
611                         } else if (tgtObsolete == null) {
612                                 // Made non-obsolete. Do we care to report this?
613                         } else {
614                                 // Obsolete message changed. Do we care to report this?
615                         }
616                 }
617
618                 protected void RenderName (XElement source, XElement target, ApiChange change)
619                 {
620                         var name = target.GetAttribute ("name");
621                         // show the constructor as it would be defined in C#
622                         name = name.Replace (".ctor", State.Type);
623
624                         var p = name.IndexOf ('(');
625                         if (p >= 0)
626                                 name = name.Substring (0, p);
627
628                         change.Append (name);
629                 }
630
631         }
632 }