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 public XElement Source { get; set; }
66 public virtual bool Find (XElement e)
68 return e.GetAttribute ("name") == Source.GetAttribute ("name");
71 XElement Find (IEnumerable<XElement> target)
73 return State.Lax ? target.FirstOrDefault (Find) : target.SingleOrDefault (Find);
76 public override void Compare (IEnumerable<XElement> source, IEnumerable<XElement> target)
81 foreach (var s in source) {
84 var t = Find (target);
86 // not in target, it was removed
91 if (Equals (s, t, modified))
94 Modified (s, t, modified);
97 // delayed, that way we show "Modified", "Added" and then "Removed"
102 // remaining == newly added in target
106 void Add (IEnumerable<XElement> elements)
109 foreach (var item in elements) {
111 if (State.IgnoreAdded.Any (re => re.IsMatch (GetDescription (item))))
114 BeforeAdding (elements);
123 void Modify (ApiChanges modified)
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);
133 Output.WriteLine ("</pre>");
137 void Remove (IEnumerable<XElement> elements)
140 foreach (var item in elements) {
141 if (State.IgnoreRemoved.Any (re => re.IsMatch (GetDescription (item))))
145 BeforeRemoving (elements);
154 public abstract string GetDescription (XElement e);
156 protected StringBuilder GetObsoleteMessage (XElement e)
158 var sb = new StringBuilder ();
159 string o = e.GetObsoleteMessage ();
161 sb.Append ("[Obsolete");
163 sb.Append (" (\"").Append (o).Append ("\")");
165 for (int i = 0; i < State.Indent + 1; i++)
171 public override bool Equals (XElement source, XElement target, ApiChanges changes)
173 RenderAttributes (source, target, changes);
175 // We don't want to compare attributes.
176 RemoveAttributes (source);
177 RemoveAttributes (target);
179 return base.Equals (source, target, changes);
182 public virtual void BeforeAdding (IEnumerable<XElement> list)
185 Output.WriteLine ("<p>Added {0}:</p><pre>", list.Count () > 1 ? GroupName : ElementName);
187 Output.Write ("<font color='green'>");
190 public override void Added (XElement target)
192 var o = GetObsoleteMessage (target);
193 if (!first && (o.Length > 0))
195 Indent ().WriteLine ("\t{0}{1}", o, GetDescription (target));
199 public virtual void AfterAdding ()
202 Output.Write ("</font>");
203 Output.WriteLine ("</pre>");
206 public override void Modified (XElement source, XElement target, ApiChanges change)
210 public virtual void BeforeRemoving (IEnumerable<XElement> list)
213 Output.WriteLine ("<p>Removed {0}:</p>\n<pre>", list.Count () > 1 ? GroupName : ElementName);
215 Output.Write ("<font color='red'>");
218 public override void Removed (XElement source)
220 var o = GetObsoleteMessage (source);
221 if (!first && (o.Length > 0))
223 Indent ().WriteLine ("\t{0}{1}", o, GetDescription (source));
227 public virtual void AfterRemoving ()
230 Output.Write ("</font>");
231 Output.WriteLine ("</pre>");
234 string RenderGenericParameter (XElement gp)
236 var sb = new StringBuilder ();
237 sb.Append (gp.GetTypeName ("name"));
239 var constraints = gp.DescendantList ("generic-parameter-constraints", "generic-parameter-constraint");
240 if (constraints != null && constraints.Count > 0) {
242 for (int i = 0; i < constraints.Count; i++) {
245 sb.Append (constraints [i].GetTypeName ("name"));
248 return sb.ToString ();
251 protected void RenderGenericParameters (XElement source, XElement target, ApiChange change)
253 var src = source.DescendantList ("generic-parameters", "generic-parameter");
254 var tgt = target.DescendantList ("generic-parameters", "generic-parameter");
255 var srcCount = src == null ? 0 : src.Count;
256 var tgtCount = tgt == null ? 0 : tgt.Count;
258 if (srcCount == 0 && tgtCount == 0)
261 change.Append ("<");
262 for (int i = 0; i < Math.Max (srcCount, tgtCount); i++) {
264 change.Append (", ");
266 change.AppendAdded (RenderGenericParameter (tgt [i]), true);
267 } else if (i >= tgtCount) {
268 change.AppendRemoved (RenderGenericParameter (src [i]), true);
270 var srcName = RenderGenericParameter (src [i]);
271 var tgtName = RenderGenericParameter (tgt [i]);
273 if (srcName != tgtName) {
274 change.AppendModified (srcName, tgtName, true);
276 change.Append (srcName);
280 change.Append (">");
283 protected string FormatValue (string type, string value)
288 if (type == "string")
289 return "\"" + value + "\"";
290 else if (type == "bool") {
304 protected void RenderParameters (XElement source, XElement target, ApiChange change)
306 var src = source.DescendantList ("parameters", "parameter");
307 var tgt = target.DescendantList ("parameters", "parameter");
308 var srcCount = src == null ? 0 : src.Count;
309 var tgtCount = tgt == null ? 0 : tgt.Count;
311 change.Append (" (");
312 for (int i = 0; i < Math.Max (srcCount, tgtCount); i++) {
314 change.Append (", ");
317 change.AppendAdded (tgt [i].GetTypeName ("type") + " " + tgt [i].GetAttribute ("name"), true);
318 } else if (i >= tgtCount) {
319 change.AppendRemoved (src [i].GetTypeName ("type") + " " + src [i].GetAttribute ("name"), true);
321 var paramSourceType = src [i].GetTypeName ("type");
322 var paramTargetType = tgt [i].GetTypeName ("type");
324 var paramSourceName = src [i].GetAttribute ("name");
325 var paramTargetName = tgt [i].GetAttribute ("name");
327 if (paramSourceType != paramTargetType) {
328 change.AppendModified (paramSourceType, paramTargetType, true);
330 change.Append (paramSourceType);
333 if (paramSourceName != paramTargetName) {
334 change.AppendModified (paramSourceName, paramTargetName, false);
336 change.Append (paramSourceName);
339 var optSource = src [i].Attribute ("optional");
340 var optTarget = tgt [i].Attribute ("optional");
341 var srcValue = FormatValue (paramSourceType, src [i].GetAttribute ("defaultValue"));
342 var tgtValue = FormatValue (paramTargetType, tgt [i].GetAttribute ("defaultValue"));
344 if (optSource != null) {
345 if (optTarget != null) {
346 change.Append (" = ");
347 if (srcValue != tgtValue) {
348 change.AppendModified (srcValue, tgtValue, false);
350 change.Append (tgtValue);
353 change.AppendRemoved (" = " + srcValue);
356 if (optTarget != null)
357 change.AppendAdded (" = " + tgtValue);
364 // Ignore any parameter name changes if requested.
365 if (State.IgnoreParameterNameChanges && !change.Breaking) {
366 change.AnyChange = false;
367 change.HasIgnoredChanges = true;
371 void RenderVTable (MethodAttributes source, MethodAttributes target, ApiChange change)
373 var srcAbstract = (source & MethodAttributes.Abstract) == MethodAttributes.Abstract;
374 var tgtAbstract = (target & MethodAttributes.Abstract) == MethodAttributes.Abstract;
375 var srcFinal = (source & MethodAttributes.Final) == MethodAttributes.Final;
376 var tgtFinal = (target & MethodAttributes.Final) == MethodAttributes.Final;
377 var srcVirtual = (source & MethodAttributes.Virtual) == MethodAttributes.Virtual;
378 var tgtVirtual = (target & MethodAttributes.Virtual) == MethodAttributes.Virtual;
379 var srcOverride = (source & MethodAttributes.VtableLayoutMask) != MethodAttributes.NewSlot;
380 var tgtOverride = (target & MethodAttributes.VtableLayoutMask) != MethodAttributes.NewSlot;
382 var srcWord = srcVirtual ? (srcOverride ? "override" : "virtual") : string.Empty;
383 var tgtWord = tgtVirtual ? (tgtOverride ? "override" : "virtual") : string.Empty;
384 var breaking = srcWord.Length > 0 && tgtWord.Length == 0;
388 change.Append ("abstract ");
389 } else if (tgtVirtual) {
390 change.AppendModified ("abstract", tgtWord, false).Append (" ");
392 change.AppendRemoved ("abstract").Append (" ");
396 change.AppendAdded ("abstract", true).Append (" ");
397 } else if (srcWord != tgtWord) {
399 change.AppendModified (srcWord, tgtWord, breaking).Append (" ");
400 } else if (tgtWord.Length > 0) {
401 change.Append (tgtWord).Append (" ");
402 } else if (srcWord.Length > 0) {
403 change.AppendRemoved (srcWord, breaking).Append (" ");
409 change.Append ("final ");
411 change.AppendRemoved ("final", false).Append (" "); // removing 'final' is not a breaking change.
414 if (tgtFinal && srcVirtual) {
415 change.AppendModified ("virtual", "final", true).Append (" "); // adding 'final' is a breaking change if the member was virtual
419 if (!srcVirtual && !srcFinal && tgtVirtual && tgtFinal) {
420 // existing member implements a member from a new interface
421 // this would show up as 'virtual final', which is redundant, so show nothing at all.
422 change.HasIgnoredChanges = true;
425 // Ignore non-breaking virtual changes.
426 if (State.IgnoreVirtualChanges && !change.Breaking) {
427 change.AnyChange = false;
428 change.HasIgnoredChanges = true;
432 protected string GetVisibility (MethodAttributes attr)
435 case MethodAttributes.Private:
436 case MethodAttributes.PrivateScope:
438 case MethodAttributes.Assembly:
440 case MethodAttributes.FamANDAssem:
441 return "private internal";
442 case MethodAttributes.FamORAssem:
443 return "protected"; // customers don't care about 'internal';
444 case MethodAttributes.Family:
446 case MethodAttributes.Public:
449 throw new NotImplementedException ();
453 protected void RenderVisibility (MethodAttributes source, MethodAttributes target, ApiChange diff)
455 source = source & MethodAttributes.MemberAccessMask;
456 target = target & MethodAttributes.MemberAccessMask;
458 if (source == target) {
459 diff.Append (GetVisibility (target));
461 var breaking = false;
463 case MethodAttributes.Private:
464 case MethodAttributes.Assembly:
465 case MethodAttributes.FamANDAssem:
466 break; // these are not publicly visible, thus not breaking
467 case MethodAttributes.FamORAssem:
468 case MethodAttributes.Family:
470 case MethodAttributes.Public:
471 // to public is not a breaking change
473 case MethodAttributes.Family:
474 case MethodAttributes.FamORAssem:
475 // not a breaking change, but should still show up in diff
478 // anything else is a breaking change
483 case MethodAttributes.Public:
485 // any change from public is breaking.
490 diff.AppendModified (GetVisibility (source), GetVisibility (target), breaking);
495 protected void RenderStatic (MethodAttributes src, MethodAttributes tgt, ApiChange diff)
497 var srcStatic = (src & MethodAttributes.Static) == MethodAttributes.Static;
498 var tgtStatic = (tgt & MethodAttributes.Static) == MethodAttributes.Static;
500 if (srcStatic != tgtStatic) {
502 diff.AppendRemoved ("static", true).Append (" ");
504 diff.AppendAdded ("static", true).Append (" ");
509 protected void RenderMethodAttributes (MethodAttributes src, MethodAttributes tgt, ApiChange diff)
511 RenderStatic (src, tgt, diff);
512 RenderVisibility (src & MethodAttributes.MemberAccessMask, tgt & MethodAttributes.MemberAccessMask, diff);
513 RenderVTable (src, tgt, diff);
516 protected void RenderMethodAttributes (XElement source, XElement target, ApiChange diff)
518 RenderMethodAttributes (source.GetMethodAttributes (), target.GetMethodAttributes (), diff);
521 protected void RemoveAttributes (XElement element)
523 var srcAttributes = element.Element ("attributes");
524 if (srcAttributes != null)
525 srcAttributes.Remove ();
527 foreach (var el in element.Elements ())
528 RemoveAttributes (el);
531 protected void RenderAttributes (XElement source, XElement target, ApiChanges changes)
533 var srcObsolete = source.GetObsoleteMessage ();
534 var tgtObsolete = target.GetObsoleteMessage ();
536 if (srcObsolete == tgtObsolete)
537 return; // nothing changed
539 if (srcObsolete == null) {
540 if (tgtObsolete == null)
541 return; // neither is obsolete
542 var change = new ApiChange ();
543 change.Header = "Obsoleted " + GroupName;
545 change.Append ("<font color='gray'>");
546 change.Append ("[Obsolete (");
547 if (tgtObsolete != string.Empty)
548 change.Append ("\"").Append (tgtObsolete).Append ("\"");
549 change.Append ("]\n");
550 change.Append (GetDescription (target));
552 change.Append ("</font>");
553 change.AnyChange = true;
554 changes.Add (source, target, change);
555 } else if (tgtObsolete == null) {
556 // Made non-obsolete. Do we care to report this?
558 // Obsolete message changed. Do we care to report this?
562 protected void RenderName (XElement source, XElement target, ApiChange change)
564 var name = target.GetAttribute ("name");
565 // show the constructor as it would be defined in C#
566 name = name.Replace (".ctor", State.Type);
568 var p = name.IndexOf ('(');
570 name = name.Substring (0, p);
572 change.Append (name);