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