8fe2784485220399c0646210de60a119268d78bc
[mono.git] / mcs / tools / corcompare / mono-api-html / ApiDiff.cs
1 //
2 // The main differences with mono-api-diff are:
3 // * this tool directly produce HTML similar to gdiff.sh used for Xamarin.iOS
4 // * this tool reports changes in an "evolutionary" way, not in a breaking way,
5 //   i.e. it does not assume the source assembly is right (but simply older)
6 // * the diff .xml output was not easy to convert back into the HTML format
7 //   that gdiff.sh produced
8 // 
9 // Authors
10 //    Sebastien Pouliot  <sebastien@xamarin.com>
11 //
12 // Copyright 2013-2014 Xamarin Inc. http://www.xamarin.com
13 // 
14 // Permission is hereby granted, free of charge, to any person obtaining
15 // a copy of this software and associated documentation files (the
16 // "Software"), to deal in the Software without restriction, including
17 // without limitation the rights to use, copy, modify, merge, publish,
18 // distribute, sublicense, and/or sell copies of the Software, and to
19 // permit persons to whom the Software is furnished to do so, subject to
20 // the following conditions:
21 //
22 // The above copyright notice and this permission notice shall be
23 // included in all copies or substantial portions of the Software.
24 //
25 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
26 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
27 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
28 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
29 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
30 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
31 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
32 //
33
34 using System;
35 using System.IO;
36 using System.Collections.Generic;
37 using System.Text.RegularExpressions;
38
39 using Mono.Options;
40
41 namespace Xamarin.ApiDiff {
42
43         public static class State {
44                 static TextWriter output;
45
46                 public static TextWriter Output { 
47                         get {
48                                 if (output == null)
49                                         output = Console.Out;
50                                 return output;
51                         }
52                         set { output = value; } 
53                 }
54
55                 public static string Assembly { get; set; }
56                 public static string Namespace { get; set; }
57                 public static string Type { get; set; }
58                 public static string BaseType { get; set; }
59
60                 public static int Indent { get; set; }
61
62                 static List<Regex> ignoreAdded = new List<Regex> ();
63                 public static List<Regex> IgnoreAdded {
64                         get { return ignoreAdded; }
65                 }
66
67                 static List<Regex> ignoreNew = new List<Regex> ();
68                 public static List<Regex> IgnoreNew {
69                         get { return ignoreNew; }
70                 }
71
72                 static List<Regex> ignoreRemoved = new List<Regex> ();
73                 public static List<Regex> IgnoreRemoved {
74                         get { return ignoreRemoved; }
75                 }
76
77                 public  static  bool    IgnoreParameterNameChanges  { get; set; }
78                 public  static  bool    IgnoreVirtualChanges        { get; set; }
79                 public  static  bool    IgnoreAddedPropertySetters  { get; set; }
80
81                 public static bool Lax;
82                 public static bool Colorize = true;
83         }
84
85         class Program {
86
87                 public static int Main (string[] args)
88                 {
89                         var showHelp = false;
90                         string diff = null;
91                         List<string> extra = null;
92
93                         var options = new OptionSet {
94                                 { "h|help", "Show this help", v => showHelp = true },
95                                 { "d|diff=", "HTML diff file out output (omit for stdout)", v => diff = v },
96                                 { "i|ignore=", "Ignore new, added, and removed members whose description matches a given C# regular expression (see below).",
97                                         v => {
98                                                 var r = new Regex (v);
99                                                 State.IgnoreAdded.Add (r);
100                                                 State.IgnoreRemoved.Add (r);
101                                                 State.IgnoreNew.Add (r);
102                                         }
103                                 },
104                                 { "a|ignore-added=", "Ignore added members whose description matches a given C# regular expression (see below).",
105                                         v => State.IgnoreAdded.Add (new Regex (v))
106                                 },
107                                 { "r|ignore-removed=", "Ignore removed members whose description matches a given C# regular expression (see below).",
108                                         v => State.IgnoreRemoved.Add (new Regex (v))
109                                 },
110                                 { "n|ignore-new=", "Ignore new namespaces and types whose description matches a given C# regular expression (see below).",
111                                         v => State.IgnoreNew.Add (new Regex (v))
112                                 },
113                                 { "ignore-changes-parameter-names", "Ignore changes to parameter names for identically prototyped methods.",
114                                         v => State.IgnoreParameterNameChanges   = v != null
115                                 },
116                                 { "ignore-changes-property-setters", "Ignore adding setters to properties.",
117                                         v => State.IgnoreAddedPropertySetters = v != null
118                                 },
119                                 { "ignore-changes-virtual", "Ignore changing non-`virtual` to `virtual` or adding `override`.",
120                                         v => State.IgnoreVirtualChanges = v != null
121                                 },
122                                 { "c|colorize:", "Colorize HTML output", v => State.Colorize = string.IsNullOrEmpty (v) ? true : bool.Parse (v) },
123                                 { "x|lax", "Ignore duplicate XML entries", v => State.Lax = true }
124                         };
125
126                         try {
127                                 extra = options.Parse (args);
128                         } catch (OptionException e) {
129                                 Console.WriteLine ("Option error: {0}", e.Message);
130                                 showHelp = true;
131                         }
132
133                         if (showHelp || extra == null || extra.Count < 2 || extra.Count > 3) {
134                                 Console.WriteLine (@"Usage: mono-api-html [options] <reference.xml> <assembly.xml> [diff.html]");
135                                 Console.WriteLine ();
136                                 Console.WriteLine ("Available options:");
137                                 options.WriteOptionDescriptions (Console.Out);
138                                 Console.WriteLine ();
139                                 Console.WriteLine ("Ignoring Members:");
140                                 Console.WriteLine ();
141                                 Console.WriteLine ("  Members that were added can be filtered out of the diff by using the");
142                                 Console.WriteLine ("  -i, --ignore-added option. The option takes a C# regular expression");
143                                 Console.WriteLine ("  to match against member descriptions. For example, to ignore the");
144                                 Console.WriteLine ("  introduction of the interfaces 'INSCopying' and 'INSCoding' on types");
145                                 Console.WriteLine ("  pass the following to mono-api-html:");
146                                 Console.WriteLine ();
147                                 Console.WriteLine ("    mono-api-html ... -i 'INSCopying$' -i 'INSCoding$'");
148                                 Console.WriteLine ();
149                                 Console.WriteLine ("  The regular expressions will match any member description ending with");
150                                 Console.WriteLine ("  'INSCopying' or 'INSCoding'.");
151                                 Console.WriteLine ();
152                                 return 1;
153                         }
154
155                         var input = extra [0];
156                         var output = extra [1];
157                         if (extra.Count == 3 && diff == null)
158                                 diff = extra [2];
159
160                         try {
161                                 var ac = new AssemblyComparer (input, output);
162                                 if (diff != null) {
163                                         string diffHtml = String.Empty;
164                                         using (var writer = new StringWriter ()) {
165                                                 State.Output = writer;
166                                                 ac.Compare ();
167                                                 diffHtml = State.Output.ToString ();
168                                         }
169                                         if (diffHtml.Length > 0) {
170                                                 using (var file = new StreamWriter (diff)) {
171                                                         file.WriteLine ("<div>");
172                                                         if (State.Colorize) {
173                                                                 file.WriteLine ("<style scoped>");
174                                                                 file.WriteLine ("\t.obsolete { color: gray; }");
175                                                                 file.WriteLine ("\t.added { color: green; }");
176                                                                 file.WriteLine ("\t.removed-inline { text-decoration: line-through; }");
177                                                                 file.WriteLine ("\t.removed-breaking-inline { color: red;}");
178                                                                 file.WriteLine ("\t.added-breaking-inline { text-decoration: underline; }");
179                                                                 file.WriteLine ("\t.nonbreaking { color: black; }");
180                                                                 file.WriteLine ("\t.breaking { color: red; }");
181                                                                 file.WriteLine ("</style>");
182                                                         }
183                                                         file.WriteLine (
184 @"<script type=""text/javascript"">
185         // Only some elements have 'data-is-[non-]breaking' attributes. Here we
186         // iterate over all descendents elements, and set 'data-is-[non-]breaking'
187         // depending on whether there are any descendents with that attribute.
188         function propagateDataAttribute (element)
189         {
190                 if (element.hasAttribute ('data-is-propagated'))
191                         return;
192
193                 var i;
194                 var any_breaking = element.hasAttribute ('data-is-breaking');
195                 var any_non_breaking = element.hasAttribute ('data-is-non-breaking');
196                 for (i = 0; i < element.children.length; i++) {
197                         var el = element.children [i];
198                         propagateDataAttribute (el);
199                         any_breaking |= el.hasAttribute ('data-is-breaking');
200                         any_non_breaking |= el.hasAttribute ('data-is-non-breaking');
201                 }
202                 
203                 if (any_breaking)
204                         element.setAttribute ('data-is-breaking', null);
205                 else if (any_non_breaking)
206                         element.setAttribute ('data-is-non-breaking', null);
207                 element.setAttribute ('data-is-propagated', null);
208         }
209
210         function hideNonBreakingChanges ()
211         {
212                 var topNodes = document.querySelectorAll ('[data-is-topmost]');
213                 var n;
214                 var i;
215                 for (n = 0; n < topNodes.length; n++) {
216                         propagateDataAttribute (topNodes [n]);
217                         var elements = topNodes [n].querySelectorAll ('[data-is-non-breaking]');
218                         for (i = 0; i < elements.length; i++) {
219                                 var el = elements [i];
220                                 if (!el.hasAttribute ('data-original-display'))
221                                         el.setAttribute ('data-original-display', el.style.display);
222                                 el.style.display = 'none';
223                         }
224                 }
225                 
226                 var links = document.getElementsByClassName ('hide-nonbreaking');
227                 for (i = 0; i < links.length; i++)
228                         links [i].style.display = 'none';
229                 links = document.getElementsByClassName ('restore-nonbreaking');
230                 for (i = 0; i < links.length; i++)
231                         links [i].style.display = '';
232         }
233
234         function showNonBreakingChanges ()
235         {
236                 var elements = document.querySelectorAll ('[data-original-display]');
237                 var i;
238                 for (i = 0; i < elements.length; i++) {
239                         var el = elements [i];
240                         el.style.display = el.getAttribute ('data-original-display');
241                 }
242
243                 var links = document.getElementsByClassName ('hide-nonbreaking');
244                 for (i = 0; i < links.length; i++)
245                         links [i].style.display = '';
246                 links = document.getElementsByClassName ('restore-nonbreaking');
247                 for (i = 0; i < links.length; i++)
248                         links [i].style.display = 'none';
249         }
250 </script>");
251                                                         if (ac.SourceAssembly == ac.TargetAssembly) {
252                                                                 file.WriteLine ("<h1>{0}.dll</h1>", ac.SourceAssembly);
253                                                         } else {
254                                                                 file.WriteLine ("<h1>{0}.dll vs {1}.dll</h1>", ac.SourceAssembly, ac.TargetAssembly);
255                                                         }
256                                                         file.WriteLine ("<a href='javascript: hideNonBreakingChanges (); ' class='hide-nonbreaking'>Hide non-breaking changes</a>");
257                                                         file.WriteLine ("<a href='javascript: showNonBreakingChanges (); ' class='restore-nonbreaking' style='display: none;'>Show non-breaking changes</a>");
258                                                         file.WriteLine ("<br/>");
259                                                         file.WriteLine ("<div data-is-topmost>");
260                                                         file.Write (diffHtml);
261                                                         file.WriteLine ("</div> <!-- end topmost div -->");
262                                                         file.WriteLine ("</div>");
263                                                 }
264                                         }
265                                 } else {
266                                         State.Output = Console.Out;
267                                         ac.Compare ();
268                                 }
269                         }
270                         catch (Exception e) {
271                                 Console.WriteLine (e);
272                                 return 1;
273                         }
274                         return 0;
275                 }
276         }
277 }