update MEF to preview 9
[mcs.git] / tools / mdoc / Mono.Documentation / monodocer.cs
blob19500268532166244763555575588841db023830
1 // Updater program for syncing Mono's ECMA-style documentation files
2 // with an assembly.
3 // By Joshua Tauberer <tauberer@for.net>
5 using System;
6 using System.Collections;
7 using System.Collections.Generic;
8 using System.Diagnostics;
9 using System.Globalization;
10 using System.IO;
11 using System.Linq;
12 using System.Text;
13 using System.Xml;
14 using System.Xml.XPath;
16 using Mono.Cecil;
17 using Mono.Options;
19 using MyXmlNodeList = System.Collections.Generic.List<System.Xml.XmlNode>;
20 using StringList = System.Collections.Generic.List<string>;
21 using StringToStringMap = System.Collections.Generic.Dictionary<string, string>;
22 using StringToXmlNodeMap = System.Collections.Generic.Dictionary<string, System.Xml.XmlNode>;
24 namespace Mono.Documentation {
26 class MDocUpdater : MDocCommand
28 string srcPath;
29 List<AssemblyDefinition> assemblies;
30 readonly DefaultAssemblyResolver assemblyResolver = new DefaultAssemblyResolver();
32 bool delete;
33 bool show_exceptions;
34 bool no_assembly_versions;
35 ExceptionLocations? exceptions;
37 int additions = 0, deletions = 0;
39 static XmlDocument slashdocs;
40 XmlReader ecmadocs;
42 string since;
44 static readonly MemberFormatter csharpFullFormatter = new CSharpFullMemberFormatter ();
45 static readonly MemberFormatter csharpFormatter = new CSharpMemberFormatter ();
46 static readonly MemberFormatter docTypeFormatter = new DocTypeMemberFormatter ();
47 static readonly MemberFormatter slashdocFormatter = new SlashDocMemberFormatter ();
48 static readonly MemberFormatter filenameFormatter = new FileNameMemberFormatter ();
50 MyXmlNodeList extensionMethods = new MyXmlNodeList ();
52 public override void Run (IEnumerable<string> args)
54 show_exceptions = DebugOutput;
55 string import = null;
56 var types = new List<string> ();
57 var p = new OptionSet () {
58 { "delete",
59 "Delete removed members from the XML files.",
60 v => delete = v != null },
61 { "exceptions:",
62 "Document potential exceptions that members can generate. {SOURCES} " +
63 "is a comma-separated list of:\n" +
64 " asm Method calls in same assembly\n" +
65 " depasm Method calls in dependent assemblies\n" +
66 " all Record all possible exceptions\n" +
67 "If nothing is specified, then only exceptions from the member will " +
68 "be listed.",
69 v => exceptions = ParseExceptionLocations (v) },
70 { "f=",
71 "Specify a {FLAG} to alter behavior. See later -f* options for available flags.",
72 v => {
73 switch (v) {
74 case "no-assembly-versions":
75 no_assembly_versions = true;
76 break;
77 default:
78 throw new Exception ("Unsupported flag `" + v + "'.");
80 } },
81 { "fno-assembly-versions",
82 "Do not generate //AssemblyVersion elements.",
83 v => no_assembly_versions = v != null },
84 { "i|import=",
85 "Import documentation from {FILE}.",
86 v => import = v },
87 { "L|lib=",
88 "Check for assembly references in {DIRECTORY}.",
89 v => assemblyResolver.AddSearchDirectory (v) },
90 { "o|out=",
91 "Root {DIRECTORY} to generate/update documentation.",
92 v => srcPath = v },
93 { "r=",
94 "Search for dependent assemblies in the directory containing {ASSEMBLY}.\n" +
95 "(Equivalent to '-L `dirname ASSEMBLY`'.)",
96 v => assemblyResolver.AddSearchDirectory (Path.GetDirectoryName (v)) },
97 { "since=",
98 "Manually specify the assembly {VERSION} that new members were added in.",
99 v => since = v },
100 { "type=",
101 "Only update documentation for {TYPE}.",
102 v => types.Add (v) },
104 var assemblies = Parse (p, args, "update",
105 "[OPTIONS]+ ASSEMBLIES",
106 "Create or update documentation from ASSEMBLIES.");
107 if (assemblies == null)
108 return;
109 if (assemblies.Count == 0)
110 Error ("No assemblies specified.");
112 foreach (var dir in assemblies
113 .Where (a => a.Contains (Path.DirectorySeparatorChar))
114 .Select (a => Path.GetDirectoryName (a)))
115 assemblyResolver.AddSearchDirectory (dir);
117 // PARSE BASIC OPTIONS AND LOAD THE ASSEMBLY TO DOCUMENT
119 if (srcPath == null)
120 throw new InvalidOperationException("The --out option is required.");
122 this.assemblies = assemblies.Select (a => LoadAssembly (a)).ToList ();
124 if (import != null && ecmadocs == null && slashdocs == null) {
125 try {
126 XmlReader r = new XmlTextReader (import);
127 if (r.Read ()) {
128 while (r.NodeType != XmlNodeType.Element) {
129 if (!r.Read ())
130 Error ("Unable to read XML file: {0}.", import);
132 if (r.LocalName == "doc") {
133 var xml = File.ReadAllText (import);
134 // Ensure Unix line endings
135 xml = xml.Replace ("\r", "");
136 slashdocs = new XmlDocument();
137 slashdocs.LoadXml (xml);
139 else if (r.LocalName == "Libraries") {
140 ecmadocs = new XmlTextReader (import);
142 else
143 Error ("Unsupported XML format within {0}.", import);
145 r.Close ();
146 } catch (Exception e) {
147 Environment.ExitCode = 1;
148 Error ("Could not load XML file: {0}.", e.Message);
152 // PERFORM THE UPDATES
154 if (types.Count > 0)
155 DoUpdateTypes (srcPath, types, srcPath);
156 #if false
157 else if (opts.@namespace != null)
158 DoUpdateNS (opts.@namespace, Path.Combine (opts.path, opts.@namespace),
159 Path.Combine (dest_dir, opts.@namespace));
160 #endif
161 else
162 DoUpdateAssemblies (srcPath, srcPath);
164 Console.WriteLine("Members Added: {0}, Members Deleted: {1}", additions, deletions);
167 static ExceptionLocations ParseExceptionLocations (string s)
169 ExceptionLocations loc = ExceptionLocations.Member;
170 if (s == null)
171 return loc;
172 foreach (var type in s.Split (',')) {
173 switch (type) {
174 case "added": loc |= ExceptionLocations.AddedMembers; break;
175 case "all": loc |= ExceptionLocations.Assembly | ExceptionLocations.DependentAssemblies; break;
176 case "asm": loc |= ExceptionLocations.Assembly; break;
177 case "depasm": loc |= ExceptionLocations.DependentAssemblies; break;
178 default: throw new NotSupportedException ("Unsupported --exceptions value: " + type);
181 return loc;
184 private void Warning (string format, params object[] args)
186 Message (TraceLevel.Warning, "mdoc: " + format, args);
189 private AssemblyDefinition LoadAssembly (string name)
191 AssemblyDefinition assembly = null;
192 try {
193 assembly = AssemblyFactory.GetAssembly (name);
194 } catch (System.IO.FileNotFoundException) { }
196 if (assembly == null)
197 throw new InvalidOperationException("Assembly " + name + " not found.");
199 assembly.Resolver = assemblyResolver;
200 return assembly;
203 private static void WriteXml(XmlElement element, System.IO.TextWriter output) {
204 OrderTypeAttributes (element);
205 XmlTextWriter writer = new XmlTextWriter(output);
206 writer.Formatting = Formatting.Indented;
207 writer.Indentation = 2;
208 writer.IndentChar = ' ';
209 element.WriteTo(writer);
210 output.WriteLine();
213 private static void WriteFile (string filename, FileMode mode, Action<TextWriter> action)
215 if (!File.Exists (filename)) {
216 using (var writer = OpenWrite (filename, mode))
217 action (writer);
218 return;
221 string tmpFile = filename + ".tmp";
222 bool move = true;
224 try {
225 using (var writer = OpenWrite (tmpFile, mode))
226 action (writer);
228 using (var a = File.OpenRead (filename))
229 using (var b = File.OpenRead (tmpFile)) {
230 if (a.Length == b.Length)
231 move = !FileContentsIdentical (a, b);
234 if (move) {
235 File.Delete (filename);
236 File.Move (tmpFile, filename);
239 finally {
240 if (!move && File.Exists (tmpFile))
241 File.Delete (tmpFile);
245 static bool FileContentsIdentical (Stream a, Stream b)
247 byte[] ba = new byte[4096];
248 byte[] bb = new byte[4096];
249 int ra, rb;
251 while ((ra = a.Read (ba, 0, ba.Length)) > 0 &&
252 (rb = b.Read (bb, 0, bb.Length)) > 0) {
253 if (ra != rb)
254 return false;
255 for (int i = 0; i < ra; ++i) {
256 if (ba [i] != bb [i])
257 return false;
260 return true;
263 private static void OrderTypeAttributes (XmlElement e)
265 foreach (XmlElement type in e.SelectNodes ("//Type")) {
266 OrderTypeAttributes (type.Attributes);
270 static readonly string[] TypeAttributeOrder = {
271 "Name", "FullName", "FullNameSP", "Maintainer"
274 private static void OrderTypeAttributes (XmlAttributeCollection c)
276 XmlAttribute[] attrs = new XmlAttribute [TypeAttributeOrder.Length];
277 for (int i = 0; i < c.Count; ++i) {
278 XmlAttribute a = c [i];
279 for (int j = 0; j < TypeAttributeOrder.Length; ++j) {
280 if (a.Name == TypeAttributeOrder [j]) {
281 attrs [j] = a;
282 break;
286 for (int i = attrs.Length-1; i >= 0; --i) {
287 XmlAttribute n = attrs [i];
288 if (n == null)
289 continue;
290 XmlAttribute r = null;
291 for (int j = i+1; j < attrs.Length; ++j) {
292 if (attrs [j] != null) {
293 r = attrs [j];
294 break;
297 if (r == null)
298 continue;
299 c.Remove (n);
300 c.InsertBefore (n, r);
304 private XmlDocument CreateIndexStub()
306 XmlDocument index = new XmlDocument();
308 XmlElement index_root = index.CreateElement("Overview");
309 index.AppendChild(index_root);
311 if (assemblies.Count == 0)
312 throw new Exception ("No assembly");
314 XmlElement index_assemblies = index.CreateElement("Assemblies");
315 index_root.AppendChild(index_assemblies);
317 XmlElement index_remarks = index.CreateElement("Remarks");
318 index_remarks.InnerText = "To be added.";
319 index_root.AppendChild(index_remarks);
321 XmlElement index_copyright = index.CreateElement("Copyright");
322 index_copyright.InnerText = "To be added.";
323 index_root.AppendChild(index_copyright);
325 XmlElement index_types = index.CreateElement("Types");
326 index_root.AppendChild(index_types);
328 return index;
331 private static void WriteNamespaceStub(string ns, string outdir) {
332 XmlDocument index = new XmlDocument();
334 XmlElement index_root = index.CreateElement("Namespace");
335 index.AppendChild(index_root);
337 index_root.SetAttribute("Name", ns);
339 XmlElement index_docs = index.CreateElement("Docs");
340 index_root.AppendChild(index_docs);
342 XmlElement index_summary = index.CreateElement("summary");
343 index_summary.InnerText = "To be added.";
344 index_docs.AppendChild(index_summary);
346 XmlElement index_remarks = index.CreateElement("remarks");
347 index_remarks.InnerText = "To be added.";
348 index_docs.AppendChild(index_remarks);
350 WriteFile (outdir + "/ns-" + ns + ".xml", FileMode.CreateNew,
351 writer => WriteXml (index.DocumentElement, writer));
354 public void DoUpdateTypes (string basepath, List<string> typenames, string dest)
356 var found = new HashSet<string> ();
357 foreach (AssemblyDefinition assembly in assemblies) {
358 foreach (DocsTypeInfo docsTypeInfo in GetTypes (assembly, typenames)) {
359 string relpath = DoUpdateType (docsTypeInfo.Type, basepath, dest, docsTypeInfo.EcmaDocs);
360 if (relpath != null)
361 found.Add (docsTypeInfo.Type.FullName);
364 var notFound = from n in typenames where !found.Contains (n) select n;
365 if (notFound.Any ())
366 throw new InvalidOperationException("Type(s) not found: " + string.Join (", ", notFound.ToArray ()));
369 public string DoUpdateType (TypeDefinition type, string basepath, string dest, XmlReader ecmaDocsType)
371 if (type.Namespace == null)
372 Warning ("warning: The type `{0}' is in the root namespace. This may cause problems with display within monodoc.",
373 type.FullName);
374 if (!IsPublic (type))
375 return null;
377 // Must get the A+B form of the type name.
378 string typename = GetTypeFileName(type);
380 string reltypefile = DocUtils.PathCombine (DocUtils.GetNamespace (type), typename + ".xml");
381 string typefile = Path.Combine (basepath, reltypefile);
382 System.IO.FileInfo file = new System.IO.FileInfo(typefile);
384 string output = null;
385 if (dest == null) {
386 output = typefile;
387 } else if (dest == "-") {
388 output = null;
389 } else {
390 output = Path.Combine (dest, reltypefile);
393 if (file.Exists) {
394 // Update
395 XmlDocument basefile = new XmlDocument();
396 try {
397 basefile.Load(typefile);
398 } catch (Exception e) {
399 throw new InvalidOperationException("Error loading " + typefile + ": " + e.Message, e);
402 DoUpdateType2("Updating", basefile, type, output, false, ecmaDocsType);
403 } else {
404 // Stub
405 XmlElement td = StubType(type, output, ecmaDocsType);
406 if (td == null)
407 return null;
409 System.IO.DirectoryInfo dir = new System.IO.DirectoryInfo (DocUtils.PathCombine (dest, type.Namespace));
410 if (!dir.Exists) {
411 dir.Create();
412 Console.WriteLine("Namespace Directory Created: " + type.Namespace);
415 return reltypefile;
418 public void DoUpdateNS (string ns, string nspath, string outpath)
420 Dictionary<TypeDefinition, object> seenTypes = new Dictionary<TypeDefinition,object> ();
421 AssemblyDefinition assembly = assemblies [0];
423 foreach (System.IO.FileInfo file in new System.IO.DirectoryInfo(nspath).GetFiles("*.xml")) {
424 XmlDocument basefile = new XmlDocument();
425 string typefile = Path.Combine(nspath, file.Name);
426 try {
427 basefile.Load(typefile);
428 } catch (Exception e) {
429 throw new InvalidOperationException("Error loading " + typefile + ": " + e.Message, e);
432 string typename =
433 GetTypeFileName (basefile.SelectSingleNode("Type/@FullName").InnerText);
434 TypeDefinition type = assembly.GetType(typename);
435 if (type == null) {
436 Warning ("Type no longer in assembly: " + typename);
437 continue;
440 seenTypes[type] = seenTypes;
441 DoUpdateType2("Updating", basefile, type, Path.Combine(outpath, file.Name), false, null);
444 // Stub types not in the directory
445 foreach (DocsTypeInfo docsTypeInfo in GetTypes (assembly, null)) {
446 TypeDefinition type = docsTypeInfo.Type;
447 if (type.Namespace != ns || seenTypes.ContainsKey(type))
448 continue;
450 XmlElement td = StubType(type, Path.Combine(outpath, GetTypeFileName(type) + ".xml"), docsTypeInfo.EcmaDocs);
451 if (td == null) continue;
455 private static string GetTypeFileName (TypeReference type)
457 return filenameFormatter.GetName (type);
460 public static string GetTypeFileName (string typename)
462 StringBuilder filename = new StringBuilder (typename.Length);
463 int numArgs = 0;
464 int numLt = 0;
465 bool copy = true;
466 for (int i = 0; i < typename.Length; ++i) {
467 char c = typename [i];
468 switch (c) {
469 case '<':
470 copy = false;
471 ++numLt;
472 break;
473 case '>':
474 --numLt;
475 if (numLt == 0) {
476 filename.Append ('`').Append ((numArgs+1).ToString());
477 numArgs = 0;
478 copy = true;
480 break;
481 case ',':
482 if (numLt == 1)
483 ++numArgs;
484 break;
485 default:
486 if (copy)
487 filename.Append (c);
488 break;
491 return filename.ToString ();
494 private void AddIndexAssembly (AssemblyDefinition assembly, XmlElement parent)
496 XmlElement index_assembly = parent.OwnerDocument.CreateElement("Assembly");
497 index_assembly.SetAttribute ("Name", assembly.Name.Name);
498 index_assembly.SetAttribute ("Version", assembly.Name.Version.ToString());
499 MakeAttributes (index_assembly, assembly.CustomAttributes, 0);
500 parent.AppendChild(index_assembly);
503 private void DoUpdateAssemblies (string source, string dest)
505 string indexfile = dest + "/index.xml";
506 XmlDocument index;
507 if (System.IO.File.Exists(indexfile)) {
508 index = new XmlDocument();
509 index.Load(indexfile);
511 // Format change
512 ClearElement(index.DocumentElement, "Assembly");
513 ClearElement(index.DocumentElement, "Attributes");
514 } else {
515 index = CreateIndexStub();
518 string defaultTitle = "Untitled";
519 if (assemblies.Count == 1)
520 defaultTitle = assemblies[0].Name.Name;
521 WriteElementInitialText(index.DocumentElement, "Title", defaultTitle);
523 XmlElement index_types = WriteElement(index.DocumentElement, "Types");
524 XmlElement index_assemblies = WriteElement(index.DocumentElement, "Assemblies");
525 index_assemblies.RemoveAll ();
528 HashSet<string> goodfiles = new HashSet<string> ();
530 foreach (AssemblyDefinition assm in assemblies) {
531 AddIndexAssembly (assm, index_assemblies);
532 DoUpdateAssembly (assm, index_types, source, dest, goodfiles);
535 SortIndexEntries (index_types);
537 CleanupFiles (dest, goodfiles);
538 CleanupIndexTypes (index_types, goodfiles);
539 CleanupExtensions (index_types);
541 WriteFile (indexfile, FileMode.Create,
542 writer => WriteXml(index.DocumentElement, writer));
545 private static char[] InvalidFilenameChars = {'\\', '/', ':', '*', '?', '"', '<', '>', '|'};
547 private void DoUpdateAssembly (AssemblyDefinition assembly, XmlElement index_types, string source, string dest, HashSet<string> goodfiles)
549 foreach (DocsTypeInfo docTypeInfo in GetTypes (assembly, null)) {
550 TypeDefinition type = docTypeInfo.Type;
551 string typename = GetTypeFileName(type);
552 if (!IsPublic (type) || typename.IndexOfAny (InvalidFilenameChars) >= 0)
553 continue;
555 string reltypepath = DoUpdateType (type, source, dest, docTypeInfo.EcmaDocs);
556 if (reltypepath == null)
557 continue;
559 // Add namespace and type nodes into the index file as needed
560 string ns = DocUtils.GetNamespace (type);
561 XmlElement nsnode = (XmlElement) index_types.SelectSingleNode("Namespace[@Name='" + ns + "']");
562 if (nsnode == null) {
563 nsnode = index_types.OwnerDocument.CreateElement("Namespace");
564 nsnode.SetAttribute ("Name", ns);
565 index_types.AppendChild(nsnode);
567 string doc_typename = GetDocTypeName (type);
568 XmlElement typenode = (XmlElement)nsnode.SelectSingleNode("Type[@Name='" + typename + "']");
569 if (typenode == null) {
570 typenode = index_types.OwnerDocument.CreateElement("Type");
571 typenode.SetAttribute("Name", typename);
572 nsnode.AppendChild(typenode);
574 if (typename != doc_typename)
575 typenode.SetAttribute("DisplayName", doc_typename);
576 else
577 typenode.RemoveAttribute("DisplayName");
578 typenode.SetAttribute ("Kind", GetTypeKind (type));
580 // Ensure the namespace index file exists
581 string onsdoc = DocUtils.PathCombine (dest, type.Namespace + ".xml");
582 string nsdoc = DocUtils.PathCombine (dest, "ns-" + type.Namespace + ".xml");
583 if (File.Exists (onsdoc)) {
584 File.Move (onsdoc, nsdoc);
587 if (!File.Exists (nsdoc)) {
588 Console.WriteLine("New Namespace File: " + type.Namespace);
589 WriteNamespaceStub(type.Namespace, dest);
592 goodfiles.Add (reltypepath);
596 class DocsTypeInfo {
597 public TypeDefinition Type;
598 public XmlReader EcmaDocs;
600 public DocsTypeInfo (TypeDefinition type, XmlReader docs)
602 this.Type = type;
603 this.EcmaDocs = docs;
607 IEnumerable<Mono.Documentation.MDocUpdater.DocsTypeInfo> GetTypes (AssemblyDefinition assembly, List<string> forTypes)
609 HashSet<string> seen = null;
610 if (forTypes != null)
611 forTypes.Sort ();
612 if (ecmadocs != null) {
613 seen = new HashSet<string> ();
614 int typeDepth = -1;
615 while (ecmadocs.Read ()) {
616 switch (ecmadocs.Name) {
617 case "Type": {
618 if (typeDepth == -1)
619 typeDepth = ecmadocs.Depth;
620 if (ecmadocs.NodeType != XmlNodeType.Element)
621 continue;
622 if (typeDepth != ecmadocs.Depth) // nested <TypeDefinition/> element?
623 continue;
624 string typename = ecmadocs.GetAttribute ("FullName");
625 string typename2 = GetTypeFileName (typename);
626 if (forTypes != null &&
627 forTypes.BinarySearch (typename) < 0 &&
628 typename != typename2 &&
629 forTypes.BinarySearch (typename2) < 0)
630 continue;
631 TypeDefinition t;
632 if ((t = assembly.GetType (typename)) == null &&
633 (t = assembly.GetType (typename2)) == null)
634 continue;
635 seen.Add (typename);
636 if (typename != typename2)
637 seen.Add (typename2);
638 Console.WriteLine (" Import: {0}", t.FullName);
639 yield return new DocsTypeInfo (t, ecmadocs);
640 break;
642 default:
643 break;
647 foreach (TypeDefinition type in assembly.GetTypes()) {
648 if (forTypes != null && forTypes.BinarySearch (type.FullName) < 0)
649 continue;
650 if (seen != null && seen.Contains (type.FullName))
651 continue;
652 yield return new DocsTypeInfo (type, null);
653 foreach (TypeDefinition nested in type.NestedTypes)
654 yield return new DocsTypeInfo (nested, null);
658 private static void SortIndexEntries (XmlElement indexTypes)
660 XmlNodeList namespaces = indexTypes.SelectNodes ("Namespace");
661 XmlNodeComparer c = new AttributeNameComparer ();
662 SortXmlNodes (indexTypes, namespaces, c);
664 for (int i = 0; i < namespaces.Count; ++i)
665 SortXmlNodes (namespaces [i], namespaces [i].SelectNodes ("Type"), c);
668 private static void SortXmlNodes (XmlNode parent, XmlNodeList children, XmlNodeComparer comparer)
670 MyXmlNodeList l = new MyXmlNodeList (children.Count);
671 for (int i = 0; i < children.Count; ++i)
672 l.Add (children [i]);
673 l.Sort (comparer);
674 for (int i = l.Count - 1; i > 0; --i) {
675 parent.InsertBefore (parent.RemoveChild ((XmlNode) l [i-1]), (XmlNode) l [i]);
679 abstract class XmlNodeComparer : IComparer, IComparer<XmlNode>
681 public abstract int Compare (XmlNode x, XmlNode y);
683 public int Compare (object x, object y)
685 return Compare ((XmlNode) x, (XmlNode) y);
689 class AttributeNameComparer : XmlNodeComparer {
690 string attribute;
692 public AttributeNameComparer ()
693 : this ("Name")
697 public AttributeNameComparer (string attribute)
699 this.attribute = attribute;
702 public override int Compare (XmlNode x, XmlNode y)
704 return x.Attributes [attribute].Value.CompareTo (y.Attributes [attribute].Value);
708 class VersionComparer : XmlNodeComparer {
709 public override int Compare (XmlNode x, XmlNode y)
711 // Some of the existing docs use e.g. 1.0.x.x, which Version doesn't like.
712 string a = GetVersion (x.InnerText);
713 string b = GetVersion (y.InnerText);
714 return new Version (a).CompareTo (new Version (b));
717 static string GetVersion (string v)
719 int n = v.IndexOf ("x");
720 if (n < 0)
721 return v;
722 return v.Substring (0, n-1);
726 private static string GetTypeKind (TypeDefinition type)
728 if (type.IsEnum)
729 return "Enumeration";
730 if (type.IsValueType)
731 return "Structure";
732 if (type.IsInterface)
733 return "Interface";
734 if (DocUtils.IsDelegate (type))
735 return "Delegate";
736 if (type.IsClass || type.FullName == "System.Enum") // FIXME
737 return "Class";
738 throw new ArgumentException ("Unknown kind for type: " + type.FullName);
741 private static bool IsPublic (TypeDefinition type)
743 TypeDefinition decl = type;
744 while (decl != null) {
745 if (!(decl.IsPublic || decl.IsNestedPublic)) {
746 return false;
748 decl = (TypeDefinition) decl.DeclaringType;
750 return true;
753 private void CleanupFiles (string dest, HashSet<string> goodfiles)
755 // Look for files that no longer correspond to types
756 foreach (System.IO.DirectoryInfo nsdir in new System.IO.DirectoryInfo(dest).GetDirectories("*")) {
757 foreach (System.IO.FileInfo typefile in nsdir.GetFiles("*.xml")) {
758 string relTypeFile = Path.Combine(nsdir.Name, typefile.Name);
759 if (!goodfiles.Contains (relTypeFile)) {
760 XmlDocument doc = new XmlDocument ();
761 doc.Load (typefile.FullName);
762 XmlElement e = doc.SelectSingleNode("/Type") as XmlElement;
763 if (UpdateAssemblyVersions(e, GetAssemblyVersions(), false)) {
764 using (TextWriter writer = OpenWrite (typefile.FullName, FileMode.Truncate))
765 WriteXml(doc.DocumentElement, writer);
766 goodfiles.Add (relTypeFile);
767 continue;
769 string newname = typefile.FullName + ".remove";
770 try { System.IO.File.Delete(newname); } catch (Exception) { }
771 try { typefile.MoveTo(newname); } catch (Exception) { }
772 Console.WriteLine("Class no longer present; file renamed: " + Path.Combine(nsdir.Name, typefile.Name));
778 private static TextWriter OpenWrite (string path, FileMode mode)
780 var w = new StreamWriter (
781 new FileStream (path, mode),
782 new UTF8Encoding (false)
784 w.NewLine = "\n";
785 return w;
788 private string[] GetAssemblyVersions ()
790 return (from a in assemblies select GetAssemblyVersion (a)).ToArray ();
793 private static void CleanupIndexTypes (XmlElement index_types, HashSet<string> goodfiles)
795 // Look for type nodes that no longer correspond to types
796 MyXmlNodeList remove = new MyXmlNodeList ();
797 foreach (XmlElement typenode in index_types.SelectNodes("Namespace/Type")) {
798 string fulltypename = Path.Combine (((XmlElement)typenode.ParentNode).GetAttribute("Name"), typenode.GetAttribute("Name") + ".xml");
799 if (!goodfiles.Contains (fulltypename)) {
800 remove.Add (typenode);
803 foreach (XmlNode n in remove)
804 n.ParentNode.RemoveChild (n);
807 private void CleanupExtensions (XmlElement index_types)
809 XmlNode e = index_types.SelectSingleNode ("/Overview/ExtensionMethods");
810 if (extensionMethods.Count == 0) {
811 if (e == null)
812 return;
813 index_types.SelectSingleNode ("/Overview").RemoveChild (e);
814 return;
816 if (e == null) {
817 e = index_types.OwnerDocument.CreateElement ("ExtensionMethods");
818 index_types.SelectSingleNode ("/Overview").AppendChild (e);
820 else
821 e.RemoveAll ();
822 extensionMethods.Sort (DefaultExtensionMethodComparer);
823 foreach (XmlNode m in extensionMethods) {
824 e.AppendChild (index_types.OwnerDocument.ImportNode (m, true));
828 class ExtensionMethodComparer : XmlNodeComparer {
829 public override int Compare (XmlNode x, XmlNode y)
831 XmlNode xLink = x.SelectSingleNode ("Member/Link");
832 XmlNode yLink = y.SelectSingleNode ("Member/Link");
834 int n = xLink.Attributes ["Type"].Value.CompareTo (
835 yLink.Attributes ["Type"].Value);
836 if (n != 0)
837 return n;
838 n = xLink.Attributes ["Member"].Value.CompareTo (
839 yLink.Attributes ["Member"].Value);
840 if (n == 0 && !object.ReferenceEquals (x, y))
841 throw new InvalidOperationException ("Duplicate extension method found!");
842 return n;
846 static readonly XmlNodeComparer DefaultExtensionMethodComparer = new ExtensionMethodComparer ();
848 public void DoUpdateType2 (string message, XmlDocument basefile, TypeDefinition type, string output, bool insertSince, XmlReader ecmaDocsType)
850 Console.WriteLine(message + ": " + type.FullName);
852 StringToXmlNodeMap seenmembers = new StringToXmlNodeMap ();
854 // Update type metadata
855 UpdateType(basefile.DocumentElement, type, ecmaDocsType);
857 if (ecmaDocsType != null) {
858 while (ecmaDocsType.Name != "Members" && ecmaDocsType.Read ()) {
859 // do nothing
861 if (ecmaDocsType.IsEmptyElement)
862 ecmaDocsType = null;
865 // Update existing members. Delete member nodes that no longer should be there,
866 // and remember what members are already documented so we don't add them again.
867 if (true) {
868 MyXmlNodeList todelete = new MyXmlNodeList ();
869 foreach (DocsNodeInfo info in GetDocumentationMembers (basefile, type, ecmaDocsType)) {
870 XmlElement oldmember = info.Node;
871 IMemberReference oldmember2 = info.Member;
872 string sig = oldmember2 != null ? MakeMemberSignature(oldmember2) : null;
874 // Interface implementations and overrides are deleted from the docs
875 // unless the overrides option is given.
876 if (oldmember2 != null && sig == null)
877 oldmember2 = null;
879 // Deleted (or signature changed)
880 if (oldmember2 == null) {
881 if (UpdateAssemblyVersions (oldmember, new string[]{ GetAssemblyVersion (type.Module.Assembly) }, false))
882 continue;
883 DeleteMember ("Member Removed", output, oldmember, todelete);
884 continue;
887 // Duplicated
888 if (seenmembers.ContainsKey (sig)) {
889 if (object.ReferenceEquals (oldmember, seenmembers [sig])) {
890 // ignore, already seen
892 else if (DefaultMemberComparer.Compare (oldmember, seenmembers [sig]) == 0)
893 DeleteMember ("Duplicate Member Found", output, oldmember, todelete);
894 else
895 Warning ("TODO: found a duplicate member '{0}', but it's not identical to the prior member found!", sig);
896 continue;
899 // Update signature information
900 UpdateMember(info);
902 seenmembers.Add (sig, oldmember);
904 foreach (XmlElement oldmember in todelete)
905 oldmember.ParentNode.RemoveChild (oldmember);
908 if (!DocUtils.IsDelegate (type)) {
909 XmlNode members = WriteElement (basefile.DocumentElement, "Members");
910 foreach (IMemberReference m in type.GetMembers()) {
911 if (m is TypeDefinition) continue;
913 string sig = MakeMemberSignature(m);
914 if (sig == null) continue;
915 if (seenmembers.ContainsKey(sig)) continue;
917 XmlElement mm = MakeMember(basefile, new DocsNodeInfo (null, m));
918 if (mm == null) continue;
919 members.AppendChild( mm );
921 Console.WriteLine("Member Added: " + mm.SelectSingleNode("MemberSignature/@Value").InnerText);
922 additions++;
926 // Import code snippets from files
927 foreach (XmlNode code in basefile.GetElementsByTagName("code")) {
928 if (!(code is XmlElement)) continue;
929 string file = ((XmlElement)code).GetAttribute("src");
930 string lang = ((XmlElement)code).GetAttribute("lang");
931 if (file != "") {
932 string src = GetCodeSource (lang, Path.Combine (srcPath, file));
933 if (src != null)
934 code.InnerText = src;
938 if (insertSince && since != null) {
939 XmlNode docs = basefile.DocumentElement.SelectSingleNode("Docs");
940 docs.AppendChild (CreateSinceNode (basefile));
943 do {
944 XmlElement d = basefile.DocumentElement ["Docs"];
945 XmlElement m = basefile.DocumentElement ["Members"];
946 if (d != null && m != null)
947 basefile.DocumentElement.InsertBefore (
948 basefile.DocumentElement.RemoveChild (d), m);
949 SortTypeMembers (m);
950 } while (false);
952 if (output == null)
953 WriteXml(basefile.DocumentElement, Console.Out);
954 else {
955 FileInfo file = new FileInfo (output);
956 if (!file.Directory.Exists) {
957 Console.WriteLine("Namespace Directory Created: " + type.Namespace);
958 file.Directory.Create ();
960 WriteFile (output, FileMode.Create,
961 writer => WriteXml(basefile.DocumentElement, writer));
965 private string GetCodeSource (string lang, string file)
967 int anchorStart;
968 if (lang == "C#" && (anchorStart = file.IndexOf (".cs#")) >= 0) {
969 // Grab the specified region
970 string region = "#region " + file.Substring (anchorStart + 4);
971 file = file.Substring (0, anchorStart + 3);
972 try {
973 using (StreamReader reader = new StreamReader (file)) {
974 string line;
975 StringBuilder src = new StringBuilder ();
976 int indent = -1;
977 while ((line = reader.ReadLine ()) != null) {
978 if (line.Trim() == region) {
979 indent = line.IndexOf (region);
980 continue;
982 if (indent >= 0 && line.Trim().StartsWith ("#endregion")) {
983 break;
985 if (indent >= 0)
986 src.Append (
987 (line.Length > 0 ? line.Substring (indent) : string.Empty) +
988 "\n");
990 return src.ToString ();
992 } catch (Exception e) {
993 Warning ("Could not load <code/> file '{0}' region '{1}': {2}",
994 file, region, show_exceptions ? e.ToString () : e.Message);
995 return null;
998 try {
999 using (StreamReader reader = new StreamReader (file))
1000 return reader.ReadToEnd ();
1001 } catch (Exception e) {
1002 Warning ("Could not load <code/> file '" + file + "': " + e.Message);
1004 return null;
1007 private IEnumerable<DocsNodeInfo> GetDocumentationMembers (XmlDocument basefile, TypeDefinition type, XmlReader ecmaDocsMembers)
1009 if (ecmaDocsMembers != null) {
1010 int membersDepth = ecmaDocsMembers.Depth;
1011 bool go = true;
1012 while (go && ecmaDocsMembers.Read ()) {
1013 switch (ecmaDocsMembers.Name) {
1014 case "Member": {
1015 if (membersDepth != ecmaDocsMembers.Depth - 1 || ecmaDocsMembers.NodeType != XmlNodeType.Element)
1016 continue;
1017 DocumentationMember dm = new DocumentationMember (ecmaDocsMembers);
1018 string xp = GetXPathForMember (dm);
1019 XmlElement oldmember = (XmlElement) basefile.SelectSingleNode (xp);
1020 IMemberReference m;
1021 if (oldmember == null) {
1022 m = GetMember (type, dm);
1023 if (m == null) {
1024 Warning ("Could not import ECMA docs for `{0}'s `{1}': Member not found.",
1025 type.FullName, dm.MemberSignatures ["C#"]);
1026 // SelectSingleNode (ecmaDocsMember, "MemberSignature[@Language=\"C#\"]/@Value").Value);
1027 continue;
1029 // oldmember lookup may have failed due to type parameter renames.
1030 // Try again.
1031 oldmember = (XmlElement) basefile.SelectSingleNode (GetXPathForMember (m));
1032 if (oldmember == null) {
1033 XmlElement members = WriteElement(basefile.DocumentElement, "Members");
1034 oldmember = basefile.CreateElement ("Member");
1035 oldmember.SetAttribute ("MemberName", dm.MemberName);
1036 members.AppendChild (oldmember);
1037 foreach (string key in Sort (dm.MemberSignatures.Keys)) {
1038 XmlElement ms = basefile.CreateElement ("MemberSignature");
1039 ms.SetAttribute ("Language", key);
1040 ms.SetAttribute ("Value", (string) dm.MemberSignatures [key]);
1041 oldmember.AppendChild (ms);
1043 oldmember.SetAttribute ("__monodocer-seen__", "true");
1044 Console.WriteLine ("Member Added: {0}", MakeMemberSignature (m));
1045 additions++;
1048 else {
1049 m = GetMember (type, new DocumentationMember (oldmember));
1050 if (m == null) {
1051 Warning ("Could not import ECMA docs for `{0}'s `{1}': Member not found.",
1052 type.FullName, dm.MemberSignatures ["C#"]);
1053 continue;
1055 oldmember.SetAttribute ("__monodocer-seen__", "true");
1057 DocsNodeInfo node = new DocsNodeInfo (oldmember, m);
1058 if (ecmaDocsMembers.Name != "Docs")
1059 throw new InvalidOperationException ("Found " + ecmaDocsMembers.Name + "; expected <Docs/>!");
1060 node.EcmaDocs = ecmaDocsMembers;
1061 yield return node;
1062 break;
1064 case "Members":
1065 if (membersDepth == ecmaDocsMembers.Depth && ecmaDocsMembers.NodeType == XmlNodeType.EndElement) {
1066 go = false;
1068 break;
1072 foreach (XmlElement oldmember in basefile.SelectNodes("Type/Members/Member")) {
1073 if (oldmember.GetAttribute ("__monodocer-seen__") == "true") {
1074 oldmember.RemoveAttribute ("__monodocer-seen__");
1075 continue;
1077 IMemberReference m = GetMember (type, new DocumentationMember (oldmember));
1078 if (m == null) {
1079 yield return new DocsNodeInfo (oldmember);
1081 else {
1082 yield return new DocsNodeInfo (oldmember, m);
1087 void DeleteMember (string reason, string output, XmlNode member, MyXmlNodeList todelete)
1089 string format = output != null
1090 ? "{0}: File='{1}'; Signature='{4}'"
1091 : "{0}: XPath='/Type[@FullName=\"{2}\"]/Members/Member[@MemberName=\"{3}\"]'; Signature='{4}'";
1092 Warning (format,
1093 reason,
1094 output,
1095 member.OwnerDocument.DocumentElement.GetAttribute ("FullName"),
1096 member.Attributes ["MemberName"].Value,
1097 member.SelectSingleNode ("MemberSignature[@Language='C#']/@Value").Value);
1098 if (!delete && MemberDocsHaveUserContent (member)) {
1099 Warning ("Member deletions must be enabled with the --delete option.");
1100 } else {
1101 todelete.Add (member);
1102 deletions++;
1106 class MemberComparer : XmlNodeComparer {
1107 public override int Compare (XmlNode x, XmlNode y)
1109 int r;
1110 string xMemberName = x.Attributes ["MemberName"].Value;
1111 string yMemberName = y.Attributes ["MemberName"].Value;
1113 // generic methods *end* with '>'
1114 // it's possible for explicitly implemented generic interfaces to
1115 // contain <...> without being a generic method
1116 if ((!xMemberName.EndsWith (">") || !yMemberName.EndsWith (">")) &&
1117 (r = xMemberName.CompareTo (yMemberName)) != 0)
1118 return r;
1120 int lt;
1121 if ((lt = xMemberName.IndexOf ("<")) >= 0)
1122 xMemberName = xMemberName.Substring (0, lt);
1123 if ((lt = yMemberName.IndexOf ("<")) >= 0)
1124 yMemberName = yMemberName.Substring (0, lt);
1125 if ((r = xMemberName.CompareTo (yMemberName)) != 0)
1126 return r;
1128 // if @MemberName matches, then it's either two different types of
1129 // members sharing the same name, e.g. field & property, or it's an
1130 // overloaded method.
1131 // for different type, sort based on MemberType value.
1132 r = x.SelectSingleNode ("MemberType").InnerText.CompareTo (
1133 y.SelectSingleNode ("MemberType").InnerText);
1134 if (r != 0)
1135 return r;
1137 // same type -- must be an overloaded method. Sort based on type
1138 // parameter count, then parameter count, then by the parameter
1139 // type names.
1140 XmlNodeList xTypeParams = x.SelectNodes ("TypeParameters/TypeParameter");
1141 XmlNodeList yTypeParams = y.SelectNodes ("TypeParameters/TypeParameter");
1142 if (xTypeParams.Count != yTypeParams.Count)
1143 return xTypeParams.Count <= yTypeParams.Count ? -1 : 1;
1144 for (int i = 0; i < xTypeParams.Count; ++i) {
1145 r = xTypeParams [i].Attributes ["Name"].Value.CompareTo (
1146 yTypeParams [i].Attributes ["Name"].Value);
1147 if (r != 0)
1148 return r;
1151 XmlNodeList xParams = x.SelectNodes ("Parameters/Parameter");
1152 XmlNodeList yParams = y.SelectNodes ("Parameters/Parameter");
1153 if (xParams.Count != yParams.Count)
1154 return xParams.Count <= yParams.Count ? -1 : 1;
1155 for (int i = 0; i < xParams.Count; ++i) {
1156 r = xParams [i].Attributes ["Type"].Value.CompareTo (
1157 yParams [i].Attributes ["Type"].Value);
1158 if (r != 0)
1159 return r;
1161 // all parameters match, but return value might not match if it was
1162 // changed between one version and another.
1163 XmlNode xReturn = x.SelectSingleNode ("ReturnValue/ReturnType");
1164 XmlNode yReturn = y.SelectSingleNode ("ReturnValue/ReturnType");
1165 if (xReturn != null && yReturn != null) {
1166 r = xReturn.InnerText.CompareTo (yReturn.InnerText);
1167 if (r != 0)
1168 return r;
1171 return 0;
1175 static readonly MemberComparer DefaultMemberComparer = new MemberComparer ();
1177 private static void SortTypeMembers (XmlNode members)
1179 if (members == null)
1180 return;
1181 SortXmlNodes (members, members.SelectNodes ("Member"), DefaultMemberComparer);
1184 private static bool MemberDocsHaveUserContent (XmlNode e)
1186 e = (XmlElement)e.SelectSingleNode("Docs");
1187 if (e == null) return false;
1188 foreach (XmlElement d in e.SelectNodes("*"))
1189 if (d.InnerText != "" && !d.InnerText.StartsWith("To be added"))
1190 return true;
1191 return false;
1194 // UPDATE HELPER FUNCTIONS
1196 private static IMemberReference GetMember (TypeDefinition type, DocumentationMember member)
1198 string membertype = member.MemberType;
1200 string returntype = member.ReturnType;
1202 string docName = member.MemberName;
1203 string[] docTypeParams = GetTypeParameters (docName);
1205 // Loop through all members in this type with the same name
1206 foreach (IMemberReference mi in GetReflectionMembers (type, docName)) {
1207 if (mi is TypeDefinition) continue;
1208 if (GetMemberType(mi) != membertype) continue;
1210 string sig = MakeMemberSignature(mi);
1211 if (sig == null) continue; // not publicly visible
1213 ParameterDefinitionCollection pis = null;
1214 string[] typeParams = null;
1215 if (mi is MethodDefinition) {
1216 MethodDefinition mb = (MethodDefinition) mi;
1217 pis = mb.Parameters;
1218 if (docTypeParams != null && mb.IsGenericMethod ()) {
1219 GenericParameterCollection args = mb.GenericParameters;
1220 if (args.Count == docTypeParams.Length) {
1221 typeParams = args.Cast<GenericParameter> ().Select (p => p.Name).ToArray ();
1225 else if (mi is PropertyDefinition)
1226 pis = ((PropertyDefinition)mi).Parameters;
1228 int mcount = member.Parameters == null ? 0 : member.Parameters.Count;
1229 int pcount = pis == null ? 0 : pis.Count;
1230 if (mcount != pcount)
1231 continue;
1233 MethodDefinition mDef = mi as MethodDefinition;
1234 if (mDef != null && !mDef.IsConstructor) {
1235 // Casting operators can overload based on return type.
1236 if (returntype != GetReplacedString (
1237 GetDocTypeFullName (((MethodDefinition)mi).ReturnType.ReturnType),
1238 typeParams, docTypeParams)) {
1239 continue;
1243 if (pcount == 0)
1244 return mi;
1245 bool good = true;
1246 for (int i = 0; i < pis.Count; i++) {
1247 string paramType = GetReplacedString (
1248 GetDocParameterType (pis [i].ParameterType),
1249 typeParams, docTypeParams);
1250 if (paramType != (string) member.Parameters [i]) {
1251 good = false;
1252 break;
1255 if (!good) continue;
1257 return mi;
1260 return null;
1263 private static IEnumerable<IMemberReference> GetReflectionMembers (TypeDefinition type, string docName)
1265 // need to worry about 4 forms of //@MemberName values:
1266 // 1. "Normal" (non-generic) member names: GetEnumerator
1267 // - Lookup as-is.
1268 // 2. Explicitly-implemented interface member names: System.Collections.IEnumerable.Current
1269 // - try as-is, and try type.member (due to "kludge" for property
1270 // support.
1271 // 3. "Normal" Generic member names: Sort<T> (CSC)
1272 // - need to remove generic parameters --> "Sort"
1273 // 4. Explicitly-implemented interface members for generic interfaces:
1274 // -- System.Collections.Generic.IEnumerable<T>.Current
1275 // - Try as-is, and try type.member, *keeping* the generic parameters.
1276 // --> System.Collections.Generic.IEnumerable<T>.Current, IEnumerable<T>.Current
1277 // 5. As of 2008-01-02, gmcs will do e.g. 'IFoo`1[A].Method' instead of
1278 // 'IFoo<A>.Method' for explicitly implemented methods; don't interpret
1279 // this as (1) or (2).
1280 if (docName.IndexOf ('<') == -1 && docName.IndexOf ('[') == -1) {
1281 // Cases 1 & 2
1282 foreach (IMemberReference mi in type.GetMembers (docName))
1283 yield return mi;
1284 if (CountChars (docName, '.') > 0)
1285 // might be a property; try only type.member instead of
1286 // namespace.type.member.
1287 foreach (IMemberReference mi in
1288 type.GetMembers (DocUtils.GetTypeDotMember (docName)))
1289 yield return mi;
1290 yield break;
1292 // cases 3 & 4
1293 int numLt = 0;
1294 int numDot = 0;
1295 int startLt, startType, startMethod;
1296 startLt = startType = startMethod = -1;
1297 for (int i = 0; i < docName.Length; ++i) {
1298 switch (docName [i]) {
1299 case '<':
1300 if (numLt == 0) {
1301 startLt = i;
1303 ++numLt;
1304 break;
1305 case '>':
1306 --numLt;
1307 if (numLt == 0 && (i + 1) < docName.Length)
1308 // there's another character in docName, so this <...> sequence is
1309 // probably part of a generic type -- case 4.
1310 startLt = -1;
1311 break;
1312 case '.':
1313 startType = startMethod;
1314 startMethod = i;
1315 ++numDot;
1316 break;
1319 string refName = startLt == -1 ? docName : docName.Substring (0, startLt);
1320 // case 3
1321 foreach (IMemberReference mi in type.GetMembers (refName))
1322 yield return mi;
1324 // case 4
1325 foreach (IMemberReference mi in type.GetMembers (refName.Substring (startType + 1)))
1326 yield return mi;
1328 // If we _still_ haven't found it, we've hit another generic naming issue:
1329 // post Mono 1.1.18, gmcs generates [[FQTN]] instead of <TypeName> for
1330 // explicitly-implemented METHOD names (not properties), e.g.
1331 // "System.Collections.Generic.IEnumerable`1[[Foo, test, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null]].GetEnumerator"
1332 // instead of "System.Collections.Generic.IEnumerable<Foo>.GetEnumerator",
1333 // which the XML docs will contain.
1335 // Alas, we can't derive the Mono name from docName, so we need to iterate
1336 // over all member names, convert them into CSC format, and compare... :-(
1337 if (numDot == 0)
1338 yield break;
1339 foreach (IMemberReference mi in type.GetMembers ()) {
1340 if (GetMemberName (mi) == docName)
1341 yield return mi;
1345 static string[] GetTypeParameters (string docName)
1347 if (docName [docName.Length-1] != '>')
1348 return null;
1349 StringList types = new StringList ();
1350 int endToken = docName.Length-2;
1351 int i = docName.Length-2;
1352 do {
1353 if (docName [i] == ',' || docName [i] == '<') {
1354 types.Add (docName.Substring (i + 1, endToken - i));
1355 endToken = i-1;
1357 if (docName [i] == '<')
1358 break;
1359 } while (--i >= 0);
1361 types.Reverse ();
1362 return types.ToArray ();
1365 static string GetReplacedString (string typeName, string[] from, string[] to)
1367 if (from == null)
1368 return typeName;
1369 for (int i = 0; i < from.Length; ++i)
1370 typeName = typeName.Replace (from [i], to [i]);
1371 return typeName;
1374 // CREATE A STUB DOCUMENTATION FILE
1376 public XmlElement StubType (TypeDefinition type, string output, XmlReader ecmaDocsType)
1378 string typesig = MakeTypeSignature(type);
1379 if (typesig == null) return null; // not publicly visible
1381 XmlDocument doc = new XmlDocument();
1382 XmlElement root = doc.CreateElement("Type");
1383 doc.AppendChild (root);
1385 DoUpdateType2 ("New Type", doc, type, output, true, ecmaDocsType);
1387 return root;
1390 private XmlElement CreateSinceNode (XmlDocument doc)
1392 XmlElement s = doc.CreateElement ("since");
1393 s.SetAttribute ("version", since);
1394 return s;
1397 // STUBBING/UPDATING FUNCTIONS
1399 public void UpdateType (XmlElement root, TypeDefinition type, XmlReader ecmaDocsType)
1401 root.SetAttribute("Name", GetDocTypeName (type));
1402 root.SetAttribute("FullName", GetDocTypeFullName (type));
1404 WriteElementAttribute(root, "TypeSignature[@Language='C#']", "Language", "C#");
1405 WriteElementAttribute(root, "TypeSignature[@Language='C#']", "Value", MakeTypeSignature(type));
1407 XmlElement ass = WriteElement(root, "AssemblyInfo");
1408 WriteElementText(ass, "AssemblyName", type.Module.Assembly.Name.Name);
1409 if (!no_assembly_versions) {
1410 UpdateAssemblyVersions (root, type, true);
1412 else {
1413 var versions = ass.SelectNodes ("AssemblyVersion").Cast<XmlNode> ().ToList ();
1414 foreach (var version in versions)
1415 ass.RemoveChild (version);
1417 if (!string.IsNullOrEmpty (type.Module.Assembly.Name.Culture))
1418 WriteElementText(ass, "AssemblyCulture", type.Module.Assembly.Name.Culture);
1419 else
1420 ClearElement(ass, "AssemblyCulture");
1422 // Why-oh-why do we put assembly attributes in each type file?
1423 // Neither monodoc nor monodocs2html use them, so I'm deleting them
1424 // since they're outdated in current docs, and a waste of space.
1425 //MakeAttributes(ass, type.Assembly, true);
1426 XmlNode assattrs = ass.SelectSingleNode("Attributes");
1427 if (assattrs != null)
1428 ass.RemoveChild(assattrs);
1430 NormalizeWhitespace(ass);
1432 if (type.IsGenericType ()) {
1433 MakeTypeParameters (root, type.GenericParameters);
1434 } else {
1435 ClearElement(root, "TypeParameters");
1438 if (type.BaseType != null) {
1439 XmlElement basenode = WriteElement(root, "Base");
1441 string basetypename = GetDocTypeFullName (type.BaseType);
1442 if (basetypename == "System.MulticastDelegate") basetypename = "System.Delegate";
1443 WriteElementText(root, "Base/BaseTypeName", basetypename);
1445 // Document how this type instantiates the generic parameters of its base type
1446 TypeReference origBase = type.BaseType.GetOriginalType ();
1447 if (origBase.IsGenericType ()) {
1448 ClearElement(basenode, "BaseTypeArguments");
1449 GenericInstanceType baseInst = type.BaseType as GenericInstanceType;
1450 GenericArgumentCollection baseGenArgs = baseInst == null ? null : baseInst.GenericArguments;
1451 GenericParameterCollection baseGenParams = origBase.GenericParameters;
1452 if (baseGenArgs.Count != baseGenParams.Count)
1453 throw new InvalidOperationException ("internal error: number of generic arguments doesn't match number of generic parameters.");
1454 for (int i = 0; baseGenArgs != null && i < baseGenArgs.Count; i++) {
1455 GenericParameter param = baseGenParams [i];
1456 TypeReference value = baseGenArgs [i];
1458 XmlElement bta = WriteElement(basenode, "BaseTypeArguments");
1459 XmlElement arg = bta.OwnerDocument.CreateElement("BaseTypeArgument");
1460 bta.AppendChild(arg);
1461 arg.SetAttribute ("TypeParamName", param.Name);
1462 arg.InnerText = GetDocTypeFullName (value);
1465 } else {
1466 ClearElement(root, "Base");
1469 if (!DocUtils.IsDelegate (type) && !type.IsEnum) {
1470 IEnumerable<TypeReference> userInterfaces = DocUtils.GetUserImplementedInterfaces (type);
1471 List<string> interface_names = userInterfaces
1472 .Select (iface => GetDocTypeFullName (iface))
1473 .OrderBy (s => s)
1474 .ToList ();
1476 XmlElement interfaces = WriteElement(root, "Interfaces");
1477 interfaces.RemoveAll();
1478 foreach (string iname in interface_names) {
1479 XmlElement iface = root.OwnerDocument.CreateElement("Interface");
1480 interfaces.AppendChild(iface);
1481 WriteElementText(iface, "InterfaceName", iname);
1483 } else {
1484 ClearElement(root, "Interfaces");
1487 MakeAttributes (root, type.CustomAttributes, 0);
1489 if (DocUtils.IsDelegate (type)) {
1490 MakeTypeParameters (root, type.GenericParameters);
1491 MakeParameters(root, type.GetMethod("Invoke").Parameters);
1492 MakeReturnValue(root, type.GetMethod("Invoke"));
1495 DocsNodeInfo typeInfo = new DocsNodeInfo (WriteElement(root, "Docs"), type);
1496 if (ecmaDocsType != null) {
1497 if (ecmaDocsType.Name != "Docs") {
1498 int depth = ecmaDocsType.Depth;
1499 while (ecmaDocsType.Read ()) {
1500 if (ecmaDocsType.Name == "Docs" && ecmaDocsType.Depth == depth + 1)
1501 break;
1504 if (!ecmaDocsType.IsStartElement ("Docs"))
1505 throw new InvalidOperationException ("Found " + ecmaDocsType.Name + "; expecting <Docs/>!");
1506 typeInfo.EcmaDocs = ecmaDocsType;
1508 MakeDocNode (typeInfo);
1510 if (!DocUtils.IsDelegate (type))
1511 WriteElement (root, "Members");
1513 NormalizeWhitespace(root);
1516 static IEnumerable<T> Sort<T> (IEnumerable<T> list)
1518 List<T> l = new List<T> (list);
1519 l.Sort ();
1520 return l;
1523 private void UpdateMember (DocsNodeInfo info)
1525 XmlElement me = (XmlElement) info.Node;
1526 IMemberReference mi = info.Member;
1527 WriteElementAttribute(me, "MemberSignature[@Language='C#']", "Language", "C#");
1528 WriteElementAttribute(me, "MemberSignature[@Language='C#']", "Value", MakeMemberSignature(mi));
1530 WriteElementText(me, "MemberType", GetMemberType(mi));
1532 if (!no_assembly_versions) {
1533 UpdateAssemblyVersions (me, mi, true);
1535 else {
1536 ClearElement (me, "AssemblyInfo");
1539 ICustomAttributeProvider p = mi as ICustomAttributeProvider;
1540 if (p != null)
1541 MakeAttributes (me, p.CustomAttributes, 0);
1543 PropertyReference pr = mi as PropertyReference;
1544 if (pr != null) {
1545 PropertyDefinition pd = pr.Resolve ();
1546 if (pd.GetMethod != null)
1547 MakeAttributes (me, pd.GetMethod.CustomAttributes, AttributeFlags.KeepExistingAttributes, "get: ");
1548 if (pd.SetMethod != null)
1549 MakeAttributes (me, pd.SetMethod.CustomAttributes, AttributeFlags.KeepExistingAttributes, "set: ");
1551 EventReference er = mi as EventReference;
1552 if (er != null) {
1553 EventDefinition ed = er.Resolve ();
1554 if (ed.AddMethod != null)
1555 MakeAttributes (me, ed.AddMethod.CustomAttributes, AttributeFlags.KeepExistingAttributes, "add: ");
1556 if (ed.RemoveMethod != null)
1557 MakeAttributes (me, ed.RemoveMethod.CustomAttributes, AttributeFlags.KeepExistingAttributes, "remove: ");
1560 MakeReturnValue(me, mi);
1561 if (mi is MethodReference) {
1562 MethodReference mb = (MethodReference) mi;
1563 if (mb.IsGenericMethod ())
1564 MakeTypeParameters (me, mb.GenericParameters);
1566 MakeParameters(me, mi);
1568 string fieldValue;
1569 if (mi is FieldDefinition && GetFieldConstValue ((FieldDefinition)mi, out fieldValue))
1570 WriteElementText(me, "MemberValue", fieldValue);
1572 info.Node = WriteElement (me, "Docs");
1573 MakeDocNode (info);
1574 UpdateExtensionMethods (me, info);
1577 static readonly string[] ValidExtensionMembers = {
1578 "Docs",
1579 "MemberSignature",
1580 "MemberType",
1581 "Parameters",
1582 "ReturnValue",
1583 "TypeParameters",
1586 static readonly string[] ValidExtensionDocMembers = {
1587 "param",
1588 "summary",
1589 "typeparam",
1592 private void UpdateExtensionMethods (XmlElement e, DocsNodeInfo info)
1594 MethodDefinition me = info.Member as MethodDefinition;
1595 if (me == null)
1596 return;
1597 if (info.Parameters.Count < 1)
1598 return;
1599 if (!DocUtils.IsExtensionMethod (me))
1600 return;
1602 XmlNode em = e.OwnerDocument.CreateElement ("ExtensionMethod");
1603 XmlNode member = e.CloneNode (true);
1604 em.AppendChild (member);
1605 RemoveExcept (member, ValidExtensionMembers);
1606 RemoveExcept (member.SelectSingleNode ("Docs"), ValidExtensionDocMembers);
1607 WriteElementText (member, "MemberType", "ExtensionMethod");
1608 XmlElement link = member.OwnerDocument.CreateElement ("Link");
1609 link.SetAttribute ("Type", slashdocFormatter.GetName (me.DeclaringType));
1610 link.SetAttribute ("Member", slashdocFormatter.GetDeclaration (me));
1611 member.AppendChild (link);
1612 AddTargets (em, info);
1614 extensionMethods.Add (em);
1617 private static void RemoveExcept (XmlNode node, string[] except)
1619 if (node == null)
1620 return;
1621 MyXmlNodeList remove = null;
1622 foreach (XmlNode n in node.ChildNodes) {
1623 if (Array.BinarySearch (except, n.Name) < 0) {
1624 if (remove == null)
1625 remove = new MyXmlNodeList ();
1626 remove.Add (n);
1629 if (remove != null)
1630 foreach (XmlNode n in remove)
1631 node.RemoveChild (n);
1634 private static void AddTargets (XmlNode member, DocsNodeInfo info)
1636 XmlElement targets = member.OwnerDocument.CreateElement ("Targets");
1637 member.PrependChild (targets);
1638 if (!(info.Parameters [0].ParameterType is GenericParameter)) {
1639 AppendElementAttributeText (targets, "Target", "Type",
1640 slashdocFormatter.GetDeclaration (info.Parameters [0].ParameterType));
1642 else {
1643 GenericParameter gp = (GenericParameter) info.Parameters [0].ParameterType;
1644 ConstraintCollection constraints = gp.Constraints;
1645 if (constraints.Count == 0)
1646 AppendElementAttributeText (targets, "Target", "Type", "System.Object");
1647 else
1648 foreach (TypeReference c in constraints)
1649 AppendElementAttributeText(targets, "Target", "Type",
1650 slashdocFormatter.GetDeclaration (c));
1654 private static bool GetFieldConstValue (FieldDefinition field, out string value)
1656 value = null;
1657 TypeDefinition type = field.DeclaringType.Resolve ();
1658 if (type != null && type.IsEnum) return false;
1660 if (type != null && type.IsGenericType ()) return false;
1661 if (!field.HasConstant)
1662 return false;
1663 if (field.IsLiteral) {
1664 object val = field.Constant;
1665 if (val == null) value = "null";
1666 else if (val is Enum) value = val.ToString();
1667 else if (val is IFormattable) {
1668 value = ((IFormattable)val).ToString();
1669 if (val is string)
1670 value = "\"" + value + "\"";
1672 if (value != null && value != "")
1673 return true;
1675 return false;
1678 // XML HELPER FUNCTIONS
1680 private static XmlElement WriteElement(XmlNode parent, string element) {
1681 XmlElement ret = (XmlElement)parent.SelectSingleNode(element);
1682 if (ret == null) {
1683 string[] path = element.Split('/');
1684 foreach (string p in path) {
1685 ret = (XmlElement)parent.SelectSingleNode(p);
1686 if (ret == null) {
1687 string ename = p;
1688 if (ename.IndexOf('[') >= 0) // strip off XPath predicate
1689 ename = ename.Substring(0, ename.IndexOf('['));
1690 ret = parent.OwnerDocument.CreateElement(ename);
1691 parent.AppendChild(ret);
1692 parent = ret;
1693 } else {
1694 parent = ret;
1698 return ret;
1700 private static void WriteElementText(XmlNode parent, string element, string value) {
1701 XmlElement node = WriteElement(parent, element);
1702 node.InnerText = value;
1705 static XmlElement AppendElementText (XmlNode parent, string element, string value)
1707 XmlElement n = parent.OwnerDocument.CreateElement (element);
1708 parent.AppendChild (n);
1709 n.InnerText = value;
1710 return n;
1713 static XmlElement AppendElementAttributeText (XmlNode parent, string element, string attribute, string value)
1715 XmlElement n = parent.OwnerDocument.CreateElement (element);
1716 parent.AppendChild (n);
1717 n.SetAttribute (attribute, value);
1718 return n;
1721 private static XmlNode CopyNode (XmlNode source, XmlNode dest)
1723 XmlNode copy = dest.OwnerDocument.ImportNode (source, true);
1724 dest.AppendChild (copy);
1725 return copy;
1728 private static void WriteElementInitialText(XmlElement parent, string element, string value) {
1729 XmlElement node = (XmlElement)parent.SelectSingleNode(element);
1730 if (node != null)
1731 return;
1732 node = WriteElement(parent, element);
1733 node.InnerText = value;
1735 private static void WriteElementAttribute(XmlElement parent, string element, string attribute, string value) {
1736 XmlElement node = WriteElement(parent, element);
1737 if (node.GetAttribute(attribute) == value) return;
1738 node.SetAttribute(attribute, value);
1740 private static void ClearElement(XmlElement parent, string name) {
1741 XmlElement node = (XmlElement)parent.SelectSingleNode(name);
1742 if (node != null)
1743 parent.RemoveChild(node);
1746 // DOCUMENTATION HELPER FUNCTIONS
1748 private void MakeDocNode (DocsNodeInfo info)
1750 List<GenericParameter> genericParams = info.GenericParameters;
1751 ParameterDefinitionCollection parameters = info.Parameters;
1752 TypeReference returntype = info.ReturnType;
1753 bool returnisreturn = info.ReturnIsReturn;
1754 XmlElement e = info.Node;
1755 bool addremarks = info.AddRemarks;
1757 WriteElementInitialText(e, "summary", "To be added.");
1759 if (parameters != null) {
1760 string[] values = new string [parameters.Count];
1761 for (int i = 0; i < values.Length; ++i)
1762 values [i] = parameters [i].Name;
1763 UpdateParameters (e, "param", values);
1766 if (genericParams != null) {
1767 string[] values = new string [genericParams.Count];
1768 for (int i = 0; i < values.Length; ++i)
1769 values [i] = genericParams [i].Name;
1770 UpdateParameters (e, "typeparam", values);
1773 string retnodename = null;
1774 if (returntype != null && returntype.FullName != "System.Void") { // FIXME
1775 retnodename = returnisreturn ? "returns" : "value";
1776 string retnodename_other = !returnisreturn ? "returns" : "value";
1778 // If it has a returns node instead of a value node, change its name.
1779 XmlElement retother = (XmlElement)e.SelectSingleNode(retnodename_other);
1780 if (retother != null) {
1781 XmlElement retnode = e.OwnerDocument.CreateElement(retnodename);
1782 foreach (XmlNode node in retother)
1783 retnode.AppendChild(node.CloneNode(true));
1784 e.ReplaceChild(retnode, retother);
1785 } else {
1786 WriteElementInitialText(e, retnodename, "To be added.");
1788 } else {
1789 ClearElement(e, "returns");
1790 ClearElement(e, "value");
1793 if (addremarks)
1794 WriteElementInitialText(e, "remarks", "To be added.");
1796 if (exceptions.HasValue && info.Member != null &&
1797 (exceptions.Value & ExceptionLocations.AddedMembers) == 0) {
1798 UpdateExceptions (e, info.Member);
1801 if (info.EcmaDocs != null) {
1802 XmlReader r = info.EcmaDocs;
1803 int depth = r.Depth;
1804 r.ReadStartElement ("Docs");
1805 while (r.Read ()) {
1806 if (r.Name == "Docs") {
1807 if (r.Depth == depth && r.NodeType == XmlNodeType.EndElement)
1808 break;
1809 else
1810 throw new InvalidOperationException ("Skipped past current <Docs/> element!");
1812 if (!r.IsStartElement ())
1813 continue;
1814 switch (r.Name) {
1815 case "param":
1816 case "typeparam": {
1817 string name = r.GetAttribute ("name");
1818 if (name == null)
1819 break;
1820 XmlNode doc = e.SelectSingleNode (
1821 r.Name + "[@name='" + name + "']");
1822 string value = r.ReadInnerXml ();
1823 if (doc != null)
1824 doc.InnerXml = value.Replace ("\r", "");
1825 break;
1827 case "altmember":
1828 case "exception":
1829 case "permission":
1830 case "seealso": {
1831 string name = r.Name;
1832 string cref = r.GetAttribute ("cref");
1833 if (cref == null)
1834 break;
1835 XmlNode doc = e.SelectSingleNode (
1836 r.Name + "[@cref='" + cref + "']");
1837 string value = r.ReadInnerXml ().Replace ("\r", "");
1838 if (doc != null)
1839 doc.InnerXml = value;
1840 else {
1841 XmlElement n = e.OwnerDocument.CreateElement (name);
1842 n.SetAttribute ("cref", cref);
1843 n.InnerXml = value;
1844 e.AppendChild (n);
1846 break;
1848 default: {
1849 string name = r.Name;
1850 string xpath = r.Name;
1851 StringList attributes = new StringList (r.AttributeCount);
1852 if (r.MoveToFirstAttribute ()) {
1853 do {
1854 attributes.Add ("@" + r.Name + "=\"" + r.Value + "\"");
1855 } while (r.MoveToNextAttribute ());
1856 r.MoveToContent ();
1858 if (attributes.Count > 0) {
1859 xpath += "[" + string.Join (" and ", attributes.ToArray ()) + "]";
1861 XmlNode doc = e.SelectSingleNode (xpath);
1862 string value = r.ReadInnerXml ().Replace ("\r", "");
1863 if (doc != null) {
1864 doc.InnerXml = value;
1866 else {
1867 XmlElement n = e.OwnerDocument.CreateElement (name);
1868 n.InnerXml = value;
1869 foreach (string a in attributes) {
1870 int eq = a.IndexOf ('=');
1871 n.SetAttribute (a.Substring (1, eq-1), a.Substring (eq+2, a.Length-eq-3));
1873 e.AppendChild (n);
1875 break;
1880 if (info.SlashDocs != null) {
1881 XmlNode elem = info.SlashDocs;
1882 if (elem != null) {
1883 if (elem.SelectSingleNode("summary") != null)
1884 ClearElement(e, "summary");
1885 if (elem.SelectSingleNode("remarks") != null)
1886 ClearElement(e, "remarks");
1887 if (elem.SelectSingleNode("value") != null)
1888 ClearElement(e, "value");
1889 if (retnodename != null && elem.SelectSingleNode(retnodename) != null)
1890 ClearElement(e, retnodename);
1892 foreach (XmlNode child in elem.ChildNodes) {
1893 switch (child.Name) {
1894 case "param":
1895 case "typeparam": {
1896 XmlAttribute name = child.Attributes ["name"];
1897 if (name == null)
1898 break;
1899 XmlElement p2 = (XmlElement) e.SelectSingleNode (child.Name + "[@name='" + name.Value + "']");
1900 if (p2 != null)
1901 p2.InnerXml = child.InnerXml;
1902 break;
1904 case "altmember":
1905 case "exception":
1906 case "permission": {
1907 XmlAttribute cref = child.Attributes ["cref"] ?? child.Attributes ["name"];
1908 if (cref == null)
1909 break;
1910 XmlElement a = (XmlElement) e.SelectSingleNode (child.Name + "[@cref='" + cref.Value + "']");
1911 if (a == null) {
1912 a = e.OwnerDocument.CreateElement (child.Name);
1913 a.SetAttribute ("cref", child.Attributes ["cref"].Value);
1914 e.AppendChild (a);
1916 a.InnerXml = child.InnerXml;
1917 break;
1919 case "seealso": {
1920 XmlAttribute cref = child.Attributes ["cref"];
1921 if (cref == null)
1922 break;
1923 XmlElement a = (XmlElement) e.SelectSingleNode ("altmember[@cref='" + cref.Value + "']");
1924 if (a == null) {
1925 a = e.OwnerDocument.CreateElement ("altmember");
1926 a.SetAttribute ("cref", child.Attributes ["cref"].Value);
1927 e.AppendChild (a);
1929 break;
1931 default:
1932 CopyNode (child, e);
1933 break;
1939 OrderDocsNodes (e, e.ChildNodes);
1940 NormalizeWhitespace(e);
1943 static readonly string[] DocsNodeOrder = {
1944 "typeparam", "param", "summary", "returns", "value", "remarks",
1947 private static void OrderDocsNodes (XmlNode docs, XmlNodeList children)
1949 MyXmlNodeList newChildren = new MyXmlNodeList (children.Count);
1950 for (int i = 0; i < DocsNodeOrder.Length; ++i) {
1951 for (int j = 0; j < children.Count; ++j) {
1952 XmlNode c = children [j];
1953 if (c.Name == DocsNodeOrder [i]) {
1954 newChildren.Add (c);
1958 if (newChildren.Count >= 0)
1959 docs.PrependChild ((XmlNode) newChildren [0]);
1960 for (int i = 1; i < newChildren.Count; ++i) {
1961 XmlNode prev = (XmlNode) newChildren [i-1];
1962 XmlNode cur = (XmlNode) newChildren [i];
1963 docs.RemoveChild (cur);
1964 docs.InsertAfter (cur, prev);
1969 private void UpdateParameters (XmlElement e, string element, string[] values)
1971 if (values != null) {
1972 XmlNode[] paramnodes = new XmlNode[values.Length];
1974 // Some documentation had param nodes with leading spaces.
1975 foreach (XmlElement paramnode in e.SelectNodes(element)){
1976 paramnode.SetAttribute("name", paramnode.GetAttribute("name").Trim());
1979 // If a member has only one parameter, we can track changes to
1980 // the name of the parameter easily.
1981 if (values.Length == 1 && e.SelectNodes(element).Count == 1) {
1982 UpdateParameterName (e, (XmlElement) e.SelectSingleNode(element), values [0]);
1985 bool reinsert = false;
1987 // Pick out existing and still-valid param nodes, and
1988 // create nodes for parameters not in the file.
1989 Hashtable seenParams = new Hashtable();
1990 for (int pi = 0; pi < values.Length; pi++) {
1991 string p = values [pi];
1992 seenParams[p] = pi;
1994 paramnodes[pi] = e.SelectSingleNode(element + "[@name='" + p + "']");
1995 if (paramnodes[pi] != null) continue;
1997 XmlElement pe = e.OwnerDocument.CreateElement(element);
1998 pe.SetAttribute("name", p);
1999 pe.InnerText = "To be added.";
2000 paramnodes[pi] = pe;
2001 reinsert = true;
2004 // Remove parameters that no longer exist and check all params are in the right order.
2005 int idx = 0;
2006 MyXmlNodeList todelete = new MyXmlNodeList ();
2007 foreach (XmlElement paramnode in e.SelectNodes(element)) {
2008 string name = paramnode.GetAttribute("name");
2009 if (!seenParams.ContainsKey(name)) {
2010 if (!delete && !paramnode.InnerText.StartsWith("To be added")) {
2011 Warning ("The following param node can only be deleted if the --delete option is given: ");
2012 if (e.ParentNode == e.OwnerDocument.DocumentElement) {
2013 // delegate type
2014 Warning ("\tXPath=/Type[@FullName=\"{0}\"]/Docs/param[@name=\"{1}\"]",
2015 e.OwnerDocument.DocumentElement.GetAttribute ("FullName"),
2016 name);
2018 else {
2019 Warning ("\tXPath=/Type[@FullName=\"{0}\"]//Member[@MemberName=\"{1}\"]/Docs/param[@name=\"{2}\"]",
2020 e.OwnerDocument.DocumentElement.GetAttribute ("FullName"),
2021 e.ParentNode.Attributes ["MemberName"].Value,
2022 name);
2024 Warning ("\tValue={0}", paramnode.OuterXml);
2025 } else {
2026 todelete.Add (paramnode);
2028 continue;
2031 if ((int)seenParams[name] != idx)
2032 reinsert = true;
2034 idx++;
2037 foreach (XmlNode n in todelete) {
2038 n.ParentNode.RemoveChild (n);
2041 // Re-insert the parameter nodes at the top of the doc section.
2042 if (reinsert)
2043 for (int pi = values.Length-1; pi >= 0; pi--)
2044 e.PrependChild(paramnodes[pi]);
2045 } else {
2046 // Clear all existing param nodes
2047 foreach (XmlNode paramnode in e.SelectNodes(element)) {
2048 if (!delete && !paramnode.InnerText.StartsWith("To be added")) {
2049 Console.WriteLine("The following param node can only be deleted if the --delete option is given:");
2050 Console.WriteLine(paramnode.OuterXml);
2051 } else {
2052 paramnode.ParentNode.RemoveChild(paramnode);
2058 private static void UpdateParameterName (XmlElement docs, XmlElement pe, string newName)
2060 string existingName = pe.GetAttribute ("name");
2061 pe.SetAttribute ("name", newName);
2062 if (existingName == newName)
2063 return;
2064 foreach (XmlElement paramref in docs.SelectNodes (".//paramref"))
2065 if (paramref.GetAttribute ("name").Trim () == existingName)
2066 paramref.SetAttribute ("name", newName);
2069 class CrefComparer : XmlNodeComparer {
2071 public CrefComparer ()
2075 public override int Compare (XmlNode x, XmlNode y)
2077 string xType = x.Attributes ["cref"].Value;
2078 string yType = y.Attributes ["cref"].Value;
2079 string xNamespace = GetNamespace (xType);
2080 string yNamespace = GetNamespace (yType);
2082 int c = xNamespace.CompareTo (yNamespace);
2083 if (c != 0)
2084 return c;
2085 return xType.CompareTo (yType);
2088 static string GetNamespace (string type)
2090 int n = type.LastIndexOf ('.');
2091 if (n >= 0)
2092 return type.Substring (0, n);
2093 return string.Empty;
2097 private void UpdateExceptions (XmlNode docs, IMemberReference member)
2099 foreach (var source in new ExceptionLookup (exceptions.Value)[member]) {
2100 string cref = slashdocFormatter.GetDeclaration (source.Exception);
2101 var node = docs.SelectSingleNode ("exception[@cref='" + cref + "']");
2102 if (node != null)
2103 continue;
2104 XmlElement e = docs.OwnerDocument.CreateElement ("exception");
2105 e.SetAttribute ("cref", cref);
2106 e.InnerXml = "To be added; from: <see cref=\"" +
2107 string.Join ("\" />, <see cref=\"",
2108 source.Sources.Select (m => slashdocFormatter.GetDeclaration (m))
2109 .ToArray ()) +
2110 "\" />";
2111 docs.AppendChild (e);
2113 SortXmlNodes (docs, docs.SelectNodes ("exception"),
2114 new CrefComparer ());
2117 private static void NormalizeWhitespace(XmlElement e) {
2118 // Remove all text and whitespace nodes from the element so it
2119 // is outputted with nice indentation and no blank lines.
2120 ArrayList deleteNodes = new ArrayList();
2121 foreach (XmlNode n in e)
2122 if (n is XmlText || n is XmlWhitespace || n is XmlSignificantWhitespace)
2123 deleteNodes.Add(n);
2124 foreach (XmlNode n in deleteNodes)
2125 n.ParentNode.RemoveChild(n);
2128 private static bool UpdateAssemblyVersions (XmlElement root, IMemberReference member, bool add)
2130 TypeDefinition type = member as TypeDefinition;
2131 if (type == null)
2132 type = member.DeclaringType as TypeDefinition;
2133 return UpdateAssemblyVersions(root, new string[]{ GetAssemblyVersion (type.Module.Assembly) }, add);
2136 private static string GetAssemblyVersion (AssemblyDefinition assembly)
2138 return assembly.Name.Version.ToString();
2141 private static bool UpdateAssemblyVersions(XmlElement root, string[] assemblyVersions, bool add)
2143 XmlElement e = (XmlElement) root.SelectSingleNode ("AssemblyInfo");
2144 if (e == null) {
2145 e = root.OwnerDocument.CreateElement("AssemblyInfo");
2146 root.AppendChild(e);
2148 List<XmlNode> matches = e.SelectNodes ("AssemblyVersion").Cast<XmlNode>()
2149 .Where(v => Array.IndexOf (assemblyVersions, v.InnerText) >= 0)
2150 .ToList ();
2151 // matches.Count > 0 && add: ignore -- already present
2152 if (matches.Count > 0 && !add) {
2153 foreach (XmlNode c in matches)
2154 e.RemoveChild (c);
2156 else if (matches.Count == 0 && add) {
2157 foreach (string sv in assemblyVersions) {
2158 XmlElement c = root.OwnerDocument.CreateElement("AssemblyVersion");
2159 c.InnerText = sv;
2160 e.AppendChild(c);
2163 // matches.Count == 0 && !add: ignore -- already not present
2165 XmlNodeList avs = e.SelectNodes ("AssemblyVersion");
2166 SortXmlNodes (e, avs, new VersionComparer ());
2168 return avs.Count != 0;
2171 // FIXME: get TypeReferences instead of string comparison?
2172 private static string[] IgnorableAttributes = {
2173 // Security related attributes
2174 "System.Reflection.AssemblyKeyFileAttribute",
2175 "System.Reflection.AssemblyDelaySignAttribute",
2176 // Present in @RefType
2177 "System.Runtime.InteropServices.OutAttribute",
2178 // For naming the indexer to use when not using indexers
2179 "System.Reflection.DefaultMemberAttribute",
2180 // for decimal constants
2181 "System.Runtime.CompilerServices.DecimalConstantAttribute",
2182 // compiler generated code
2183 "System.Runtime.CompilerServices.CompilerGeneratedAttribute",
2184 // more compiler generated code, e.g. iterator methods
2185 "System.Diagnostics.DebuggerHiddenAttribute",
2186 "System.Runtime.CompilerServices.FixedBufferAttribute",
2187 "System.Runtime.CompilerServices.UnsafeValueTypeAttribute",
2188 // extension methods
2189 "System.Runtime.CompilerServices.ExtensionAttribute",
2192 [Flags]
2193 enum AttributeFlags {
2194 None,
2195 KeepExistingAttributes = 0x1,
2198 private void MakeAttributes (XmlElement root, CustomAttributeCollection attributes, AttributeFlags flags)
2200 MakeAttributes (root, attributes, flags, null);
2203 private void MakeAttributes (XmlElement root, CustomAttributeCollection attributes, AttributeFlags flags, string prefix)
2205 bool keepExisting = (flags & AttributeFlags.KeepExistingAttributes) != 0;
2206 if (attributes.Count == 0) {
2207 if (!keepExisting)
2208 ClearElement(root, "Attributes");
2209 return;
2212 bool b = false;
2213 XmlElement e = (XmlElement)root.SelectSingleNode("Attributes");
2214 if (e != null && !keepExisting)
2215 e.RemoveAll();
2216 else if (e == null)
2217 e = root.OwnerDocument.CreateElement("Attributes");
2219 foreach (CustomAttribute attribute in attributes.Cast<CustomAttribute> ()
2220 .OrderBy (ca => ca.Constructor.DeclaringType.FullName)) {
2221 if (!attribute.Resolve ()) {
2222 // skip?
2223 Warning ("warning: could not resolve type {0}.",
2224 attribute.Constructor.DeclaringType.FullName);
2226 TypeDefinition attrType = attribute.Constructor.DeclaringType as TypeDefinition;
2227 if (attrType != null && !IsPublic (attrType))
2228 continue;
2229 if (slashdocFormatter.GetName (attribute.Constructor.DeclaringType) == null)
2230 continue;
2232 if (Array.IndexOf (IgnorableAttributes, attribute.Constructor.DeclaringType.FullName) >= 0)
2233 continue;
2235 b = true;
2237 StringList fields = new StringList ();
2239 ParameterDefinitionCollection parameters = attribute.Constructor.Parameters;
2240 for (int i = 0; i < attribute.ConstructorParameters.Count; ++i) {
2241 fields.Add (MakeAttributesValueString (
2242 attribute.ConstructorParameters [i],
2243 parameters [i].ParameterType));
2245 var namedArgs =
2246 (from de in attribute.Fields.Cast<DictionaryEntry> ()
2247 select new { Type=attribute.GetFieldType (de.Key.ToString ()), Name=de.Key, Value=de.Value })
2248 .Concat (
2249 (from de in attribute.Properties.Cast<DictionaryEntry> ()
2250 select new { Type=attribute.GetPropertyType (de.Key.ToString ()), Name=de.Key, Value=de.Value }))
2251 .OrderBy (v => v.Name);
2252 foreach (var d in namedArgs)
2253 fields.Add (string.Format ("{0}={1}", d.Name,
2254 MakeAttributesValueString (d.Value, d.Type)));
2256 string a2 = String.Join(", ", fields.ToArray ());
2257 if (a2 != "") a2 = "(" + a2 + ")";
2259 XmlElement ae = root.OwnerDocument.CreateElement("Attribute");
2260 e.AppendChild(ae);
2262 string name = attribute.Constructor.DeclaringType.FullName;
2263 if (name.EndsWith("Attribute")) name = name.Substring(0, name.Length-"Attribute".Length);
2264 WriteElementText(ae, "AttributeName", prefix + name + a2);
2267 if (b && e.ParentNode == null)
2268 root.AppendChild(e);
2269 else if (!b)
2270 ClearElement(root, "Attributes");
2272 NormalizeWhitespace(e);
2275 private static string MakeAttributesValueString (object v, TypeReference valueType)
2277 if (v == null)
2278 return "null";
2279 if (valueType.FullName == "System.Type")
2280 return "typeof(" + v.ToString () + ")";
2281 if (valueType.FullName == "System.String")
2282 return "\"" + v.ToString () + "\"";
2283 if (v is Boolean)
2284 return (bool)v ? "true" : "false";
2285 TypeDefinition valueDef = valueType.Resolve ();
2286 if (valueDef == null || !valueDef.IsEnum)
2287 return v.ToString ();
2288 string typename = GetDocTypeFullName (valueType);
2289 var values = GetEnumerationValues (valueDef);
2290 long c = ToInt64 (v);
2291 if (values.ContainsKey (c))
2292 return typename + "." + values [c];
2293 if (valueDef.CustomAttributes.Cast<CustomAttribute> ()
2294 .Any (ca => ca.Constructor.DeclaringType.FullName == "System.FlagsAttribute")) {
2295 return string.Join (" | ",
2296 (from i in values.Keys
2297 where (c & i) != 0
2298 select typename + "." + values [i])
2299 .ToArray ());
2301 return "(" + GetDocTypeFullName (valueType) + ") " + v.ToString ();
2304 private static Dictionary<long, string> GetEnumerationValues (TypeDefinition type)
2306 var values = new Dictionary<long, string> ();
2307 foreach (var f in
2308 (from f in type.Fields.Cast<FieldDefinition> ()
2309 where !(f.IsRuntimeSpecialName || f.IsSpecialName)
2310 select f)) {
2311 values [ToInt64 (f.Constant)] = f.Name;
2313 return values;
2316 static long ToInt64 (object value)
2318 if (value is ulong)
2319 return (long) (ulong) value;
2320 return Convert.ToInt64 (value);
2323 private void MakeParameters (XmlElement root, ParameterDefinitionCollection parameters)
2325 XmlElement e = WriteElement(root, "Parameters");
2326 e.RemoveAll();
2327 foreach (ParameterDefinition p in parameters) {
2328 XmlElement pe = root.OwnerDocument.CreateElement("Parameter");
2329 e.AppendChild(pe);
2330 pe.SetAttribute("Name", p.Name);
2331 pe.SetAttribute("Type", GetDocParameterType (p.ParameterType));
2332 if (p.ParameterType is ReferenceType) {
2333 if (p.IsOut) pe.SetAttribute("RefType", "out");
2334 else pe.SetAttribute("RefType", "ref");
2336 MakeAttributes (pe, p.CustomAttributes, 0);
2340 private void MakeTypeParameters (XmlElement root, GenericParameterCollection typeParams)
2342 if (typeParams == null || typeParams.Count == 0) {
2343 XmlElement f = (XmlElement) root.SelectSingleNode ("TypeParameters");
2344 if (f != null)
2345 root.RemoveChild (f);
2346 return;
2348 XmlElement e = WriteElement(root, "TypeParameters");
2349 e.RemoveAll();
2350 foreach (GenericParameter t in typeParams) {
2351 XmlElement pe = root.OwnerDocument.CreateElement("TypeParameter");
2352 e.AppendChild(pe);
2353 pe.SetAttribute("Name", t.Name);
2354 MakeAttributes (pe, t.CustomAttributes, 0);
2355 XmlElement ce = (XmlElement) e.SelectSingleNode ("Constraints");
2356 ConstraintCollection constraints = t.Constraints;
2357 GenericParameterAttributes attrs = t.Attributes;
2358 if (attrs == GenericParameterAttributes.NonVariant && constraints.Count == 0) {
2359 if (ce != null)
2360 e.RemoveChild (ce);
2361 continue;
2363 if (ce != null)
2364 ce.RemoveAll();
2365 else {
2366 ce = root.OwnerDocument.CreateElement ("Constraints");
2368 pe.AppendChild (ce);
2369 if ((attrs & GenericParameterAttributes.Contravariant) != 0)
2370 AppendElementText (ce, "ParameterAttribute", "Contravariant");
2371 if ((attrs & GenericParameterAttributes.Covariant) != 0)
2372 AppendElementText (ce, "ParameterAttribute", "Covariant");
2373 if ((attrs & GenericParameterAttributes.DefaultConstructorConstraint) != 0)
2374 AppendElementText (ce, "ParameterAttribute", "DefaultConstructorConstraint");
2375 if ((attrs & GenericParameterAttributes.NotNullableValueTypeConstraint) != 0)
2376 AppendElementText (ce, "ParameterAttribute", "NotNullableValueTypeConstraint");
2377 if ((attrs & GenericParameterAttributes.ReferenceTypeConstraint) != 0)
2378 AppendElementText (ce, "ParameterAttribute", "ReferenceTypeConstraint");
2379 foreach (TypeReference c in constraints) {
2380 TypeDefinition cd = c.Resolve ();
2381 AppendElementText (ce,
2382 (cd != null && cd.IsInterface) ? "InterfaceName" : "BaseTypeName",
2383 GetDocTypeFullName (c));
2388 private void MakeParameters (XmlElement root, IMemberReference mi)
2390 if (mi is MethodDefinition && ((MethodDefinition) mi).IsConstructor)
2391 MakeParameters (root, ((MethodDefinition)mi).Parameters);
2392 else if (mi is MethodDefinition) {
2393 MethodDefinition mb = (MethodDefinition) mi;
2394 ParameterDefinitionCollection parameters = mb.Parameters;
2395 MakeParameters(root, parameters);
2396 if (parameters.Count > 0 && DocUtils.IsExtensionMethod (mb)) {
2397 XmlElement p = (XmlElement) root.SelectSingleNode ("Parameters/Parameter[position()=1]");
2398 p.SetAttribute ("RefType", "this");
2401 else if (mi is PropertyDefinition) {
2402 ParameterDefinitionCollection parameters = ((PropertyDefinition)mi).Parameters;
2403 if (parameters.Count > 0)
2404 MakeParameters(root, parameters);
2405 else
2406 return;
2408 else if (mi is FieldDefinition) return;
2409 else if (mi is EventDefinition) return;
2410 else throw new ArgumentException();
2413 private static string GetDocParameterType (TypeReference type)
2415 return GetDocTypeFullName (type).Replace ("@", "&");
2418 private void MakeReturnValue (XmlElement root, TypeReference type, CustomAttributeCollection attributes)
2420 XmlElement e = WriteElement(root, "ReturnValue");
2421 e.RemoveAll();
2422 WriteElementText(e, "ReturnType", GetDocTypeFullName (type));
2423 if (attributes != null)
2424 MakeAttributes(e, attributes, 0);
2427 private void MakeReturnValue (XmlElement root, IMemberReference mi)
2429 if (mi is MethodDefinition && ((MethodDefinition) mi).IsConstructor)
2430 return;
2431 else if (mi is MethodDefinition)
2432 MakeReturnValue (root, ((MethodDefinition)mi).ReturnType.ReturnType, ((MethodDefinition)mi).ReturnType.CustomAttributes);
2433 else if (mi is PropertyDefinition)
2434 MakeReturnValue (root, ((PropertyDefinition)mi).PropertyType, null);
2435 else if (mi is FieldDefinition)
2436 MakeReturnValue (root, ((FieldDefinition)mi).FieldType, null);
2437 else if (mi is EventDefinition)
2438 MakeReturnValue (root, ((EventDefinition)mi).EventType, null);
2439 else
2440 throw new ArgumentException(mi + " is a " + mi.GetType().FullName);
2443 private XmlElement MakeMember(XmlDocument doc, DocsNodeInfo info)
2445 IMemberReference mi = info.Member;
2446 if (mi is TypeDefinition) return null;
2448 string sigs = MakeMemberSignature(mi);
2449 if (sigs == null) return null; // not publicly visible
2451 // no documentation for property/event accessors. Is there a better way of doing this?
2452 if (mi.Name.StartsWith("get_")) return null;
2453 if (mi.Name.StartsWith("set_")) return null;
2454 if (mi.Name.StartsWith("add_")) return null;
2455 if (mi.Name.StartsWith("remove_")) return null;
2456 if (mi.Name.StartsWith("raise_")) return null;
2458 XmlElement me = doc.CreateElement("Member");
2459 me.SetAttribute("MemberName", GetMemberName (mi));
2461 info.Node = me;
2462 UpdateMember(info);
2463 if (exceptions.HasValue &&
2464 (exceptions.Value & ExceptionLocations.AddedMembers) != 0)
2465 UpdateExceptions (info.Node, info.Member);
2467 if (since != null) {
2468 XmlNode docs = me.SelectSingleNode("Docs");
2469 docs.AppendChild (CreateSinceNode (doc));
2472 return me;
2475 private static string GetMemberName (IMemberReference mi)
2477 MethodDefinition mb = mi as MethodDefinition;
2478 if (mb == null) {
2479 PropertyDefinition pi = mi as PropertyDefinition;
2480 if (pi == null)
2481 return mi.Name;
2482 return DocUtils.GetPropertyName (pi);
2484 StringBuilder sb = new StringBuilder (mi.Name.Length);
2485 if (!DocUtils.IsExplicitlyImplemented (mb))
2486 sb.Append (mi.Name);
2487 else {
2488 TypeReference iface;
2489 MethodReference ifaceMethod;
2490 DocUtils.GetInfoForExplicitlyImplementedMethod (mb, out iface, out ifaceMethod);
2491 sb.Append (GetDocTypeFullName (iface));
2492 sb.Append ('.');
2493 sb.Append (ifaceMethod.Name);
2495 if (mb.IsGenericMethod ()) {
2496 GenericParameterCollection typeParams = mb.GenericParameters;
2497 if (typeParams.Count > 0) {
2498 sb.Append ("<");
2499 sb.Append (typeParams [0].Name);
2500 for (int i = 1; i < typeParams.Count; ++i)
2501 sb.Append (",").Append (typeParams [i].Name);
2502 sb.Append (">");
2505 return sb.ToString ();
2508 private static int CountChars (string s, char c)
2510 int count = 0;
2511 for (int i = 0; i < s.Length; ++i) {
2512 if (s [i] == c)
2513 ++count;
2515 return count;
2518 /// SIGNATURE GENERATION FUNCTIONS
2520 static string MakeTypeSignature (TypeReference type)
2522 return csharpFormatter.GetDeclaration (type);
2525 static string MakeMemberSignature (IMemberReference mi)
2527 return csharpFullFormatter.GetDeclaration (mi);
2530 static string GetMemberType (IMemberReference mi)
2532 if (mi is MethodDefinition && ((MethodDefinition) mi).IsConstructor)
2533 return "Constructor";
2534 if (mi is MethodDefinition)
2535 return "Method";
2536 if (mi is PropertyDefinition)
2537 return "Property";
2538 if (mi is FieldDefinition)
2539 return "Field";
2540 if (mi is EventDefinition)
2541 return "Event";
2542 throw new ArgumentException();
2545 private static string GetDocTypeName (TypeReference type)
2547 return docTypeFormatter.GetName (type);
2550 private static string GetDocTypeFullName (TypeReference type)
2552 return DocTypeFullMemberFormatter.Default.GetName (type);
2555 class DocsNodeInfo {
2556 public DocsNodeInfo (XmlElement node)
2558 this.Node = node;
2561 public DocsNodeInfo (XmlElement node, TypeDefinition type)
2562 : this (node)
2564 SetType (type);
2567 public DocsNodeInfo (XmlElement node, IMemberReference member)
2568 : this (node)
2570 SetMemberInfo (member);
2573 void SetType (TypeDefinition type)
2575 if (type == null)
2576 throw new ArgumentNullException ("type");
2577 GenericParameters = new List<GenericParameter> (type.GenericParameters.Cast<GenericParameter> ());
2578 List<TypeReference> declTypes = DocUtils.GetDeclaringTypes (type);
2579 int maxGenArgs = DocUtils.GetGenericArgumentCount (type);
2580 for (int i = 0; i < declTypes.Count - 1; ++i) {
2581 int remove = System.Math.Min (maxGenArgs,
2582 DocUtils.GetGenericArgumentCount (declTypes [i]));
2583 maxGenArgs -= remove;
2584 while (remove-- > 0)
2585 GenericParameters.RemoveAt (0);
2587 if (DocUtils.IsDelegate (type)) {
2588 Parameters = type.GetMethod("Invoke").Parameters;
2589 ReturnType = type.GetMethod("Invoke").ReturnType.ReturnType;
2591 SetSlashDocs (type);
2594 void SetMemberInfo (IMemberReference member)
2596 if (member == null)
2597 throw new ArgumentNullException ("member");
2598 ReturnIsReturn = true;
2599 AddRemarks = true;
2600 Member = member;
2602 if (member is MethodReference ) {
2603 MethodReference mr = (MethodReference) member;
2604 Parameters = mr.Parameters;
2605 if (mr.IsGenericMethod ()) {
2606 GenericParameters = new List<GenericParameter> (mr.GenericParameters.Cast<GenericParameter> ());
2609 else if (member is PropertyDefinition) {
2610 Parameters = ((PropertyDefinition) member).Parameters;
2613 if (member is MethodDefinition) {
2614 ReturnType = ((MethodDefinition) member).ReturnType.ReturnType;
2615 } else if (member is PropertyDefinition) {
2616 ReturnType = ((PropertyDefinition) member).PropertyType;
2617 ReturnIsReturn = false;
2620 // no remarks section for enum members
2621 if (member.DeclaringType != null && ((TypeDefinition) member.DeclaringType).IsEnum)
2622 AddRemarks = false;
2623 SetSlashDocs (member);
2626 private void SetSlashDocs (IMemberReference member)
2628 if (slashdocs == null)
2629 return;
2631 string slashdocsig = slashdocFormatter.GetDeclaration (member);
2632 if (slashdocsig != null)
2633 SlashDocs = slashdocs.SelectSingleNode ("doc/members/member[@name='" + slashdocsig + "']");
2636 public TypeReference ReturnType;
2637 public List<GenericParameter> GenericParameters;
2638 public ParameterDefinitionCollection Parameters;
2639 public bool ReturnIsReturn;
2640 public XmlElement Node;
2641 public bool AddRemarks = true;
2642 public XmlNode SlashDocs;
2643 public XmlReader EcmaDocs;
2644 public IMemberReference Member;
2647 static string GetXPathForMember (DocumentationMember member)
2649 StringBuilder xpath = new StringBuilder ();
2650 xpath.Append ("//Members/Member[@MemberName=\"")
2651 .Append (member.MemberName)
2652 .Append ("\"]");
2653 if (member.Parameters != null && member.Parameters.Count > 0) {
2654 xpath.Append ("/Parameters[count(Parameter) = ")
2655 .Append (member.Parameters.Count);
2656 for (int i = 0; i < member.Parameters.Count; ++i) {
2657 xpath.Append (" and Parameter [").Append (i+1).Append ("]/@Type=\"");
2658 xpath.Append (member.Parameters [i]);
2659 xpath.Append ("\"");
2661 xpath.Append ("]/..");
2663 return xpath.ToString ();
2666 public static string GetXPathForMember (XPathNavigator member)
2668 StringBuilder xpath = new StringBuilder ();
2669 xpath.Append ("//Type[@FullName=\"")
2670 .Append (member.SelectSingleNode ("../../@FullName").Value)
2671 .Append ("\"]/");
2672 xpath.Append ("Members/Member[@MemberName=\"")
2673 .Append (member.SelectSingleNode ("@MemberName").Value)
2674 .Append ("\"]");
2675 XPathNodeIterator parameters = member.Select ("Parameters/Parameter");
2676 if (parameters.Count > 0) {
2677 xpath.Append ("/Parameters[count(Parameter) = ")
2678 .Append (parameters.Count);
2679 int i = 0;
2680 while (parameters.MoveNext ()) {
2681 ++i;
2682 xpath.Append (" and Parameter [").Append (i).Append ("]/@Type=\"");
2683 xpath.Append (parameters.Current.Value);
2684 xpath.Append ("\"");
2686 xpath.Append ("]/..");
2688 return xpath.ToString ();
2691 public static string GetXPathForMember (IMemberReference member)
2693 StringBuilder xpath = new StringBuilder ();
2694 xpath.Append ("//Type[@FullName=\"")
2695 .Append (member.DeclaringType.FullName)
2696 .Append ("\"]/");
2697 xpath.Append ("Members/Member[@MemberName=\"")
2698 .Append (GetMemberName (member))
2699 .Append ("\"]");
2701 ParameterDefinitionCollection parameters = null;
2702 if (member is MethodDefinition)
2703 parameters = ((MethodDefinition) member).Parameters;
2704 else if (member is PropertyDefinition) {
2705 parameters = ((PropertyDefinition) member).Parameters;
2707 if (parameters != null && parameters.Count > 0) {
2708 xpath.Append ("/Parameters[count(Parameter) = ")
2709 .Append (parameters.Count);
2710 for (int i = 0; i < parameters.Count; ++i) {
2711 xpath.Append (" and Parameter [").Append (i+1).Append ("]/@Type=\"");
2712 xpath.Append (GetDocParameterType (parameters [i].ParameterType));
2713 xpath.Append ("\"");
2715 xpath.Append ("]/..");
2717 return xpath.ToString ();
2721 static class CecilExtensions {
2722 public static IEnumerable<IMemberReference> GetMembers (this TypeDefinition type)
2724 foreach (var c in type.Constructors)
2725 yield return (IMemberReference) c;
2726 foreach (var e in type.Events)
2727 yield return (IMemberReference) e;
2728 foreach (var f in type.Fields)
2729 yield return (IMemberReference) f;
2730 foreach (var m in type.Methods)
2731 yield return (IMemberReference) m;
2732 foreach (var t in type.NestedTypes)
2733 yield return (IMemberReference) t;
2734 foreach (var p in type.Properties)
2735 yield return (IMemberReference) p;
2738 public static IEnumerable<IMemberReference> GetMembers (this TypeDefinition type, string member)
2740 return GetMembers (type).Where (m => m.Name == member);
2743 public static IMemberReference GetMember (this TypeDefinition type, string member)
2745 return GetMembers (type, member).EnsureZeroOrOne ();
2748 static T EnsureZeroOrOne<T> (this IEnumerable<T> source)
2750 if (source.Count () > 1)
2751 throw new InvalidOperationException ("too many matches");
2752 return source.FirstOrDefault ();
2755 public static MethodDefinition GetMethod (this TypeDefinition type, string method)
2757 return type.Methods.Cast<MethodDefinition> ()
2758 .Where (m => m.Name == method)
2759 .EnsureZeroOrOne ();
2762 public static IEnumerable<IMemberReference> GetDefaultMembers (this TypeReference type)
2764 TypeDefinition def = type as TypeDefinition;
2765 if (def == null)
2766 return new IMemberReference [0];
2767 CustomAttribute defMemberAttr = type.CustomAttributes.Cast<CustomAttribute> ()
2768 .Where (c => c.Constructor.DeclaringType.FullName == "System.Reflection.DefaultMemberAttribute")
2769 .FirstOrDefault ();
2770 if (defMemberAttr == null)
2771 return new IMemberReference [0];
2772 string name = (string) defMemberAttr.ConstructorParameters [0];
2773 return def.Properties.Cast<PropertyDefinition> ()
2774 .Where (p => p.Name == name)
2775 .Select (p => (IMemberReference) p);
2778 public static IEnumerable<TypeDefinition> GetTypes (this AssemblyDefinition assembly)
2780 return assembly.Modules.Cast<ModuleDefinition> ()
2781 .SelectMany (md => md.Types.Cast<TypeDefinition> ());
2784 public static TypeDefinition GetType (this AssemblyDefinition assembly, string type)
2786 return GetTypes (assembly)
2787 .Where (td => td.FullName == type)
2788 .EnsureZeroOrOne ();
2791 public static bool IsGenericType (this TypeReference type)
2793 return type.GenericParameters.Count > 0;
2796 public static bool IsGenericMethod (this MethodReference method)
2798 return method.GenericParameters.Count > 0;
2801 public static IMemberReference Resolve (this IMemberReference member)
2803 EventReference er = member as EventReference;
2804 if (er != null)
2805 return er.Resolve ();
2806 FieldReference fr = member as FieldReference;
2807 if (fr != null)
2808 return fr.Resolve ();
2809 MethodReference mr = member as MethodReference;
2810 if (mr != null)
2811 return mr.Resolve ();
2812 PropertyReference pr = member as PropertyReference;
2813 if (pr != null)
2814 return pr.Resolve ();
2815 TypeReference tr = member as TypeReference;
2816 if (tr != null)
2817 return tr.Resolve ();
2818 throw new NotSupportedException ("Cannot find definition for " + member.ToString ());
2822 static class DocUtils {
2823 public static bool IsExplicitlyImplemented (MethodDefinition method)
2825 return method.IsPrivate && method.IsFinal && method.IsVirtual;
2828 public static string GetTypeDotMember (string name)
2830 int startType, startMethod;
2831 startType = startMethod = -1;
2832 for (int i = 0; i < name.Length; ++i) {
2833 if (name [i] == '.') {
2834 startType = startMethod;
2835 startMethod = i;
2838 return name.Substring (startType+1);
2841 public static string GetMember (string name)
2843 int i = name.LastIndexOf ('.');
2844 if (i == -1)
2845 return name;
2846 return name.Substring (i+1);
2849 public static void GetInfoForExplicitlyImplementedMethod (
2850 MethodDefinition method, out TypeReference iface, out MethodReference ifaceMethod)
2852 iface = null;
2853 ifaceMethod = null;
2854 if (method.Overrides.Count != 1)
2855 throw new InvalidOperationException ("Could not determine interface type for explicitly-implemented interface member " + method.Name);
2856 iface = method.Overrides [0].DeclaringType;
2857 ifaceMethod = method.Overrides [0];
2860 public static string GetPropertyName (PropertyDefinition pi)
2862 // Issue: (g)mcs-generated assemblies that explicitly implement
2863 // properties don't specify the full namespace, just the
2864 // TypeName.Property; .NET uses Full.Namespace.TypeName.Property.
2865 MethodDefinition method = pi.GetMethod;
2866 if (method == null)
2867 method = pi.SetMethod;
2868 if (!IsExplicitlyImplemented (method))
2869 return pi.Name;
2871 // Need to determine appropriate namespace for this member.
2872 TypeReference iface;
2873 MethodReference ifaceMethod;
2874 GetInfoForExplicitlyImplementedMethod (method, out iface, out ifaceMethod);
2875 return string.Join (".", new string[]{
2876 DocTypeFullMemberFormatter.Default.GetName (iface),
2877 GetMember (pi.Name)});
2880 public static string GetNamespace (TypeReference type)
2882 if (type.GetOriginalType ().IsNested)
2883 type = type.GetOriginalType ();
2884 while (type != null && type.IsNested)
2885 type = type.DeclaringType;
2886 if (type == null)
2887 return string.Empty;
2888 return type.Namespace;
2891 public static string PathCombine (string dir, string path)
2893 if (dir == null)
2894 dir = "";
2895 if (path == null)
2896 path = "";
2897 return Path.Combine (dir, path);
2900 public static bool IsExtensionMethod (MethodDefinition method)
2902 return
2903 method.CustomAttributes.Cast<CustomAttribute> ()
2904 .Where (m => m.Constructor.DeclaringType.FullName == "System.Runtime.CompilerServices.ExtensionAttribute")
2905 .Any () &&
2906 method.DeclaringType.CustomAttributes.Cast<CustomAttribute> ()
2907 .Where (m => m.Constructor.DeclaringType.FullName == "System.Runtime.CompilerServices.ExtensionAttribute")
2908 .Any ();
2911 public static bool IsDelegate (TypeDefinition type)
2913 TypeReference baseRef = type.BaseType;
2914 if (baseRef == null)
2915 return false;
2916 return !type.IsAbstract && baseRef.FullName == "System.Delegate" || // FIXME
2917 baseRef.FullName == "System.MulticastDelegate";
2920 public static List<TypeReference> GetDeclaringTypes (TypeReference type)
2922 List<TypeReference> decls = new List<TypeReference> ();
2923 decls.Add (type);
2924 while (type.DeclaringType != null) {
2925 decls.Add (type.DeclaringType);
2926 type = type.DeclaringType;
2928 decls.Reverse ();
2929 return decls;
2932 public static int GetGenericArgumentCount (TypeReference type)
2934 GenericInstanceType inst = type as GenericInstanceType;
2935 return inst != null
2936 ? inst.GenericArguments.Count
2937 : type.GenericParameters.Count;
2940 public static IEnumerable<TypeReference> GetUserImplementedInterfaces (TypeDefinition type)
2942 HashSet<string> inheritedInterfaces = GetInheritedInterfaces (type);
2943 List<TypeReference> userInterfaces = new List<TypeReference> ();
2944 foreach (TypeReference iface in type.Interfaces) {
2945 TypeReference lookup = iface.Resolve () ?? iface;
2946 if (!inheritedInterfaces.Contains (GetQualifiedTypeName (lookup)))
2947 userInterfaces.Add (iface);
2949 return userInterfaces;
2952 private static string GetQualifiedTypeName (TypeReference type)
2954 return "[" + type.Scope.Name + "]" + type.FullName;
2957 private static HashSet<string> GetInheritedInterfaces (TypeDefinition type)
2959 HashSet<string> inheritedInterfaces = new HashSet<string> ();
2960 Action<TypeDefinition> a = null;
2961 a = t => {
2962 if (t == null) return;
2963 foreach (TypeReference r in t.Interfaces) {
2964 inheritedInterfaces.Add (GetQualifiedTypeName (r));
2965 a (r.Resolve ());
2968 TypeReference baseRef = type.BaseType;
2969 while (baseRef != null) {
2970 TypeDefinition baseDef = baseRef.Resolve ();
2971 if (baseDef != null) {
2972 a (baseDef);
2973 baseRef = baseDef.BaseType;
2975 else
2976 baseRef = null;
2978 foreach (TypeReference r in type.Interfaces)
2979 a (r.Resolve ());
2980 return inheritedInterfaces;
2984 class DocumentationMember {
2985 public StringToStringMap MemberSignatures = new StringToStringMap ();
2986 public string ReturnType;
2987 public StringList Parameters;
2988 public string MemberName;
2989 public string MemberType;
2991 public DocumentationMember (XmlReader reader)
2993 MemberName = reader.GetAttribute ("MemberName");
2994 int depth = reader.Depth;
2995 bool go = true;
2996 StringList p = new StringList ();
2997 do {
2998 if (reader.NodeType != XmlNodeType.Element)
2999 continue;
3000 switch (reader.Name) {
3001 case "MemberSignature":
3002 MemberSignatures [reader.GetAttribute ("Language")] = reader.GetAttribute ("Value");
3003 break;
3004 case "MemberType":
3005 MemberType = reader.ReadElementString ();
3006 break;
3007 case "ReturnType":
3008 if (reader.Depth == depth + 2)
3009 ReturnType = reader.ReadElementString ();
3010 break;
3011 case "Parameter":
3012 if (reader.Depth == depth + 2)
3013 p.Add (reader.GetAttribute ("Type"));
3014 break;
3015 case "Docs":
3016 if (reader.Depth == depth + 1)
3017 go = false;
3018 break;
3020 } while (go && reader.Read () && reader.Depth >= depth);
3021 if (p.Count > 0) {
3022 Parameters = p;
3026 public DocumentationMember (XmlNode node)
3028 MemberName = node.Attributes ["MemberName"].Value;
3029 foreach (XmlNode n in node.SelectNodes ("MemberSignature")) {
3030 XmlAttribute l = n.Attributes ["Language"];
3031 XmlAttribute v = n.Attributes ["Value"];
3032 if (l != null && v != null)
3033 MemberSignatures [l.Value] = v.Value;
3035 MemberType = node.SelectSingleNode ("MemberType").InnerText;
3036 XmlNode rt = node.SelectSingleNode ("ReturnValue/ReturnType");
3037 if (rt != null)
3038 ReturnType = rt.InnerText;
3039 XmlNodeList p = node.SelectNodes ("Parameters/Parameter");
3040 if (p.Count > 0) {
3041 Parameters = new StringList (p.Count);
3042 for (int i = 0; i < p.Count; ++i)
3043 Parameters.Add (p [i].Attributes ["Type"].Value);
3048 public enum MemberFormatterState {
3049 None,
3050 WithinArray,
3051 WithinGenericTypeContainer,
3054 public abstract class MemberFormatter {
3055 public virtual string GetName (IMemberReference member)
3057 TypeReference type = member as TypeReference;
3058 if (type != null)
3059 return GetTypeName (type);
3060 MethodReference method = member as MethodReference;
3061 if (method != null && method.Name == ".ctor") // method.IsConstructor
3062 return GetConstructorName (method);
3063 if (method != null)
3064 return GetMethodName (method);
3065 PropertyReference prop = member as PropertyReference;
3066 if (prop != null)
3067 return GetPropertyName (prop);
3068 FieldReference field = member as FieldReference;
3069 if (field != null)
3070 return GetFieldName (field);
3071 EventReference e = member as EventReference;
3072 if (e != null)
3073 return GetEventName (e);
3074 throw new NotSupportedException ("Can't handle: " +
3075 (member == null ? "null" : member.GetType().ToString()));
3078 protected virtual string GetTypeName (TypeReference type)
3080 if (type == null)
3081 throw new ArgumentNullException ("type");
3082 return _AppendTypeName (new StringBuilder (type.Name.Length), type).ToString ();
3085 protected virtual char[] ArrayDelimeters {
3086 get {return new char[]{'[', ']'};}
3089 protected virtual MemberFormatterState MemberFormatterState { get; set; }
3091 protected StringBuilder _AppendTypeName (StringBuilder buf, TypeReference type)
3093 if (type is ArrayType) {
3094 TypeSpecification spec = type as TypeSpecification;
3095 _AppendTypeName (buf, spec != null ? spec.ElementType : type.GetOriginalType ())
3096 .Append (ArrayDelimeters [0]);
3097 var origState = MemberFormatterState;
3098 MemberFormatterState = MemberFormatterState.WithinArray;
3099 ArrayType array = (ArrayType) type;
3100 int rank = array.Rank;
3101 if (rank > 1)
3102 buf.Append (new string (',', rank-1));
3103 MemberFormatterState = origState;
3104 return buf.Append (ArrayDelimeters [1]);
3106 if (type is ReferenceType) {
3107 return AppendRefTypeName (buf, type);
3109 if (type is PointerType) {
3110 return AppendPointerTypeName (buf, type);
3112 AppendNamespace (buf, type);
3113 if (type is GenericParameter) {
3114 return AppendTypeName (buf, type);
3116 GenericInstanceType genInst = type as GenericInstanceType;
3117 if (type.GenericParameters.Count == 0 &&
3118 (genInst == null ? true : genInst.GenericArguments.Count == 0)) {
3119 return AppendFullTypeName (buf, type);
3121 return AppendGenericType (buf, type);
3124 protected virtual StringBuilder AppendNamespace (StringBuilder buf, TypeReference type)
3126 string ns = DocUtils.GetNamespace (type);
3127 if (ns != null && ns.Length > 0)
3128 buf.Append (ns).Append ('.');
3129 return buf;
3132 private StringBuilder AppendFullTypeName (StringBuilder buf, TypeReference type)
3134 if (type.DeclaringType != null)
3135 AppendFullTypeName (buf, type.DeclaringType).Append (NestedTypeSeparator);
3136 return AppendTypeName (buf, type);
3139 protected virtual StringBuilder AppendTypeName (StringBuilder buf, TypeReference type)
3141 return AppendTypeName (buf, type.Name);
3144 protected virtual StringBuilder AppendTypeName (StringBuilder buf, string typename)
3146 int n = typename.IndexOf ("`");
3147 if (n >= 0)
3148 return buf.Append (typename.Substring (0, n));
3149 return buf.Append (typename);
3152 protected virtual string RefTypeModifier {
3153 get {return "@";}
3156 protected virtual StringBuilder AppendRefTypeName (StringBuilder buf, TypeReference type)
3158 TypeSpecification spec = type as TypeSpecification;
3159 return _AppendTypeName (buf, spec != null ? spec.ElementType : type.GetOriginalType ())
3160 .Append (RefTypeModifier);
3163 protected virtual string PointerModifier {
3164 get {return "*";}
3167 protected virtual StringBuilder AppendPointerTypeName (StringBuilder buf, TypeReference type)
3169 TypeSpecification spec = type as TypeSpecification;
3170 return _AppendTypeName (buf, spec != null ? spec.ElementType : type.GetOriginalType ())
3171 .Append (PointerModifier);
3174 protected virtual char[] GenericTypeContainer {
3175 get {return new char[]{'<', '>'};}
3178 protected virtual char NestedTypeSeparator {
3179 get {return '.';}
3182 protected virtual StringBuilder AppendGenericType (StringBuilder buf, TypeReference type)
3184 List<TypeReference> decls = DocUtils.GetDeclaringTypes (
3185 type is GenericInstanceType ? type.GetOriginalType () : type);
3186 List<TypeReference> genArgs = GetGenericArguments (type);
3187 int argIdx = 0;
3188 int prev = 0;
3189 bool insertNested = false;
3190 foreach (var decl in decls) {
3191 TypeReference declDef = decl.Resolve () ?? decl;
3192 if (insertNested) {
3193 buf.Append (NestedTypeSeparator);
3195 insertNested = true;
3196 AppendTypeName (buf, declDef);
3197 int ac = DocUtils.GetGenericArgumentCount (declDef);
3198 int c = ac - prev;
3199 prev = ac;
3200 if (c > 0) {
3201 buf.Append (GenericTypeContainer [0]);
3202 var origState = MemberFormatterState;
3203 MemberFormatterState = MemberFormatterState.WithinGenericTypeContainer;
3204 _AppendTypeName (buf, genArgs [argIdx++]);
3205 for (int i = 1; i < c; ++i)
3206 _AppendTypeName (buf.Append (","), genArgs [argIdx++]);
3207 MemberFormatterState = origState;
3208 buf.Append (GenericTypeContainer [1]);
3211 return buf;
3214 private List<TypeReference> GetGenericArguments (TypeReference type)
3216 var args = new List<TypeReference> ();
3217 GenericInstanceType inst = type as GenericInstanceType;
3218 if (inst != null)
3219 args.AddRange (inst.GenericArguments.Cast<TypeReference> ());
3220 else
3221 args.AddRange (type.GenericParameters.Cast<TypeReference> ());
3222 return args;
3225 protected virtual StringBuilder AppendGenericTypeConstraints (StringBuilder buf, TypeReference type)
3227 return buf;
3230 protected virtual string GetConstructorName (MethodReference constructor)
3232 return constructor.Name;
3235 protected virtual string GetMethodName (MethodReference method)
3237 return method.Name;
3240 protected virtual string GetPropertyName (PropertyReference property)
3242 return property.Name;
3245 protected virtual string GetFieldName (FieldReference field)
3247 return field.Name;
3250 protected virtual string GetEventName (EventReference e)
3252 return e.Name;
3255 public virtual string GetDeclaration (IMemberReference member)
3257 if (member == null)
3258 throw new ArgumentNullException ("member");
3259 TypeDefinition type = member as TypeDefinition;
3260 if (type != null)
3261 return GetTypeDeclaration (type);
3262 MethodDefinition method = member as MethodDefinition;
3263 if (method != null && method.IsConstructor)
3264 return GetConstructorDeclaration (method);
3265 if (method != null)
3266 return GetMethodDeclaration (method);
3267 PropertyDefinition prop = member as PropertyDefinition;
3268 if (prop != null)
3269 return GetPropertyDeclaration (prop);
3270 FieldDefinition field = member as FieldDefinition;
3271 if (field != null)
3272 return GetFieldDeclaration (field);
3273 EventDefinition e = member as EventDefinition;
3274 if (e != null)
3275 return GetEventDeclaration (e);
3276 throw new NotSupportedException ("Can't handle: " + member.GetType().ToString());
3279 protected virtual string GetTypeDeclaration (TypeDefinition type)
3281 if (type == null)
3282 throw new ArgumentNullException ("type");
3283 StringBuilder buf = new StringBuilder (type.Name.Length);
3284 _AppendTypeName (buf, type);
3285 AppendGenericTypeConstraints (buf, type);
3286 return buf.ToString ();
3289 protected virtual string GetConstructorDeclaration (MethodDefinition constructor)
3291 return GetConstructorName (constructor);
3294 protected virtual string GetMethodDeclaration (MethodDefinition method)
3296 // Special signature for destructors.
3297 if (method.Name == "Finalize" && method.Parameters.Count == 0)
3298 return GetFinalizerName (method);
3300 StringBuilder buf = new StringBuilder ();
3302 AppendVisibility (buf, method);
3303 if (buf.Length == 0 &&
3304 !(DocUtils.IsExplicitlyImplemented (method) && !method.IsSpecialName))
3305 return null;
3307 AppendModifiers (buf, method);
3309 if (buf.Length != 0)
3310 buf.Append (" ");
3311 buf.Append (GetName (method.ReturnType.ReturnType)).Append (" ");
3313 AppendMethodName (buf, method);
3314 AppendGenericMethod (buf, method).Append (" ");
3315 AppendParameters (buf, method, method.Parameters);
3316 AppendGenericMethodConstraints (buf, method);
3317 return buf.ToString ();
3320 protected virtual StringBuilder AppendMethodName (StringBuilder buf, MethodDefinition method)
3322 return buf.Append (method.Name);
3325 protected virtual string GetFinalizerName (MethodDefinition method)
3327 return "Finalize";
3330 protected virtual StringBuilder AppendVisibility (StringBuilder buf, MethodDefinition method)
3332 return buf;
3335 protected virtual StringBuilder AppendModifiers (StringBuilder buf, MethodDefinition method)
3337 return buf;
3340 protected virtual StringBuilder AppendGenericMethod (StringBuilder buf, MethodDefinition method)
3342 return buf;
3345 protected virtual StringBuilder AppendParameters (StringBuilder buf, MethodDefinition method, ParameterDefinitionCollection parameters)
3347 return buf;
3350 protected virtual StringBuilder AppendGenericMethodConstraints (StringBuilder buf, MethodDefinition method)
3352 return buf;
3355 protected virtual string GetPropertyDeclaration (PropertyDefinition property)
3357 return GetPropertyName (property);
3360 protected virtual string GetFieldDeclaration (FieldDefinition field)
3362 return GetFieldName (field);
3365 protected virtual string GetEventDeclaration (EventDefinition e)
3367 return GetEventName (e);
3371 class CSharpFullMemberFormatter : MemberFormatter {
3373 protected override StringBuilder AppendNamespace (StringBuilder buf, TypeReference type)
3375 string ns = DocUtils.GetNamespace (type);
3376 if (GetCSharpType (type.FullName) == null && ns != null && ns.Length > 0 && ns != "System")
3377 buf.Append (ns).Append ('.');
3378 return buf;
3381 private string GetCSharpType (string t)
3383 switch (t) {
3384 case "System.Byte": return "byte";
3385 case "System.SByte": return "sbyte";
3386 case "System.Int16": return "short";
3387 case "System.Int32": return "int";
3388 case "System.Int64": return "long";
3390 case "System.UInt16": return "ushort";
3391 case "System.UInt32": return "uint";
3392 case "System.UInt64": return "ulong";
3394 case "System.Single": return "float";
3395 case "System.Double": return "double";
3396 case "System.Decimal": return "decimal";
3397 case "System.Boolean": return "bool";
3398 case "System.Char": return "char";
3399 case "System.Void": return "void";
3400 case "System.String": return "string";
3401 case "System.Object": return "object";
3403 return null;
3406 protected override StringBuilder AppendTypeName (StringBuilder buf, TypeReference type)
3408 if (type is GenericParameter)
3409 return AppendGenericParameterConstraints (buf, (GenericParameter) type).Append (type.Name);
3410 string t = type.FullName;
3411 if (!t.StartsWith ("System.")) {
3412 return base.AppendTypeName (buf, type);
3415 string s = GetCSharpType (t);
3416 if (s != null)
3417 return buf.Append (s);
3419 return base.AppendTypeName (buf, type);
3422 private StringBuilder AppendGenericParameterConstraints (StringBuilder buf, GenericParameter type)
3424 if (MemberFormatterState != MemberFormatterState.WithinGenericTypeContainer)
3425 return buf;
3426 GenericParameterAttributes attrs = type.Attributes;
3427 bool isout = (attrs & GenericParameterAttributes.Covariant) != 0;
3428 bool isin = (attrs & GenericParameterAttributes.Contravariant) != 0;
3429 if (isin)
3430 buf.Append ("in ");
3431 else if (isout)
3432 buf.Append ("out ");
3433 return buf;
3436 protected override string GetTypeDeclaration (TypeDefinition type)
3438 string visibility = GetTypeVisibility (type.Attributes);
3439 if (visibility == null)
3440 return null;
3442 StringBuilder buf = new StringBuilder ();
3444 buf.Append (visibility);
3445 buf.Append (" ");
3447 MemberFormatter full = new CSharpFullMemberFormatter ();
3449 if (DocUtils.IsDelegate (type)) {
3450 buf.Append("delegate ");
3451 MethodDefinition invoke = type.GetMethod ("Invoke");
3452 buf.Append (full.GetName (invoke.ReturnType.ReturnType)).Append (" ");
3453 buf.Append (GetName (type));
3454 AppendParameters (buf, invoke, invoke.Parameters);
3455 AppendGenericTypeConstraints (buf, type);
3456 buf.Append (";");
3458 return buf.ToString();
3461 if (type.IsAbstract && !type.IsInterface)
3462 buf.Append("abstract ");
3463 if (type.IsSealed && !DocUtils.IsDelegate (type) && !type.IsValueType)
3464 buf.Append("sealed ");
3465 buf.Replace ("abstract sealed", "static");
3467 buf.Append (GetTypeKind (type));
3468 buf.Append (" ");
3469 buf.Append (GetCSharpType (type.FullName) == null
3470 ? GetName (type)
3471 : type.Name);
3473 if (!type.IsEnum) {
3474 TypeReference basetype = type.BaseType;
3475 if (basetype != null && basetype.FullName == "System.Object" || type.IsValueType) // FIXME
3476 basetype = null;
3478 List<string> interface_names = DocUtils.GetUserImplementedInterfaces (type)
3479 .Select (iface => full.GetName (iface))
3480 .OrderBy (s => s)
3481 .ToList ();
3483 if (basetype != null || interface_names.Count > 0)
3484 buf.Append (" : ");
3486 if (basetype != null) {
3487 buf.Append (full.GetName (basetype));
3488 if (interface_names.Count > 0)
3489 buf.Append (", ");
3492 for (int i = 0; i < interface_names.Count; i++){
3493 if (i != 0)
3494 buf.Append (", ");
3495 buf.Append (interface_names [i]);
3497 AppendGenericTypeConstraints (buf, type);
3500 return buf.ToString ();
3503 static string GetTypeKind (TypeDefinition t)
3505 if (t.IsEnum)
3506 return "enum";
3507 if (t.IsValueType)
3508 return "struct";
3509 if (t.IsClass || t.FullName == "System.Enum")
3510 return "class";
3511 if (t.IsInterface)
3512 return "interface";
3513 throw new ArgumentException(t.FullName);
3516 static string GetTypeVisibility (TypeAttributes ta)
3518 switch (ta & TypeAttributes.VisibilityMask) {
3519 case TypeAttributes.Public:
3520 case TypeAttributes.NestedPublic:
3521 return "public";
3523 case TypeAttributes.NestedFamily:
3524 case TypeAttributes.NestedFamORAssem:
3525 return "protected";
3527 default:
3528 return null;
3532 protected override StringBuilder AppendGenericTypeConstraints (StringBuilder buf, TypeReference type)
3534 if (type.GenericParameters.Count == 0)
3535 return buf;
3536 return AppendConstraints (buf, type.GenericParameters);
3539 private StringBuilder AppendConstraints (StringBuilder buf, GenericParameterCollection genArgs)
3541 foreach (GenericParameter genArg in genArgs) {
3542 GenericParameterAttributes attrs = genArg.Attributes;
3543 ConstraintCollection constraints = genArg.Constraints;
3544 if (attrs == GenericParameterAttributes.NonVariant && constraints.Count == 0)
3545 continue;
3547 bool isref = (attrs & GenericParameterAttributes.ReferenceTypeConstraint) != 0;
3548 bool isvt = (attrs & GenericParameterAttributes.NotNullableValueTypeConstraint) != 0;
3549 bool isnew = (attrs & GenericParameterAttributes.DefaultConstructorConstraint) != 0;
3550 bool comma = false;
3552 if (!isref && !isvt && !isnew && constraints.Count == 0)
3553 continue;
3554 buf.Append (" where ").Append (genArg.Name).Append (" : ");
3555 if (isref) {
3556 buf.Append ("class");
3557 comma = true;
3559 else if (isvt) {
3560 buf.Append ("struct");
3561 comma = true;
3563 if (constraints.Count > 0 && !isvt) {
3564 if (comma)
3565 buf.Append (", ");
3566 buf.Append (GetTypeName (constraints [0]));
3567 for (int i = 1; i < constraints.Count; ++i)
3568 buf.Append (", ").Append (GetTypeName (constraints [i]));
3570 if (isnew && !isvt) {
3571 if (comma)
3572 buf.Append (", ");
3573 buf.Append ("new()");
3576 return buf;
3579 protected override string GetConstructorDeclaration (MethodDefinition constructor)
3581 StringBuilder buf = new StringBuilder ();
3582 AppendVisibility (buf, constructor);
3583 if (buf.Length == 0)
3584 return null;
3586 buf.Append (' ');
3587 base.AppendTypeName (buf, constructor.DeclaringType.Name).Append (' ');
3588 AppendParameters (buf, constructor, constructor.Parameters);
3589 buf.Append (';');
3591 return buf.ToString ();
3594 protected override string GetMethodDeclaration (MethodDefinition method)
3596 string decl = base.GetMethodDeclaration (method);
3597 if (decl != null)
3598 return decl + ";";
3599 return null;
3602 protected override StringBuilder AppendMethodName (StringBuilder buf, MethodDefinition method)
3604 if (DocUtils.IsExplicitlyImplemented (method)) {
3605 TypeReference iface;
3606 MethodReference ifaceMethod;
3607 DocUtils.GetInfoForExplicitlyImplementedMethod (method, out iface, out ifaceMethod);
3608 return buf.Append (new CSharpMemberFormatter ().GetName (iface))
3609 .Append ('.')
3610 .Append (ifaceMethod.Name);
3612 return base.AppendMethodName (buf, method);
3615 protected override StringBuilder AppendGenericMethodConstraints (StringBuilder buf, MethodDefinition method)
3617 if (method.GenericParameters.Count == 0)
3618 return buf;
3619 return AppendConstraints (buf, method.GenericParameters);
3622 protected override string RefTypeModifier {
3623 get {return "";}
3626 protected override string GetFinalizerName (MethodDefinition method)
3628 return "~" + method.DeclaringType.Name + " ()";
3631 protected override StringBuilder AppendVisibility (StringBuilder buf, MethodDefinition method)
3633 if (method == null)
3634 return buf;
3635 if (method.IsPublic)
3636 return buf.Append ("public");
3637 if (method.IsFamily || method.IsFamilyOrAssembly)
3638 return buf.Append ("protected");
3639 return buf;
3642 protected override StringBuilder AppendModifiers (StringBuilder buf, MethodDefinition method)
3644 string modifiers = String.Empty;
3645 if (method.IsStatic) modifiers += " static";
3646 if (method.IsVirtual && !method.IsAbstract) {
3647 if ((method.Attributes & MethodAttributes.NewSlot) != 0) modifiers += " virtual";
3648 else modifiers += " override";
3650 TypeDefinition declType = (TypeDefinition) method.DeclaringType;
3651 if (method.IsAbstract && !declType.IsInterface) modifiers += " abstract";
3652 if (method.IsFinal) modifiers += " sealed";
3653 if (modifiers == " virtual sealed") modifiers = "";
3655 return buf.Append (modifiers);
3658 protected override StringBuilder AppendGenericMethod (StringBuilder buf, MethodDefinition method)
3660 if (method.IsGenericMethod ()) {
3661 GenericParameterCollection args = method.GenericParameters;
3662 if (args.Count > 0) {
3663 buf.Append ("<");
3664 buf.Append (args [0].Name);
3665 for (int i = 1; i < args.Count; ++i)
3666 buf.Append (",").Append (args [i].Name);
3667 buf.Append (">");
3670 return buf;
3673 protected override StringBuilder AppendParameters (StringBuilder buf, MethodDefinition method, ParameterDefinitionCollection parameters)
3675 return AppendParameters (buf, method, parameters, '(', ')');
3678 private StringBuilder AppendParameters (StringBuilder buf, MethodDefinition method, ParameterDefinitionCollection parameters, char begin, char end)
3680 buf.Append (begin);
3682 if (parameters.Count > 0) {
3683 if (DocUtils.IsExtensionMethod (method))
3684 buf.Append ("this ");
3685 AppendParameter (buf, parameters [0]);
3686 for (int i = 1; i < parameters.Count; ++i) {
3687 buf.Append (", ");
3688 AppendParameter (buf, parameters [i]);
3692 return buf.Append (end);
3695 private StringBuilder AppendParameter (StringBuilder buf, ParameterDefinition parameter)
3697 if (parameter.ParameterType is ReferenceType) {
3698 if (parameter.IsOut)
3699 buf.Append ("out ");
3700 else
3701 buf.Append ("ref ");
3703 buf.Append (GetName (parameter.ParameterType)).Append (" ");
3704 return buf.Append (parameter.Name);
3707 protected override string GetPropertyDeclaration (PropertyDefinition property)
3709 MethodDefinition method;
3711 string get_visible = null;
3712 if ((method = property.GetMethod) != null &&
3713 (DocUtils.IsExplicitlyImplemented (method) ||
3714 (!method.IsPrivate && !method.IsAssembly && !method.IsFamilyAndAssembly)))
3715 get_visible = AppendVisibility (new StringBuilder (), method).ToString ();
3716 string set_visible = null;
3717 if ((method = property.SetMethod) != null &&
3718 (DocUtils.IsExplicitlyImplemented (method) ||
3719 (!method.IsPrivate && !method.IsAssembly && !method.IsFamilyAndAssembly)))
3720 set_visible = AppendVisibility (new StringBuilder (), method).ToString ();
3722 if ((set_visible == null) && (get_visible == null))
3723 return null;
3725 string visibility;
3726 StringBuilder buf = new StringBuilder ();
3727 if (get_visible != null && (set_visible == null || (set_visible != null && get_visible == set_visible)))
3728 buf.Append (visibility = get_visible);
3729 else if (set_visible != null && get_visible == null)
3730 buf.Append (visibility = set_visible);
3731 else
3732 buf.Append (visibility = "public");
3734 // Pick an accessor to use for static/virtual/override/etc. checks.
3735 method = property.SetMethod;
3736 if (method == null)
3737 method = property.GetMethod;
3739 string modifiers = String.Empty;
3740 if (method.IsStatic) modifiers += " static";
3741 if (method.IsVirtual && !method.IsAbstract) {
3742 if ((method.Attributes & MethodAttributes.NewSlot) != 0)
3743 modifiers += " virtual";
3744 else
3745 modifiers += " override";
3747 TypeDefinition declDef = (TypeDefinition) method.DeclaringType;
3748 if (method.IsAbstract && !declDef.IsInterface)
3749 modifiers += " abstract";
3750 if (method.IsFinal)
3751 modifiers += " sealed";
3752 if (modifiers == " virtual sealed")
3753 modifiers = "";
3754 buf.Append (modifiers).Append (' ');
3756 buf.Append (GetName (property.PropertyType)).Append (' ');
3758 IEnumerable<IMemberReference> defs = property.DeclaringType.GetDefaultMembers ();
3759 string name = property.Name;
3760 foreach (IMemberReference mi in defs) {
3761 if (mi == property) {
3762 name = "this";
3763 break;
3766 buf.Append (name == "this" ? name : DocUtils.GetPropertyName (property));
3768 if (property.Parameters.Count != 0) {
3769 AppendParameters (buf, method, property.Parameters, '[', ']');
3772 buf.Append (" {");
3773 if (set_visible != null) {
3774 if (set_visible != visibility)
3775 buf.Append (' ').Append (set_visible);
3776 buf.Append (" set;");
3778 if (get_visible != null) {
3779 if (get_visible != visibility)
3780 buf.Append (' ').Append (get_visible);
3781 buf.Append (" get;");
3783 buf.Append (" }");
3785 return buf [0] != ' ' ? buf.ToString () : buf.ToString (1, buf.Length-1);
3788 protected override string GetFieldDeclaration (FieldDefinition field)
3790 TypeDefinition declType = (TypeDefinition) field.DeclaringType;
3791 if (declType.IsEnum && field.Name == "value__")
3792 return null; // This member of enums aren't documented.
3794 StringBuilder buf = new StringBuilder ();
3795 AppendFieldVisibility (buf, field);
3796 if (buf.Length == 0)
3797 return null;
3799 if (declType.IsEnum)
3800 return field.Name;
3802 if (field.IsStatic && !field.IsLiteral)
3803 buf.Append (" static");
3804 if (field.IsInitOnly)
3805 buf.Append (" readonly");
3806 if (field.IsLiteral)
3807 buf.Append (" const");
3809 buf.Append (' ').Append (GetName (field.FieldType)).Append (' ');
3810 buf.Append (field.Name);
3811 AppendFieldValue (buf, field);
3812 buf.Append (';');
3814 return buf.ToString ();
3817 static StringBuilder AppendFieldVisibility (StringBuilder buf, FieldDefinition field)
3819 if (field.IsPublic)
3820 return buf.Append ("public");
3821 if (field.IsFamily || field.IsFamilyOrAssembly)
3822 return buf.Append ("protected");
3823 return buf;
3826 static StringBuilder AppendFieldValue (StringBuilder buf, FieldDefinition field)
3828 // enums have a value__ field, which we ignore
3829 if (((TypeDefinition ) field.DeclaringType).IsEnum ||
3830 field.DeclaringType.IsGenericType ())
3831 return buf;
3832 if (field.HasConstant && field.IsLiteral) {
3833 object val = null;
3834 try {
3835 val = field.Constant;
3836 } catch {
3837 return buf;
3839 if (val == null)
3840 buf.Append (" = ").Append ("null");
3841 else if (val is Enum)
3842 buf.Append (" = ").Append (val.ToString ());
3843 else if (val is IFormattable) {
3844 string value = ((IFormattable)val).ToString();
3845 if (val is string)
3846 value = "\"" + value + "\"";
3847 buf.Append (" = ").Append (value);
3850 return buf;
3853 protected override string GetEventDeclaration (EventDefinition e)
3855 StringBuilder buf = new StringBuilder ();
3856 if (AppendVisibility (buf, e.AddMethod).Length == 0) {
3857 return null;
3860 AppendModifiers (buf, e.AddMethod);
3862 buf.Append (" event ");
3863 buf.Append (GetName (e.EventType)).Append (' ');
3864 buf.Append (e.Name).Append (';');
3866 return buf.ToString ();
3870 class CSharpMemberFormatter : CSharpFullMemberFormatter {
3871 protected override StringBuilder AppendNamespace (StringBuilder buf, TypeReference type)
3873 return buf;
3877 class DocTypeFullMemberFormatter : MemberFormatter {
3878 public static readonly MemberFormatter Default = new DocTypeFullMemberFormatter ();
3880 protected override char NestedTypeSeparator {
3881 get {return '+';}
3885 class DocTypeMemberFormatter : DocTypeFullMemberFormatter {
3886 protected override StringBuilder AppendNamespace (StringBuilder buf, TypeReference type)
3888 return buf;
3892 class SlashDocMemberFormatter : MemberFormatter {
3894 protected override char[] GenericTypeContainer {
3895 get {return new char[]{'{', '}'};}
3898 private bool AddTypeCount = true;
3900 private TypeReference genDeclType;
3901 private MethodReference genDeclMethod;
3903 protected override StringBuilder AppendTypeName (StringBuilder buf, TypeReference type)
3905 if (type is GenericParameter) {
3906 int l = buf.Length;
3907 if (genDeclType != null) {
3908 GenericParameterCollection genArgs = genDeclType.GenericParameters;
3909 for (int i = 0; i < genArgs.Count; ++i) {
3910 if (genArgs [i].Name == type.Name) {
3911 buf.Append ('`').Append (i);
3912 break;
3916 if (genDeclMethod != null) {
3917 GenericParameterCollection genArgs = null;
3918 if (genDeclMethod.IsGenericMethod ()) {
3919 genArgs = genDeclMethod.GenericParameters;
3920 for (int i = 0; i < genArgs.Count; ++i) {
3921 if (genArgs [i].Name == type.Name) {
3922 buf.Append ("``").Append (i);
3923 break;
3928 if (genDeclType == null && genDeclMethod == null) {
3929 // Probably from within an explicitly implemented interface member,
3930 // where CSC uses parameter names instead of indices (why?), e.g.
3931 // MyList`2.Mono#DocTest#Generic#IFoo{A}#Method``1(`0,``0) instead of
3932 // MyList`2.Mono#DocTest#Generic#IFoo{`0}#Method``1(`0,``0).
3933 buf.Append (type.Name);
3935 if (buf.Length == l) {
3936 throw new Exception (string.Format (
3937 "Unable to translate generic parameter {0}; genDeclType={1}, genDeclMethod={2}",
3938 type.Name, genDeclType, genDeclMethod));
3941 else {
3942 base.AppendTypeName (buf, type);
3943 if (AddTypeCount) {
3944 int numArgs = type.GenericParameters.Count;
3945 if (type.DeclaringType != null)
3946 numArgs -= type.GenericParameters.Count;
3947 if (numArgs > 0) {
3948 buf.Append ('`').Append (numArgs);
3952 return buf;
3955 protected override StringBuilder AppendGenericType (StringBuilder buf, TypeReference type)
3957 if (!AddTypeCount)
3958 base.AppendGenericType (buf, type);
3959 else
3960 AppendType (buf, type);
3961 return buf;
3964 private StringBuilder AppendType (StringBuilder buf, TypeReference type)
3966 List<TypeReference> decls = DocUtils.GetDeclaringTypes (type);
3967 bool insertNested = false;
3968 int prevParamCount = 0;
3969 foreach (var decl in decls) {
3970 if (insertNested)
3971 buf.Append (NestedTypeSeparator);
3972 insertNested = true;
3973 base.AppendTypeName (buf, decl);
3974 int argCount = DocUtils.GetGenericArgumentCount (decl);
3975 int numArgs = argCount - prevParamCount;
3976 prevParamCount = argCount;
3977 if (numArgs > 0)
3978 buf.Append ('`').Append (numArgs);
3980 return buf;
3983 public override string GetDeclaration (IMemberReference member)
3985 TypeReference r = member as TypeReference;
3986 if (r != null) {
3987 return "T:" + GetTypeName (r);
3989 return base.GetDeclaration (member);
3992 protected override string GetConstructorName (MethodReference constructor)
3994 return GetMethodDefinitionName (constructor, "#ctor");
3997 protected override string GetMethodName (MethodReference method)
3999 string name = null;
4000 MethodDefinition methodDef = method as MethodDefinition;
4001 if (methodDef == null || !DocUtils.IsExplicitlyImplemented (methodDef))
4002 name = method.Name;
4003 else {
4004 TypeReference iface;
4005 MethodReference ifaceMethod;
4006 DocUtils.GetInfoForExplicitlyImplementedMethod (methodDef, out iface, out ifaceMethod);
4007 AddTypeCount = false;
4008 name = GetTypeName (iface) + "." + ifaceMethod.Name;
4009 AddTypeCount = true;
4011 return GetMethodDefinitionName (method, name);
4014 private string GetMethodDefinitionName (MethodReference method, string name)
4016 StringBuilder buf = new StringBuilder ();
4017 buf.Append (GetTypeName (method.DeclaringType));
4018 buf.Append ('.');
4019 buf.Append (name.Replace (".", "#"));
4020 if (method.IsGenericMethod ()) {
4021 GenericParameterCollection genArgs = method.GenericParameters;
4022 if (genArgs.Count > 0)
4023 buf.Append ("``").Append (genArgs.Count);
4025 ParameterDefinitionCollection parameters = method.Parameters;
4026 try {
4027 genDeclType = method.DeclaringType;
4028 genDeclMethod = method;
4029 AppendParameters (buf, method.DeclaringType.GenericParameters, parameters);
4031 finally {
4032 genDeclType = null;
4033 genDeclMethod = null;
4035 return buf.ToString ();
4038 private StringBuilder AppendParameters (StringBuilder buf, GenericParameterCollection genArgs, ParameterDefinitionCollection parameters)
4040 if (parameters.Count == 0)
4041 return buf;
4043 buf.Append ('(');
4045 AppendParameter (buf, genArgs, parameters [0]);
4046 for (int i = 1; i < parameters.Count; ++i) {
4047 buf.Append (',');
4048 AppendParameter (buf, genArgs, parameters [i]);
4051 return buf.Append (')');
4054 private StringBuilder AppendParameter (StringBuilder buf, GenericParameterCollection genArgs, ParameterDefinition parameter)
4056 AddTypeCount = false;
4057 buf.Append (GetTypeName (parameter.ParameterType));
4058 AddTypeCount = true;
4059 return buf;
4062 protected override string GetPropertyName (PropertyReference property)
4064 string name = null;
4066 PropertyDefinition propertyDef = property as PropertyDefinition;
4067 MethodDefinition method = null;
4068 if (propertyDef != null)
4069 method = propertyDef.GetMethod ?? propertyDef.SetMethod;
4070 if (method != null && !DocUtils.IsExplicitlyImplemented (method))
4071 name = property.Name;
4072 else {
4073 TypeReference iface;
4074 MethodReference ifaceMethod;
4075 DocUtils.GetInfoForExplicitlyImplementedMethod (method, out iface, out ifaceMethod);
4076 AddTypeCount = false;
4077 name = string.Join ("#", new string[]{
4078 GetTypeName (iface).Replace (".", "#"),
4079 DocUtils.GetMember (property.Name)
4081 AddTypeCount = true;
4084 StringBuilder buf = new StringBuilder ();
4085 buf.Append (GetName (property.DeclaringType));
4086 buf.Append ('.');
4087 buf.Append (name);
4088 ParameterDefinitionCollection parameters = property.Parameters;
4089 if (parameters.Count > 0) {
4090 genDeclType = property.DeclaringType;
4091 buf.Append ('(');
4092 GenericParameterCollection genArgs = property.DeclaringType.GenericParameters;
4093 AppendParameter (buf, genArgs, parameters [0]);
4094 for (int i = 1; i < parameters.Count; ++i) {
4095 buf.Append (',');
4096 AppendParameter (buf, genArgs, parameters [i]);
4098 buf.Append (')');
4099 genDeclType = null;
4101 return buf.ToString ();
4104 protected override string GetFieldName (FieldReference field)
4106 return string.Format ("{0}.{1}",
4107 GetName (field.DeclaringType), field.Name);
4110 protected override string GetEventName (EventReference e)
4112 return string.Format ("{0}.{1}",
4113 GetName (e.DeclaringType), e.Name);
4116 protected override string GetTypeDeclaration (TypeDefinition type)
4118 string name = GetName (type);
4119 if (type == null)
4120 return null;
4121 return "T:" + name;
4124 protected override string GetConstructorDeclaration (MethodDefinition constructor)
4126 string name = GetName (constructor);
4127 if (name == null)
4128 return null;
4129 return "M:" + name;
4132 protected override string GetMethodDeclaration (MethodDefinition method)
4134 string name = GetName (method);
4135 if (name == null)
4136 return null;
4137 if (method.Name == "op_Implicit" || method.Name == "op_Explicit") {
4138 genDeclType = method.DeclaringType;
4139 genDeclMethod = method;
4140 name += "~" + GetName (method.ReturnType.ReturnType);
4141 genDeclType = null;
4142 genDeclMethod = null;
4144 return "M:" + name;
4147 protected override string GetPropertyDeclaration (PropertyDefinition property)
4149 string name = GetName (property);
4150 if (name == null)
4151 return null;
4152 return "P:" + name;
4155 protected override string GetFieldDeclaration (FieldDefinition field)
4157 string name = GetName (field);
4158 if (name == null)
4159 return null;
4160 return "F:" + name;
4163 protected override string GetEventDeclaration (EventDefinition e)
4165 string name = GetName (e);
4166 if (name == null)
4167 return null;
4168 return "E:" + name;
4172 class FileNameMemberFormatter : SlashDocMemberFormatter {
4173 protected override StringBuilder AppendNamespace (StringBuilder buf, TypeReference type)
4175 return buf;
4178 protected override char NestedTypeSeparator {
4179 get {return '+';}