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 protected virtual bool IsBreakingRemoval (XElement e)
49 public void Compare (XElement source, XElement target)
51 var s = source.Element (GroupName);
52 var t = target.Element (GroupName);
53 if (XNode.DeepEquals (s, t))
57 Add (t.Elements (ElementName));
58 } else if (t == null) {
59 Remove (s.Elements (ElementName));
61 Compare (s.Elements (ElementName), t.Elements (ElementName));
65 public override void SetContext (XElement current)
69 string GetContainingType (XElement el)
71 return el.Ancestors ("class").First ().Attribute ("type").Value;
74 bool IsInInterface (XElement el)
76 return GetContainingType (el) == "interface";
79 public XElement Source { get; set; }
81 public virtual bool Find (XElement e)
83 return e.GetAttribute ("name") == Source.GetAttribute ("name");
86 XElement Find (IEnumerable<XElement> target)
88 return State.Lax ? target.FirstOrDefault (Find) : target.SingleOrDefault (Find);
91 public override void Compare (IEnumerable<XElement> source, IEnumerable<XElement> target)
96 foreach (var s in source) {
99 var t = Find (target);
101 // not in target, it was removed
106 if (Equals (s, t, modified))
109 Modified (s, t, modified);
112 // delayed, that way we show "Modified", "Added" and then "Removed"
117 // remaining == newly added in target
121 void Add (IEnumerable<XElement> elements)
124 foreach (var item in elements) {
126 if (State.IgnoreAdded.Any (re => re.IsMatch (GetDescription (item))))
129 BeforeAdding (elements);
138 void Modify (ApiChanges modified)
140 foreach (var changes in modified) {
141 Output.WriteLine ("<p>{0}:</p>", changes.Key);
142 Output.WriteLine ("<pre>");
143 foreach (var element in changes.Value) {
144 Output.Write ("<div {0}>", element.Breaking ? "data-is-breaking" : "data-is-non-breaking");
145 foreach (var line in element.Member.ToString ().Split ('\n'))
146 Output.WriteLine ("\t{0}", line);
147 Output.Write ("</div>");
150 Output.WriteLine ("</pre>");
154 void Remove (IEnumerable<XElement> elements)
157 foreach (var item in elements) {
158 if (State.IgnoreRemoved.Any (re => re.IsMatch (GetDescription (item))))
162 BeforeRemoving (elements);
171 public abstract string GetDescription (XElement e);
173 protected StringBuilder GetObsoleteMessage (XElement e)
175 var sb = new StringBuilder ();
176 string o = e.GetObsoleteMessage ();
178 sb.Append ("[Obsolete");
180 sb.Append (" (\"").Append (o).Append ("\")");
182 for (int i = 0; i < State.Indent + 1; i++)
188 public override bool Equals (XElement source, XElement target, ApiChanges changes)
190 RenderAttributes (source, target, changes);
192 // We don't want to compare attributes.
193 RemoveAttributes (source);
194 RemoveAttributes (target);
196 return base.Equals (source, target, changes);
199 public virtual void BeforeAdding (IEnumerable<XElement> list)
202 bool isInterface = list.Count () > 0 && IsInInterface (list.First ());
203 Output.WriteLine ("<div>");
204 Output.WriteLine ("<p>Added {0}:</p>", list.Count () > 1 ? GroupName : ElementName);
205 Output.WriteLine ("<pre>");
208 public override void Added (XElement target)
210 var o = GetObsoleteMessage (target);
211 if (!first && (o.Length > 0))
214 bool isInterface = IsInInterface (target);
215 Output.Write ("\t<span class='added added-{0} {1}' {2}>", ElementName, isInterface ? "breaking" : string.Empty, isInterface ? "data-is-breaking" : "data-is-non-breaking");
216 Output.Write ("{0}{1}", o, GetDescription (target));
217 Output.WriteLine ("</span>");
221 public virtual void AfterAdding ()
223 Output.WriteLine ("</pre>");
224 Output.WriteLine ("</div>");
227 public override void Modified (XElement source, XElement target, ApiChanges change)
231 public virtual void BeforeRemoving (IEnumerable<XElement> list)
234 Output.WriteLine ("<p>Removed {0}:</p>\n", list.Count () > 1 ? GroupName : ElementName);
235 Output.WriteLine ("<pre>");
238 public override void Removed (XElement source)
240 var o = GetObsoleteMessage (source);
241 if (!first && (o.Length > 0))
244 bool is_breaking = IsBreakingRemoval (source);
247 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);
248 Output.Write ("{0}{1}", o, GetDescription (source));
249 Output.WriteLine ("</span>");
253 public virtual void AfterRemoving ()
255 Output.WriteLine ("</pre>");;
258 string RenderGenericParameter (XElement gp)
260 var sb = new StringBuilder ();
261 sb.Append (gp.GetTypeName ("name"));
263 var constraints = gp.DescendantList ("generic-parameter-constraints", "generic-parameter-constraint");
264 if (constraints != null && constraints.Count > 0) {
266 for (int i = 0; i < constraints.Count; i++) {
269 sb.Append (constraints [i].GetTypeName ("name"));
272 return sb.ToString ();
275 protected void RenderGenericParameters (XElement source, XElement target, ApiChange change)
277 var src = source.DescendantList ("generic-parameters", "generic-parameter");
278 var tgt = target.DescendantList ("generic-parameters", "generic-parameter");
279 var srcCount = src == null ? 0 : src.Count;
280 var tgtCount = tgt == null ? 0 : tgt.Count;
282 if (srcCount == 0 && tgtCount == 0)
285 change.Append ("<");
286 for (int i = 0; i < Math.Max (srcCount, tgtCount); i++) {
288 change.Append (", ");
290 change.AppendAdded (RenderGenericParameter (tgt [i]), true);
291 } else if (i >= tgtCount) {
292 change.AppendRemoved (RenderGenericParameter (src [i]), true);
294 var srcName = RenderGenericParameter (src [i]);
295 var tgtName = RenderGenericParameter (tgt [i]);
297 if (srcName != tgtName) {
298 change.AppendModified (srcName, tgtName, true);
300 change.Append (srcName);
304 change.Append (">");
307 protected string FormatValue (string type, string value)
312 if (type == "string")
313 return "\"" + value + "\"";
314 else if (type == "bool") {
328 protected void RenderParameters (XElement source, XElement target, ApiChange change)
330 var src = source.DescendantList ("parameters", "parameter");
331 var tgt = target.DescendantList ("parameters", "parameter");
332 var srcCount = src == null ? 0 : src.Count;
333 var tgtCount = tgt == null ? 0 : tgt.Count;
335 change.Append (" (");
336 for (int i = 0; i < Math.Max (srcCount, tgtCount); i++) {
338 change.Append (", ");
341 change.AppendAdded (tgt [i].GetTypeName ("type") + " " + tgt [i].GetAttribute ("name"), true);
342 } else if (i >= tgtCount) {
343 change.AppendRemoved (src [i].GetTypeName ("type") + " " + src [i].GetAttribute ("name"), true);
345 var paramSourceType = src [i].GetTypeName ("type");
346 var paramTargetType = tgt [i].GetTypeName ("type");
348 var paramSourceName = src [i].GetAttribute ("name");
349 var paramTargetName = tgt [i].GetAttribute ("name");
351 if (paramSourceType != paramTargetType) {
352 change.AppendModified (paramSourceType, paramTargetType, true);
354 change.Append (paramSourceType);
357 if (paramSourceName != paramTargetName) {
358 change.AppendModified (paramSourceName, paramTargetName, false);
360 change.Append (paramSourceName);
363 var optSource = src [i].Attribute ("optional");
364 var optTarget = tgt [i].Attribute ("optional");
365 var srcValue = FormatValue (paramSourceType, src [i].GetAttribute ("defaultValue"));
366 var tgtValue = FormatValue (paramTargetType, tgt [i].GetAttribute ("defaultValue"));
368 if (optSource != null) {
369 if (optTarget != null) {
370 change.Append (" = ");
371 if (srcValue != tgtValue) {
372 change.AppendModified (srcValue, tgtValue, false);
374 change.Append (tgtValue);
377 change.AppendRemoved (" = " + srcValue);
380 if (optTarget != null)
381 change.AppendAdded (" = " + tgtValue);
388 // Ignore any parameter name changes if requested.
389 if (State.IgnoreParameterNameChanges && !change.Breaking) {
390 change.AnyChange = false;
391 change.HasIgnoredChanges = true;
395 void RenderVTable (MethodAttributes source, MethodAttributes target, ApiChange change)
397 var srcAbstract = (source & MethodAttributes.Abstract) == MethodAttributes.Abstract;
398 var tgtAbstract = (target & MethodAttributes.Abstract) == MethodAttributes.Abstract;
399 var srcFinal = (source & MethodAttributes.Final) == MethodAttributes.Final;
400 var tgtFinal = (target & MethodAttributes.Final) == MethodAttributes.Final;
401 var srcVirtual = (source & MethodAttributes.Virtual) == MethodAttributes.Virtual;
402 var tgtVirtual = (target & MethodAttributes.Virtual) == MethodAttributes.Virtual;
403 var srcOverride = (source & MethodAttributes.VtableLayoutMask) != MethodAttributes.NewSlot;
404 var tgtOverride = (target & MethodAttributes.VtableLayoutMask) != MethodAttributes.NewSlot;
406 var srcWord = srcVirtual ? (srcOverride ? "override" : "virtual") : string.Empty;
407 var tgtWord = tgtVirtual ? (tgtOverride ? "override" : "virtual") : string.Empty;
408 var breaking = srcWord.Length > 0 && tgtWord.Length == 0;
412 change.Append ("abstract ");
413 } else if (tgtVirtual) {
414 change.AppendModified ("abstract", tgtWord, false).Append (" ");
416 change.AppendRemoved ("abstract").Append (" ");
420 change.AppendAdded ("abstract", true).Append (" ");
421 } else if (srcWord != tgtWord) {
423 change.AppendModified (srcWord, tgtWord, breaking).Append (" ");
424 } else if (tgtWord.Length > 0) {
425 change.Append (tgtWord).Append (" ");
426 } else if (srcWord.Length > 0) {
427 change.AppendRemoved (srcWord, breaking).Append (" ");
433 change.Append ("final ");
435 change.AppendRemoved ("final", false).Append (" "); // removing 'final' is not a breaking change.
438 if (tgtFinal && srcVirtual) {
439 change.AppendModified ("virtual", "final", true).Append (" "); // adding 'final' is a breaking change if the member was virtual
443 if (!srcVirtual && !srcFinal && tgtVirtual && tgtFinal) {
444 // existing member implements a member from a new interface
445 // this would show up as 'virtual final', which is redundant, so show nothing at all.
446 change.HasIgnoredChanges = true;
449 // Ignore non-breaking virtual changes.
450 if (State.IgnoreVirtualChanges && !change.Breaking) {
451 change.AnyChange = false;
452 change.HasIgnoredChanges = true;
455 var tgtSecurity = (source & MethodAttributes.HasSecurity) == MethodAttributes.HasSecurity;
456 var srcSecurity = (target & MethodAttributes.HasSecurity) == MethodAttributes.HasSecurity;
458 if (tgtSecurity != srcSecurity)
459 change.HasIgnoredChanges = true;
461 var srcPInvoke = (source & MethodAttributes.PinvokeImpl) == MethodAttributes.PinvokeImpl;
462 var tgtPInvoke = (target & MethodAttributes.PinvokeImpl) == MethodAttributes.PinvokeImpl;
463 if (srcPInvoke != tgtPInvoke)
464 change.HasIgnoredChanges = true;
467 protected string GetVisibility (MethodAttributes attr)
470 case MethodAttributes.Private:
471 case MethodAttributes.PrivateScope:
473 case MethodAttributes.Assembly:
475 case MethodAttributes.FamANDAssem:
476 return "private internal";
477 case MethodAttributes.FamORAssem:
478 return "protected"; // customers don't care about 'internal';
479 case MethodAttributes.Family:
481 case MethodAttributes.Public:
484 throw new NotImplementedException ();
488 protected void RenderVisibility (MethodAttributes source, MethodAttributes target, ApiChange diff)
490 source = source & MethodAttributes.MemberAccessMask;
491 target = target & MethodAttributes.MemberAccessMask;
493 if (source == target) {
494 diff.Append (GetVisibility (target));
496 var breaking = false;
498 case MethodAttributes.Private:
499 case MethodAttributes.Assembly:
500 case MethodAttributes.FamANDAssem:
501 break; // these are not publicly visible, thus not breaking
502 case MethodAttributes.FamORAssem:
503 case MethodAttributes.Family:
505 case MethodAttributes.Public:
506 // to public is not a breaking change
508 case MethodAttributes.Family:
509 case MethodAttributes.FamORAssem:
510 // not a breaking change, but should still show up in diff
513 // anything else is a breaking change
518 case MethodAttributes.Public:
520 // any change from public is breaking.
525 diff.AppendModified (GetVisibility (source), GetVisibility (target), breaking);
530 protected void RenderStatic (MethodAttributes src, MethodAttributes tgt, ApiChange diff)
532 var srcStatic = (src & MethodAttributes.Static) == MethodAttributes.Static;
533 var tgtStatic = (tgt & MethodAttributes.Static) == MethodAttributes.Static;
535 if (srcStatic != tgtStatic) {
537 diff.AppendRemoved ("static", true).Append (" ");
539 diff.AppendAdded ("static", true).Append (" ");
544 protected void RenderMethodAttributes (MethodAttributes src, MethodAttributes tgt, ApiChange diff)
546 RenderStatic (src, tgt, diff);
547 RenderVisibility (src & MethodAttributes.MemberAccessMask, tgt & MethodAttributes.MemberAccessMask, diff);
548 RenderVTable (src, tgt, diff);
551 protected void RenderMethodAttributes (XElement source, XElement target, ApiChange diff)
553 RenderMethodAttributes (source.GetMethodAttributes (), target.GetMethodAttributes (), diff);
556 protected void RemoveAttributes (XElement element)
558 var srcAttributes = element.Element ("attributes");
559 if (srcAttributes != null)
560 srcAttributes.Remove ();
562 foreach (var el in element.Elements ())
563 RemoveAttributes (el);
566 protected void RenderAttributes (XElement source, XElement target, ApiChanges changes)
568 var srcObsolete = source.GetObsoleteMessage ();
569 var tgtObsolete = target.GetObsoleteMessage ();
571 if (srcObsolete == tgtObsolete)
572 return; // nothing changed
574 if (srcObsolete == null) {
575 if (tgtObsolete == null)
576 return; // neither is obsolete
577 var change = new ApiChange ();
578 change.Header = "Obsoleted " + GroupName;
579 change.Append (string.Format ("<span class='obsolete obsolete-{0}' data-is-non-breaking>", ElementName));
580 change.Append ("[Obsolete (");
581 if (tgtObsolete != string.Empty)
582 change.Append ("\"").Append (tgtObsolete).Append ("\"");
583 change.Append (")]\n");
584 change.Append (GetDescription (target));
585 change.Append ("</span>");
586 change.AnyChange = true;
587 changes.Add (source, target, change);
588 } else if (tgtObsolete == null) {
589 // Made non-obsolete. Do we care to report this?
591 // Obsolete message changed. Do we care to report this?
595 protected void RenderName (XElement source, XElement target, ApiChange change)
597 var name = target.GetAttribute ("name");
598 // show the constructor as it would be defined in C#
599 name = name.Replace (".ctor", State.Type);
601 var p = name.IndexOf ('(');
603 name = name.Substring (0, p);
605 change.Append (name);