3 // Sebastien Pouliot <sebastien@xamarin.com>
5 // Copyright 2013-2014 Xamarin Inc. http://www.xamarin.com
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:
15 // The above copyright notice and this permission notice shall be
16 // included in all copies or substantial portions of the Software.
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.
28 using System.Collections.Generic;
30 using System.Reflection;
32 using System.Xml.Linq;
34 namespace Xamarin.ApiDiff {
36 public abstract class MemberComparer : Comparer {
38 // true if this is the first element being added or removed in the group being rendered
41 public abstract string GroupName { get; }
42 public abstract string ElementName { get; }
44 public void Compare (XElement source, XElement target)
46 var s = source.Element (GroupName);
47 var t = target.Element (GroupName);
48 if (XNode.DeepEquals (s, t))
52 Add (t.Elements (ElementName));
53 } else if (t == null) {
54 Remove (s.Elements (ElementName));
56 Compare (s.Elements (ElementName), t.Elements (ElementName));
60 public override void SetContext (XElement current)
64 string GetContainingType (XElement el)
66 return el.Ancestors ("class").First ().Attribute ("type").Value;
69 bool IsInInterface (XElement el)
71 return GetContainingType (el) == "interface";
74 public XElement Source { get; set; }
76 public virtual bool Find (XElement e)
78 return e.GetAttribute ("name") == Source.GetAttribute ("name");
81 XElement Find (IEnumerable<XElement> target)
83 return State.Lax ? target.FirstOrDefault (Find) : target.SingleOrDefault (Find);
86 public override void Compare (IEnumerable<XElement> source, IEnumerable<XElement> target)
91 foreach (var s in source) {
94 var t = Find (target);
96 // not in target, it was removed
101 if (Equals (s, t, modified))
104 Modified (s, t, modified);
107 // delayed, that way we show "Modified", "Added" and then "Removed"
112 // remaining == newly added in target
116 void Add (IEnumerable<XElement> elements)
119 foreach (var item in elements) {
121 if (State.IgnoreAdded.Any (re => re.IsMatch (GetDescription (item))))
124 BeforeAdding (elements);
133 void Modify (ApiChanges modified)
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);
143 Output.WriteLine ("</pre>");
147 void Remove (IEnumerable<XElement> elements)
150 foreach (var item in elements) {
151 if (State.IgnoreRemoved.Any (re => re.IsMatch (GetDescription (item))))
155 BeforeRemoving (elements);
164 public abstract string GetDescription (XElement e);
166 protected StringBuilder GetObsoleteMessage (XElement e)
168 var sb = new StringBuilder ();
169 string o = e.GetObsoleteMessage ();
171 sb.Append ("[Obsolete");
173 sb.Append (" (\"").Append (o).Append ("\")");
175 for (int i = 0; i < State.Indent + 1; i++)
181 public override bool Equals (XElement source, XElement target, ApiChanges changes)
183 RenderAttributes (source, target, changes);
185 // We don't want to compare attributes.
186 RemoveAttributes (source);
187 RemoveAttributes (target);
189 return base.Equals (source, target, changes);
192 public virtual void BeforeAdding (IEnumerable<XElement> list)
195 Output.WriteLine ("<p>Added {0}:</p>", list.Count () > 1 ? GroupName : ElementName);
197 bool isInterface = list.Count () > 0 && IsInInterface (list.First ());
198 Output.WriteLine (State.Colorize ? string.Format ("<pre style='color: {0}'>", isInterface ? "red" : "green") : "<pre>");
201 public override void Added (XElement target)
203 var o = GetObsoleteMessage (target);
204 if (!first && (o.Length > 0))
206 Indent ().WriteLine ("\t{0}{1}", o, GetDescription (target));
210 public virtual void AfterAdding ()
212 Output.WriteLine ("</pre>");;
215 public override void Modified (XElement source, XElement target, ApiChanges change)
219 public virtual void BeforeRemoving (IEnumerable<XElement> list)
222 Output.WriteLine ("<p>Removed {0}:</p>\n", list.Count () > 1 ? GroupName : ElementName);
223 Output.WriteLine (State.Colorize ? "<pre style='color: red'>" : "<pre>");
226 public override void Removed (XElement source)
228 var o = GetObsoleteMessage (source);
229 if (!first && (o.Length > 0))
231 Indent ().WriteLine ("\t{0}{1}", o, GetDescription (source));
235 public virtual void AfterRemoving ()
237 Output.WriteLine ("</pre>");;
240 string RenderGenericParameter (XElement gp)
242 var sb = new StringBuilder ();
243 sb.Append (gp.GetTypeName ("name"));
245 var constraints = gp.DescendantList ("generic-parameter-constraints", "generic-parameter-constraint");
246 if (constraints != null && constraints.Count > 0) {
248 for (int i = 0; i < constraints.Count; i++) {
251 sb.Append (constraints [i].GetTypeName ("name"));
254 return sb.ToString ();
257 protected void RenderGenericParameters (XElement source, XElement target, ApiChange change)
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;
264 if (srcCount == 0 && tgtCount == 0)
267 change.Append ("<");
268 for (int i = 0; i < Math.Max (srcCount, tgtCount); i++) {
270 change.Append (", ");
272 change.AppendAdded (RenderGenericParameter (tgt [i]), true);
273 } else if (i >= tgtCount) {
274 change.AppendRemoved (RenderGenericParameter (src [i]), true);
276 var srcName = RenderGenericParameter (src [i]);
277 var tgtName = RenderGenericParameter (tgt [i]);
279 if (srcName != tgtName) {
280 change.AppendModified (srcName, tgtName, true);
282 change.Append (srcName);
286 change.Append (">");
289 protected string FormatValue (string type, string value)
294 if (type == "string")
295 return "\"" + value + "\"";
296 else if (type == "bool") {
310 protected void RenderParameters (XElement source, XElement target, ApiChange change)
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;
317 change.Append (" (");
318 for (int i = 0; i < Math.Max (srcCount, tgtCount); i++) {
320 change.Append (", ");
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);
327 var paramSourceType = src [i].GetTypeName ("type");
328 var paramTargetType = tgt [i].GetTypeName ("type");
330 var paramSourceName = src [i].GetAttribute ("name");
331 var paramTargetName = tgt [i].GetAttribute ("name");
333 if (paramSourceType != paramTargetType) {
334 change.AppendModified (paramSourceType, paramTargetType, true);
336 change.Append (paramSourceType);
339 if (paramSourceName != paramTargetName) {
340 change.AppendModified (paramSourceName, paramTargetName, false);
342 change.Append (paramSourceName);
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"));
350 if (optSource != null) {
351 if (optTarget != null) {
352 change.Append (" = ");
353 if (srcValue != tgtValue) {
354 change.AppendModified (srcValue, tgtValue, false);
356 change.Append (tgtValue);
359 change.AppendRemoved (" = " + srcValue);
362 if (optTarget != null)
363 change.AppendAdded (" = " + tgtValue);
370 // Ignore any parameter name changes if requested.
371 if (State.IgnoreParameterNameChanges && !change.Breaking) {
372 change.AnyChange = false;
373 change.HasIgnoredChanges = true;
377 void RenderVTable (MethodAttributes source, MethodAttributes target, ApiChange change)
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;
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;
394 change.Append ("abstract ");
395 } else if (tgtVirtual) {
396 change.AppendModified ("abstract", tgtWord, false).Append (" ");
398 change.AppendRemoved ("abstract").Append (" ");
402 change.AppendAdded ("abstract", true).Append (" ");
403 } else if (srcWord != tgtWord) {
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 (" ");
415 change.Append ("final ");
417 change.AppendRemoved ("final", false).Append (" "); // removing 'final' is not a breaking change.
420 if (tgtFinal && srcVirtual) {
421 change.AppendModified ("virtual", "final", true).Append (" "); // adding 'final' is a breaking change if the member was virtual
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;
431 // Ignore non-breaking virtual changes.
432 if (State.IgnoreVirtualChanges && !change.Breaking) {
433 change.AnyChange = false;
434 change.HasIgnoredChanges = true;
437 var tgtSecurity = (source & MethodAttributes.HasSecurity) == MethodAttributes.HasSecurity;
438 var srcSecurity = (target & MethodAttributes.HasSecurity) == MethodAttributes.HasSecurity;
440 if (tgtSecurity != srcSecurity)
441 change.HasIgnoredChanges = true;
443 var srcPInvoke = (source & MethodAttributes.PinvokeImpl) == MethodAttributes.PinvokeImpl;
444 var tgtPInvoke = (target & MethodAttributes.PinvokeImpl) == MethodAttributes.PinvokeImpl;
445 if (srcPInvoke != tgtPInvoke)
446 change.HasIgnoredChanges = true;
449 protected string GetVisibility (MethodAttributes attr)
452 case MethodAttributes.Private:
453 case MethodAttributes.PrivateScope:
455 case MethodAttributes.Assembly:
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:
463 case MethodAttributes.Public:
466 throw new NotImplementedException ();
470 protected void RenderVisibility (MethodAttributes source, MethodAttributes target, ApiChange diff)
472 source = source & MethodAttributes.MemberAccessMask;
473 target = target & MethodAttributes.MemberAccessMask;
475 if (source == target) {
476 diff.Append (GetVisibility (target));
478 var breaking = false;
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:
487 case MethodAttributes.Public:
488 // to public is not a breaking change
490 case MethodAttributes.Family:
491 case MethodAttributes.FamORAssem:
492 // not a breaking change, but should still show up in diff
495 // anything else is a breaking change
500 case MethodAttributes.Public:
502 // any change from public is breaking.
507 diff.AppendModified (GetVisibility (source), GetVisibility (target), breaking);
512 protected void RenderStatic (MethodAttributes src, MethodAttributes tgt, ApiChange diff)
514 var srcStatic = (src & MethodAttributes.Static) == MethodAttributes.Static;
515 var tgtStatic = (tgt & MethodAttributes.Static) == MethodAttributes.Static;
517 if (srcStatic != tgtStatic) {
519 diff.AppendRemoved ("static", true).Append (" ");
521 diff.AppendAdded ("static", true).Append (" ");
526 protected void RenderMethodAttributes (MethodAttributes src, MethodAttributes tgt, ApiChange diff)
528 RenderStatic (src, tgt, diff);
529 RenderVisibility (src & MethodAttributes.MemberAccessMask, tgt & MethodAttributes.MemberAccessMask, diff);
530 RenderVTable (src, tgt, diff);
533 protected void RenderMethodAttributes (XElement source, XElement target, ApiChange diff)
535 RenderMethodAttributes (source.GetMethodAttributes (), target.GetMethodAttributes (), diff);
538 protected void RemoveAttributes (XElement element)
540 var srcAttributes = element.Element ("attributes");
541 if (srcAttributes != null)
542 srcAttributes.Remove ();
544 foreach (var el in element.Elements ())
545 RemoveAttributes (el);
548 protected void RenderAttributes (XElement source, XElement target, ApiChanges changes)
550 var srcObsolete = source.GetObsoleteMessage ();
551 var tgtObsolete = target.GetObsoleteMessage ();
553 if (srcObsolete == tgtObsolete)
554 return; // nothing changed
556 if (srcObsolete == null) {
557 if (tgtObsolete == null)
558 return; // neither is obsolete
559 var change = new ApiChange ();
560 change.Header = "Obsoleted " + GroupName;
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));
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?
575 // Obsolete message changed. Do we care to report this?
579 protected void RenderName (XElement source, XElement target, ApiChange change)
581 var name = target.GetAttribute ("name");
582 // show the constructor as it would be defined in C#
583 name = name.Replace (".ctor", State.Type);
585 var p = name.IndexOf ('(');
587 name = name.Substring (0, p);
589 change.Append (name);