Merge pull request #980 from StephenMcConnel/bug-18638
[mono.git] / mcs / tools / corcompare / 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                 public void Compare (XElement source, XElement target)
45                 {
46                         var s = source.Element (GroupName);
47                         var t = target.Element (GroupName);
48                         if (XNode.DeepEquals (s, t))
49                                 return;
50
51                         if (s == null) {
52                                 Add (t.Elements (ElementName));
53                         } else if (t == null) {
54                                 Remove (s.Elements (ElementName));
55                         } else {
56                                 Compare (s.Elements (ElementName), t.Elements (ElementName));
57                         }
58                 }
59
60                 public override void SetContext (XElement current)
61                 {
62                 }
63
64                 public XElement Source { get; set; }
65
66                 public virtual bool Find (XElement e)
67                 {
68                         return e.GetAttribute ("name") == Source.GetAttribute ("name");
69                 }
70
71                 XElement Find (IEnumerable<XElement> target)
72                 {
73                         return State.Lax ? target.FirstOrDefault (Find) : target.SingleOrDefault (Find);
74                 }
75
76                 public override void Compare (IEnumerable<XElement> source, IEnumerable<XElement> target)
77                 {
78                         removed.Clear ();
79                         modified.Clear ();
80
81                         foreach (var s in source) {
82                                 SetContext (s);
83                                 Source = s;
84                                 var t = Find (target);
85                                 if (t == null) {
86                                         // not in target, it was removed
87                                         removed.Add (s);
88                                 } else {
89                                         t.Remove ();
90                                         // possibly modified
91                                         if (Equals (s, t, modified))
92                                                 continue;
93
94                                         Modified (s, t, modified);
95                                 }
96                         }
97                         // delayed, that way we show "Modified", "Added" and then "Removed"
98                         Remove (removed);
99
100                         Modify (modified);
101
102                         // remaining == newly added in target
103                         Add (target);
104                 }
105
106                 void Add (IEnumerable<XElement> elements)
107                 {
108                         bool a = false;
109                         foreach (var item in elements) {
110                                 SetContext (item);
111                                 if (State.IgnoreAdded.Any (re => re.IsMatch (GetDescription (item))))
112                                         continue;
113                                 if (!a) {
114                                         BeforeAdding (elements);
115                                         a = true;
116                                 }
117                                 Added (item);
118                         }
119                         if (a)
120                                 AfterAdding ();
121                 }
122
123                 void Modify (ApiChanges modified)
124                 {
125                         foreach (var changes in modified) {
126                                 Output.WriteLine ("<p>{0}:</p>", changes.Key);
127                                 Output.WriteLine ("<pre>");
128                                 foreach (var element in changes.Value) {
129                                         foreach (var line in element.Member.ToString ().Split ('\n'))
130                                                 Output.WriteLine ("\t{0}", line);
131
132                                 }
133                                 Output.WriteLine ("</pre>");
134                         }
135                 }
136
137                 void Remove (IEnumerable<XElement> elements)
138                 {
139                         bool r = false;
140                         foreach (var item in elements) {
141                                 if (State.IgnoreRemoved.Any (re => re.IsMatch (GetDescription (item))))
142                                         continue;
143                                 SetContext (item);
144                                 if (!r) {
145                                         BeforeRemoving (elements);
146                                         r = true;
147                                 }
148                                 Removed (item);
149                         }
150                         if (r)
151                                 AfterRemoving ();
152                 }
153                         
154                 public abstract string GetDescription (XElement e);
155
156                 protected StringBuilder GetObsoleteMessage (XElement e)
157                 {
158                         var sb = new StringBuilder ();
159                         string o = e.GetObsoleteMessage ();
160                         if (o != null) {
161                                 sb.Append ("[Obsolete");
162                                 if (o.Length > 0)
163                                         sb.Append (" (\"").Append (o).Append ("\")");
164                                 sb.AppendLine ("]");
165                                 for (int i = 0; i < State.Indent + 1; i++)
166                                         sb.Append ('\t');
167                         }
168                         return sb;
169                 }
170
171                 public override bool Equals (XElement source, XElement target, ApiChanges changes)
172                 {
173                         RenderAttributes (source, target, changes);
174
175                         // We don't want to compare attributes.
176                         RemoveAttributes (source);
177                         RemoveAttributes (target);
178
179                         return base.Equals (source, target, changes);
180                 }
181
182                 public virtual void BeforeAdding (IEnumerable<XElement> list)
183                 {
184                         first = true;
185                         Output.WriteLine ("<p>Added {0}:</p><pre>", list.Count () > 1 ? GroupName : ElementName);
186                         if (State.Colorize)
187                                 Output.Write ("<font color='green'>");
188                 }
189
190                 public override void Added (XElement target)
191                 {
192                         var o = GetObsoleteMessage (target);
193                         if (!first && (o.Length > 0))
194                                 Output.WriteLine ();
195                         Indent ().WriteLine ("\t{0}{1}", o, GetDescription (target));
196                         first = false;
197                 }
198
199                 public virtual void AfterAdding ()
200                 {
201                         if (State.Colorize)
202                                 Output.Write ("</font>");
203                         Output.WriteLine ("</pre>");
204                 }
205
206                 public override void Modified (XElement source, XElement target, ApiChanges change)
207                 {
208                 }
209
210                 public virtual void BeforeRemoving (IEnumerable<XElement> list)
211                 {
212                         first = true;
213                         Output.WriteLine ("<p>Removed {0}:</p>\n<pre>", list.Count () > 1 ? GroupName : ElementName);
214                         if (State.Colorize)
215                                 Output.Write ("<font color='red'>");
216                 }
217
218                 public override void Removed (XElement source)
219                 {
220                         var o = GetObsoleteMessage (source);
221                         if (!first && (o.Length > 0))
222                                 Output.WriteLine ();
223                         Indent ().WriteLine ("\t{0}{1}", o, GetDescription (source));
224                         first = false;
225                 }
226
227                 public virtual void AfterRemoving ()
228                 {
229                         if (State.Colorize)
230                                 Output.Write ("</font>");
231                         Output.WriteLine ("</pre>");
232                 }
233
234                 protected void RenderGenericParameters (XElement source, XElement target, ApiChange change)
235                 {
236                         var src = source.DescendantList ("generic-parameters", "generic-parameter");
237                         var tgt = target.DescendantList ("generic-parameters", "generic-parameter");
238                         var srcCount = src == null ? 0 : src.Count;
239                         var tgtCount = tgt == null ? 0 : tgt.Count;
240
241                         if (srcCount == 0 && tgtCount == 0)
242                                 return;
243
244                         change.Append ("&lt;");
245                         for (int i = 0; i < Math.Max (srcCount, tgtCount); i++) {
246                                 if (i > 0)
247                                         change.Append (", ");
248                                 if (i >= srcCount) {
249                                         change.AppendAdded (tgt [i].GetTypeName ("name"), true);
250                                 } else if (i >= tgtCount) {
251                                         change.AppendRemoved (src [i].GetTypeName ("name"), true);
252                                 } else {
253                                         var srcName = src [i].GetTypeName ("name");
254                                         var tgtName = tgt [i].GetTypeName ("name");
255
256                                         if (srcName != tgtName) {
257                                                 change.AppendModified (srcName, tgtName, true);
258                                         } else {
259                                                 change.Append (srcName);
260                                         }
261                                         }
262                                 }
263                         change.Append ("&gt;");
264                 }
265
266                 protected string FormatValue (string type, string value)
267                 {
268                         if (value == null)
269                                 return "null";
270
271                         if (type == "string")
272                                 return "\"" + value + "\"";
273                         else if (type == "bool") {
274                                 switch (value) {
275                                 case "True":
276                                         return "true";
277                                 case "False":
278                                         return "false";
279                                 default:
280                                         return value;
281                                 }
282                         }
283
284                         return value;
285                 }
286
287                 protected void RenderParameters (XElement source, XElement target, ApiChange change)
288                 {
289                         var src = source.DescendantList ("parameters", "parameter");
290                         var tgt = target.DescendantList ("parameters", "parameter");
291                         var srcCount = src == null ? 0 : src.Count;
292                         var tgtCount = tgt == null ? 0 : tgt.Count;
293
294                         change.Append (" (");
295                         for (int i = 0; i < Math.Max (srcCount, tgtCount); i++) {
296                                 if (i > 0)
297                                         change.Append (", ");
298
299                                 if (i >= srcCount) {
300                                         change.AppendAdded (tgt [i].GetTypeName ("type") + " " + tgt [i].GetAttribute ("name"), true);
301                                 } else if (i >= tgtCount) {
302                                         change.AppendRemoved (src [i].GetTypeName ("type") + " " + src [i].GetAttribute ("name"), true);
303                                 } else {
304                                         var paramSourceType = src [i].GetTypeName ("type");
305                                         var paramTargetType = tgt [i].GetTypeName ("type");
306
307                                         var paramSourceName = src [i].GetAttribute ("name");
308                                         var paramTargetName = tgt [i].GetAttribute ("name");
309
310                                         if (paramSourceType != paramTargetType) {
311                                                 change.AppendModified (paramSourceType, paramTargetType, true);
312                                         } else {
313                                                 change.Append (paramSourceType);
314                                         }
315                                         change.Append (" ");
316                                         if (paramSourceName != paramTargetName) {
317                                                 change.AppendModified (paramSourceName, paramTargetName, false);
318                                         } else {
319                                                 change.Append (paramSourceName);
320                                         }
321
322                                         var optSource = src [i].Attribute ("optional");
323                                         var optTarget = tgt [i].Attribute ("optional");
324                                         var srcValue = FormatValue (paramSourceType, src [i].GetAttribute ("defaultValue"));
325                                         var tgtValue = FormatValue (paramTargetType, tgt [i].GetAttribute ("defaultValue"));
326
327                                         if (optSource != null) {
328                                                 if (optTarget != null) {
329                                                         change.Append (" = ");
330                                                         if (srcValue != tgtValue) {
331                                                                 change.AppendModified (srcValue, tgtValue, false);
332                                                         } else {
333                                                                 change.Append (tgtValue);
334                                                         }
335                                                 } else {
336                                                         change.AppendRemoved (" = " + srcValue);
337                                                 }
338                                         } else {
339                                                 if (optTarget != null)
340                                                         change.AppendAdded (" = " + tgtValue);
341                                         }
342                                 }
343                         }
344
345                         change.Append (")");
346
347                         // Ignore any parameter name changes if requested.
348                         if (State.IgnoreParameterNameChanges && !change.Breaking) {
349                                 change.AnyChange = false;
350                                 change.HasIgnoredChanges = true;
351                         }
352                 }
353
354                 void RenderVTable (MethodAttributes source, MethodAttributes target, ApiChange change)
355                 {
356                         var srcAbstract = (source & MethodAttributes.Abstract) == MethodAttributes.Abstract;
357                         var tgtAbstract = (target & MethodAttributes.Abstract) == MethodAttributes.Abstract;
358                         var srcFinal = (source & MethodAttributes.Final) == MethodAttributes.Final;
359                         var tgtFinal = (target & MethodAttributes.Final) == MethodAttributes.Final;
360                         var srcVirtual = (source & MethodAttributes.Virtual) == MethodAttributes.Virtual;
361                         var tgtVirtual = (target & MethodAttributes.Virtual) == MethodAttributes.Virtual;
362                         var srcOverride = (source & MethodAttributes.VtableLayoutMask) != MethodAttributes.NewSlot;
363                         var tgtOverride = (target & MethodAttributes.VtableLayoutMask) != MethodAttributes.NewSlot;
364
365                         var srcWord = srcVirtual ? (srcOverride ? "override" : "virtual") : string.Empty;
366                         var tgtWord = tgtVirtual ? (tgtOverride ? "override" : "virtual") : string.Empty;
367                         var breaking = srcWord.Length > 0 && tgtWord.Length == 0;
368
369                         if (srcAbstract) {
370                                 if (tgtAbstract) {
371                                         change.Append ("abstract ");
372                                 } else if (tgtVirtual) {
373                                         change.AppendModified ("abstract", tgtWord, false).Append (" ");
374                                 } else {
375                                         change.AppendRemoved ("abstract").Append (" ");
376                                 }
377                         } else {
378                                 if (tgtAbstract) {
379                                         change.AppendAdded ("abstract", true).Append (" ");
380                                 } else if (srcWord != tgtWord) {
381                                         if (!tgtFinal)
382                                                 change.AppendModified (srcWord, tgtWord, breaking).Append (" ");
383                                 } else if (tgtWord.Length > 0) {
384                                         change.Append (tgtWord).Append (" ");
385                                 } else if (srcWord.Length > 0) {
386                                         change.AppendRemoved (srcWord, breaking).Append (" ");
387                                 }
388                         }
389
390                         if (srcFinal) {
391                                 if (tgtFinal) {
392                                         change.Append ("final ");
393                                 } else {
394                                         change.AppendRemoved ("final", false).Append (" "); // removing 'final' is not a breaking change.
395                                 }
396                         } else {
397                                 if (tgtFinal && srcVirtual) {
398                                         change.AppendModified ("virtual", "final", true).Append (" "); // adding 'final' is a breaking change if the member was virtual
399                                 }
400                         }
401
402                         if (!srcVirtual && !srcFinal && tgtVirtual && tgtFinal) {
403                                 // existing member implements a member from a new interface
404                                 // this would show up as 'virtual final', which is redundant, so show nothing at all.
405                                 change.HasIgnoredChanges = true;
406                         }
407
408                         // Ignore non-breaking virtual changes.
409                         if (State.IgnoreVirtualChanges && !change.Breaking) {
410                                 change.AnyChange = false;
411                                 change.HasIgnoredChanges = true;
412                         }
413                 }
414
415                 protected string GetVisibility (MethodAttributes attr)
416                 {
417                         switch (attr) {
418                         case MethodAttributes.Private:
419                         case MethodAttributes.PrivateScope:
420                                 return "private";
421                         case MethodAttributes.Assembly:
422                                 return "internal";
423                         case MethodAttributes.FamANDAssem:
424                                 return "private internal";
425                         case MethodAttributes.FamORAssem:
426                                 return "protected"; // customers don't care about 'internal';
427                         case MethodAttributes.Family:
428                                 return "protected";
429                         case MethodAttributes.Public:
430                                 return "public";
431                         default:
432                                 throw new NotImplementedException ();
433                         }
434                 }
435
436                 protected void RenderVisibility (MethodAttributes source, MethodAttributes target, ApiChange diff)
437                 {
438                         source = source & MethodAttributes.MemberAccessMask;
439                         target = target & MethodAttributes.MemberAccessMask;
440
441                         if (source == target) {
442                                 diff.Append (GetVisibility (target));
443                         } else {
444                                 var breaking = false;
445                                 switch (source) {
446                                 case MethodAttributes.Private:
447                                 case MethodAttributes.Assembly:
448                                 case MethodAttributes.FamANDAssem:
449                                         break; // these are not publicly visible, thus not breaking
450                                 case MethodAttributes.FamORAssem:
451                                 case MethodAttributes.Family:
452                                         switch (target) {
453                                         case MethodAttributes.Public:
454                                                 // to public is not a breaking change
455                                                 break;
456                                         case MethodAttributes.Family:
457                                         case MethodAttributes.FamORAssem:
458                                                 // not a breaking change, but should still show up in diff
459                                                 break;
460                                         default:
461                                                 // anything else is a breaking change
462                                                 breaking = true;
463                                                 break;
464                                         }
465                                         break;
466                                 case MethodAttributes.Public:
467                                 default:
468                                         // any change from public is breaking.
469                                         breaking = true;
470                                         break;
471                                 }
472
473                                 diff.AppendModified (GetVisibility (source), GetVisibility (target), breaking);
474                         }
475                         diff.Append (" ");
476                 }
477
478                 protected void RenderStatic (MethodAttributes src, MethodAttributes tgt, ApiChange diff)
479                 {
480                         var srcStatic = (src & MethodAttributes.Static) == MethodAttributes.Static;
481                         var tgtStatic = (tgt & MethodAttributes.Static) == MethodAttributes.Static;
482
483                         if (srcStatic != tgtStatic) {
484                                 if (srcStatic) {
485                                         diff.AppendRemoved ("static", true).Append (" ");
486                                 } else {
487                                         diff.AppendAdded ("static", true).Append (" ");
488                                 }
489                         }
490                 }
491
492                 protected void RenderMethodAttributes (MethodAttributes src, MethodAttributes tgt, ApiChange diff)
493                 {
494                         RenderStatic (src, tgt, diff);
495                         RenderVisibility (src & MethodAttributes.MemberAccessMask, tgt & MethodAttributes.MemberAccessMask, diff);
496                         RenderVTable (src, tgt, diff);
497                 }
498
499                 protected void RenderMethodAttributes (XElement source, XElement target, ApiChange diff)
500                 {
501                         RenderMethodAttributes (source.GetMethodAttributes (), target.GetMethodAttributes (), diff);
502                 }
503
504                 protected void RemoveAttributes (XElement element)
505                 {
506                         var srcAttributes = element.Element ("attributes");
507                         if (srcAttributes != null)
508                                 srcAttributes.Remove ();
509
510                         foreach (var el in element.Elements ())
511                                 RemoveAttributes (el);
512                 }
513
514                 protected void RenderAttributes (XElement source, XElement target, ApiChanges changes)
515                 {
516                         var srcObsolete = source.GetObsoleteMessage ();
517                         var tgtObsolete = target.GetObsoleteMessage ();
518
519                         if (srcObsolete == tgtObsolete)
520                                 return; // nothing changed
521
522                         if (srcObsolete == null) {
523                                 if (tgtObsolete == null)
524                                         return; // neither is obsolete
525                                 var change = new ApiChange ();
526                                 change.Header = "Obsoleted " + GroupName;
527                                 if (State.Colorize)
528                                         change.Append ("<font color='gray'>");
529                                 change.Append ("[Obsolete (");
530                                 if (tgtObsolete != string.Empty)
531                                         change.Append ("\"").Append (tgtObsolete).Append ("\"");
532                                 change.Append ("]\n");
533                                 change.Append (GetDescription (target));
534                                 if (State.Colorize)
535                                         change.Append ("</font>");
536                                 change.AnyChange = true;
537                                 changes.Add (source, target, change);
538                         } else if (tgtObsolete == null) {
539                                 // Made non-obsolete. Do we care to report this?
540                         } else {
541                                 // Obsolete message changed. Do we care to report this?
542                         }
543                 }
544
545                 protected void RenderName (XElement source, XElement target, ApiChange change)
546                 {
547                         var name = target.GetAttribute ("name");
548                         // show the constructor as it would be defined in C#
549                         name = name.Replace (".ctor", State.Type);
550
551                         var p = name.IndexOf ('(');
552                         if (p >= 0)
553                                 name = name.Substring (0, p);
554
555                         change.Append (name);
556                 }
557
558         }
559 }