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 if (State.IgnoreNonbreaking && changes.Value.All (c => !c.Breaking))
143 Output.WriteLine ("<p>{0}:</p>", changes.Key);
144 Output.WriteLine ("<pre>");
145 foreach (var element in changes.Value) {
146 if (State.IgnoreNonbreaking && !element.Breaking)
148 Output.Write ("<div {0}>", element.Breaking ? "data-is-breaking" : "data-is-non-breaking");
149 foreach (var line in element.Member.ToString ().Split ('\n'))
150 Output.WriteLine ("\t{0}", line);
151 Output.Write ("</div>");
154 Output.WriteLine ("</pre>");
158 void Remove (IEnumerable<XElement> elements)
161 foreach (var item in elements) {
162 if (State.IgnoreRemoved.Any (re => re.IsMatch (GetDescription (item))))
165 if (State.IgnoreNonbreaking && !IsBreakingRemoval (item))
168 BeforeRemoving (elements);
177 public abstract string GetDescription (XElement e);
179 protected StringBuilder GetObsoleteMessage (XElement e)
181 var sb = new StringBuilder ();
182 string o = e.GetObsoleteMessage ();
184 sb.Append ("[Obsolete");
186 sb.Append (" (\"").Append (o).Append ("\")");
188 for (int i = 0; i < State.Indent + 1; i++)
194 public override bool Equals (XElement source, XElement target, ApiChanges changes)
196 RenderAttributes (source, target, changes);
198 // We don't want to compare attributes.
199 RemoveAttributes (source);
200 RemoveAttributes (target);
202 return base.Equals (source, target, changes);
205 public virtual void BeforeAdding (IEnumerable<XElement> list)
208 Output.WriteLine ("<div>");
209 Output.WriteLine ("<p>Added {0}:</p>", list.Count () > 1 ? GroupName : ElementName);
210 Output.WriteLine ("<pre>");
213 public override void Added (XElement target, bool wasParentAdded)
215 var o = GetObsoleteMessage (target);
216 if (!first && (o.Length > 0))
219 bool isInterfaceBreakingChange = !wasParentAdded && IsInInterface (target);
220 Output.Write ("\t<span class='added added-{0} {1}' {2}>", ElementName, isInterfaceBreakingChange ? "breaking" : string.Empty, isInterfaceBreakingChange ? "data-is-breaking" : "data-is-non-breaking");
221 Output.Write ("{0}{1}", o, GetDescription (target));
222 Output.WriteLine ("</span>");
226 public virtual void AfterAdding ()
228 Output.WriteLine ("</pre>");
229 Output.WriteLine ("</div>");
232 public override void Modified (XElement source, XElement target, ApiChanges change)
236 public virtual void BeforeRemoving (IEnumerable<XElement> list)
239 Output.WriteLine ("<p>Removed {0}:</p>\n", list.Count () > 1 ? GroupName : ElementName);
240 Output.WriteLine ("<pre>");
243 public override void Removed (XElement source)
245 var o = GetObsoleteMessage (source);
246 if (!first && (o.Length > 0))
249 bool is_breaking = IsBreakingRemoval (source);
252 Output.Write ("\t<span class='removed removed-{0} {2}' {1}>", ElementName, is_breaking ? "data-is-breaking" : "data-is-non-breaking", is_breaking ? "breaking" : string.Empty);
253 Output.Write ("{0}{1}", o, GetDescription (source));
254 Output.WriteLine ("</span>");
258 public virtual void AfterRemoving ()
260 Output.WriteLine ("</pre>");;
263 string RenderGenericParameter (XElement gp)
265 var sb = new StringBuilder ();
266 sb.Append (gp.GetTypeName ("name"));
268 var constraints = gp.DescendantList ("generic-parameter-constraints", "generic-parameter-constraint");
269 if (constraints != null && constraints.Count > 0) {
271 for (int i = 0; i < constraints.Count; i++) {
274 sb.Append (constraints [i].GetTypeName ("name"));
277 return sb.ToString ();
280 protected void RenderGenericParameters (XElement source, XElement target, ApiChange change)
282 var src = source.DescendantList ("generic-parameters", "generic-parameter");
283 var tgt = target.DescendantList ("generic-parameters", "generic-parameter");
284 var srcCount = src == null ? 0 : src.Count;
285 var tgtCount = tgt == null ? 0 : tgt.Count;
287 if (srcCount == 0 && tgtCount == 0)
290 change.Append ("<");
291 for (int i = 0; i < Math.Max (srcCount, tgtCount); i++) {
293 change.Append (", ");
295 change.AppendAdded (RenderGenericParameter (tgt [i]), true);
296 } else if (i >= tgtCount) {
297 change.AppendRemoved (RenderGenericParameter (src [i]), true);
299 var srcName = RenderGenericParameter (src [i]);
300 var tgtName = RenderGenericParameter (tgt [i]);
302 if (srcName != tgtName) {
303 change.AppendModified (srcName, tgtName, true);
305 change.Append (srcName);
309 change.Append (">");
312 protected string FormatValue (string type, string value)
317 if (type == "string")
318 return "\"" + value + "\"";
319 else if (type == "bool") {
333 protected void RenderParameters (XElement source, XElement target, ApiChange change)
335 var src = source.DescendantList ("parameters", "parameter");
336 var tgt = target.DescendantList ("parameters", "parameter");
337 var srcCount = src == null ? 0 : src.Count;
338 var tgtCount = tgt == null ? 0 : tgt.Count;
340 change.Append (" (");
341 for (int i = 0; i < Math.Max (srcCount, tgtCount); i++) {
343 change.Append (", ");
345 string mods_tgt = tgt [i].GetAttribute ("direction") ?? "";
346 string mods_src = src [i].GetAttribute ("direction") ?? "";
348 if (mods_tgt.Length > 0)
349 mods_tgt = mods_tgt + " ";
351 if (mods_src.Length > 0)
352 mods_src = mods_src + " ";
355 change.AppendAdded (mods_tgt + tgt [i].GetTypeName ("type") + " " + tgt [i].GetAttribute ("name"), true);
356 } else if (i >= tgtCount) {
357 change.AppendRemoved (mods_src + src [i].GetTypeName ("type") + " " + src [i].GetAttribute ("name"), true);
359 var paramSourceType = src [i].GetTypeName ("type");
360 var paramTargetType = tgt [i].GetTypeName ("type");
362 var paramSourceName = src [i].GetAttribute ("name");
363 var paramTargetName = tgt [i].GetAttribute ("name");
365 if (mods_src != mods_tgt) {
366 change.AppendModified (mods_src, mods_tgt, true);
368 change.Append (mods_src);
371 if (paramSourceType != paramTargetType) {
372 change.AppendModified (paramSourceType, paramTargetType, true);
374 change.Append (paramSourceType);
377 if (paramSourceName != paramTargetName) {
378 change.AppendModified (paramSourceName, paramTargetName, true);
380 change.Append (paramSourceName);
383 var optSource = src [i].Attribute ("optional");
384 var optTarget = tgt [i].Attribute ("optional");
385 var srcValue = FormatValue (paramSourceType, src [i].GetAttribute ("defaultValue"));
386 var tgtValue = FormatValue (paramTargetType, tgt [i].GetAttribute ("defaultValue"));
388 if (optSource != null) {
389 if (optTarget != null) {
390 change.Append (" = ");
391 if (srcValue != tgtValue) {
392 change.AppendModified (srcValue, tgtValue, false);
394 change.Append (tgtValue);
397 change.AppendRemoved (" = " + srcValue);
400 if (optTarget != null)
401 change.AppendAdded (" = " + tgtValue);
408 // Ignore any parameter name changes if requested.
409 if (State.IgnoreParameterNameChanges && !change.Breaking) {
410 change.AnyChange = false;
411 change.HasIgnoredChanges = true;
415 void RenderVTable (MethodAttributes source, MethodAttributes target, ApiChange change)
417 var srcAbstract = (source & MethodAttributes.Abstract) == MethodAttributes.Abstract;
418 var tgtAbstract = (target & MethodAttributes.Abstract) == MethodAttributes.Abstract;
419 var srcFinal = (source & MethodAttributes.Final) == MethodAttributes.Final;
420 var tgtFinal = (target & MethodAttributes.Final) == MethodAttributes.Final;
421 var srcVirtual = (source & MethodAttributes.Virtual) == MethodAttributes.Virtual;
422 var tgtVirtual = (target & MethodAttributes.Virtual) == MethodAttributes.Virtual;
423 var srcOverride = (source & MethodAttributes.VtableLayoutMask) != MethodAttributes.NewSlot;
424 var tgtOverride = (target & MethodAttributes.VtableLayoutMask) != MethodAttributes.NewSlot;
426 var srcWord = srcVirtual ? (srcOverride ? "override" : "virtual") : string.Empty;
427 var tgtWord = tgtVirtual ? (tgtOverride ? "override" : "virtual") : string.Empty;
428 var breaking = srcWord.Length > 0 && tgtWord.Length == 0;
432 change.Append ("abstract ");
433 } else if (tgtVirtual) {
434 change.AppendModified ("abstract", tgtWord, false).Append (" ");
436 change.AppendRemoved ("abstract").Append (" ");
440 change.AppendAdded ("abstract", true).Append (" ");
441 } else if (srcWord != tgtWord) {
443 if (State.IgnoreVirtualChanges) {
444 change.HasIgnoredChanges = true;
446 change.AppendModified (srcWord, tgtWord, breaking).Append (" ");
449 } else if (tgtWord.Length > 0) {
450 change.Append (tgtWord).Append (" ");
451 } else if (srcWord.Length > 0) {
452 change.AppendRemoved (srcWord, breaking).Append (" ");
458 change.Append ("final ");
460 if (srcVirtual && !tgtVirtual && State.IgnoreVirtualChanges) {
461 change.HasIgnoredChanges = true;
463 change.AppendRemoved ("final", false).Append (" "); // removing 'final' is not a breaking change.
467 if (tgtFinal && srcVirtual) {
468 change.AppendModified ("virtual", "final", true).Append (" "); // adding 'final' is a breaking change if the member was virtual
472 if (!srcVirtual && !srcFinal && tgtVirtual && tgtFinal) {
473 // existing member implements a member from a new interface
474 // this would show up as 'virtual final', which is redundant, so show nothing at all.
475 change.HasIgnoredChanges = true;
478 // Ignore non-breaking virtual changes.
479 if (State.IgnoreVirtualChanges && !change.Breaking) {
480 change.AnyChange = false;
481 change.HasIgnoredChanges = true;
484 var tgtSecurity = (source & MethodAttributes.HasSecurity) == MethodAttributes.HasSecurity;
485 var srcSecurity = (target & MethodAttributes.HasSecurity) == MethodAttributes.HasSecurity;
487 if (tgtSecurity != srcSecurity)
488 change.HasIgnoredChanges = true;
490 var srcPInvoke = (source & MethodAttributes.PinvokeImpl) == MethodAttributes.PinvokeImpl;
491 var tgtPInvoke = (target & MethodAttributes.PinvokeImpl) == MethodAttributes.PinvokeImpl;
492 if (srcPInvoke != tgtPInvoke)
493 change.HasIgnoredChanges = true;
496 protected string GetVisibility (MethodAttributes attr)
499 case MethodAttributes.Private:
500 case MethodAttributes.PrivateScope:
502 case MethodAttributes.Assembly:
504 case MethodAttributes.FamANDAssem:
505 return "private internal";
506 case MethodAttributes.FamORAssem:
507 return "protected"; // customers don't care about 'internal';
508 case MethodAttributes.Family:
510 case MethodAttributes.Public:
513 throw new NotImplementedException ();
517 protected void RenderVisibility (MethodAttributes source, MethodAttributes target, ApiChange diff)
519 source = source & MethodAttributes.MemberAccessMask;
520 target = target & MethodAttributes.MemberAccessMask;
522 if (source == target) {
523 diff.Append (GetVisibility (target));
525 var breaking = false;
527 case MethodAttributes.Private:
528 case MethodAttributes.Assembly:
529 case MethodAttributes.FamANDAssem:
530 break; // these are not publicly visible, thus not breaking
531 case MethodAttributes.FamORAssem:
532 case MethodAttributes.Family:
534 case MethodAttributes.Public:
535 // to public is not a breaking change
537 case MethodAttributes.Family:
538 case MethodAttributes.FamORAssem:
539 // not a breaking change, but should still show up in diff
542 // anything else is a breaking change
547 case MethodAttributes.Public:
549 // any change from public is breaking.
554 diff.AppendModified (GetVisibility (source), GetVisibility (target), breaking);
559 protected void RenderStatic (MethodAttributes src, MethodAttributes tgt, ApiChange diff)
561 var srcStatic = (src & MethodAttributes.Static) == MethodAttributes.Static;
562 var tgtStatic = (tgt & MethodAttributes.Static) == MethodAttributes.Static;
564 if (srcStatic != tgtStatic) {
566 diff.AppendRemoved ("static", true).Append (" ");
568 diff.AppendAdded ("static", true).Append (" ");
573 protected void RenderMethodAttributes (MethodAttributes src, MethodAttributes tgt, ApiChange diff)
575 RenderStatic (src, tgt, diff);
576 RenderVisibility (src & MethodAttributes.MemberAccessMask, tgt & MethodAttributes.MemberAccessMask, diff);
577 RenderVTable (src, tgt, diff);
580 protected void RenderMethodAttributes (XElement source, XElement target, ApiChange diff)
582 RenderMethodAttributes (source.GetMethodAttributes (), target.GetMethodAttributes (), diff);
585 protected void RemoveAttributes (XElement element)
587 var srcAttributes = element.Element ("attributes");
588 if (srcAttributes != null)
589 srcAttributes.Remove ();
591 foreach (var el in element.Elements ())
592 RemoveAttributes (el);
595 protected void RenderAttributes (XElement source, XElement target, ApiChanges changes)
597 var srcObsolete = source.GetObsoleteMessage ();
598 var tgtObsolete = target.GetObsoleteMessage ();
600 if (srcObsolete == tgtObsolete)
601 return; // nothing changed
603 if (srcObsolete == null) {
604 if (tgtObsolete == null)
605 return; // neither is obsolete
606 var change = new ApiChange ();
607 change.Header = "Obsoleted " + GroupName;
608 change.Append (string.Format ("<span class='obsolete obsolete-{0}' data-is-non-breaking>", ElementName));
609 change.Append ("[Obsolete (");
610 if (tgtObsolete != string.Empty)
611 change.Append ("\"").Append (tgtObsolete).Append ("\"");
612 change.Append (")]\n");
613 change.Append (GetDescription (target));
614 change.Append ("</span>");
615 change.AnyChange = true;
616 changes.Add (source, target, change);
617 } else if (tgtObsolete == null) {
618 // Made non-obsolete. Do we care to report this?
620 // Obsolete message changed. Do we care to report this?
624 protected void RenderName (XElement source, XElement target, ApiChange change)
626 var name = target.GetAttribute ("name");
627 // show the constructor as it would be defined in C#
628 name = name.Replace (".ctor", State.Type);
630 var p = name.IndexOf ('(');
632 name = name.Substring (0, p);
634 change.Append (name);