// Updater program for syncing Mono's ECMA-style documentation files // with an assembly. // By Joshua Tauberer using System; using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Text; using System.Xml; using System.Xml.XPath; using Mono.Cecil; using Mono.Options; using MyXmlNodeList = System.Collections.Generic.List; using StringList = System.Collections.Generic.List; using StringToStringMap = System.Collections.Generic.Dictionary; using StringToXmlNodeMap = System.Collections.Generic.Dictionary; namespace Mono.Documentation { static class NativeTypeManager { static Dictionary toNativeType = new Dictionary(){ {"int", "nint"}, {"Int32", "nint"}, {"System.Int32", "System.nint"}, {"uint", "nuint"}, {"UInt32", "nuint"}, {"System.UInt32", "System.nuint"}, {"float", "nfloat"}, {"Single", "nfloat"}, {"System.Single", "System.nfloat"}, {"SizeF", "CoreGraphics.CGSize"}, {"System.Drawing.SizeF", "CoreGraphics.CGSize"}, {"PointF", "CoreGraphics.CGPoint"}, {"System.Drawing.PointF", "CoreGraphics.CGPoint"}, {"RectangleF", "CoreGraphics.CGRect" }, {"System.Drawing.RectangleF", "CoreGraphics.CGRect"} }; static Dictionary fromNativeType = new Dictionary(){ {"nint", "int"}, {"System.nint", "System.Int32"}, {"nuint", "uint"}, {"System.nuint", "System.UInt32"}, {"nfloat", "float"}, {"System.nfloat", "System.Single"}, {"CoreGraphics.CGSize", "System.Drawing.SizeF"}, {"CoreGraphics.CGPoint", "System.Drawing.PointF"}, {"CoreGraphics.CGRect", "System.Drawing.RectangleF"}, {"MonoTouch.CoreGraphics.CGSize", "System.Drawing.SizeF"}, {"MonoTouch.CoreGraphics.CGPoint", "System.Drawing.PointF"}, {"MonoTouch.CoreGraphics.CGRect", "System.Drawing.RectangleF"} }; public static string ConvertToNativeType(string typename) { string nvalue; bool isOut=false; bool isArray=false; string valueToCompare = StripToComparableType (typename, ref isOut, ref isArray); if (toNativeType.TryGetValue (valueToCompare, out nvalue)) { if (isArray) { nvalue += "[]"; } if (isOut) { nvalue += "&"; } return nvalue; } return typename; } public static string ConvertFromNativeType(string typename) { string nvalue; bool isOut=false; bool isArray=false; string valueToCompare = StripToComparableType (typename, ref isOut, ref isArray); if (fromNativeType.TryGetValue (valueToCompare, out nvalue)) { if (isArray) { nvalue += "[]"; } if (isOut) { nvalue += "&"; } return nvalue; } // it wasn't one of the native types ... just return it return typename; } static string StripToComparableType (string typename, ref bool isOut, ref bool isArray) { string valueToCompare = typename; if (typename.EndsWith ("[]")) { valueToCompare = typename.Substring (0, typename.Length - 2); isArray = true; } if (typename.EndsWith ("&")) { valueToCompare = typename.Substring (0, typename.Length - 1); isOut = true; } if (typename.Contains ("<")) { // TODO: Need to recursively process generic parameters } return valueToCompare; } public static string GetTranslatedName(TypeReference t) { string typename = t.FullName; bool isInAssembly = MDocUpdater.IsInAssemblies (t.Module.Name); if (isInAssembly && !typename.StartsWith ("System") && MDocUpdater.HasDroppedNamespace (t)) { string nameWithDropped = string.Format ("{0}.{1}", MDocUpdater.droppedNamespace, typename); return nameWithDropped; } return typename; } } class MDocUpdater : MDocCommand { string srcPath; List assemblies; readonly DefaultAssemblyResolver assemblyResolver = new DefaultAssemblyResolver(); bool multiassembly; bool delete; bool show_exceptions; bool no_assembly_versions, ignore_missing_types; ExceptionLocations? exceptions; internal int additions = 0, deletions = 0; List importers = new List (); DocumentationEnumerator docEnum; string since; static readonly MemberFormatter docTypeFormatter = new DocTypeMemberFormatter (); static readonly MemberFormatter filenameFormatter = new FileNameMemberFormatter (); static MemberFormatter[] typeFormatters = new MemberFormatter[]{ new CSharpMemberFormatter (), new ILMemberFormatter (), }; static MemberFormatter[] memberFormatters = new MemberFormatter[]{ new CSharpFullMemberFormatter (), new ILFullMemberFormatter (), }; internal static readonly MemberFormatter slashdocFormatter = new SlashDocMemberFormatter (); MyXmlNodeList extensionMethods = new MyXmlNodeList (); HashSet forwardedTypes = new HashSet (); public static string droppedNamespace = string.Empty; public static bool HasDroppedNamespace(TypeDefinition forType) { return HasDroppedNamespace(forType.Module); } public static bool HasDroppedNamespace(MemberReference forMember) { return HasDroppedNamespace(forMember.Module); } public static bool HasDroppedNamespace(AssemblyDefinition forAssembly) { return HasDroppedNamespace(forAssembly.MainModule); } public static bool HasDroppedNamespace(ModuleDefinition forModule) { return !string.IsNullOrWhiteSpace (droppedNamespace) && droppedAssemblies.Any(da => da == forModule.Name); } public static bool HasDroppedAnyNamespace () { return !string.IsNullOrWhiteSpace (droppedNamespace); } static List droppedAssemblies = new List(); public string PreserveTag { get; set; } public static MDocUpdater Instance { get; private set; } public static bool SwitchingToMagicTypes { get; private set; } public override void Run (IEnumerable args) { Instance = this; show_exceptions = DebugOutput; var types = new List (); var p = new OptionSet () { { "delete", "Delete removed members from the XML files.", v => delete = v != null }, { "exceptions:", "Document potential exceptions that members can generate. {SOURCES} " + "is a comma-separated list of:\n" + " asm Method calls in same assembly\n" + " depasm Method calls in dependent assemblies\n" + " all Record all possible exceptions\n" + " added Modifier; only create s\n" + " for NEW types/members\n" + "If nothing is specified, then only exceptions from the member will " + "be listed.", v => exceptions = ParseExceptionLocations (v) }, { "f=", "Specify a {FLAG} to alter behavior. See later -f* options for available flags.", v => { switch (v) { case "ignore-missing-types": ignore_missing_types = true; break; case "no-assembly-versions": no_assembly_versions = true; break; default: throw new Exception ("Unsupported flag `" + v + "'."); } } }, { "fignore-missing-types", "Do not report an error if a --type=TYPE type\nwas not found.", v => ignore_missing_types = v != null }, { "fno-assembly-versions", "Do not generate //AssemblyVersion elements.", v => no_assembly_versions = v != null }, { "i|import=", "Import documentation from {FILE}.", v => AddImporter (v) }, { "L|lib=", "Check for assembly references in {DIRECTORY}.", v => assemblyResolver.AddSearchDirectory (v) }, { "library=", "Ignored for compatibility with update-ecma-xml.", v => {} }, { "o|out=", "Root {DIRECTORY} to generate/update documentation.", v => srcPath = v }, { "r=", "Search for dependent assemblies in the directory containing {ASSEMBLY}.\n" + "(Equivalent to '-L `dirname ASSEMBLY`'.)", v => assemblyResolver.AddSearchDirectory (Path.GetDirectoryName (v)) }, { "since=", "Manually specify the assembly {VERSION} that new members were added in.", v => since = v }, { "type=", "Only update documentation for {TYPE}.", v => types.Add (v) }, { "dropns=", "When processing assembly {ASSEMBLY}, strip off leading namespace {PREFIX}:\n" + " e.g. --dropns ASSEMBLY=PREFIX", v => { var parts = v.Split ('='); if (parts.Length != 2) { Console.Error.WriteLine ("Invalid dropns input"); return; } var assembly = Path.GetFileName (parts [0].Trim ()); var prefix = parts [1].Trim(); droppedAssemblies.Add (assembly); droppedNamespace = prefix; } }, { "ntypes", "If the new assembly is switching to 'magic types', then this switch should be defined.", v => SwitchingToMagicTypes = true }, { "preserve", "Do not delete members that don't exist in the assembly, but rather mark them as preserved.", v => PreserveTag = "true" }, { "multiassembly", "Allow types to be in multiple assemblies.", v => multiassembly = true }, }; var assemblies = Parse (p, args, "update", "[OPTIONS]+ ASSEMBLIES", "Create or update documentation from ASSEMBLIES."); if (assemblies == null) return; if (assemblies.Count == 0) Error ("No assemblies specified."); foreach (var dir in assemblies .Where (a => a.Contains (Path.DirectorySeparatorChar)) .Select (a => Path.GetDirectoryName (a))) assemblyResolver.AddSearchDirectory (dir); // PARSE BASIC OPTIONS AND LOAD THE ASSEMBLY TO DOCUMENT if (srcPath == null) throw new InvalidOperationException("The --out option is required."); this.assemblies = assemblies.Select (a => LoadAssembly (a)).ToList (); // Store types that have been forwarded to avoid duplicate generation GatherForwardedTypes (); docEnum = docEnum ?? new DocumentationEnumerator (); // PERFORM THE UPDATES if (types.Count > 0) { types.Sort (); DoUpdateTypes (srcPath, types, srcPath); } #if false else if (opts.@namespace != null) DoUpdateNS (opts.@namespace, Path.Combine (opts.path, opts.@namespace), Path.Combine (dest_dir, opts.@namespace)); #endif else DoUpdateAssemblies (srcPath, srcPath); Console.WriteLine("Members Added: {0}, Members Deleted: {1}", additions, deletions); } public static bool IsInAssemblies(string name) { var query = Instance.assemblies.Where (a => a.MainModule.Name == name).ToArray (); return query.Length > 0; } void AddImporter (string path) { try { XmlReader r = new XmlTextReader (path); if (r.Read ()) { while (r.NodeType != XmlNodeType.Element) { if (!r.Read ()) Error ("Unable to read XML file: {0}.", path); } if (r.LocalName == "doc") { importers.Add (new MsxdocDocumentationImporter (path)); } else if (r.LocalName == "Libraries") { var ecmadocs = new XmlTextReader (path); docEnum = new EcmaDocumentationEnumerator (this, ecmadocs); importers.Add (new EcmaDocumentationImporter (ecmadocs)); } else Error ("Unsupported XML format within {0}.", path); } r.Close (); } catch (Exception e) { Environment.ExitCode = 1; Error ("Could not load XML file: {0}.", e.Message); } } void GatherForwardedTypes () { foreach (var asm in assemblies) foreach (var type in asm.MainModule.ExportedTypes.Where (t => t.IsForwarder).Select (t => t.FullName)) forwardedTypes.Add (type); } static ExceptionLocations ParseExceptionLocations (string s) { ExceptionLocations loc = ExceptionLocations.Member; if (s == null) return loc; foreach (var type in s.Split (',')) { switch (type) { case "added": loc |= ExceptionLocations.AddedMembers; break; case "all": loc |= ExceptionLocations.Assembly | ExceptionLocations.DependentAssemblies; break; case "asm": loc |= ExceptionLocations.Assembly; break; case "depasm": loc |= ExceptionLocations.DependentAssemblies; break; default: throw new NotSupportedException ("Unsupported --exceptions value: " + type); } } return loc; } internal void Warning (string format, params object[] args) { Message (TraceLevel.Warning, "mdoc: " + format, args); } private AssemblyDefinition LoadAssembly (string name) { AssemblyDefinition assembly = null; try { assembly = AssemblyDefinition.ReadAssembly (name, new ReaderParameters { AssemblyResolver = assemblyResolver }); } catch (System.IO.FileNotFoundException) { } if (assembly == null) throw new InvalidOperationException("Assembly " + name + " not found."); return assembly; } private static void WriteXml(XmlElement element, System.IO.TextWriter output) { OrderTypeAttributes (element); XmlTextWriter writer = new XmlTextWriter(output); writer.Formatting = Formatting.Indented; writer.Indentation = 2; writer.IndentChar = ' '; element.WriteTo(writer); output.WriteLine(); } private static void WriteFile (string filename, FileMode mode, Action action) { Action creator = file => { using (var writer = OpenWrite (file, mode)) action (writer); }; MdocFile.UpdateFile (filename, creator); } private static void OrderTypeAttributes (XmlElement e) { foreach (XmlElement type in e.SelectNodes ("//Type")) { OrderTypeAttributes (type.Attributes); } } static readonly string[] TypeAttributeOrder = { "Name", "FullName", "FullNameSP", "Maintainer" }; private static void OrderTypeAttributes (XmlAttributeCollection c) { XmlAttribute[] attrs = new XmlAttribute [TypeAttributeOrder.Length]; for (int i = 0; i < c.Count; ++i) { XmlAttribute a = c [i]; for (int j = 0; j < TypeAttributeOrder.Length; ++j) { if (a.Name == TypeAttributeOrder [j]) { attrs [j] = a; break; } } } for (int i = attrs.Length-1; i >= 0; --i) { XmlAttribute n = attrs [i]; if (n == null) continue; XmlAttribute r = null; for (int j = i+1; j < attrs.Length; ++j) { if (attrs [j] != null) { r = attrs [j]; break; } } if (r == null) continue; if (c [n.Name] != null) { c.RemoveNamedItem (n.Name); c.InsertBefore (n, r); } } } private XmlDocument CreateIndexStub() { XmlDocument index = new XmlDocument(); XmlElement index_root = index.CreateElement("Overview"); index.AppendChild(index_root); if (assemblies.Count == 0) throw new Exception ("No assembly"); XmlElement index_assemblies = index.CreateElement("Assemblies"); index_root.AppendChild(index_assemblies); XmlElement index_remarks = index.CreateElement("Remarks"); index_remarks.InnerText = "To be added."; index_root.AppendChild(index_remarks); XmlElement index_copyright = index.CreateElement("Copyright"); index_copyright.InnerText = "To be added."; index_root.AppendChild(index_copyright); XmlElement index_types = index.CreateElement("Types"); index_root.AppendChild(index_types); return index; } private static void WriteNamespaceStub(string ns, string outdir) { XmlDocument index = new XmlDocument(); XmlElement index_root = index.CreateElement("Namespace"); index.AppendChild(index_root); index_root.SetAttribute("Name", ns); XmlElement index_docs = index.CreateElement("Docs"); index_root.AppendChild(index_docs); XmlElement index_summary = index.CreateElement("summary"); index_summary.InnerText = "To be added."; index_docs.AppendChild(index_summary); XmlElement index_remarks = index.CreateElement("remarks"); index_remarks.InnerText = "To be added."; index_docs.AppendChild(index_remarks); WriteFile (outdir + "/ns-" + ns + ".xml", FileMode.CreateNew, writer => WriteXml (index.DocumentElement, writer)); } public void DoUpdateTypes (string basepath, List typenames, string dest) { var index = CreateIndexForTypes (dest); var found = new HashSet (); foreach (AssemblyDefinition assembly in assemblies) { foreach (TypeDefinition type in docEnum.GetDocumentationTypes (assembly, typenames)) { string relpath = DoUpdateType (type, basepath, dest); if (relpath == null) continue; found.Add (type.FullName); if (index == null) continue; index.Add (assembly); index.Add (type); } } if (index != null) index.Write (); if (ignore_missing_types) return; var notFound = from n in typenames where !found.Contains (n) select n; if (notFound.Any ()) throw new InvalidOperationException("Type(s) not found: " + string.Join (", ", notFound.ToArray ())); } class IndexForTypes { MDocUpdater app; string indexFile; XmlDocument index; XmlElement index_types; XmlElement index_assemblies; public IndexForTypes (MDocUpdater app, string indexFile, XmlDocument index) { this.app = app; this.indexFile = indexFile; this.index = index; index_types = WriteElement (index.DocumentElement, "Types"); index_assemblies = WriteElement (index.DocumentElement, "Assemblies"); } public void Add (AssemblyDefinition assembly) { if (index_assemblies.SelectSingleNode ("Assembly[@Name='" + assembly.Name.Name + "']") != null) return; app.AddIndexAssembly (assembly, index_assemblies); } public void Add (TypeDefinition type) { app.AddIndexType (type, index_types); } public void Write () { SortIndexEntries (index_types); WriteFile (indexFile, FileMode.Create, writer => WriteXml (index.DocumentElement, writer)); } } IndexForTypes CreateIndexForTypes (string dest) { string indexFile = Path.Combine (dest, "index.xml"); if (File.Exists (indexFile)) return null; return new IndexForTypes (this, indexFile, CreateIndexStub ()); } /// Constructs the presumed path to the type's documentation file /// true, if the type file was found, false otherwise. /// A typle that contains 1) the 'reltypefile', 2) the 'typefile', and 3) the file info bool TryFindTypeFile(string nsname, string typename, string basepath, out Tuple result) { string reltypefile = DocUtils.PathCombine (nsname, typename + ".xml"); string typefile = Path.Combine (basepath, reltypefile); System.IO.FileInfo file = new System.IO.FileInfo(typefile); result = new Tuple (reltypefile, typefile, file); return file.Exists; } public string DoUpdateType (TypeDefinition type, string basepath, string dest) { if (type.Namespace == null) Warning ("warning: The type `{0}' is in the root namespace. This may cause problems with display within monodoc.", type.FullName); if (!IsPublic (type)) return null; // Must get the A+B form of the type name. string typename = GetTypeFileName(type); string nsname = DocUtils.GetNamespace (type); // Find the file, if it exists string[] searchLocations = new string[] { nsname }; if (MDocUpdater.HasDroppedNamespace (type)) { // If dropping namespace, types may have moved into a couple of different places. var newSearchLocations = searchLocations.Union (new string[] { string.Format ("{0}.{1}", droppedNamespace, nsname), nsname.Replace (droppedNamespace + ".", string.Empty), MDocUpdater.droppedNamespace }); searchLocations = newSearchLocations.ToArray (); } string reltypefile="", typefile=""; System.IO.FileInfo file = null; foreach (var f in searchLocations) { Tuple result; bool fileExists = TryFindTypeFile (f, typename, basepath, out result); if (fileExists) { reltypefile = result.Item1; typefile = result.Item2; file = result.Item3; break; } } if (file == null || !file.Exists) { // we were not able to find a file, let's use the original type informatio. // so that we create the stub in the right place. Tuple result; TryFindTypeFile (nsname, typename, basepath, out result); reltypefile = result.Item1; typefile = result.Item2; file = result.Item3; } string output = null; if (dest == null) { output = typefile; } else if (dest == "-") { output = null; } else { output = Path.Combine (dest, reltypefile); } if (file != null && file.Exists) { // Update XmlDocument basefile = new XmlDocument(); try { basefile.Load(typefile); } catch (Exception e) { throw new InvalidOperationException("Error loading " + typefile + ": " + e.Message, e); } DoUpdateType2("Updating", basefile, type, output, false); } else { // Stub XmlElement td = StubType(type, output); if (td == null) return null; } return reltypefile; } public void DoUpdateNS (string ns, string nspath, string outpath) { Dictionary seenTypes = new Dictionary (); AssemblyDefinition assembly = assemblies [0]; foreach (System.IO.FileInfo file in new System.IO.DirectoryInfo(nspath).GetFiles("*.xml")) { XmlDocument basefile = new XmlDocument(); string typefile = Path.Combine(nspath, file.Name); try { basefile.Load(typefile); } catch (Exception e) { throw new InvalidOperationException("Error loading " + typefile + ": " + e.Message, e); } string typename = GetTypeFileName (basefile.SelectSingleNode("Type/@FullName").InnerText); TypeDefinition type = assembly.GetType(typename); if (type == null) { // -- if (!string.IsNullOrWhiteSpace (droppedNamespace)) { string nameWithNs = string.Format ("{0}.{1}", droppedNamespace, typename); type = assembly.GetType (nameWithNs); if (type == null) { Warning ("Type no longer in assembly: " + typename); continue; } } //-- } seenTypes[type] = seenTypes; DoUpdateType2("Updating", basefile, type, Path.Combine(outpath, file.Name), false); } // Stub types not in the directory foreach (TypeDefinition type in docEnum.GetDocumentationTypes (assembly, null)) { if (type.Namespace != ns || seenTypes.ContainsKey(type)) continue; XmlElement td = StubType(type, Path.Combine(outpath, GetTypeFileName(type) + ".xml")); if (td == null) continue; } } private static string GetTypeFileName (TypeReference type) { return filenameFormatter.GetName (type); } public static string GetTypeFileName (string typename) { StringBuilder filename = new StringBuilder (typename.Length); int numArgs = 0; int numLt = 0; bool copy = true; for (int i = 0; i < typename.Length; ++i) { char c = typename [i]; switch (c) { case '<': copy = false; ++numLt; break; case '>': --numLt; if (numLt == 0) { filename.Append ('`').Append ((numArgs+1).ToString()); numArgs = 0; copy = true; } break; case ',': if (numLt == 1) ++numArgs; break; default: if (copy) filename.Append (c); break; } } return filename.ToString (); } private void AddIndexAssembly (AssemblyDefinition assembly, XmlElement parent) { XmlElement index_assembly = null; if (multiassembly) index_assembly = (XmlElement)parent.SelectSingleNode ("Assembly[@Name='"+ assembly.Name.Name +"']"); if (index_assembly == null) index_assembly = parent.OwnerDocument.CreateElement ("Assembly"); index_assembly.SetAttribute ("Name", assembly.Name.Name); index_assembly.SetAttribute ("Version", assembly.Name.Version.ToString()); AssemblyNameDefinition name = assembly.Name; if (name.HasPublicKey) { XmlElement pubkey = parent.OwnerDocument.CreateElement ("AssemblyPublicKey"); var key = new StringBuilder (name.PublicKey.Length*3 + 2); key.Append ("["); foreach (byte b in name.PublicKey) key.AppendFormat ("{0,2:x2} ", b); key.Append ("]"); pubkey.InnerText = key.ToString (); index_assembly.AppendChild (pubkey); } if (!string.IsNullOrEmpty (name.Culture)) { XmlElement culture = parent.OwnerDocument.CreateElement ("AssemblyCulture"); culture.InnerText = name.Culture; index_assembly.AppendChild (culture); } MakeAttributes (index_assembly, GetCustomAttributes (assembly.CustomAttributes, "")); parent.AppendChild(index_assembly); } private void AddIndexType (TypeDefinition type, XmlElement index_types) { string typename = GetTypeFileName(type); // Add namespace and type nodes into the index file as needed string ns = DocUtils.GetNamespace (type); XmlElement nsnode = (XmlElement) index_types.SelectSingleNode ("Namespace[@Name='" + ns + "']"); if (nsnode == null) { nsnode = index_types.OwnerDocument.CreateElement("Namespace"); nsnode.SetAttribute ("Name", ns); index_types.AppendChild (nsnode); } string doc_typename = GetDocTypeName (type); XmlElement typenode = (XmlElement) nsnode.SelectSingleNode ("Type[@Name='" + typename + "']"); if (typenode == null) { typenode = index_types.OwnerDocument.CreateElement ("Type"); typenode.SetAttribute ("Name", typename); nsnode.AppendChild (typenode); } if (typename != doc_typename) typenode.SetAttribute("DisplayName", doc_typename); else typenode.RemoveAttribute("DisplayName"); typenode.SetAttribute ("Kind", GetTypeKind (type)); } private void DoUpdateAssemblies (string source, string dest) { string indexfile = dest + "/index.xml"; XmlDocument index; if (System.IO.File.Exists(indexfile)) { index = new XmlDocument(); index.Load(indexfile); // Format change ClearElement(index.DocumentElement, "Assembly"); ClearElement(index.DocumentElement, "Attributes"); } else { index = CreateIndexStub(); } string defaultTitle = "Untitled"; if (assemblies.Count == 1) defaultTitle = assemblies[0].Name.Name; WriteElementInitialText(index.DocumentElement, "Title", defaultTitle); XmlElement index_types = WriteElement(index.DocumentElement, "Types"); XmlElement index_assemblies = WriteElement(index.DocumentElement, "Assemblies"); if (!multiassembly) index_assemblies.RemoveAll (); HashSet goodfiles = new HashSet (StringComparer.OrdinalIgnoreCase); foreach (AssemblyDefinition assm in assemblies) { AddIndexAssembly (assm, index_assemblies); DoUpdateAssembly (assm, index_types, source, dest, goodfiles); } SortIndexEntries (index_types); CleanupFiles (dest, goodfiles); CleanupIndexTypes (index_types, goodfiles); CleanupExtensions (index_types); WriteFile (indexfile, FileMode.Create, writer => WriteXml(index.DocumentElement, writer)); } private static char[] InvalidFilenameChars = {'\\', '/', ':', '*', '?', '"', '<', '>', '|'}; private void DoUpdateAssembly (AssemblyDefinition assembly, XmlElement index_types, string source, string dest, HashSet goodfiles) { foreach (TypeDefinition type in docEnum.GetDocumentationTypes (assembly, null)) { string typename = GetTypeFileName(type); if (!IsPublic (type) || typename.IndexOfAny (InvalidFilenameChars) >= 0 || forwardedTypes.Contains (type.FullName)) continue; string reltypepath = DoUpdateType (type, source, dest); if (reltypepath == null) continue; // Add namespace and type nodes into the index file as needed AddIndexType (type, index_types); // Ensure the namespace index file exists string namespaceToUse = type.Namespace; if (HasDroppedNamespace(assembly)) { namespaceToUse = string.Format ("{0}.{1}", droppedNamespace, namespaceToUse); } string onsdoc = DocUtils.PathCombine (dest, namespaceToUse + ".xml"); string nsdoc = DocUtils.PathCombine (dest, "ns-" + namespaceToUse + ".xml"); if (File.Exists (onsdoc)) { File.Move (onsdoc, nsdoc); } if (!File.Exists (nsdoc)) { Console.WriteLine("New Namespace File: " + type.Namespace); WriteNamespaceStub(namespaceToUse, dest); } goodfiles.Add (reltypepath); } } private static void SortIndexEntries (XmlElement indexTypes) { XmlNodeList namespaces = indexTypes.SelectNodes ("Namespace"); XmlNodeComparer c = new AttributeNameComparer (); SortXmlNodes (indexTypes, namespaces, c); for (int i = 0; i < namespaces.Count; ++i) SortXmlNodes (namespaces [i], namespaces [i].SelectNodes ("Type"), c); } private static void SortXmlNodes (XmlNode parent, XmlNodeList children, XmlNodeComparer comparer) { MyXmlNodeList l = new MyXmlNodeList (children.Count); for (int i = 0; i < children.Count; ++i) l.Add (children [i]); l.Sort (comparer); for (int i = l.Count - 1; i > 0; --i) { parent.InsertBefore (parent.RemoveChild ((XmlNode) l [i-1]), (XmlNode) l [i]); } } abstract class XmlNodeComparer : IComparer, IComparer { public abstract int Compare (XmlNode x, XmlNode y); public int Compare (object x, object y) { return Compare ((XmlNode) x, (XmlNode) y); } } class AttributeNameComparer : XmlNodeComparer { string attribute; public AttributeNameComparer () : this ("Name") { } public AttributeNameComparer (string attribute) { this.attribute = attribute; } public override int Compare (XmlNode x, XmlNode y) { return x.Attributes [attribute].Value.CompareTo (y.Attributes [attribute].Value); } } class VersionComparer : XmlNodeComparer { public override int Compare (XmlNode x, XmlNode y) { // Some of the existing docs use e.g. 1.0.x.x, which Version doesn't like. string a = GetVersion (x.InnerText); string b = GetVersion (y.InnerText); return new Version (a).CompareTo (new Version (b)); } static string GetVersion (string v) { int n = v.IndexOf ("x"); if (n < 0) return v; return v.Substring (0, n-1); } } private static string GetTypeKind (TypeDefinition type) { if (type.IsEnum) return "Enumeration"; if (type.IsValueType) return "Structure"; if (type.IsInterface) return "Interface"; if (DocUtils.IsDelegate (type)) return "Delegate"; if (type.IsClass || type.FullName == "System.Enum") // FIXME return "Class"; throw new ArgumentException ("Unknown kind for type: " + type.FullName); } public static bool IsPublic (TypeDefinition type) { TypeDefinition decl = type; while (decl != null) { if (!(decl.IsPublic || decl.IsNestedPublic || decl.IsNestedFamily || decl.IsNestedFamily || decl.IsNestedFamilyOrAssembly)) { return false; } decl = (TypeDefinition) decl.DeclaringType; } return true; } private void CleanupFiles (string dest, HashSet goodfiles) { // Look for files that no longer correspond to types foreach (System.IO.DirectoryInfo nsdir in new System.IO.DirectoryInfo(dest).GetDirectories("*")) { foreach (System.IO.FileInfo typefile in nsdir.GetFiles("*.xml")) { string relTypeFile = Path.Combine(nsdir.Name, typefile.Name); if (!goodfiles.Contains (relTypeFile)) { XmlDocument doc = new XmlDocument (); doc.Load (typefile.FullName); XmlElement e = doc.SelectSingleNode("/Type") as XmlElement; var assemblyNameNode = doc.SelectSingleNode ("/Type/AssemblyInfo/AssemblyName"); if (assemblyNameNode == null){ Warning ("Did not find /Type/AssemblyInfo/AssemblyName on {0}", typefile.FullName); continue; } string assemblyName = assemblyNameNode.InnerText; AssemblyDefinition assembly = assemblies.FirstOrDefault (a => a.Name.Name == assemblyName); Action saveDoc = () => { using (TextWriter writer = OpenWrite (typefile.FullName, FileMode.Truncate)) WriteXml(doc.DocumentElement, writer); }; if (e != null && !no_assembly_versions && assembly != null && assemblyName != null && UpdateAssemblyVersions (e, assembly, GetAssemblyVersions(assemblyName), false)) { saveDoc (); goodfiles.Add (relTypeFile); continue; } Action actuallyDelete = () => { string newname = typefile.FullName + ".remove"; try { System.IO.File.Delete (newname); } catch (Exception) { Warning ("Unable to delete existing file: {0}", newname); } try { typefile.MoveTo (newname); } catch (Exception) { Warning ("Unable to rename to: {0}", newname); } Console.WriteLine ("Class no longer present; file renamed: " + Path.Combine (nsdir.Name, typefile.Name)); }; if (string.IsNullOrWhiteSpace (PreserveTag)) { // only do this if there was not a -preserve saveDoc (); var unifiedAssemblyNode = doc.SelectSingleNode ("/Type/AssemblyInfo[@apistyle='unified']"); var classicAssemblyNode = doc.SelectSingleNode ("/Type/AssemblyInfo[@apistyle='classic']"); var unifiedMembers = doc.SelectNodes ("//Member[@apistyle='unified']|//Member/AssemblyInfo[@apistyle='unified']"); var classicMembers = doc.SelectNodes ("//Member[@apistyle='classic']|//Member/AssemblyInfo[@apistyle='classic']"); bool isUnifiedRun = HasDroppedAnyNamespace (); bool isClassicOrNormalRun = !isUnifiedRun; Action removeStyles = (x, style) => { var styledNodes = doc.SelectNodes("//*[@apistyle='"+ style.ToString ().ToLowerInvariant () +"']"); if (styledNodes != null && styledNodes.Count > 0) { foreach(var node in styledNodes.Cast ()) { node.ParentNode.RemoveChild (node); } } saveDoc (); }; if (isClassicOrNormalRun) { if (unifiedAssemblyNode != null || unifiedMembers.Count > 0) { Warning ("*** this type is marked as unified, not deleting during this run: {0}", typefile.FullName); // if truly removed from both assemblies, it will be removed fully during the unified run removeStyles (doc, ApiStyle.Classic); continue; } else { // we should be safe to delete here because it was not marked as a unified assembly actuallyDelete (); } } if (isUnifiedRun) { if (classicAssemblyNode != null || classicMembers.Count > 0) { Warning ("*** this type is marked as classic, not deleting {0}", typefile.FullName); continue; } else { // safe to delete because it wasn't marked as a classic assembly, so the type is gone in both. actuallyDelete (); } } } } } } } private static TextWriter OpenWrite (string path, FileMode mode) { var w = new StreamWriter ( new FileStream (path, mode), new UTF8Encoding (false) ); w.NewLine = "\n"; return w; } private string[] GetAssemblyVersions (string assemblyName) { return (from a in assemblies where a.Name.Name == assemblyName select GetAssemblyVersion (a)).ToArray (); } private static void CleanupIndexTypes (XmlElement index_types, HashSet goodfiles) { // Look for type nodes that no longer correspond to types MyXmlNodeList remove = new MyXmlNodeList (); foreach (XmlElement typenode in index_types.SelectNodes("Namespace/Type")) { string fulltypename = Path.Combine (((XmlElement)typenode.ParentNode).GetAttribute("Name"), typenode.GetAttribute("Name") + ".xml"); if (!goodfiles.Contains (fulltypename)) { remove.Add (typenode); } } foreach (XmlNode n in remove) n.ParentNode.RemoveChild (n); } private void CleanupExtensions (XmlElement index_types) { XmlNode e = index_types.SelectSingleNode ("/Overview/ExtensionMethods"); if (extensionMethods.Count == 0) { if (e == null) return; index_types.SelectSingleNode ("/Overview").RemoveChild (e); return; } if (e == null) { e = index_types.OwnerDocument.CreateElement ("ExtensionMethods"); index_types.SelectSingleNode ("/Overview").AppendChild (e); } else e.RemoveAll (); extensionMethods.Sort (DefaultExtensionMethodComparer); foreach (XmlNode m in extensionMethods) { e.AppendChild (index_types.OwnerDocument.ImportNode (m, true)); } } class ExtensionMethodComparer : XmlNodeComparer { public override int Compare (XmlNode x, XmlNode y) { XmlNode xLink = x.SelectSingleNode ("Member/Link"); XmlNode yLink = y.SelectSingleNode ("Member/Link"); int n = xLink.Attributes ["Type"].Value.CompareTo ( yLink.Attributes ["Type"].Value); if (n != 0) return n; n = xLink.Attributes ["Member"].Value.CompareTo ( yLink.Attributes ["Member"].Value); if (n == 0 && !object.ReferenceEquals (x, y)) throw new InvalidOperationException ("Duplicate extension method found!"); return n; } } static readonly XmlNodeComparer DefaultExtensionMethodComparer = new ExtensionMethodComparer (); public void DoUpdateType2 (string message, XmlDocument basefile, TypeDefinition type, string output, bool insertSince) { Console.WriteLine(message + ": " + type.FullName); StringToXmlNodeMap seenmembers = new StringToXmlNodeMap (); // Update type metadata UpdateType(basefile.DocumentElement, type); // Update existing members. Delete member nodes that no longer should be there, // and remember what members are already documented so we don't add them again. MyXmlNodeList todelete = new MyXmlNodeList (); foreach (DocsNodeInfo info in docEnum.GetDocumentationMembers (basefile, type)) { XmlElement oldmember = info.Node; MemberReference oldmember2 = info.Member; if (info.Member != null && info.Node != null) { // Check for an error condition where the xml MemberName doesn't match the matched member var memberName = GetMemberName (info.Member); var memberAttribute = info.Node.Attributes ["MemberName"]; if (memberAttribute == null || (memberAttribute.Value != memberName && memberAttribute.Value.Split (',').Length != memberName.Split (',').Length)) { oldmember.SetAttribute ("MemberName", memberName); } } string sig = oldmember2 != null ? memberFormatters [0].GetDeclaration (oldmember2) : null; // Interface implementations and overrides are deleted from the docs // unless the overrides option is given. if (oldmember2 != null && sig == null) oldmember2 = null; // Deleted (or signature changed) if (oldmember2 == null) { if (!no_assembly_versions && UpdateAssemblyVersions (oldmember, type.Module.Assembly, new string[]{ GetAssemblyVersion (type.Module.Assembly) }, false)) continue; DeleteMember ("Member Removed", output, oldmember, todelete, type); continue; } // Duplicated if (seenmembers.ContainsKey (sig)) { if (object.ReferenceEquals (oldmember, seenmembers [sig])) { // ignore, already seen } else if (DefaultMemberComparer.Compare (oldmember, seenmembers [sig]) == 0) DeleteMember ("Duplicate Member Found", output, oldmember, todelete, type); else Warning ("TODO: found a duplicate member '{0}', but it's not identical to the prior member found!", sig); continue; } // Update signature information UpdateMember(info); // get all apistyles of sig from info.Node var styles = oldmember.GetElementsByTagName ("MemberSignature").Cast () .Where (x => x.GetAttribute ("Language") == "C#" && !seenmembers.ContainsKey(x.GetAttribute("Value"))) .Select (x => x.GetAttribute ("Value")); foreach (var stylesig in styles) { seenmembers.Add (stylesig, oldmember); } } foreach (XmlElement oldmember in todelete) oldmember.ParentNode.RemoveChild (oldmember); if (!DocUtils.IsDelegate (type)) { XmlNode members = WriteElement (basefile.DocumentElement, "Members"); var typemembers = type.GetMembers() .Where(m => { if (m is TypeDefinition) return false; string sig = memberFormatters [0].GetDeclaration (m); if (sig == null) return false; if (seenmembers.ContainsKey(sig)) return false; // Verify that the member isn't an explicitly implemented // member of an internal interface, in which case we shouldn't return true. MethodDefinition methdef = null; if (m is MethodDefinition) methdef = m as MethodDefinition; else if (m is PropertyDefinition) { var prop = m as PropertyDefinition; methdef = prop.GetMethod ?? prop.SetMethod; } if (methdef != null) { TypeReference iface; MethodReference imethod; if (methdef.Overrides.Count == 1) { DocUtils.GetInfoForExplicitlyImplementedMethod (methdef, out iface, out imethod); if (!IsPublic (iface.Resolve ())) return false; } } return true; }) .ToArray(); foreach (MemberReference m in typemembers) { XmlElement mm = MakeMember(basefile, new DocsNodeInfo (null, m)); if (mm == null) continue; if (MDocUpdater.SwitchingToMagicTypes) { // this is a unified style API that obviously doesn't exist in the classic API. Let's mark // it with apistyle="unified", so that it's not displayed for classic style APIs mm.SetAttribute ("apistyle", "unified"); } members.AppendChild( mm ); Console.WriteLine("Member Added: " + mm.SelectSingleNode("MemberSignature/@Value").InnerText); additions++; } } // Import code snippets from files foreach (XmlNode code in basefile.GetElementsByTagName("code")) { if (!(code is XmlElement)) continue; string file = ((XmlElement)code).GetAttribute("src"); string lang = ((XmlElement)code).GetAttribute("lang"); if (file != "") { string src = GetCodeSource (lang, Path.Combine (srcPath, file)); if (src != null) code.InnerText = src; } } if (insertSince && since != null) { XmlNode docs = basefile.DocumentElement.SelectSingleNode("Docs"); docs.AppendChild (CreateSinceNode (basefile)); } do { XmlElement d = basefile.DocumentElement ["Docs"]; XmlElement m = basefile.DocumentElement ["Members"]; if (d != null && m != null) basefile.DocumentElement.InsertBefore ( basefile.DocumentElement.RemoveChild (d), m); SortTypeMembers (m); } while (false); if (output == null) WriteXml(basefile.DocumentElement, Console.Out); else { FileInfo file = new FileInfo (output); if (!file.Directory.Exists) { Console.WriteLine("Namespace Directory Created: " + type.Namespace); file.Directory.Create (); } WriteFile (output, FileMode.Create, writer => WriteXml(basefile.DocumentElement, writer)); } } private string GetCodeSource (string lang, string file) { int anchorStart; if (lang == "C#" && (anchorStart = file.IndexOf (".cs#")) >= 0) { // Grab the specified region string region = "#region " + file.Substring (anchorStart + 4); file = file.Substring (0, anchorStart + 3); try { using (StreamReader reader = new StreamReader (file)) { string line; StringBuilder src = new StringBuilder (); int indent = -1; while ((line = reader.ReadLine ()) != null) { if (line.Trim() == region) { indent = line.IndexOf (region); continue; } if (indent >= 0 && line.Trim().StartsWith ("#endregion")) { break; } if (indent >= 0) src.Append ( (line.Length > 0 ? line.Substring (indent) : string.Empty) + "\n"); } return src.ToString (); } } catch (Exception e) { Warning ("Could not load file '{0}' region '{1}': {2}", file, region, show_exceptions ? e.ToString () : e.Message); return null; } } try { using (StreamReader reader = new StreamReader (file)) return reader.ReadToEnd (); } catch (Exception e) { Warning ("Could not load file '" + file + "': " + e.Message); } return null; } void DeleteMember (string reason, string output, XmlNode member, MyXmlNodeList todelete, TypeDefinition type) { string format = output != null ? "{0}: File='{1}'; Signature='{4}'" : "{0}: XPath='/Type[@FullName=\"{2}\"]/Members/Member[@MemberName=\"{3}\"]'; Signature='{4}'"; string signature = member.SelectSingleNode ("MemberSignature[@Language='C#']/@Value").Value; Warning (format, reason, output, member.OwnerDocument.DocumentElement.GetAttribute ("FullName"), member.Attributes ["MemberName"].Value, signature); // Identify all of the different states that could affect our decision to delete the member bool shouldPreserve = !string.IsNullOrWhiteSpace (PreserveTag); bool hasContent = MemberDocsHaveUserContent (member); bool shouldDelete = !shouldPreserve && (delete || !hasContent); bool unifiedRun = HasDroppedNamespace (type); var classicAssemblyInfo = member.SelectSingleNode ("AssemblyInfo[@apistyle='classic']"); bool nodeIsClassic = classicAssemblyInfo != null || member.HasApiStyle (ApiStyle.Classic); var unifiedAssemblyInfo = member.SelectSingleNode ("AssemblyInfo[@apistyle='unified']"); bool nodeIsUnified = unifiedAssemblyInfo != null || member.HasApiStyle (ApiStyle.Unified); Action actuallyDelete = () => { todelete.Add (member); deletions++; }; if (!shouldDelete) { // explicitly not deleting string message = shouldPreserve ? "Not deleting '{0}' due to --preserve." : "Not deleting '{0}'; must be enabled with the --delete option"; Warning (message, signature); } else if (unifiedRun && nodeIsClassic) { // this is a unified run, and the member doesn't exist, but is marked as being in the classic assembly. member.RemoveApiStyle (ApiStyle.Unified); Warning ("Not removing '{0}' since it's still in the classic assembly.", signature); } else if (unifiedRun && !nodeIsClassic) { // unified run, and the node is not classic, which means it doesn't exist anywhere. actuallyDelete (); } else { if (!nodeIsClassic && !nodeIsUnified) { // regular codepath (ie. not classic/unified) actuallyDelete (); } else { // this is a classic run Warning ("Removing classic from '{0}' ... will be removed in the unified run if not present there.", signature); member.RemoveApiStyle (ApiStyle.Classic); if (classicAssemblyInfo != null) { member.RemoveChild (classicAssemblyInfo); } } } } class MemberComparer : XmlNodeComparer { public override int Compare (XmlNode x, XmlNode y) { int r; string xMemberName = x.Attributes ["MemberName"].Value; string yMemberName = y.Attributes ["MemberName"].Value; // generic methods *end* with '>' // it's possible for explicitly implemented generic interfaces to // contain <...> without being a generic method if ((!xMemberName.EndsWith (">") || !yMemberName.EndsWith (">")) && (r = xMemberName.CompareTo (yMemberName)) != 0) return r; int lt; if ((lt = xMemberName.IndexOf ("<")) >= 0) xMemberName = xMemberName.Substring (0, lt); if ((lt = yMemberName.IndexOf ("<")) >= 0) yMemberName = yMemberName.Substring (0, lt); if ((r = xMemberName.CompareTo (yMemberName)) != 0) return r; // if @MemberName matches, then it's either two different types of // members sharing the same name, e.g. field & property, or it's an // overloaded method. // for different type, sort based on MemberType value. r = x.SelectSingleNode ("MemberType").InnerText.CompareTo ( y.SelectSingleNode ("MemberType").InnerText); if (r != 0) return r; // same type -- must be an overloaded method. Sort based on type // parameter count, then parameter count, then by the parameter // type names. XmlNodeList xTypeParams = x.SelectNodes ("TypeParameters/TypeParameter"); XmlNodeList yTypeParams = y.SelectNodes ("TypeParameters/TypeParameter"); if (xTypeParams.Count != yTypeParams.Count) return xTypeParams.Count <= yTypeParams.Count ? -1 : 1; for (int i = 0; i < xTypeParams.Count; ++i) { r = xTypeParams [i].Attributes ["Name"].Value.CompareTo ( yTypeParams [i].Attributes ["Name"].Value); if (r != 0) return r; } XmlNodeList xParams = x.SelectNodes ("Parameters/Parameter"); XmlNodeList yParams = y.SelectNodes ("Parameters/Parameter"); if (xParams.Count != yParams.Count) return xParams.Count <= yParams.Count ? -1 : 1; for (int i = 0; i < xParams.Count; ++i) { r = xParams [i].Attributes ["Type"].Value.CompareTo ( yParams [i].Attributes ["Type"].Value); if (r != 0) return r; } // all parameters match, but return value might not match if it was // changed between one version and another. XmlNode xReturn = x.SelectSingleNode ("ReturnValue/ReturnType"); XmlNode yReturn = y.SelectSingleNode ("ReturnValue/ReturnType"); if (xReturn != null && yReturn != null) { r = xReturn.InnerText.CompareTo (yReturn.InnerText); if (r != 0) return r; } return 0; } } static readonly MemberComparer DefaultMemberComparer = new MemberComparer (); private static void SortTypeMembers (XmlNode members) { if (members == null) return; SortXmlNodes (members, members.SelectNodes ("Member"), DefaultMemberComparer); } private static bool MemberDocsHaveUserContent (XmlNode e) { e = (XmlElement)e.SelectSingleNode("Docs"); if (e == null) return false; foreach (XmlElement d in e.SelectNodes("*")) if (d.InnerText != "" && !d.InnerText.StartsWith("To be added")) return true; return false; } // UPDATE HELPER FUNCTIONS // CREATE A STUB DOCUMENTATION FILE public XmlElement StubType (TypeDefinition type, string output) { string typesig = typeFormatters [0].GetDeclaration (type); if (typesig == null) return null; // not publicly visible XmlDocument doc = new XmlDocument(); XmlElement root = doc.CreateElement("Type"); doc.AppendChild (root); DoUpdateType2 ("New Type", doc, type, output, true); return root; } private XmlElement CreateSinceNode (XmlDocument doc) { XmlElement s = doc.CreateElement ("since"); s.SetAttribute ("version", since); return s; } // STUBBING/UPDATING FUNCTIONS public void UpdateType (XmlElement root, TypeDefinition type) { root.SetAttribute("Name", GetDocTypeName (type)); root.SetAttribute("FullName", GetDocTypeFullName (type)); foreach (MemberFormatter f in typeFormatters) { string element = "TypeSignature[@Language='" + f.Language + "']"; string valueToUse = f.GetDeclaration (type); AddXmlNode ( root.SelectNodes (element).Cast ().ToArray (), x => x.GetAttribute ("Value") == valueToUse, x => x.SetAttribute ("Value", valueToUse), () => { var node = WriteElementAttribute (root, element, "Language", f.Language, forceNewElement: true); var newnode = WriteElementAttribute (root, node, "Value", valueToUse); return newnode; }, type); } AddAssemblyNameToNode (root, type); string assemblyInfoNodeFilter = MDocUpdater.HasDroppedNamespace (type) ? "[@apistyle='unified']" : "[not(@apistyle) or @apistyle='classic']"; Func assemblyFilter = x => x.SelectSingleNode ("AssemblyName").InnerText == type.Module.Assembly.Name.Name; foreach(var ass in root.SelectNodes ("AssemblyInfo" + assemblyInfoNodeFilter).Cast ().Where (assemblyFilter)) { WriteElementText(ass, "AssemblyName", type.Module.Assembly.Name.Name); if (!no_assembly_versions) { UpdateAssemblyVersions (ass, type, true); } else { var versions = ass.SelectNodes ("AssemblyVersion").Cast ().ToList (); foreach (var version in versions) ass.RemoveChild (version); } if (!string.IsNullOrEmpty (type.Module.Assembly.Name.Culture)) WriteElementText(ass, "AssemblyCulture", type.Module.Assembly.Name.Culture); else ClearElement(ass, "AssemblyCulture"); // Why-oh-why do we put assembly attributes in each type file? // Neither monodoc nor monodocs2html use them, so I'm deleting them // since they're outdated in current docs, and a waste of space. //MakeAttributes(ass, type.Assembly, true); XmlNode assattrs = ass.SelectSingleNode("Attributes"); if (assattrs != null) ass.RemoveChild(assattrs); NormalizeWhitespace(ass); } if (type.IsGenericType ()) { MakeTypeParameters (root, type.GenericParameters, type, MDocUpdater.HasDroppedNamespace(type)); } else { ClearElement(root, "TypeParameters"); } if (type.BaseType != null) { XmlElement basenode = WriteElement(root, "Base"); string basetypename = GetDocTypeFullName (type.BaseType); if (basetypename == "System.MulticastDelegate") basetypename = "System.Delegate"; WriteElementText(root, "Base/BaseTypeName", basetypename); // Document how this type instantiates the generic parameters of its base type TypeReference origBase = type.BaseType.GetElementType (); if (origBase.IsGenericType ()) { ClearElement(basenode, "BaseTypeArguments"); GenericInstanceType baseInst = type.BaseType as GenericInstanceType; IList baseGenArgs = baseInst == null ? null : baseInst.GenericArguments; IList baseGenParams = origBase.GenericParameters; if (baseGenArgs.Count != baseGenParams.Count) throw new InvalidOperationException ("internal error: number of generic arguments doesn't match number of generic parameters."); for (int i = 0; baseGenArgs != null && i < baseGenArgs.Count; i++) { GenericParameter param = baseGenParams [i]; TypeReference value = baseGenArgs [i]; XmlElement bta = WriteElement(basenode, "BaseTypeArguments"); XmlElement arg = bta.OwnerDocument.CreateElement("BaseTypeArgument"); bta.AppendChild(arg); arg.SetAttribute ("TypeParamName", param.Name); arg.InnerText = GetDocTypeFullName (value); } } } else { ClearElement(root, "Base"); } if (!DocUtils.IsDelegate (type) && !type.IsEnum) { IEnumerable userInterfaces = DocUtils.GetUserImplementedInterfaces (type); List interface_names = userInterfaces .Select (iface => GetDocTypeFullName (iface)) .OrderBy (s => s) .ToList (); XmlElement interfaces = WriteElement(root, "Interfaces"); interfaces.RemoveAll(); foreach (string iname in interface_names) { XmlElement iface = root.OwnerDocument.CreateElement("Interface"); interfaces.AppendChild(iface); WriteElementText(iface, "InterfaceName", iname); } } else { ClearElement(root, "Interfaces"); } MakeAttributes (root, GetCustomAttributes (type), type); if (DocUtils.IsDelegate (type)) { MakeTypeParameters (root, type.GenericParameters, type, MDocUpdater.HasDroppedNamespace(type)); var member = type.GetMethod ("Invoke"); MakeParameters(root, member, member.Parameters); MakeReturnValue(root, member); } DocsNodeInfo typeInfo = new DocsNodeInfo (WriteElement(root, "Docs"), type); MakeDocNode (typeInfo); if (!DocUtils.IsDelegate (type)) WriteElement (root, "Members"); OrderTypeNodes (root, root.ChildNodes); NormalizeWhitespace(root); } /// Adds an AssemblyInfo with AssemblyName node to an XmlElement. /// The assembly that was either added, or was already present static XmlElement AddAssemblyNameToNode (XmlElement root, TypeDefinition type) { return AddAssemblyNameToNode (root, type.Module); } /// Adds an AssemblyInfo with AssemblyName node to an XmlElement. /// The assembly that was either added, or was already present static XmlElement AddAssemblyNameToNode (XmlElement root, ModuleDefinition module) { Func assemblyFilter = x => { var existingName = x.SelectSingleNode ("AssemblyName"); return existingName != null && existingName.InnerText == module.Assembly.Name.Name; }; return AddAssemblyXmlNode ( root.SelectNodes ("AssemblyInfo").Cast ().ToArray (), assemblyFilter, x => WriteElementText (x, "AssemblyName", module.Assembly.Name.Name), () => { XmlElement ass = WriteElement (root, "AssemblyInfo", forceNewElement: true); if (MDocUpdater.HasDroppedNamespace (module)) ass.SetAttribute ("apistyle", "unified"); return ass; }, module); } static readonly string[] TypeNodeOrder = { "TypeSignature", "MemberOfLibrary", "AssemblyInfo", "ThreadingSafetyStatement", "ThreadSafetyStatement", "TypeParameters", "Base", "Interfaces", "Attributes", "Parameters", "ReturnValue", "Docs", "Members", "TypeExcluded", }; static void OrderTypeNodes (XmlNode member, XmlNodeList children) { ReorderNodes (member, children, TypeNodeOrder); } internal static IEnumerable Sort (IEnumerable list) { List l = new List (list); l.Sort (); return l; } private void UpdateMember (DocsNodeInfo info) { XmlElement me = (XmlElement) info.Node; MemberReference mi = info.Member; foreach (MemberFormatter f in memberFormatters) { string element = "MemberSignature[@Language='" + f.Language + "']"; var valueToUse = f.GetDeclaration (mi); AddXmlNode ( me.SelectNodes (element).Cast ().ToArray(), x => x.GetAttribute("Value") == valueToUse, x => x.SetAttribute ("Value", valueToUse), () => { var node = WriteElementAttribute (me, element, "Language", f.Language, forceNewElement:true); var newNode = WriteElementAttribute (me, node, "Value", valueToUse); return newNode; }, mi); } WriteElementText(me, "MemberType", GetMemberType(mi)); if (!no_assembly_versions) { if (!multiassembly) UpdateAssemblyVersions (me, mi, true); else { var node = AddAssemblyNameToNode (me, mi.Module); UpdateAssemblyVersionForAssemblyInfo (node, me, new[] { GetAssemblyVersion (mi.Module.Assembly) }, add: true); } } else { ClearElement (me, "AssemblyInfo"); } MakeAttributes (me, GetCustomAttributes (mi), mi.DeclaringType); MakeReturnValue(me, mi, MDocUpdater.HasDroppedNamespace(mi)); if (mi is MethodReference) { MethodReference mb = (MethodReference) mi; if (mb.IsGenericMethod ()) MakeTypeParameters (me, mb.GenericParameters, mi, MDocUpdater.HasDroppedNamespace(mi)); } MakeParameters(me, mi, MDocUpdater.HasDroppedNamespace(mi)); string fieldValue; if (mi is FieldDefinition && GetFieldConstValue ((FieldDefinition)mi, out fieldValue)) WriteElementText(me, "MemberValue", fieldValue); info.Node = WriteElement (me, "Docs"); MakeDocNode (info); OrderMemberNodes (me, me.ChildNodes); UpdateExtensionMethods (me, info); } static void AddXmlNode (XmlElement[] relevant, Func valueMatches, Action setValue, Func makeNewNode, MemberReference member) { AddXmlNode (relevant, valueMatches, setValue, makeNewNode, member.Module); } static void AddXmlNode (XmlElement[] relevant, Func valueMatches, Action setValue, Func makeNewNode, TypeDefinition type) { AddXmlNode (relevant, valueMatches, setValue, makeNewNode, type.Module); } static XmlElement AddAssemblyXmlNode (XmlElement[] relevant, Func valueMatches, Action setValue, Func makeNewNode, ModuleDefinition module) { bool isUnified = MDocUpdater.HasDroppedNamespace (module); XmlElement thisAssemblyNode = relevant.FirstOrDefault (valueMatches); if (thisAssemblyNode == null) { thisAssemblyNode = makeNewNode (); setValue (thisAssemblyNode); } if (isUnified) { thisAssemblyNode.AddApiStyle (ApiStyle.Unified); foreach (var otherNodes in relevant.Where (n => n != thisAssemblyNode && n.DoesNotHaveApiStyle (ApiStyle.Unified))) { otherNodes.AddApiStyle (ApiStyle.Classic); } } return thisAssemblyNode; } /// Adds an xml node, reusing the node if it's available /// The existing set of nodes /// Checks to see if the node's value matches what you're trying to write. /// Sets the node's value /// Creates a new node, if valueMatches returns false. static void AddXmlNode (XmlElement[] relevant, Func valueMatches, Action setValue, Func makeNewNode, ModuleDefinition module) { bool shouldDuplicate = MDocUpdater.HasDroppedNamespace (module); var styleToUse = shouldDuplicate ? ApiStyle.Unified : ApiStyle.Classic; var existing = relevant; bool done = false; bool addedOldApiStyle = false; if (shouldDuplicate) { existing = existing.Where (n => n.HasApiStyle (styleToUse)).ToArray (); foreach (var n in relevant.Where (n => n.DoesNotHaveApiStyle (styleToUse))) { if (valueMatches (n)) { done = true; } else { n.AddApiStyle (ApiStyle.Classic); addedOldApiStyle = true; } } } if (!done) { if (!existing.Any ()) { var newNode = makeNewNode (); if (shouldDuplicate && addedOldApiStyle) { newNode.AddApiStyle (ApiStyle.Unified); } } else { var itemToReuse = existing.First (); setValue (itemToReuse); if (shouldDuplicate && addedOldApiStyle) { itemToReuse.AddApiStyle (styleToUse); } } } } static readonly string[] MemberNodeOrder = { "MemberSignature", "MemberType", "AssemblyInfo", "Attributes", "ReturnValue", "TypeParameters", "Parameters", "MemberValue", "Docs", "Excluded", "ExcludedLibrary", "Link", }; static void OrderMemberNodes (XmlNode member, XmlNodeList children) { ReorderNodes (member, children, MemberNodeOrder); } static void ReorderNodes (XmlNode node, XmlNodeList children, string[] ordering) { MyXmlNodeList newChildren = new MyXmlNodeList (children.Count); for (int i = 0; i < ordering.Length; ++i) { for (int j = 0; j < children.Count; ++j) { XmlNode c = children [j]; if (c.Name == ordering [i]) { newChildren.Add (c); } } } if (newChildren.Count >= 0) node.PrependChild ((XmlNode) newChildren [0]); for (int i = 1; i < newChildren.Count; ++i) { XmlNode prev = (XmlNode) newChildren [i-1]; XmlNode cur = (XmlNode) newChildren [i]; node.RemoveChild (cur); node.InsertAfter (cur, prev); } } IEnumerable GetCustomAttributes (MemberReference mi) { IEnumerable attrs = Enumerable.Empty(); ICustomAttributeProvider p = mi as ICustomAttributeProvider; if (p != null) attrs = attrs.Concat (GetCustomAttributes (p.CustomAttributes, "")); PropertyDefinition pd = mi as PropertyDefinition; if (pd != null) { if (pd.GetMethod != null) attrs = attrs.Concat (GetCustomAttributes (pd.GetMethod.CustomAttributes, "get: ")); if (pd.SetMethod != null) attrs = attrs.Concat (GetCustomAttributes (pd.SetMethod.CustomAttributes, "set: ")); } EventDefinition ed = mi as EventDefinition; if (ed != null) { if (ed.AddMethod != null) attrs = attrs.Concat (GetCustomAttributes (ed.AddMethod.CustomAttributes, "add: ")); if (ed.RemoveMethod != null) attrs = attrs.Concat (GetCustomAttributes (ed.RemoveMethod.CustomAttributes, "remove: ")); } return attrs; } IEnumerable GetCustomAttributes (IList attributes, string prefix) { foreach (CustomAttribute attribute in attributes.OrderBy (ca => ca.AttributeType.FullName)) { TypeDefinition attrType = attribute.AttributeType as TypeDefinition; if (attrType != null && !IsPublic (attrType)) continue; if (slashdocFormatter.GetName (attribute.AttributeType) == null) continue; if (Array.IndexOf (IgnorableAttributes, attribute.AttributeType.FullName) >= 0) continue; StringList fields = new StringList (); for (int i = 0; i < attribute.ConstructorArguments.Count; ++i) { CustomAttributeArgument argument = attribute.ConstructorArguments [i]; fields.Add (MakeAttributesValueString ( argument.Value, argument.Type)); } var namedArgs = (from namedArg in attribute.Fields select new { Type=namedArg.Argument.Type, Name=namedArg.Name, Value=namedArg.Argument.Value }) .Concat ( (from namedArg in attribute.Properties select new { Type=namedArg.Argument.Type, Name=namedArg.Name, Value=namedArg.Argument.Value })) .OrderBy (v => v.Name); foreach (var d in namedArgs) fields.Add (string.Format ("{0}={1}", d.Name, MakeAttributesValueString (d.Value, d.Type))); string a2 = String.Join(", ", fields.ToArray ()); if (a2 != "") a2 = "(" + a2 + ")"; string name = attribute.GetDeclaringType(); if (name.EndsWith("Attribute")) name = name.Substring(0, name.Length-"Attribute".Length); yield return prefix + name + a2; } } static readonly string[] ValidExtensionMembers = { "Docs", "MemberSignature", "MemberType", "Parameters", "ReturnValue", "TypeParameters", }; static readonly string[] ValidExtensionDocMembers = { "param", "summary", "typeparam", }; private void UpdateExtensionMethods (XmlElement e, DocsNodeInfo info) { MethodDefinition me = info.Member as MethodDefinition; if (me == null) return; if (info.Parameters.Count < 1) return; if (!DocUtils.IsExtensionMethod (me)) return; XmlNode em = e.OwnerDocument.CreateElement ("ExtensionMethod"); XmlNode member = e.CloneNode (true); em.AppendChild (member); RemoveExcept (member, ValidExtensionMembers); RemoveExcept (member.SelectSingleNode ("Docs"), ValidExtensionDocMembers); WriteElementText (member, "MemberType", "ExtensionMethod"); XmlElement link = member.OwnerDocument.CreateElement ("Link"); link.SetAttribute ("Type", slashdocFormatter.GetName (me.DeclaringType)); link.SetAttribute ("Member", slashdocFormatter.GetDeclaration (me)); member.AppendChild (link); AddTargets (em, info); extensionMethods.Add (em); } private static void RemoveExcept (XmlNode node, string[] except) { if (node == null) return; MyXmlNodeList remove = null; foreach (XmlNode n in node.ChildNodes) { if (Array.BinarySearch (except, n.Name) < 0) { if (remove == null) remove = new MyXmlNodeList (); remove.Add (n); } } if (remove != null) foreach (XmlNode n in remove) node.RemoveChild (n); } private static void AddTargets (XmlNode member, DocsNodeInfo info) { XmlElement targets = member.OwnerDocument.CreateElement ("Targets"); member.PrependChild (targets); if (!(info.Parameters [0].ParameterType is GenericParameter)) { AppendElementAttributeText (targets, "Target", "Type", slashdocFormatter.GetDeclaration (info.Parameters [0].ParameterType)); } else { GenericParameter gp = (GenericParameter) info.Parameters [0].ParameterType; IList constraints = gp.Constraints; if (constraints.Count == 0) AppendElementAttributeText (targets, "Target", "Type", "System.Object"); else foreach (TypeReference c in constraints) AppendElementAttributeText(targets, "Target", "Type", slashdocFormatter.GetDeclaration (c)); } } private static bool GetFieldConstValue (FieldDefinition field, out string value) { value = null; TypeDefinition type = field.DeclaringType.Resolve (); if (type != null && type.IsEnum) return false; if (type != null && type.IsGenericType ()) return false; if (!field.HasConstant) return false; if (field.IsLiteral) { object val = field.Constant; if (val == null) value = "null"; else if (val is Enum) value = val.ToString(); else if (val is IFormattable) { value = ((IFormattable)val).ToString(); if (val is string) value = "\"" + value + "\""; } if (value != null && value != "") return true; } return false; } // XML HELPER FUNCTIONS internal static XmlElement WriteElement(XmlNode parent, string element, bool forceNewElement = false) { XmlElement ret = (XmlElement)parent.SelectSingleNode(element); if (ret == null || forceNewElement) { string[] path = element.Split('/'); foreach (string p in path) { ret = (XmlElement)parent.SelectSingleNode(p); if (ret == null || forceNewElement) { string ename = p; if (ename.IndexOf('[') >= 0) // strip off XPath predicate ename = ename.Substring(0, ename.IndexOf('[')); ret = parent.OwnerDocument.CreateElement(ename); parent.AppendChild(ret); parent = ret; } else { parent = ret; } } } return ret; } private static XmlElement WriteElementText(XmlNode parent, string element, string value, bool forceNewElement = false) { XmlElement node = WriteElement(parent, element, forceNewElement: forceNewElement); node.InnerText = value; return node; } static XmlElement AppendElementText (XmlNode parent, string element, string value) { XmlElement n = parent.OwnerDocument.CreateElement (element); parent.AppendChild (n); n.InnerText = value; return n; } static XmlElement AppendElementAttributeText (XmlNode parent, string element, string attribute, string value) { XmlElement n = parent.OwnerDocument.CreateElement (element); parent.AppendChild (n); n.SetAttribute (attribute, value); return n; } internal static XmlNode CopyNode (XmlNode source, XmlNode dest) { XmlNode copy = dest.OwnerDocument.ImportNode (source, true); dest.AppendChild (copy); return copy; } private static void WriteElementInitialText(XmlElement parent, string element, string value) { XmlElement node = (XmlElement)parent.SelectSingleNode(element); if (node != null) return; node = WriteElement(parent, element); node.InnerText = value; } private static XmlElement WriteElementAttribute(XmlElement parent, string element, string attribute, string value, bool forceNewElement = false) { XmlElement node = WriteElement(parent, element, forceNewElement:forceNewElement); return WriteElementAttribute (parent, node, attribute, value); } private static XmlElement WriteElementAttribute(XmlElement parent, XmlElement node, string attribute, string value) { if (node.GetAttribute (attribute) != value) { node.SetAttribute (attribute, value); } return node; } internal static void ClearElement(XmlElement parent, string name) { XmlElement node = (XmlElement)parent.SelectSingleNode(name); if (node != null) parent.RemoveChild(node); } // DOCUMENTATION HELPER FUNCTIONS private void MakeDocNode (DocsNodeInfo info) { List genericParams = info.GenericParameters; IList parameters = info.Parameters; TypeReference returntype = info.ReturnType; bool returnisreturn = info.ReturnIsReturn; XmlElement e = info.Node; bool addremarks = info.AddRemarks; WriteElementInitialText(e, "summary", "To be added."); if (parameters != null) { string[] values = new string [parameters.Count]; for (int i = 0; i < values.Length; ++i) values [i] = parameters [i].Name; UpdateParameters (e, "param", values); } if (genericParams != null) { string[] values = new string [genericParams.Count]; for (int i = 0; i < values.Length; ++i) values [i] = genericParams [i].Name; UpdateParameters (e, "typeparam", values); } string retnodename = null; if (returntype != null && returntype.FullName != "System.Void") { // FIXME retnodename = returnisreturn ? "returns" : "value"; string retnodename_other = !returnisreturn ? "returns" : "value"; // If it has a returns node instead of a value node, change its name. XmlElement retother = (XmlElement)e.SelectSingleNode(retnodename_other); if (retother != null) { XmlElement retnode = e.OwnerDocument.CreateElement(retnodename); foreach (XmlNode node in retother) retnode.AppendChild(node.CloneNode(true)); e.ReplaceChild(retnode, retother); } else { WriteElementInitialText(e, retnodename, "To be added."); } } else { ClearElement(e, "returns"); ClearElement(e, "value"); } if (addremarks) WriteElementInitialText(e, "remarks", "To be added."); if (exceptions.HasValue && info.Member != null && (exceptions.Value & ExceptionLocations.AddedMembers) == 0) { UpdateExceptions (e, info.Member); } foreach (DocumentationImporter importer in importers) importer.ImportDocumentation (info); OrderDocsNodes (e, e.ChildNodes); NormalizeWhitespace(e); } static readonly string[] DocsNodeOrder = { "typeparam", "param", "summary", "returns", "value", "remarks", }; private static void OrderDocsNodes (XmlNode docs, XmlNodeList children) { ReorderNodes (docs, children, DocsNodeOrder); } private void UpdateParameters (XmlElement e, string element, string[] values) { if (values != null) { XmlNode[] paramnodes = new XmlNode[values.Length]; // Some documentation had param nodes with leading spaces. foreach (XmlElement paramnode in e.SelectNodes(element)){ paramnode.SetAttribute("name", paramnode.GetAttribute("name").Trim()); } // If a member has only one parameter, we can track changes to // the name of the parameter easily. if (values.Length == 1 && e.SelectNodes(element).Count == 1) { UpdateParameterName (e, (XmlElement) e.SelectSingleNode(element), values [0]); } bool reinsert = false; // Pick out existing and still-valid param nodes, and // create nodes for parameters not in the file. Hashtable seenParams = new Hashtable(); for (int pi = 0; pi < values.Length; pi++) { string p = values [pi]; seenParams[p] = pi; paramnodes[pi] = e.SelectSingleNode(element + "[@name='" + p + "']"); if (paramnodes[pi] != null) continue; XmlElement pe = e.OwnerDocument.CreateElement(element); pe.SetAttribute("name", p); pe.InnerText = "To be added."; paramnodes[pi] = pe; reinsert = true; } // Remove parameters that no longer exist and check all params are in the right order. int idx = 0; MyXmlNodeList todelete = new MyXmlNodeList (); foreach (XmlElement paramnode in e.SelectNodes(element)) { string name = paramnode.GetAttribute("name"); if (!seenParams.ContainsKey(name)) { if (!delete && !paramnode.InnerText.StartsWith("To be added")) { Warning ("The following param node can only be deleted if the --delete option is given: "); if (e.ParentNode == e.OwnerDocument.DocumentElement) { // delegate type Warning ("\tXPath=/Type[@FullName=\"{0}\"]/Docs/param[@name=\"{1}\"]", e.OwnerDocument.DocumentElement.GetAttribute ("FullName"), name); } else { Warning ("\tXPath=/Type[@FullName=\"{0}\"]//Member[@MemberName=\"{1}\"]/Docs/param[@name=\"{2}\"]", e.OwnerDocument.DocumentElement.GetAttribute ("FullName"), e.ParentNode.Attributes ["MemberName"].Value, name); } Warning ("\tValue={0}", paramnode.OuterXml); } else { todelete.Add (paramnode); } continue; } if ((int)seenParams[name] != idx) reinsert = true; idx++; } foreach (XmlNode n in todelete) { n.ParentNode.RemoveChild (n); } // Re-insert the parameter nodes at the top of the doc section. if (reinsert) for (int pi = values.Length-1; pi >= 0; pi--) e.PrependChild(paramnodes[pi]); } else { // Clear all existing param nodes foreach (XmlNode paramnode in e.SelectNodes(element)) { if (!delete && !paramnode.InnerText.StartsWith("To be added")) { Console.WriteLine("The following param node can only be deleted if the --delete option is given:"); Console.WriteLine(paramnode.OuterXml); } else { paramnode.ParentNode.RemoveChild(paramnode); } } } } private static void UpdateParameterName (XmlElement docs, XmlElement pe, string newName) { string existingName = pe.GetAttribute ("name"); pe.SetAttribute ("name", newName); if (existingName == newName) return; foreach (XmlElement paramref in docs.SelectNodes (".//paramref")) if (paramref.GetAttribute ("name").Trim () == existingName) paramref.SetAttribute ("name", newName); } class CrefComparer : XmlNodeComparer { public CrefComparer () { } public override int Compare (XmlNode x, XmlNode y) { string xType = x.Attributes ["cref"].Value; string yType = y.Attributes ["cref"].Value; string xNamespace = GetNamespace (xType); string yNamespace = GetNamespace (yType); int c = xNamespace.CompareTo (yNamespace); if (c != 0) return c; return xType.CompareTo (yType); } static string GetNamespace (string type) { int n = type.LastIndexOf ('.'); if (n >= 0) return type.Substring (0, n); return string.Empty; } } private void UpdateExceptions (XmlNode docs, MemberReference member) { string indent = new string (' ', 10); foreach (var source in new ExceptionLookup (exceptions.Value)[member]) { string cref = slashdocFormatter.GetDeclaration (source.Exception); var node = docs.SelectSingleNode ("exception[@cref='" + cref + "']"); if (node != null) continue; XmlElement e = docs.OwnerDocument.CreateElement ("exception"); e.SetAttribute ("cref", cref); e.InnerXml = "To be added; from:\n" + indent + ",\n" + indent + " slashdocFormatter.GetDeclaration (m)) .OrderBy (s => s)) + "\" />"; docs.AppendChild (e); } SortXmlNodes (docs, docs.SelectNodes ("exception"), new CrefComparer ()); } private static void NormalizeWhitespace(XmlElement e) { // Remove all text and whitespace nodes from the element so it // is outputted with nice indentation and no blank lines. ArrayList deleteNodes = new ArrayList(); foreach (XmlNode n in e) if (n is XmlText || n is XmlWhitespace || n is XmlSignificantWhitespace) deleteNodes.Add(n); foreach (XmlNode n in deleteNodes) n.ParentNode.RemoveChild(n); } private static bool UpdateAssemblyVersions (XmlElement root, MemberReference member, bool add) { TypeDefinition type = member as TypeDefinition; if (type == null) type = member.DeclaringType as TypeDefinition; var versions = new string[] { GetAssemblyVersion (type.Module.Assembly) }; if (root.LocalName == "AssemblyInfo") return UpdateAssemblyVersionForAssemblyInfo (root, root.ParentNode as XmlElement, versions, add: true); else return UpdateAssemblyVersions (root, type.Module.Assembly, versions, add); } private static string GetAssemblyVersion (AssemblyDefinition assembly) { return assembly.Name.Version.ToString(); } private static bool UpdateAssemblyVersions(XmlElement root, AssemblyDefinition assembly, string[] assemblyVersions, bool add) { XmlElement av = (XmlElement) root.SelectSingleNode ("AssemblyVersions"); if (av != null) { // AssemblyVersions is not part of the spec root.RemoveChild (av); } string oldNodeFilter = "AssemblyInfo[not(@apistyle) or @apistyle='classic']"; string newNodeFilter = "AssemblyInfo[@apistyle='unified']"; string thisNodeFilter = MDocUpdater.HasDroppedNamespace (assembly) ? newNodeFilter : oldNodeFilter; string thatNodeFilter = MDocUpdater.HasDroppedNamespace (assembly) ? oldNodeFilter : newNodeFilter; XmlElement e = (XmlElement) root.SelectSingleNode (thisNodeFilter); if (e == null) { e = root.OwnerDocument.CreateElement("AssemblyInfo"); if (MDocUpdater.HasDroppedNamespace (assembly)) { e.SetAttribute ("apistyle", "unified"); } root.AppendChild(e); } var thatNode = (XmlElement) root.SelectSingleNode (thatNodeFilter); if (MDocUpdater.HasDroppedNamespace (assembly) && thatNode != null) { // there's a classic node, we should add apistyles e.SetAttribute ("apistyle", "unified"); thatNode.SetAttribute ("apistyle", "classic"); } return UpdateAssemblyVersionForAssemblyInfo (e, root, assemblyVersions, add); } static bool UpdateAssemblyVersionForAssemblyInfo (XmlElement e, XmlElement root, string[] assemblyVersions, bool add) { List matches = e.SelectNodes ("AssemblyVersion").Cast ().Where (v => Array.IndexOf (assemblyVersions, v.InnerText) >= 0).ToList (); // matches.Count > 0 && add: ignore -- already present if (matches.Count > 0 && !add) { foreach (XmlNode c in matches) e.RemoveChild (c); } else if (matches.Count == 0 && add) { foreach (string sv in assemblyVersions) { XmlElement c = root.OwnerDocument.CreateElement("AssemblyVersion"); c.InnerText = sv; e.AppendChild(c); } } // matches.Count == 0 && !add: ignore -- already not present XmlNodeList avs = e.SelectNodes ("AssemblyVersion"); SortXmlNodes (e, avs, new VersionComparer ()); bool anyNodesLeft = avs.Count != 0; if (!anyNodesLeft) { e.ParentNode.RemoveChild (e); } return anyNodesLeft; } // FIXME: get TypeReferences instead of string comparison? private static string[] IgnorableAttributes = { // Security related attributes "System.Reflection.AssemblyKeyFileAttribute", "System.Reflection.AssemblyDelaySignAttribute", // Present in @RefType "System.Runtime.InteropServices.OutAttribute", // For naming the indexer to use when not using indexers "System.Reflection.DefaultMemberAttribute", // for decimal constants "System.Runtime.CompilerServices.DecimalConstantAttribute", // compiler generated code "System.Runtime.CompilerServices.CompilerGeneratedAttribute", // more compiler generated code, e.g. iterator methods "System.Diagnostics.DebuggerHiddenAttribute", "System.Runtime.CompilerServices.FixedBufferAttribute", "System.Runtime.CompilerServices.UnsafeValueTypeAttribute", // extension methods "System.Runtime.CompilerServices.ExtensionAttribute", // Used to differentiate 'object' from C#4 'dynamic' "System.Runtime.CompilerServices.DynamicAttribute", }; private void MakeAttributes (XmlElement root, IEnumerable attributes, TypeReference t=null) { if (!attributes.Any ()) { ClearElement (root, "Attributes"); return; } XmlElement e = (XmlElement)root.SelectSingleNode("Attributes"); if (e != null) e.RemoveAll(); else if (e == null) e = root.OwnerDocument.CreateElement("Attributes"); foreach (string attribute in attributes) { XmlElement ae = root.OwnerDocument.CreateElement("Attribute"); e.AppendChild(ae); WriteElementText(ae, "AttributeName", attribute); } if (e.ParentNode == null) root.AppendChild(e); NormalizeWhitespace(e); } public static string MakeAttributesValueString (object v, TypeReference valueType) { var formatters = new [] { new AttributeValueFormatter (), new ApplePlatformEnumFormatter (), new StandardFlagsEnumFormatter (), new DefaultAttributeValueFormatter (), }; ResolvedTypeInfo type = new ResolvedTypeInfo (valueType); foreach (var formatter in formatters) { string formattedValue; if (formatter.TryFormatValue (v, type, out formattedValue)) { return formattedValue; } } // this should never occur because the DefaultAttributeValueFormatter will always // successfully format the value ... but this is needed to satisfy the compiler :) throw new InvalidDataException (string.Format ("Unable to format attribute value ({0})", v.ToString ())); } internal static IDictionary GetEnumerationValues (TypeDefinition type) { var values = new Dictionary (); foreach (var f in (from f in type.Fields where !(f.IsRuntimeSpecialName || f.IsSpecialName) select f)) { values [ToInt64 (f.Constant)] = f.Name; } return values; } internal static long ToInt64 (object value) { if (value is ulong) return (long) (ulong) value; return Convert.ToInt64 (value); } private void MakeParameters (XmlElement root, MemberReference member, IList parameters, bool shouldDuplicateWithNew=false) { XmlElement e = WriteElement(root, "Parameters"); int i = 0; foreach (ParameterDefinition p in parameters) { XmlElement pe; // param info var ptype = GetDocParameterType (p.ParameterType); var newPType = ptype; if (MDocUpdater.SwitchingToMagicTypes) { newPType = NativeTypeManager.ConvertFromNativeType (ptype); } // now find the existing node, if it's there so we can reuse it. var nodes = root.SelectSingleNode ("Parameters").SelectNodes ("Parameter") .Cast ().Where (x => x.GetAttribute ("Name") == p.Name) .ToArray(); if (nodes.Count () == 0) { // wasn't found, let's make sure it wasn't just cause the param name was changed nodes = root.SelectSingleNode ("Parameters").SelectNodes ("Parameter") .Cast () .Skip (i) // this makes sure we don't inadvertently "reuse" nodes when adding new ones .Where (x => x.GetAttribute ("Name") != p.Name && (x.GetAttribute ("Type") == ptype || x.GetAttribute ("Type") == newPType)) .Take(1) // there might be more than one that meets this parameter ... only take the first. .ToArray(); } AddXmlNode (nodes, x => x.GetAttribute ("Type") == ptype, x => x.SetAttribute ("Type", ptype), () => { pe = root.OwnerDocument.CreateElement ("Parameter"); e.AppendChild (pe); pe.SetAttribute ("Name", p.Name); pe.SetAttribute ("Type", ptype); if (p.ParameterType is ByReferenceType) { if (p.IsOut) pe.SetAttribute ("RefType", "out"); else pe.SetAttribute ("RefType", "ref"); } MakeAttributes (pe, GetCustomAttributes (p.CustomAttributes, "")); return pe; }, member); i++; } } private void MakeTypeParameters (XmlElement root, IList typeParams, MemberReference member, bool shouldDuplicateWithNew) { if (typeParams == null || typeParams.Count == 0) { XmlElement f = (XmlElement) root.SelectSingleNode ("TypeParameters"); if (f != null) root.RemoveChild (f); return; } XmlElement e = WriteElement(root, "TypeParameters"); var nodes = e.SelectNodes ("TypeParameter").Cast ().ToArray (); foreach (GenericParameter t in typeParams) { IList constraints = t.Constraints; GenericParameterAttributes attrs = t.Attributes; AddXmlNode ( nodes, x => { var baseType = e.SelectSingleNode("BaseTypeName"); // TODO: should this comparison take into account BaseTypeName? return x.GetAttribute("Name") == t.Name; }, x => {}, // no additional action required () => { XmlElement pe = root.OwnerDocument.CreateElement("TypeParameter"); e.AppendChild(pe); pe.SetAttribute("Name", t.Name); MakeAttributes (pe, GetCustomAttributes (t.CustomAttributes, ""), t.DeclaringType); XmlElement ce = (XmlElement) e.SelectSingleNode ("Constraints"); if (attrs == GenericParameterAttributes.NonVariant && constraints.Count == 0) { if (ce != null) e.RemoveChild (ce); return pe; } if (ce != null) ce.RemoveAll(); else { ce = root.OwnerDocument.CreateElement ("Constraints"); } pe.AppendChild (ce); if ((attrs & GenericParameterAttributes.Contravariant) != 0) AppendElementText (ce, "ParameterAttribute", "Contravariant"); if ((attrs & GenericParameterAttributes.Covariant) != 0) AppendElementText (ce, "ParameterAttribute", "Covariant"); if ((attrs & GenericParameterAttributes.DefaultConstructorConstraint) != 0) AppendElementText (ce, "ParameterAttribute", "DefaultConstructorConstraint"); if ((attrs & GenericParameterAttributes.NotNullableValueTypeConstraint) != 0) AppendElementText (ce, "ParameterAttribute", "NotNullableValueTypeConstraint"); if ((attrs & GenericParameterAttributes.ReferenceTypeConstraint) != 0) AppendElementText (ce, "ParameterAttribute", "ReferenceTypeConstraint"); foreach (TypeReference c in constraints) { TypeDefinition cd = c.Resolve (); AppendElementText (ce, (cd != null && cd.IsInterface) ? "InterfaceName" : "BaseTypeName", GetDocTypeFullName (c)); } return pe; }, member); } } private void MakeParameters (XmlElement root, MemberReference mi, bool shouldDuplicateWithNew) { if (mi is MethodDefinition && ((MethodDefinition) mi).IsConstructor) MakeParameters (root, mi, ((MethodDefinition)mi).Parameters, shouldDuplicateWithNew); else if (mi is MethodDefinition) { MethodDefinition mb = (MethodDefinition) mi; IList parameters = mb.Parameters; MakeParameters(root, mi, parameters, shouldDuplicateWithNew); if (parameters.Count > 0 && DocUtils.IsExtensionMethod (mb)) { XmlElement p = (XmlElement) root.SelectSingleNode ("Parameters/Parameter[position()=1]"); p.SetAttribute ("RefType", "this"); } } else if (mi is PropertyDefinition) { IList parameters = ((PropertyDefinition)mi).Parameters; if (parameters.Count > 0) MakeParameters(root, mi, parameters, shouldDuplicateWithNew); else return; } else if (mi is FieldDefinition) return; else if (mi is EventDefinition) return; else throw new ArgumentException(); } internal static string GetDocParameterType (TypeReference type) { return GetDocTypeFullName (type).Replace ("@", "&"); } private void MakeReturnValue (XmlElement root, TypeReference type, IList attributes, bool shouldDuplicateWithNew=false) { XmlElement e = WriteElement(root, "ReturnValue"); var valueToUse = GetDocTypeFullName (type); AddXmlNode (e.SelectNodes("ReturnType").Cast ().ToArray (), x => x.InnerText == valueToUse, x => x.InnerText = valueToUse, () => { var newNode = WriteElementText(e, "ReturnType", valueToUse, forceNewElement: true); if (attributes != null) MakeAttributes(e, GetCustomAttributes (attributes, ""), type); return newNode; }, type); } private void MakeReturnValue (XmlElement root, MemberReference mi, bool shouldDuplicateWithNew=false) { if (mi is MethodDefinition && ((MethodDefinition) mi).IsConstructor) return; else if (mi is MethodDefinition) MakeReturnValue (root, ((MethodDefinition)mi).ReturnType, ((MethodDefinition)mi).MethodReturnType.CustomAttributes, shouldDuplicateWithNew); else if (mi is PropertyDefinition) MakeReturnValue (root, ((PropertyDefinition)mi).PropertyType, null, shouldDuplicateWithNew); else if (mi is FieldDefinition) MakeReturnValue (root, ((FieldDefinition)mi).FieldType, null, shouldDuplicateWithNew); else if (mi is EventDefinition) MakeReturnValue (root, ((EventDefinition)mi).EventType, null, shouldDuplicateWithNew); else throw new ArgumentException(mi + " is a " + mi.GetType().FullName); } private XmlElement MakeMember(XmlDocument doc, DocsNodeInfo info) { MemberReference mi = info.Member; if (mi is TypeDefinition) return null; string sigs = memberFormatters [0].GetDeclaration (mi); if (sigs == null) return null; // not publicly visible // no documentation for property/event accessors. Is there a better way of doing this? if (mi.Name.StartsWith("get_")) return null; if (mi.Name.StartsWith("set_")) return null; if (mi.Name.StartsWith("add_")) return null; if (mi.Name.StartsWith("remove_")) return null; if (mi.Name.StartsWith("raise_")) return null; XmlElement me = doc.CreateElement("Member"); me.SetAttribute("MemberName", GetMemberName (mi)); info.Node = me; UpdateMember(info); if (exceptions.HasValue && (exceptions.Value & ExceptionLocations.AddedMembers) != 0) UpdateExceptions (info.Node, info.Member); if (since != null) { XmlNode docs = me.SelectSingleNode("Docs"); docs.AppendChild (CreateSinceNode (doc)); } return me; } internal static string GetMemberName (MemberReference mi) { MethodDefinition mb = mi as MethodDefinition; if (mb == null) { PropertyDefinition pi = mi as PropertyDefinition; if (pi == null) return mi.Name; return DocUtils.GetPropertyName (pi); } StringBuilder sb = new StringBuilder (mi.Name.Length); if (!DocUtils.IsExplicitlyImplemented (mb)) sb.Append (mi.Name); else { TypeReference iface; MethodReference ifaceMethod; DocUtils.GetInfoForExplicitlyImplementedMethod (mb, out iface, out ifaceMethod); sb.Append (GetDocTypeFullName (iface)); sb.Append ('.'); sb.Append (ifaceMethod.Name); } if (mb.IsGenericMethod ()) { IList typeParams = mb.GenericParameters; if (typeParams.Count > 0) { sb.Append ("<"); sb.Append (typeParams [0].Name); for (int i = 1; i < typeParams.Count; ++i) sb.Append (",").Append (typeParams [i].Name); sb.Append (">"); } } return sb.ToString (); } /// SIGNATURE GENERATION FUNCTIONS internal static bool IsPrivate (MemberReference mi) { return memberFormatters [0].GetDeclaration (mi) == null; } internal static string GetMemberType (MemberReference mi) { if (mi is MethodDefinition && ((MethodDefinition) mi).IsConstructor) return "Constructor"; if (mi is MethodDefinition) return "Method"; if (mi is PropertyDefinition) return "Property"; if (mi is FieldDefinition) return "Field"; if (mi is EventDefinition) return "Event"; throw new ArgumentException(); } private static string GetDocTypeName (TypeReference type) { return docTypeFormatter.GetName (type); } internal static string GetDocTypeFullName (TypeReference type) { return DocTypeFullMemberFormatter.Default.GetName (type); } internal static string GetXPathForMember (DocumentationMember member) { StringBuilder xpath = new StringBuilder (); xpath.Append ("//Members/Member[@MemberName=\"") .Append (member.MemberName) .Append ("\"]"); if (member.Parameters != null && member.Parameters.Count > 0) { xpath.Append ("/Parameters[count(Parameter) = ") .Append (member.Parameters.Count); for (int i = 0; i < member.Parameters.Count; ++i) { xpath.Append (" and Parameter [").Append (i+1).Append ("]/@Type=\""); xpath.Append (member.Parameters [i]); xpath.Append ("\""); } xpath.Append ("]/.."); } return xpath.ToString (); } public static string GetXPathForMember (XPathNavigator member) { StringBuilder xpath = new StringBuilder (); xpath.Append ("//Type[@FullName=\"") .Append (member.SelectSingleNode ("../../@FullName").Value) .Append ("\"]/"); xpath.Append ("Members/Member[@MemberName=\"") .Append (member.SelectSingleNode ("@MemberName").Value) .Append ("\"]"); XPathNodeIterator parameters = member.Select ("Parameters/Parameter"); if (parameters.Count > 0) { xpath.Append ("/Parameters[count(Parameter) = ") .Append (parameters.Count); int i = 0; while (parameters.MoveNext ()) { ++i; xpath.Append (" and Parameter [").Append (i).Append ("]/@Type=\""); xpath.Append (parameters.Current.Value); xpath.Append ("\""); } xpath.Append ("]/.."); } return xpath.ToString (); } public static string GetXPathForMember (MemberReference member) { StringBuilder xpath = new StringBuilder (); xpath.Append ("//Type[@FullName=\"") .Append (member.DeclaringType.FullName) .Append ("\"]/"); xpath.Append ("Members/Member[@MemberName=\"") .Append (GetMemberName (member)) .Append ("\"]"); IList parameters = null; if (member is MethodDefinition) parameters = ((MethodDefinition) member).Parameters; else if (member is PropertyDefinition) { parameters = ((PropertyDefinition) member).Parameters; } if (parameters != null && parameters.Count > 0) { xpath.Append ("/Parameters[count(Parameter) = ") .Append (parameters.Count); for (int i = 0; i < parameters.Count; ++i) { xpath.Append (" and Parameter [").Append (i+1).Append ("]/@Type=\""); xpath.Append (GetDocParameterType (parameters [i].ParameterType)); xpath.Append ("\""); } xpath.Append ("]/.."); } return xpath.ToString (); } } static class CecilExtensions { public static string GetDeclaringType(this CustomAttribute attribute) { var type = attribute.Constructor.DeclaringType; var typeName = type.FullName; string translatedType = NativeTypeManager.GetTranslatedName (type); return translatedType; } public static IEnumerable GetMembers (this TypeDefinition type) { foreach (var c in type.Methods.Where (m => m.IsConstructor)) yield return (MemberReference) c; foreach (var e in type.Events) yield return (MemberReference) e; foreach (var f in type.Fields) yield return (MemberReference) f; foreach (var m in type.Methods.Where (m => !m.IsConstructor)) yield return (MemberReference) m; foreach (var t in type.NestedTypes) yield return (MemberReference) t; foreach (var p in type.Properties) yield return (MemberReference) p; } public static IEnumerable GetMembers (this TypeDefinition type, string member) { return GetMembers (type).Where (m => m.Name == member); } public static MemberReference GetMember (this TypeDefinition type, string member) { return GetMembers (type, member).EnsureZeroOrOne (); } static T EnsureZeroOrOne (this IEnumerable source) { if (source.Count () > 1) throw new InvalidOperationException ("too many matches"); return source.FirstOrDefault (); } public static MethodDefinition GetMethod (this TypeDefinition type, string method) { return type.Methods .Where (m => m.Name == method) .EnsureZeroOrOne (); } public static IEnumerable GetDefaultMembers (this TypeReference type) { TypeDefinition def = type as TypeDefinition; if (def == null) return new MemberReference [0]; CustomAttribute defMemberAttr = def.CustomAttributes .FirstOrDefault (c => c.AttributeType.FullName == "System.Reflection.DefaultMemberAttribute"); if (defMemberAttr == null) return new MemberReference [0]; string name = (string) defMemberAttr.ConstructorArguments [0].Value; return def.Properties .Where (p => p.Name == name) .Select (p => (MemberReference) p); } public static IEnumerable GetTypes (this AssemblyDefinition assembly) { return assembly.Modules.SelectMany (md => md.GetAllTypes ()); } public static TypeDefinition GetType (this AssemblyDefinition assembly, string type) { return GetTypes (assembly) .Where (td => td.FullName == type) .EnsureZeroOrOne (); } public static bool IsGenericType (this TypeReference type) { return type.GenericParameters.Count > 0; } public static bool IsGenericMethod (this MethodReference method) { return method.GenericParameters.Count > 0; } public static MemberReference Resolve (this MemberReference member) { FieldReference fr = member as FieldReference; if (fr != null) return fr.Resolve (); MethodReference mr = member as MethodReference; if (mr != null) return mr.Resolve (); TypeReference tr = member as TypeReference; if (tr != null) return tr.Resolve (); PropertyReference pr = member as PropertyReference; if (pr != null) return pr; EventReference er = member as EventReference; if (er != null) return er; throw new NotSupportedException ("Cannot find definition for " + member.ToString ()); } public static TypeReference GetUnderlyingType (this TypeDefinition type) { if (!type.IsEnum) return type; return type.Fields.First (f => f.Name == "value__").FieldType; } public static IEnumerable GetAllTypes (this ModuleDefinition self) { return self.Types.SelectMany (t => t.GetAllTypes ()); } static IEnumerable GetAllTypes (this TypeDefinition self) { yield return self; if (!self.HasNestedTypes) yield break; foreach (var type in self.NestedTypes.SelectMany (t => t.GetAllTypes ())) yield return type; } } enum ApiStyle { Classic, Unified } static class DocUtils { public static bool DoesNotHaveApiStyle(this XmlElement element, ApiStyle style) { string styleString = style.ToString ().ToLowerInvariant (); string apistylevalue = element.GetAttribute ("apistyle"); return apistylevalue != styleString || string.IsNullOrWhiteSpace(apistylevalue); } public static bool HasApiStyle(this XmlElement element, ApiStyle style) { string styleString = style.ToString ().ToLowerInvariant (); return element.GetAttribute ("apistyle") == styleString; } public static bool HasApiStyle(this XmlNode node, ApiStyle style) { var attribute = node.Attributes ["apistyle"]; return attribute != null && attribute.Value == style.ToString ().ToLowerInvariant (); } public static void AddApiStyle(this XmlElement element, ApiStyle style) { string styleString = style.ToString ().ToLowerInvariant (); var existingValue = element.GetAttribute ("apistyle"); if (string.IsNullOrWhiteSpace (existingValue) || existingValue != styleString) { element.SetAttribute ("apistyle", styleString); } } public static void RemoveApiStyle (this XmlElement element, ApiStyle style) { string styleString = style.ToString ().ToLowerInvariant (); string existingValue = element.GetAttribute ("apistyle"); if (string.IsNullOrWhiteSpace (existingValue) || existingValue == styleString) { element.RemoveAttribute ("apistyle"); } } public static void RemoveApiStyle (this XmlNode node, ApiStyle style) { var styleAttribute = node.Attributes ["apistyle"]; if (styleAttribute != null && styleAttribute.Value == style.ToString ().ToLowerInvariant ()) { node.Attributes.Remove (styleAttribute); } } public static bool IsExplicitlyImplemented (MethodDefinition method) { return method.IsPrivate && method.IsFinal && method.IsVirtual; } public static string GetTypeDotMember (string name) { int startType, startMethod; startType = startMethod = -1; for (int i = 0; i < name.Length; ++i) { if (name [i] == '.') { startType = startMethod; startMethod = i; } } return name.Substring (startType+1); } public static string GetMember (string name) { int i = name.LastIndexOf ('.'); if (i == -1) return name; return name.Substring (i+1); } public static void GetInfoForExplicitlyImplementedMethod ( MethodDefinition method, out TypeReference iface, out MethodReference ifaceMethod) { iface = null; ifaceMethod = null; if (method.Overrides.Count != 1) throw new InvalidOperationException ("Could not determine interface type for explicitly-implemented interface member " + method.Name); iface = method.Overrides [0].DeclaringType; ifaceMethod = method.Overrides [0]; } public static string GetPropertyName (PropertyDefinition pi) { // Issue: (g)mcs-generated assemblies that explicitly implement // properties don't specify the full namespace, just the // TypeName.Property; .NET uses Full.Namespace.TypeName.Property. MethodDefinition method = pi.GetMethod; if (method == null) method = pi.SetMethod; if (!IsExplicitlyImplemented (method)) return pi.Name; // Need to determine appropriate namespace for this member. TypeReference iface; MethodReference ifaceMethod; GetInfoForExplicitlyImplementedMethod (method, out iface, out ifaceMethod); return string.Join (".", new string[]{ DocTypeFullMemberFormatter.Default.GetName (iface), GetMember (pi.Name)}); } public static string GetNamespace (TypeReference type) { if (type.GetElementType ().IsNested) type = type.GetElementType (); while (type != null && type.IsNested) type = type.DeclaringType; if (type == null) return string.Empty; string typeNS = type.Namespace; // first, make sure this isn't a type reference to another assembly/module bool isInAssembly = MDocUpdater.IsInAssemblies(type.Module.Name); if (isInAssembly && !typeNS.StartsWith ("System") && MDocUpdater.HasDroppedNamespace (type)) { typeNS = string.Format ("{0}.{1}", MDocUpdater.droppedNamespace, typeNS); } return typeNS; } public static string PathCombine (string dir, string path) { if (dir == null) dir = ""; if (path == null) path = ""; return Path.Combine (dir, path); } public static bool IsExtensionMethod (MethodDefinition method) { return method.CustomAttributes .Any (m => m.AttributeType.FullName == "System.Runtime.CompilerServices.ExtensionAttribute") && method.DeclaringType.CustomAttributes .Any (m => m.AttributeType.FullName == "System.Runtime.CompilerServices.ExtensionAttribute"); } public static bool IsDelegate (TypeDefinition type) { TypeReference baseRef = type.BaseType; if (baseRef == null) return false; return !type.IsAbstract && baseRef.FullName == "System.Delegate" || // FIXME baseRef.FullName == "System.MulticastDelegate"; } public static List GetDeclaringTypes (TypeReference type) { List decls = new List (); decls.Add (type); while (type.DeclaringType != null) { decls.Add (type.DeclaringType); type = type.DeclaringType; } decls.Reverse (); return decls; } public static int GetGenericArgumentCount (TypeReference type) { GenericInstanceType inst = type as GenericInstanceType; return inst != null ? inst.GenericArguments.Count : type.GenericParameters.Count; } public static IEnumerable GetUserImplementedInterfaces (TypeDefinition type) { HashSet inheritedInterfaces = GetInheritedInterfaces (type); List userInterfaces = new List (); foreach (TypeReference iface in type.Interfaces) { TypeReference lookup = iface.Resolve () ?? iface; if (!inheritedInterfaces.Contains (GetQualifiedTypeName (lookup))) userInterfaces.Add (iface); } return userInterfaces.Where (i => MDocUpdater.IsPublic (i.Resolve ())); } private static string GetQualifiedTypeName (TypeReference type) { return "[" + type.Scope.Name + "]" + type.FullName; } private static HashSet GetInheritedInterfaces (TypeDefinition type) { HashSet inheritedInterfaces = new HashSet (); Action a = null; a = t => { if (t == null) return; foreach (TypeReference r in t.Interfaces) { inheritedInterfaces.Add (GetQualifiedTypeName (r)); a (r.Resolve ()); } }; TypeReference baseRef = type.BaseType; while (baseRef != null) { TypeDefinition baseDef = baseRef.Resolve (); if (baseDef != null) { a (baseDef); baseRef = baseDef.BaseType; } else baseRef = null; } foreach (TypeReference r in type.Interfaces) a (r.Resolve ()); return inheritedInterfaces; } } class DocsNodeInfo { public DocsNodeInfo (XmlElement node) { this.Node = node; } public DocsNodeInfo (XmlElement node, TypeDefinition type) : this (node) { SetType (type); } public DocsNodeInfo (XmlElement node, MemberReference member) : this (node) { SetMemberInfo (member); } void SetType (TypeDefinition type) { if (type == null) throw new ArgumentNullException ("type"); Type = type; GenericParameters = new List (type.GenericParameters); List declTypes = DocUtils.GetDeclaringTypes (type); int maxGenArgs = DocUtils.GetGenericArgumentCount (type); for (int i = 0; i < declTypes.Count - 1; ++i) { int remove = System.Math.Min (maxGenArgs, DocUtils.GetGenericArgumentCount (declTypes [i])); maxGenArgs -= remove; while (remove-- > 0) GenericParameters.RemoveAt (0); } if (DocUtils.IsDelegate (type)) { Parameters = type.GetMethod("Invoke").Parameters; ReturnType = type.GetMethod("Invoke").ReturnType; ReturnIsReturn = true; } } void SetMemberInfo (MemberReference member) { if (member == null) throw new ArgumentNullException ("member"); ReturnIsReturn = true; AddRemarks = true; Member = member; if (member is MethodReference ) { MethodReference mr = (MethodReference) member; Parameters = mr.Parameters; if (mr.IsGenericMethod ()) { GenericParameters = new List (mr.GenericParameters); } } else if (member is PropertyDefinition) { Parameters = ((PropertyDefinition) member).Parameters; } if (member is MethodDefinition) { ReturnType = ((MethodDefinition) member).ReturnType; } else if (member is PropertyDefinition) { ReturnType = ((PropertyDefinition) member).PropertyType; ReturnIsReturn = false; } // no remarks section for enum members if (member.DeclaringType != null && ((TypeDefinition) member.DeclaringType).IsEnum) AddRemarks = false; } public TypeReference ReturnType; public List GenericParameters; public IList Parameters; public bool ReturnIsReturn; public XmlElement Node; public bool AddRemarks = true; public MemberReference Member; public TypeDefinition Type; public override string ToString () { return string.Format ("{0} - {1} - {2}", Type, Member, Node == null ? "no xml" : "with xml"); } } class DocumentationEnumerator { public virtual IEnumerable GetDocumentationTypes (AssemblyDefinition assembly, List forTypes) { return GetDocumentationTypes (assembly, forTypes, null); } protected IEnumerable GetDocumentationTypes (AssemblyDefinition assembly, List forTypes, HashSet seen) { foreach (TypeDefinition type in assembly.GetTypes()) { if (forTypes != null && forTypes.BinarySearch (type.FullName) < 0) continue; if (seen != null && seen.Contains (type.FullName)) continue; yield return type; foreach (TypeDefinition nested in type.NestedTypes) yield return nested; } } public virtual IEnumerable GetDocumentationMembers (XmlDocument basefile, TypeDefinition type) { foreach (XmlElement oldmember in basefile.SelectNodes("Type/Members/Member")) { if (oldmember.GetAttribute ("__monodocer-seen__") == "true") { oldmember.RemoveAttribute ("__monodocer-seen__"); continue; } MemberReference m = GetMember (type, new DocumentationMember (oldmember)); if (m == null) { yield return new DocsNodeInfo (oldmember); } else { yield return new DocsNodeInfo (oldmember, m); } } } protected static MemberReference GetMember (TypeDefinition type, DocumentationMember member) { string membertype = member.MemberType; string returntype = member.ReturnType; string docName = member.MemberName; string[] docTypeParams = GetTypeParameters (docName, member.TypeParameters); // If we're using 'magic types', then we might get false positives ... in those cases, we keep searching MemberReference likelyCandidate = null; // Loop through all members in this type with the same name var reflectedMembers = GetReflectionMembers (type, docName).ToArray (); foreach (MemberReference mi in reflectedMembers) { bool matchedMagicType = false; if (mi is TypeDefinition) continue; if (MDocUpdater.GetMemberType(mi) != membertype) continue; if (MDocUpdater.IsPrivate (mi)) continue; IList pis = null; string[] typeParams = null; if (mi is MethodDefinition) { MethodDefinition mb = (MethodDefinition) mi; pis = mb.Parameters; if (mb.IsGenericMethod ()) { IList args = mb.GenericParameters; typeParams = args.Select (p => p.Name).ToArray (); } } else if (mi is PropertyDefinition) pis = ((PropertyDefinition)mi).Parameters; // check type parameters int methodTcount = member.TypeParameters == null ? 0 : member.TypeParameters.Count; int reflectionTcount = typeParams == null ? 0 : typeParams.Length; if (methodTcount != reflectionTcount) continue; // check member parameters int mcount = member.Parameters == null ? 0 : member.Parameters.Count; int pcount = pis == null ? 0 : pis.Count; if (mcount != pcount) continue; MethodDefinition mDef = mi as MethodDefinition; if (mDef != null && !mDef.IsConstructor) { // Casting operators can overload based on return type. string rtype = GetReplacedString ( MDocUpdater.GetDocTypeFullName (((MethodDefinition)mi).ReturnType), typeParams, docTypeParams); string originalRType = rtype; if (MDocUpdater.SwitchingToMagicTypes) { rtype = NativeTypeManager.ConvertFromNativeType (rtype); } if ((returntype != rtype && originalRType == rtype) || (MDocUpdater.SwitchingToMagicTypes && returntype != originalRType && returntype != rtype && originalRType != rtype)) { continue; } if (originalRType != rtype) matchedMagicType = true; } if (pcount == 0) return mi; bool good = true; for (int i = 0; i < pis.Count; i++) { string paramType = GetReplacedString ( MDocUpdater.GetDocParameterType (pis [i].ParameterType), typeParams, docTypeParams); // if magictypes, replace paramType to "classic value" ... so the comparison works string originalParamType = paramType; if (MDocUpdater.SwitchingToMagicTypes) { paramType = NativeTypeManager.ConvertFromNativeType (paramType); } string xmlMemberType = member.Parameters [i]; if ((!paramType.Equals(xmlMemberType) && paramType.Equals(originalParamType)) || (MDocUpdater.SwitchingToMagicTypes && !originalParamType.Equals(xmlMemberType) && !paramType.Equals(xmlMemberType) && !paramType.Equals(originalParamType))) { // did not match ... if we're dropping the namespace, and the paramType has the dropped // namespace, we should see if it matches when added bool stillDoesntMatch = true; if (MDocUpdater.HasDroppedNamespace(type) && paramType.StartsWith (MDocUpdater.droppedNamespace)) { string withDroppedNs = string.Format ("{0}.{1}", MDocUpdater.droppedNamespace, xmlMemberType); stillDoesntMatch = withDroppedNs != paramType; } if (stillDoesntMatch) { good = false; break; } } if (originalParamType != paramType) matchedMagicType = true; } if (!good) continue; if (MDocUpdater.SwitchingToMagicTypes && likelyCandidate == null && matchedMagicType) { // we matched this on a magic type conversion ... let's keep going to see if there's another one we should look at that matches more closely likelyCandidate = mi; continue; } return mi; } return likelyCandidate; } static string[] GetTypeParameters (string docName, IEnumerable knownParameters) { if (docName [docName.Length-1] != '>') return null; StringList types = new StringList (); int endToken = docName.Length-2; int i = docName.Length-2; do { if (docName [i] == ',' || docName [i] == '<') { types.Add (docName.Substring (i + 1, endToken - i)); endToken = i-1; } if (docName [i] == '<') break; } while (--i >= 0); types.Reverse (); var arrayTypes = types.ToArray (); if (knownParameters != null && knownParameters.Any () && arrayTypes.Length != knownParameters.Count ()) return knownParameters.ToArray (); else return arrayTypes; } protected static IEnumerable GetReflectionMembers (TypeDefinition type, string docName) { // In case of dropping the namespace, we have to remove the dropped NS // so that docName will match what's in the assembly/type if (MDocUpdater.HasDroppedNamespace (type) && docName.StartsWith(MDocUpdater.droppedNamespace + ".")) { int droppedNsLength = MDocUpdater.droppedNamespace.Length; docName = docName.Substring (droppedNsLength + 1, docName.Length - droppedNsLength - 1); } // need to worry about 4 forms of //@MemberName values: // 1. "Normal" (non-generic) member names: GetEnumerator // - Lookup as-is. // 2. Explicitly-implemented interface member names: System.Collections.IEnumerable.Current // - try as-is, and try type.member (due to "kludge" for property // support. // 3. "Normal" Generic member names: Sort (CSC) // - need to remove generic parameters --> "Sort" // 4. Explicitly-implemented interface members for generic interfaces: // -- System.Collections.Generic.IEnumerable.Current // - Try as-is, and try type.member, *keeping* the generic parameters. // --> System.Collections.Generic.IEnumerable.Current, IEnumerable.Current // 5. As of 2008-01-02, gmcs will do e.g. 'IFoo`1[A].Method' instead of // 'IFoo.Method' for explicitly implemented methods; don't interpret // this as (1) or (2). if (docName.IndexOf ('<') == -1 && docName.IndexOf ('[') == -1) { // Cases 1 & 2 foreach (MemberReference mi in type.GetMembers (docName)) yield return mi; if (CountChars (docName, '.') > 0) // might be a property; try only type.member instead of // namespace.type.member. foreach (MemberReference mi in type.GetMembers (DocUtils.GetTypeDotMember (docName))) yield return mi; yield break; } // cases 3 & 4 int numLt = 0; int numDot = 0; int startLt, startType, startMethod; startLt = startType = startMethod = -1; for (int i = 0; i < docName.Length; ++i) { switch (docName [i]) { case '<': if (numLt == 0) { startLt = i; } ++numLt; break; case '>': --numLt; if (numLt == 0 && (i + 1) < docName.Length) // there's another character in docName, so this <...> sequence is // probably part of a generic type -- case 4. startLt = -1; break; case '.': startType = startMethod; startMethod = i; ++numDot; break; } } string refName = startLt == -1 ? docName : docName.Substring (0, startLt); // case 3 foreach (MemberReference mi in type.GetMembers (refName)) yield return mi; // case 4 foreach (MemberReference mi in type.GetMembers (refName.Substring (startType + 1))) yield return mi; // If we _still_ haven't found it, we've hit another generic naming issue: // post Mono 1.1.18, gmcs generates [[FQTN]] instead of for // explicitly-implemented METHOD names (not properties), e.g. // "System.Collections.Generic.IEnumerable`1[[Foo, test, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null]].GetEnumerator" // instead of "System.Collections.Generic.IEnumerable.GetEnumerator", // which the XML docs will contain. // // Alas, we can't derive the Mono name from docName, so we need to iterate // over all member names, convert them into CSC format, and compare... :-( if (numDot == 0) yield break; foreach (MemberReference mi in type.GetMembers ()) { if (MDocUpdater.GetMemberName (mi) == docName) yield return mi; } } static string GetReplacedString (string typeName, string[] from, string[] to) { if (from == null) return typeName; for (int i = 0; i < from.Length; ++i) typeName = typeName.Replace (from [i], to [i]); return typeName; } private static int CountChars (string s, char c) { int count = 0; for (int i = 0; i < s.Length; ++i) { if (s [i] == c) ++count; } return count; } } class EcmaDocumentationEnumerator : DocumentationEnumerator { XmlReader ecmadocs; MDocUpdater app; public EcmaDocumentationEnumerator (MDocUpdater app, XmlReader ecmaDocs) { this.app = app; this.ecmadocs = ecmaDocs; } public override IEnumerable GetDocumentationTypes (AssemblyDefinition assembly, List forTypes) { HashSet seen = new HashSet (); return GetDocumentationTypes (assembly, forTypes, seen) .Concat (base.GetDocumentationTypes (assembly, forTypes, seen)); } new IEnumerable GetDocumentationTypes (AssemblyDefinition assembly, List forTypes, HashSet seen) { int typeDepth = -1; while (ecmadocs.Read ()) { switch (ecmadocs.Name) { case "Type": { if (typeDepth == -1) typeDepth = ecmadocs.Depth; if (ecmadocs.NodeType != XmlNodeType.Element) continue; if (typeDepth != ecmadocs.Depth) // nested element? continue; string typename = ecmadocs.GetAttribute ("FullName"); string typename2 = MDocUpdater.GetTypeFileName (typename); if (forTypes != null && forTypes.BinarySearch (typename) < 0 && typename != typename2 && forTypes.BinarySearch (typename2) < 0) continue; TypeDefinition t; if ((t = assembly.GetType (typename)) == null && (t = assembly.GetType (typename2)) == null) continue; seen.Add (typename); if (typename != typename2) seen.Add (typename2); Console.WriteLine (" Import: {0}", t.FullName); if (ecmadocs.Name != "Docs") { int depth = ecmadocs.Depth; while (ecmadocs.Read ()) { if (ecmadocs.Name == "Docs" && ecmadocs.Depth == depth + 1) break; } } if (!ecmadocs.IsStartElement ("Docs")) throw new InvalidOperationException ("Found " + ecmadocs.Name + "; expecting !"); yield return t; break; } default: break; } } } public override IEnumerable GetDocumentationMembers (XmlDocument basefile, TypeDefinition type) { return GetMembers (basefile, type) .Concat (base.GetDocumentationMembers (basefile, type)); } private IEnumerable GetMembers (XmlDocument basefile, TypeDefinition type) { while (ecmadocs.Name != "Members" && ecmadocs.Read ()) { // do nothing } if (ecmadocs.IsEmptyElement) yield break; int membersDepth = ecmadocs.Depth; bool go = true; while (go && ecmadocs.Read ()) { switch (ecmadocs.Name) { case "Member": { if (membersDepth != ecmadocs.Depth - 1 || ecmadocs.NodeType != XmlNodeType.Element) continue; DocumentationMember dm = new DocumentationMember (ecmadocs); string xp = MDocUpdater.GetXPathForMember (dm); XmlElement oldmember = (XmlElement) basefile.SelectSingleNode (xp); MemberReference m; if (oldmember == null) { m = GetMember (type, dm); if (m == null) { app.Warning ("Could not import ECMA docs for `{0}'s `{1}': Member not found.", type.FullName, dm.MemberSignatures ["C#"]); // SelectSingleNode (ecmaDocsMember, "MemberSignature[@Language=\"C#\"]/@Value").Value); continue; } // oldmember lookup may have failed due to type parameter renames. // Try again. oldmember = (XmlElement) basefile.SelectSingleNode (MDocUpdater.GetXPathForMember (m)); if (oldmember == null) { XmlElement members = MDocUpdater.WriteElement (basefile.DocumentElement, "Members"); oldmember = basefile.CreateElement ("Member"); oldmember.SetAttribute ("MemberName", dm.MemberName); members.AppendChild (oldmember); foreach (string key in MDocUpdater.Sort (dm.MemberSignatures.Keys)) { XmlElement ms = basefile.CreateElement ("MemberSignature"); ms.SetAttribute ("Language", key); ms.SetAttribute ("Value", (string) dm.MemberSignatures [key]); oldmember.AppendChild (ms); } oldmember.SetAttribute ("__monodocer-seen__", "true"); Console.WriteLine ("Member Added: {0}", oldmember.SelectSingleNode("MemberSignature[@Language='C#']/@Value").InnerText); app.additions++; } } else { m = GetMember (type, new DocumentationMember (oldmember)); if (m == null) { app.Warning ("Could not import ECMA docs for `{0}'s `{1}': Member not found.", type.FullName, dm.MemberSignatures ["C#"]); continue; } oldmember.SetAttribute ("__monodocer-seen__", "true"); } DocsNodeInfo node = new DocsNodeInfo (oldmember, m); if (ecmadocs.Name != "Docs") throw new InvalidOperationException ("Found " + ecmadocs.Name + "; expected !"); yield return node; break; } case "Members": if (membersDepth == ecmadocs.Depth && ecmadocs.NodeType == XmlNodeType.EndElement) { go = false; } break; } } } } abstract class DocumentationImporter { public abstract void ImportDocumentation (DocsNodeInfo info); } class MsxdocDocumentationImporter : DocumentationImporter { XmlDocument slashdocs; public MsxdocDocumentationImporter (string file) { var xml = File.ReadAllText (file); // Ensure Unix line endings xml = xml.Replace ("\r", ""); slashdocs = new XmlDocument(); slashdocs.LoadXml (xml); } public override void ImportDocumentation (DocsNodeInfo info) { XmlNode elem = GetDocs (info.Member ?? info.Type); if (elem == null) return; XmlElement e = info.Node; if (elem.SelectSingleNode("summary") != null) MDocUpdater.ClearElement(e, "summary"); if (elem.SelectSingleNode("remarks") != null) MDocUpdater.ClearElement(e, "remarks"); if (elem.SelectSingleNode ("value") != null || elem.SelectSingleNode ("returns") != null) { MDocUpdater.ClearElement(e, "value"); MDocUpdater.ClearElement(e, "returns"); } foreach (XmlNode child in elem.ChildNodes) { switch (child.Name) { case "param": case "typeparam": { XmlAttribute name = child.Attributes ["name"]; if (name == null) break; XmlElement p2 = (XmlElement) e.SelectSingleNode (child.Name + "[@name='" + name.Value + "']"); if (p2 != null) p2.InnerXml = child.InnerXml; break; } // Occasionally XML documentation will use on // properties, so let's try to normalize things. case "value": case "returns": { XmlElement v = e.OwnerDocument.CreateElement (info.ReturnIsReturn ? "returns" : "value"); v.InnerXml = child.InnerXml; e.AppendChild (v); break; } case "altmember": case "exception": case "permission": { XmlAttribute cref = child.Attributes ["cref"] ?? child.Attributes ["name"]; if (cref == null) break; XmlElement a = (XmlElement) e.SelectSingleNode (child.Name + "[@cref='" + cref.Value + "']"); if (a == null) { a = e.OwnerDocument.CreateElement (child.Name); a.SetAttribute ("cref", cref.Value); e.AppendChild (a); } a.InnerXml = child.InnerXml; break; } case "seealso": { XmlAttribute cref = child.Attributes ["cref"]; if (cref == null) break; XmlElement a = (XmlElement) e.SelectSingleNode ("altmember[@cref='" + cref.Value + "']"); if (a == null) { a = e.OwnerDocument.CreateElement ("altmember"); a.SetAttribute ("cref", cref.Value); e.AppendChild (a); } break; } default: { bool add = true; if (child.NodeType == XmlNodeType.Element && e.SelectNodes (child.Name).Cast().Any (n => n.OuterXml == child.OuterXml)) add = false; if (add) MDocUpdater.CopyNode (child, e); break; } } } } private XmlNode GetDocs (MemberReference member) { string slashdocsig = MDocUpdater.slashdocFormatter.GetDeclaration (member); if (slashdocsig != null) return slashdocs.SelectSingleNode ("doc/members/member[@name='" + slashdocsig + "']"); return null; } } class EcmaDocumentationImporter : DocumentationImporter { XmlReader ecmadocs; public EcmaDocumentationImporter (XmlReader ecmaDocs) { this.ecmadocs = ecmaDocs; } public override void ImportDocumentation (DocsNodeInfo info) { if (!ecmadocs.IsStartElement ("Docs")) { return; } XmlElement e = info.Node; int depth = ecmadocs.Depth; ecmadocs.ReadStartElement ("Docs"); while (ecmadocs.Read ()) { if (ecmadocs.Name == "Docs") { if (ecmadocs.Depth == depth && ecmadocs.NodeType == XmlNodeType.EndElement) break; else throw new InvalidOperationException ("Skipped past current element!"); } if (!ecmadocs.IsStartElement ()) continue; switch (ecmadocs.Name) { case "param": case "typeparam": { string name = ecmadocs.GetAttribute ("name"); if (name == null) break; XmlNode doc = e.SelectSingleNode ( ecmadocs.Name + "[@name='" + name + "']"); string value = ecmadocs.ReadInnerXml (); if (doc != null) doc.InnerXml = value.Replace ("\r", ""); break; } case "altmember": case "exception": case "permission": case "seealso": { string name = ecmadocs.Name; string cref = ecmadocs.GetAttribute ("cref"); if (cref == null) break; XmlNode doc = e.SelectSingleNode ( ecmadocs.Name + "[@cref='" + cref + "']"); string value = ecmadocs.ReadInnerXml ().Replace ("\r", ""); if (doc != null) doc.InnerXml = value; else { XmlElement n = e.OwnerDocument.CreateElement (name); n.SetAttribute ("cref", cref); n.InnerXml = value; e.AppendChild (n); } break; } default: { string name = ecmadocs.Name; string xpath = ecmadocs.Name; StringList attributes = new StringList (ecmadocs.AttributeCount); if (ecmadocs.MoveToFirstAttribute ()) { do { attributes.Add ("@" + ecmadocs.Name + "=\"" + ecmadocs.Value + "\""); } while (ecmadocs.MoveToNextAttribute ()); ecmadocs.MoveToContent (); } if (attributes.Count > 0) { xpath += "[" + string.Join (" and ", attributes.ToArray ()) + "]"; } XmlNode doc = e.SelectSingleNode (xpath); string value = ecmadocs.ReadInnerXml ().Replace ("\r", ""); if (doc != null) { doc.InnerXml = value; } else { XmlElement n = e.OwnerDocument.CreateElement (name); n.InnerXml = value; foreach (string a in attributes) { int eq = a.IndexOf ('='); n.SetAttribute (a.Substring (1, eq-1), a.Substring (eq+2, a.Length-eq-3)); } e.AppendChild (n); } break; } } } } } class DocumentationMember { public StringToStringMap MemberSignatures = new StringToStringMap (); public string ReturnType; public StringList Parameters; public StringList TypeParameters; public string MemberName; public string MemberType; public DocumentationMember (XmlReader reader) { MemberName = reader.GetAttribute ("MemberName"); int depth = reader.Depth; bool go = true; StringList p = new StringList (); StringList tp = new StringList (); do { if (reader.NodeType != XmlNodeType.Element) continue; bool shouldUse = true; try { string apistyle = reader.GetAttribute ("apistyle"); shouldUse = string.IsNullOrWhiteSpace(apistyle) || apistyle == "classic"; // only use this tag if it's an 'classic' style node } catch (Exception ex) {} switch (reader.Name) { case "MemberSignature": if (shouldUse) { MemberSignatures [reader.GetAttribute ("Language")] = reader.GetAttribute ("Value"); } break; case "MemberType": MemberType = reader.ReadElementString (); break; case "ReturnType": if (reader.Depth == depth + 2 && shouldUse) ReturnType = reader.ReadElementString (); break; case "Parameter": if (reader.Depth == depth + 2 && shouldUse) p.Add (reader.GetAttribute ("Type")); break; case "TypeParameter": if (reader.Depth == depth + 2 && shouldUse) tp.Add (reader.GetAttribute ("Name")); break; case "Docs": if (reader.Depth == depth + 1) go = false; break; } } while (go && reader.Read () && reader.Depth >= depth); if (p.Count > 0) { Parameters = p; } if (tp.Count > 0) { TypeParameters = tp; } else { DiscernTypeParameters (); } } public DocumentationMember (XmlNode node) { MemberName = node.Attributes ["MemberName"].Value; foreach (XmlNode n in node.SelectNodes ("MemberSignature")) { XmlAttribute l = n.Attributes ["Language"]; XmlAttribute v = n.Attributes ["Value"]; XmlAttribute apistyle = n.Attributes ["apistyle"]; bool shouldUse = apistyle == null || apistyle.Value == "classic"; if (l != null && v != null && shouldUse) MemberSignatures [l.Value] = v.Value; } MemberType = node.SelectSingleNode ("MemberType").InnerText; XmlNode rt = node.SelectSingleNode ("ReturnValue/ReturnType[not(@apistyle) or @apistyle='classic']"); if (rt != null) ReturnType = rt.InnerText; XmlNodeList p = node.SelectNodes ("Parameters/Parameter[not(@apistyle) or @apistyle='classic']"); if (p.Count > 0) { Parameters = new StringList (p.Count); for (int i = 0; i < p.Count; ++i) Parameters.Add (p [i].Attributes ["Type"].Value); } XmlNodeList tp = node.SelectNodes ("TypeParameters/TypeParameter[not(@apistyle) or @apistyle='classic']"); if (tp.Count > 0) { TypeParameters = new StringList (tp.Count); for (int i = 0; i < tp.Count; ++i) TypeParameters.Add (tp [i].Attributes ["Name"].Value); } else { DiscernTypeParameters (); } } void DiscernTypeParameters () { // see if we can discern the param list from the name if (MemberName.Contains ("<") && MemberName.EndsWith (">")) { var starti = MemberName.IndexOf ("<") + 1; var endi = MemberName.LastIndexOf (">"); var paramlist = MemberName.Substring (starti, endi - starti); var tparams = paramlist.Split (new char[] {','}, StringSplitOptions.RemoveEmptyEntries); TypeParameters = new StringList (tparams); } } } public class DynamicParserContext { public ReadOnlyCollection TransformFlags; public int TransformIndex; public DynamicParserContext (ICustomAttributeProvider provider) { CustomAttribute da; if (provider.HasCustomAttributes && (da = (provider.CustomAttributes.Cast() .SingleOrDefault (ca => ca.GetDeclaringType() == "System.Runtime.CompilerServices.DynamicAttribute"))) != null) { CustomAttributeArgument[] values = da.ConstructorArguments.Count == 0 ? new CustomAttributeArgument [0] : (CustomAttributeArgument[]) da.ConstructorArguments [0].Value; TransformFlags = new ReadOnlyCollection (values.Select (t => (bool) t.Value).ToArray()); } } } public enum MemberFormatterState { None, WithinGenericTypeParameters, } public abstract class MemberFormatter { public virtual string Language { get {return "";} } public string GetName (MemberReference member) { return GetName (member, null); } public virtual string GetName (MemberReference member, DynamicParserContext context) { TypeReference type = member as TypeReference; if (type != null) return GetTypeName (type, context); MethodReference method = member as MethodReference; if (method != null && method.Name == ".ctor") // method.IsConstructor return GetConstructorName (method); if (method != null) return GetMethodName (method); PropertyReference prop = member as PropertyReference; if (prop != null) return GetPropertyName (prop); FieldReference field = member as FieldReference; if (field != null) return GetFieldName (field); EventReference e = member as EventReference; if (e != null) return GetEventName (e); throw new NotSupportedException ("Can't handle: " + (member == null ? "null" : member.GetType().ToString())); } protected virtual string GetTypeName (TypeReference type) { return GetTypeName (type, null); } protected virtual string GetTypeName (TypeReference type, DynamicParserContext context) { if (type == null) throw new ArgumentNullException ("type"); return _AppendTypeName (new StringBuilder (type.Name.Length), type, context).ToString (); } protected virtual char[] ArrayDelimeters { get {return new char[]{'[', ']'};} } protected virtual MemberFormatterState MemberFormatterState { get; set; } protected StringBuilder _AppendTypeName (StringBuilder buf, TypeReference type, DynamicParserContext context) { if (type is ArrayType) { TypeSpecification spec = type as TypeSpecification; _AppendTypeName (buf, spec != null ? spec.ElementType : type.GetElementType (), context); return AppendArrayModifiers (buf, (ArrayType) type); } if (type is ByReferenceType) { return AppendRefTypeName (buf, type, context); } if (type is PointerType) { return AppendPointerTypeName (buf, type, context); } if (type is GenericParameter) { return AppendTypeName (buf, type, context); } AppendNamespace (buf, type); GenericInstanceType genInst = type as GenericInstanceType; if (type.GenericParameters.Count == 0 && (genInst == null ? true : genInst.GenericArguments.Count == 0)) { return AppendFullTypeName (buf, type, context); } return AppendGenericType (buf, type, context); } protected virtual StringBuilder AppendNamespace (StringBuilder buf, TypeReference type) { string ns = DocUtils.GetNamespace (type); if (ns != null && ns.Length > 0) buf.Append (ns).Append ('.'); return buf; } protected virtual StringBuilder AppendFullTypeName (StringBuilder buf, TypeReference type, DynamicParserContext context) { if (type.DeclaringType != null) AppendFullTypeName (buf, type.DeclaringType, context).Append (NestedTypeSeparator); return AppendTypeName (buf, type, context); } protected virtual StringBuilder AppendTypeName (StringBuilder buf, TypeReference type, DynamicParserContext context) { if (context != null) context.TransformIndex++; return AppendTypeName (buf, type.Name); } protected virtual StringBuilder AppendTypeName (StringBuilder buf, string typename) { int n = typename.IndexOf ("`"); if (n >= 0) return buf.Append (typename.Substring (0, n)); return buf.Append (typename); } protected virtual StringBuilder AppendArrayModifiers (StringBuilder buf, ArrayType array) { buf.Append (ArrayDelimeters [0]); int rank = array.Rank; if (rank > 1) buf.Append (new string (',', rank-1)); return buf.Append (ArrayDelimeters [1]); } protected virtual string RefTypeModifier { get {return "@";} } protected virtual StringBuilder AppendRefTypeName (StringBuilder buf, TypeReference type, DynamicParserContext context) { TypeSpecification spec = type as TypeSpecification; return _AppendTypeName (buf, spec != null ? spec.ElementType : type.GetElementType (), context) .Append (RefTypeModifier); } protected virtual string PointerModifier { get {return "*";} } protected virtual StringBuilder AppendPointerTypeName (StringBuilder buf, TypeReference type, DynamicParserContext context) { TypeSpecification spec = type as TypeSpecification; return _AppendTypeName (buf, spec != null ? spec.ElementType : type.GetElementType (), context) .Append (PointerModifier); } protected virtual char[] GenericTypeContainer { get {return new char[]{'<', '>'};} } protected virtual char NestedTypeSeparator { get {return '.';} } protected virtual StringBuilder AppendGenericType (StringBuilder buf, TypeReference type, DynamicParserContext context) { List decls = DocUtils.GetDeclaringTypes ( type is GenericInstanceType ? type.GetElementType () : type); List genArgs = GetGenericArguments (type); int argIdx = 0; int prev = 0; bool insertNested = false; foreach (var decl in decls) { TypeReference declDef = decl.Resolve () ?? decl; if (insertNested) { buf.Append (NestedTypeSeparator); } insertNested = true; AppendTypeName (buf, declDef, context); int ac = DocUtils.GetGenericArgumentCount (declDef); int c = ac - prev; prev = ac; if (c > 0) { buf.Append (GenericTypeContainer [0]); var origState = MemberFormatterState; MemberFormatterState = MemberFormatterState.WithinGenericTypeParameters; _AppendTypeName (buf, genArgs [argIdx++], context); for (int i = 1; i < c; ++i) { _AppendTypeName (buf.Append (","), genArgs [argIdx++], context); } MemberFormatterState = origState; buf.Append (GenericTypeContainer [1]); } } return buf; } protected List GetGenericArguments (TypeReference type) { var args = new List (); GenericInstanceType inst = type as GenericInstanceType; if (inst != null) args.AddRange (inst.GenericArguments.Cast ()); else args.AddRange (type.GenericParameters.Cast ()); return args; } protected virtual StringBuilder AppendGenericTypeConstraints (StringBuilder buf, TypeReference type) { return buf; } protected virtual string GetConstructorName (MethodReference constructor) { return constructor.Name; } protected virtual string GetMethodName (MethodReference method) { return method.Name; } protected virtual string GetPropertyName (PropertyReference property) { return property.Name; } protected virtual string GetFieldName (FieldReference field) { return field.Name; } protected virtual string GetEventName (EventReference e) { return e.Name; } public virtual string GetDeclaration (MemberReference member) { if (member == null) throw new ArgumentNullException ("member"); TypeDefinition type = member as TypeDefinition; if (type != null) return GetTypeDeclaration (type); MethodDefinition method = member as MethodDefinition; if (method != null && method.IsConstructor) return GetConstructorDeclaration (method); if (method != null) return GetMethodDeclaration (method); PropertyDefinition prop = member as PropertyDefinition; if (prop != null) return GetPropertyDeclaration (prop); FieldDefinition field = member as FieldDefinition; if (field != null) return GetFieldDeclaration (field); EventDefinition e = member as EventDefinition; if (e != null) return GetEventDeclaration (e); throw new NotSupportedException ("Can't handle: " + member.GetType().ToString()); } protected virtual string GetTypeDeclaration (TypeDefinition type) { if (type == null) throw new ArgumentNullException ("type"); StringBuilder buf = new StringBuilder (type.Name.Length); _AppendTypeName (buf, type, null); AppendGenericTypeConstraints (buf, type); return buf.ToString (); } protected virtual string GetConstructorDeclaration (MethodDefinition constructor) { return GetConstructorName (constructor); } protected virtual string GetMethodDeclaration (MethodDefinition method) { if (method.HasCustomAttributes && method.CustomAttributes.Cast().Any( ca => ca.GetDeclaringType() == "System.Diagnostics.Contracts.ContractInvariantMethodAttribute")) return null; // Special signature for destructors. if (method.Name == "Finalize" && method.Parameters.Count == 0) return GetFinalizerName (method); StringBuilder buf = new StringBuilder (); AppendVisibility (buf, method); if (buf.Length == 0 && !(DocUtils.IsExplicitlyImplemented (method) && !method.IsSpecialName)) return null; AppendModifiers (buf, method); if (buf.Length != 0) buf.Append (" "); buf.Append (GetTypeName (method.ReturnType, new DynamicParserContext (method.MethodReturnType))).Append (" "); AppendMethodName (buf, method); AppendGenericMethod (buf, method).Append (" "); AppendParameters (buf, method, method.Parameters); AppendGenericMethodConstraints (buf, method); return buf.ToString (); } protected virtual StringBuilder AppendMethodName (StringBuilder buf, MethodDefinition method) { return buf.Append (method.Name); } protected virtual string GetFinalizerName (MethodDefinition method) { return "Finalize"; } protected virtual StringBuilder AppendVisibility (StringBuilder buf, MethodDefinition method) { return buf; } protected virtual StringBuilder AppendModifiers (StringBuilder buf, MethodDefinition method) { return buf; } protected virtual StringBuilder AppendGenericMethod (StringBuilder buf, MethodDefinition method) { return buf; } protected virtual StringBuilder AppendParameters (StringBuilder buf, MethodDefinition method, IList parameters) { return buf; } protected virtual StringBuilder AppendGenericMethodConstraints (StringBuilder buf, MethodDefinition method) { return buf; } protected virtual string GetPropertyDeclaration (PropertyDefinition property) { return GetPropertyName (property); } protected virtual string GetFieldDeclaration (FieldDefinition field) { return GetFieldName (field); } protected virtual string GetEventDeclaration (EventDefinition e) { return GetEventName (e); } } class ILFullMemberFormatter : MemberFormatter { public override string Language { get {return "ILAsm";} } protected override char NestedTypeSeparator { get { return '/'; } } protected override StringBuilder AppendNamespace (StringBuilder buf, TypeReference type) { if (GetBuiltinType (type.FullName) != null) return buf; string ns = DocUtils.GetNamespace (type); if (ns != null && ns.Length > 0) { if (type.IsValueType) buf.Append ("valuetype "); else buf.Append ("class "); buf.Append (ns).Append ('.'); } return buf; } protected static string GetBuiltinType (string t) { switch (t) { case "System.Byte": return "unsigned int8"; case "System.SByte": return "int8"; case "System.Int16": return "int16"; case "System.Int32": return "int32"; case "System.Int64": return "int64"; case "System.IntPtr": return "native int"; case "System.UInt16": return "unsigned int16"; case "System.UInt32": return "unsigned int32"; case "System.UInt64": return "unsigned int64"; case "System.UIntPtr": return "native unsigned int"; case "System.Single": return "float32"; case "System.Double": return "float64"; case "System.Boolean": return "bool"; case "System.Char": return "char"; case "System.Void": return "void"; case "System.String": return "string"; case "System.Object": return "object"; } return null; } protected override StringBuilder AppendTypeName (StringBuilder buf, string typename) { return buf.Append (typename); } protected override StringBuilder AppendTypeName (StringBuilder buf, TypeReference type, DynamicParserContext context) { if (type is GenericParameter) { AppendGenericParameterConstraints (buf, (GenericParameter) type).Append (type.Name); return buf; } string s = GetBuiltinType (type.FullName); if (s != null) { return buf.Append (s); } return base.AppendTypeName (buf, type, context); } private StringBuilder AppendGenericParameterConstraints (StringBuilder buf, GenericParameter type) { if (MemberFormatterState != MemberFormatterState.WithinGenericTypeParameters) { return buf.Append (type.Owner is TypeReference ? "!" : "!!"); } GenericParameterAttributes attrs = type.Attributes; if ((attrs & GenericParameterAttributes.ReferenceTypeConstraint) != 0) buf.Append ("class "); if ((attrs & GenericParameterAttributes.NotNullableValueTypeConstraint) != 0) buf.Append ("struct "); if ((attrs & GenericParameterAttributes.DefaultConstructorConstraint) != 0) buf.Append (".ctor "); IList constraints = type.Constraints; MemberFormatterState = 0; if (constraints.Count > 0) { var full = new ILFullMemberFormatter (); buf.Append ("(").Append (full.GetName (constraints [0])); for (int i = 1; i < constraints.Count; ++i) { buf.Append (", ").Append (full.GetName (constraints [i])); } buf.Append (") "); } MemberFormatterState = MemberFormatterState.WithinGenericTypeParameters; if ((attrs & GenericParameterAttributes.Covariant) != 0) buf.Append ("+ "); if ((attrs & GenericParameterAttributes.Contravariant) != 0) buf.Append ("- "); return buf; } protected override string GetTypeDeclaration (TypeDefinition type) { string visibility = GetTypeVisibility (type.Attributes); if (visibility == null) return null; StringBuilder buf = new StringBuilder (); buf.Append (".class "); if (type.IsNested) buf.Append ("nested "); buf.Append (visibility).Append (" "); if (type.IsInterface) buf.Append ("interface "); if (type.IsSequentialLayout) buf.Append ("sequential "); if (type.IsAutoLayout) buf.Append ("auto "); if (type.IsAnsiClass) buf.Append ("ansi "); if (type.IsAbstract) buf.Append ("abstract "); if (type.IsSerializable) buf.Append ("serializable "); if (type.IsSealed) buf.Append ("sealed "); if (type.IsBeforeFieldInit) buf.Append ("beforefieldinit "); var state = MemberFormatterState; MemberFormatterState = MemberFormatterState.WithinGenericTypeParameters; buf.Append (GetName (type)); MemberFormatterState = state; var full = new ILFullMemberFormatter (); if (type.BaseType != null) { buf.Append (" extends "); if (type.BaseType.FullName == "System.Object") buf.Append ("System.Object"); else buf.Append (full.GetName (type.BaseType).Substring ("class ".Length)); } bool first = true; foreach (var name in type.Interfaces.Where (i => MDocUpdater.IsPublic (i.Resolve ())) .Select (i => full.GetName (i)) .OrderBy (n => n)) { if (first) { buf.Append (" implements "); first = false; } else { buf.Append (", "); } buf.Append (name); } return buf.ToString (); } protected override StringBuilder AppendGenericType (StringBuilder buf, TypeReference type, DynamicParserContext context) { List decls = DocUtils.GetDeclaringTypes ( type is GenericInstanceType ? type.GetElementType () : type); bool first = true; foreach (var decl in decls) { TypeReference declDef = decl.Resolve () ?? decl; if (!first) { buf.Append (NestedTypeSeparator); } first = false; AppendTypeName (buf, declDef, context); } buf.Append ('<'); first = true; foreach (TypeReference arg in GetGenericArguments (type)) { if (!first) buf.Append (", "); first = false; _AppendTypeName (buf, arg, context); } buf.Append ('>'); return buf; } static string GetTypeVisibility (TypeAttributes ta) { switch (ta & TypeAttributes.VisibilityMask) { case TypeAttributes.Public: case TypeAttributes.NestedPublic: return "public"; case TypeAttributes.NestedFamily: case TypeAttributes.NestedFamORAssem: return "protected"; default: return null; } } protected override string GetConstructorDeclaration (MethodDefinition constructor) { return GetMethodDeclaration (constructor); } protected override string GetMethodDeclaration (MethodDefinition method) { if (method.IsPrivate && !DocUtils.IsExplicitlyImplemented (method)) return null; var buf = new StringBuilder (); buf.Append (".method "); AppendVisibility (buf, method); if (method.IsStatic) buf.Append ("static "); if (method.IsHideBySig) buf.Append ("hidebysig "); if (method.IsPInvokeImpl) { var info = method.PInvokeInfo; buf.Append ("pinvokeimpl (\"") .Append (info.Module.Name) .Append ("\" as \"") .Append (info.EntryPoint) .Append ("\""); if (info.IsCharSetAuto) buf.Append (" auto"); if (info.IsCharSetUnicode) buf.Append (" unicode"); if (info.IsCharSetAnsi) buf.Append (" ansi"); if (info.IsCallConvCdecl) buf.Append (" cdecl"); if (info.IsCallConvStdCall) buf.Append (" stdcall"); if (info.IsCallConvWinapi) buf.Append (" winapi"); if (info.IsCallConvThiscall) buf.Append (" thiscall"); if (info.SupportsLastError) buf.Append (" lasterr"); buf.Append (")"); } if (method.IsSpecialName) buf.Append ("specialname "); if (method.IsRuntimeSpecialName) buf.Append ("rtspecialname "); if (method.IsNewSlot) buf.Append ("newslot "); if (method.IsVirtual) buf.Append ("virtual "); if (!method.IsStatic) buf.Append ("instance "); _AppendTypeName (buf, method.ReturnType, new DynamicParserContext (method.MethodReturnType)); buf.Append (' ') .Append (method.Name); if (method.IsGenericMethod ()) { var state = MemberFormatterState; MemberFormatterState = MemberFormatterState.WithinGenericTypeParameters; IList args = method.GenericParameters; if (args.Count > 0) { buf.Append ("<"); _AppendTypeName (buf, args [0], null); for (int i = 1; i < args.Count; ++i) _AppendTypeName (buf.Append (", "), args [i], null); buf.Append (">"); } MemberFormatterState = state; } buf.Append ('('); bool first = true; for (int i = 0; i < method.Parameters.Count; ++i) { if (!first) buf.Append (", "); first = false; _AppendTypeName (buf, method.Parameters [i].ParameterType, new DynamicParserContext (method.Parameters [i])); buf.Append (' '); buf.Append (method.Parameters [i].Name); } buf.Append (')'); if (method.IsIL) buf.Append (" cil"); if (method.IsRuntime) buf.Append (" runtime"); if (method.IsManaged) buf.Append (" managed"); return buf.ToString (); } protected override StringBuilder AppendMethodName (StringBuilder buf, MethodDefinition method) { if (DocUtils.IsExplicitlyImplemented (method)) { TypeReference iface; MethodReference ifaceMethod; DocUtils.GetInfoForExplicitlyImplementedMethod (method, out iface, out ifaceMethod); return buf.Append (new CSharpMemberFormatter ().GetName (iface)) .Append ('.') .Append (ifaceMethod.Name); } return base.AppendMethodName (buf, method); } protected override string RefTypeModifier { get {return "";} } protected override StringBuilder AppendVisibility (StringBuilder buf, MethodDefinition method) { if (method.IsPublic) return buf.Append ("public "); if (method.IsFamilyAndAssembly) return buf.Append ("familyandassembly"); if (method.IsFamilyOrAssembly) return buf.Append ("familyorassembly"); if (method.IsFamily) return buf.Append ("family"); return buf; } protected override StringBuilder AppendModifiers (StringBuilder buf, MethodDefinition method) { string modifiers = String.Empty; if (method.IsStatic) modifiers += " static"; if (method.IsVirtual && !method.IsAbstract) { if ((method.Attributes & MethodAttributes.NewSlot) != 0) modifiers += " virtual"; else modifiers += " override"; } TypeDefinition declType = (TypeDefinition) method.DeclaringType; if (method.IsAbstract && !declType.IsInterface) modifiers += " abstract"; if (method.IsFinal) modifiers += " sealed"; if (modifiers == " virtual sealed") modifiers = ""; return buf.Append (modifiers); } protected override StringBuilder AppendGenericMethod (StringBuilder buf, MethodDefinition method) { if (method.IsGenericMethod ()) { IList args = method.GenericParameters; if (args.Count > 0) { buf.Append ("<"); buf.Append (args [0].Name); for (int i = 1; i < args.Count; ++i) buf.Append (",").Append (args [i].Name); buf.Append (">"); } } return buf; } protected override StringBuilder AppendParameters (StringBuilder buf, MethodDefinition method, IList parameters) { return AppendParameters (buf, method, parameters, '(', ')'); } private StringBuilder AppendParameters (StringBuilder buf, MethodDefinition method, IList parameters, char begin, char end) { buf.Append (begin); if (parameters.Count > 0) { if (DocUtils.IsExtensionMethod (method)) buf.Append ("this "); AppendParameter (buf, parameters [0]); for (int i = 1; i < parameters.Count; ++i) { buf.Append (", "); AppendParameter (buf, parameters [i]); } } return buf.Append (end); } private StringBuilder AppendParameter (StringBuilder buf, ParameterDefinition parameter) { if (parameter.ParameterType is ByReferenceType) { if (parameter.IsOut) buf.Append ("out "); else buf.Append ("ref "); } buf.Append (GetName (parameter.ParameterType)).Append (" "); return buf.Append (parameter.Name); } protected override string GetPropertyDeclaration (PropertyDefinition property) { MethodDefinition gm = null, sm = null; string get_visible = null; if ((gm = property.GetMethod) != null && (DocUtils.IsExplicitlyImplemented (gm) || (!gm.IsPrivate && !gm.IsAssembly && !gm.IsFamilyAndAssembly))) get_visible = AppendVisibility (new StringBuilder (), gm).ToString (); string set_visible = null; if ((sm = property.SetMethod) != null && (DocUtils.IsExplicitlyImplemented (sm) || (!sm.IsPrivate && !sm.IsAssembly && !sm.IsFamilyAndAssembly))) set_visible = AppendVisibility (new StringBuilder (), sm).ToString (); if ((set_visible == null) && (get_visible == null)) return null; StringBuilder buf = new StringBuilder () .Append (".property "); if (!(gm ?? sm).IsStatic) buf.Append ("instance "); _AppendTypeName (buf, property.PropertyType, new DynamicParserContext (property)); buf.Append (' ').Append (property.Name); if (!property.HasParameters || property.Parameters.Count == 0) return buf.ToString (); buf.Append ('('); bool first = true; foreach (ParameterDefinition p in property.Parameters) { if (!first) buf.Append (", "); first = false; _AppendTypeName (buf, p.ParameterType, new DynamicParserContext (p)); } buf.Append (')'); return buf.ToString (); } protected override string GetFieldDeclaration (FieldDefinition field) { TypeDefinition declType = (TypeDefinition) field.DeclaringType; if (declType.IsEnum && field.Name == "value__") return null; // This member of enums aren't documented. StringBuilder buf = new StringBuilder (); AppendFieldVisibility (buf, field); if (buf.Length == 0) return null; buf.Insert (0, ".field "); if (field.IsStatic) buf.Append ("static "); if (field.IsInitOnly) buf.Append ("initonly "); if (field.IsLiteral) buf.Append ("literal "); _AppendTypeName (buf, field.FieldType, new DynamicParserContext (field)); buf.Append (' ').Append (field.Name); AppendFieldValue (buf, field); return buf.ToString (); } static StringBuilder AppendFieldVisibility (StringBuilder buf, FieldDefinition field) { if (field.IsPublic) return buf.Append ("public "); if (field.IsFamilyAndAssembly) return buf.Append ("familyandassembly "); if (field.IsFamilyOrAssembly) return buf.Append ("familyorassembly "); if (field.IsFamily) return buf.Append ("family "); return buf; } static StringBuilder AppendFieldValue (StringBuilder buf, FieldDefinition field) { // enums have a value__ field, which we ignore if (field.DeclaringType.IsGenericType ()) return buf; if (field.HasConstant && field.IsLiteral) { object val = null; try { val = field.Constant; } catch { return buf; } if (val == null) buf.Append (" = ").Append ("null"); else if (val is Enum) buf.Append (" = ") .Append (GetBuiltinType (field.DeclaringType.GetUnderlyingType ().FullName)) .Append ('(') .Append (val.ToString ()) .Append (')'); else if (val is IFormattable) { string value = ((IFormattable)val).ToString(); buf.Append (" = "); if (val is string) buf.Append ("\"" + value + "\""); else buf.Append (GetBuiltinType (field.DeclaringType.GetUnderlyingType ().FullName)) .Append ('(') .Append (value) .Append (')'); } } return buf; } protected override string GetEventDeclaration (EventDefinition e) { StringBuilder buf = new StringBuilder (); if (AppendVisibility (buf, e.AddMethod).Length == 0) { return null; } buf.Length = 0; buf.Append (".event ") .Append (GetName (e.EventType)) .Append (' ') .Append (e.Name); return buf.ToString (); } } class ILMemberFormatter : ILFullMemberFormatter { protected override StringBuilder AppendNamespace (StringBuilder buf, TypeReference type) { return buf; } } class ILNativeTypeMemberFormatter : ILFullMemberFormatter { protected static string _GetBuiltinType (string t) { //string moddedType = base.GetBuiltinType (t); return null; //return moddedType; } } class CSharpNativeTypeMemberFormatter : CSharpFullMemberFormatter { protected override string GetCSharpType (string t) { string moddedType = base.GetCSharpType (t); switch (moddedType) { case "int": return "nint"; case "uint": return "nuint"; case "float": return "nfloat"; case "System.Drawing.SizeF": return "CoreGraphics.CGSize"; case "System.Drawing.PointF": return "CoreGraphics.CGPoint"; case "System.Drawing.RectangleF": return "CoreGraphics.CGPoint"; } return null; } } class CSharpFullMemberFormatter : MemberFormatter { public override string Language { get {return "C#";} } protected override StringBuilder AppendNamespace (StringBuilder buf, TypeReference type) { string ns = DocUtils.GetNamespace (type); if (GetCSharpType (type.FullName) == null && ns != null && ns.Length > 0 && ns != "System") buf.Append (ns).Append ('.'); return buf; } protected virtual string GetCSharpType (string t) { switch (t) { case "System.Byte": return "byte"; case "System.SByte": return "sbyte"; case "System.Int16": return "short"; case "System.Int32": return "int"; case "System.Int64": return "long"; case "System.UInt16": return "ushort"; case "System.UInt32": return "uint"; case "System.UInt64": return "ulong"; case "System.Single": return "float"; case "System.Double": return "double"; case "System.Decimal": return "decimal"; case "System.Boolean": return "bool"; case "System.Char": return "char"; case "System.Void": return "void"; case "System.String": return "string"; case "System.Object": return "object"; } return null; } protected override StringBuilder AppendTypeName (StringBuilder buf, TypeReference type, DynamicParserContext context) { if (context != null && context.TransformFlags != null && (context.TransformFlags.Count == 0 || context.TransformFlags [context.TransformIndex])) { context.TransformIndex++; return buf.Append ("dynamic"); } if (type is GenericParameter) return AppendGenericParameterConstraints (buf, (GenericParameter) type, context).Append (type.Name); string t = type.FullName; if (!t.StartsWith ("System.")) { return base.AppendTypeName (buf, type, context); } string s = GetCSharpType (t); if (s != null) { if (context != null) context.TransformIndex++; return buf.Append (s); } return base.AppendTypeName (buf, type, context); } private StringBuilder AppendGenericParameterConstraints (StringBuilder buf, GenericParameter type, DynamicParserContext context) { if (MemberFormatterState != MemberFormatterState.WithinGenericTypeParameters) return buf; GenericParameterAttributes attrs = type.Attributes; bool isout = (attrs & GenericParameterAttributes.Covariant) != 0; bool isin = (attrs & GenericParameterAttributes.Contravariant) != 0; if (isin) buf.Append ("in "); else if (isout) buf.Append ("out "); return buf; } protected override string GetTypeDeclaration (TypeDefinition type) { string visibility = GetTypeVisibility (type.Attributes); if (visibility == null) return null; StringBuilder buf = new StringBuilder (); buf.Append (visibility); buf.Append (" "); MemberFormatter full = new CSharpFullMemberFormatter (); if (DocUtils.IsDelegate (type)) { buf.Append("delegate "); MethodDefinition invoke = type.GetMethod ("Invoke"); buf.Append (full.GetName (invoke.ReturnType, new DynamicParserContext (invoke.MethodReturnType))).Append (" "); buf.Append (GetName (type)); AppendParameters (buf, invoke, invoke.Parameters); AppendGenericTypeConstraints (buf, type); buf.Append (";"); return buf.ToString(); } if (type.IsAbstract && !type.IsInterface) buf.Append("abstract "); if (type.IsSealed && !DocUtils.IsDelegate (type) && !type.IsValueType) buf.Append("sealed "); buf.Replace ("abstract sealed", "static"); buf.Append (GetTypeKind (type)); buf.Append (" "); buf.Append (GetCSharpType (type.FullName) == null ? GetName (type) : type.Name); if (!type.IsEnum) { TypeReference basetype = type.BaseType; if (basetype != null && basetype.FullName == "System.Object" || type.IsValueType) // FIXME basetype = null; List interface_names = DocUtils.GetUserImplementedInterfaces (type) .Select (iface => full.GetName (iface)) .OrderBy (s => s) .ToList (); if (basetype != null || interface_names.Count > 0) buf.Append (" : "); if (basetype != null) { buf.Append (full.GetName (basetype)); if (interface_names.Count > 0) buf.Append (", "); } for (int i = 0; i < interface_names.Count; i++){ if (i != 0) buf.Append (", "); buf.Append (interface_names [i]); } AppendGenericTypeConstraints (buf, type); } return buf.ToString (); } static string GetTypeKind (TypeDefinition t) { if (t.IsEnum) return "enum"; if (t.IsValueType) return "struct"; if (t.IsClass || t.FullName == "System.Enum") return "class"; if (t.IsInterface) return "interface"; throw new ArgumentException(t.FullName); } static string GetTypeVisibility (TypeAttributes ta) { switch (ta & TypeAttributes.VisibilityMask) { case TypeAttributes.Public: case TypeAttributes.NestedPublic: return "public"; case TypeAttributes.NestedFamily: case TypeAttributes.NestedFamORAssem: return "protected"; default: return null; } } protected override StringBuilder AppendGenericTypeConstraints (StringBuilder buf, TypeReference type) { if (type.GenericParameters.Count == 0) return buf; return AppendConstraints (buf, type.GenericParameters); } private StringBuilder AppendConstraints (StringBuilder buf, IList genArgs) { foreach (GenericParameter genArg in genArgs) { GenericParameterAttributes attrs = genArg.Attributes; IList constraints = genArg.Constraints; if (attrs == GenericParameterAttributes.NonVariant && constraints.Count == 0) continue; bool isref = (attrs & GenericParameterAttributes.ReferenceTypeConstraint) != 0; bool isvt = (attrs & GenericParameterAttributes.NotNullableValueTypeConstraint) != 0; bool isnew = (attrs & GenericParameterAttributes.DefaultConstructorConstraint) != 0; bool comma = false; if (!isref && !isvt && !isnew && constraints.Count == 0) continue; buf.Append (" where ").Append (genArg.Name).Append (" : "); if (isref) { buf.Append ("class"); comma = true; } else if (isvt) { buf.Append ("struct"); comma = true; } if (constraints.Count > 0 && !isvt) { if (comma) buf.Append (", "); buf.Append (GetTypeName (constraints [0])); for (int i = 1; i < constraints.Count; ++i) buf.Append (", ").Append (GetTypeName (constraints [i])); } if (isnew && !isvt) { if (comma) buf.Append (", "); buf.Append ("new()"); } } return buf; } protected override string GetConstructorDeclaration (MethodDefinition constructor) { StringBuilder buf = new StringBuilder (); AppendVisibility (buf, constructor); if (buf.Length == 0) return null; buf.Append (' '); base.AppendTypeName (buf, constructor.DeclaringType.Name).Append (' '); AppendParameters (buf, constructor, constructor.Parameters); buf.Append (';'); return buf.ToString (); } protected override string GetMethodDeclaration (MethodDefinition method) { string decl = base.GetMethodDeclaration (method); if (decl != null) return decl + ";"; return null; } protected override StringBuilder AppendMethodName (StringBuilder buf, MethodDefinition method) { if (DocUtils.IsExplicitlyImplemented (method)) { TypeReference iface; MethodReference ifaceMethod; DocUtils.GetInfoForExplicitlyImplementedMethod (method, out iface, out ifaceMethod); return buf.Append (new CSharpMemberFormatter ().GetName (iface)) .Append ('.') .Append (ifaceMethod.Name); } return base.AppendMethodName (buf, method); } protected override StringBuilder AppendGenericMethodConstraints (StringBuilder buf, MethodDefinition method) { if (method.GenericParameters.Count == 0) return buf; return AppendConstraints (buf, method.GenericParameters); } protected override string RefTypeModifier { get {return "";} } protected override string GetFinalizerName (MethodDefinition method) { return "~" + method.DeclaringType.Name + " ()"; } protected override StringBuilder AppendVisibility (StringBuilder buf, MethodDefinition method) { if (method == null) return buf; if (method.IsPublic) return buf.Append ("public"); if (method.IsFamily || method.IsFamilyOrAssembly) return buf.Append ("protected"); return buf; } protected override StringBuilder AppendModifiers (StringBuilder buf, MethodDefinition method) { string modifiers = String.Empty; if (method.IsStatic) modifiers += " static"; if (method.IsVirtual && !method.IsAbstract) { if ((method.Attributes & MethodAttributes.NewSlot) != 0) modifiers += " virtual"; else modifiers += " override"; } TypeDefinition declType = (TypeDefinition) method.DeclaringType; if (method.IsAbstract && !declType.IsInterface) modifiers += " abstract"; if (method.IsFinal) modifiers += " sealed"; if (modifiers == " virtual sealed") modifiers = ""; return buf.Append (modifiers); } protected override StringBuilder AppendGenericMethod (StringBuilder buf, MethodDefinition method) { if (method.IsGenericMethod ()) { IList args = method.GenericParameters; if (args.Count > 0) { buf.Append ("<"); buf.Append (args [0].Name); for (int i = 1; i < args.Count; ++i) buf.Append (",").Append (args [i].Name); buf.Append (">"); } } return buf; } protected override StringBuilder AppendParameters (StringBuilder buf, MethodDefinition method, IList parameters) { return AppendParameters (buf, method, parameters, '(', ')'); } private StringBuilder AppendParameters (StringBuilder buf, MethodDefinition method, IList parameters, char begin, char end) { buf.Append (begin); if (parameters.Count > 0) { if (DocUtils.IsExtensionMethod (method)) buf.Append ("this "); AppendParameter (buf, parameters [0]); for (int i = 1; i < parameters.Count; ++i) { buf.Append (", "); AppendParameter (buf, parameters [i]); } } return buf.Append (end); } private StringBuilder AppendParameter (StringBuilder buf, ParameterDefinition parameter) { if (parameter.ParameterType is ByReferenceType) { if (parameter.IsOut) buf.Append ("out "); else buf.Append ("ref "); } buf.Append (GetTypeName (parameter.ParameterType, new DynamicParserContext (parameter))).Append (" "); buf.Append (parameter.Name); if (parameter.HasDefault && parameter.IsOptional && parameter.HasConstant) { buf.AppendFormat (" = {0}", MDocUpdater.MakeAttributesValueString (parameter.Constant, parameter.ParameterType)); } return buf; } protected override string GetPropertyDeclaration (PropertyDefinition property) { MethodDefinition method; string get_visible = null; if ((method = property.GetMethod) != null && (DocUtils.IsExplicitlyImplemented (method) || (!method.IsPrivate && !method.IsAssembly && !method.IsFamilyAndAssembly))) get_visible = AppendVisibility (new StringBuilder (), method).ToString (); string set_visible = null; if ((method = property.SetMethod) != null && (DocUtils.IsExplicitlyImplemented (method) || (!method.IsPrivate && !method.IsAssembly && !method.IsFamilyAndAssembly))) set_visible = AppendVisibility (new StringBuilder (), method).ToString (); if ((set_visible == null) && (get_visible == null)) return null; string visibility; StringBuilder buf = new StringBuilder (); if (get_visible != null && (set_visible == null || (set_visible != null && get_visible == set_visible))) buf.Append (visibility = get_visible); else if (set_visible != null && get_visible == null) buf.Append (visibility = set_visible); else buf.Append (visibility = "public"); // Pick an accessor to use for static/virtual/override/etc. checks. method = property.SetMethod; if (method == null) method = property.GetMethod; string modifiers = String.Empty; if (method.IsStatic) modifiers += " static"; if (method.IsVirtual && !method.IsAbstract) { if ((method.Attributes & MethodAttributes.NewSlot) != 0) modifiers += " virtual"; else modifiers += " override"; } TypeDefinition declDef = (TypeDefinition) method.DeclaringType; if (method.IsAbstract && !declDef.IsInterface) modifiers += " abstract"; if (method.IsFinal) modifiers += " sealed"; if (modifiers == " virtual sealed") modifiers = ""; buf.Append (modifiers).Append (' '); buf.Append (GetTypeName (property.PropertyType, new DynamicParserContext (property))).Append (' '); IEnumerable defs = property.DeclaringType.GetDefaultMembers (); string name = property.Name; foreach (MemberReference mi in defs) { if (mi == property) { name = "this"; break; } } buf.Append (name == "this" ? name : DocUtils.GetPropertyName (property)); if (property.Parameters.Count != 0) { AppendParameters (buf, method, property.Parameters, '[', ']'); } buf.Append (" {"); if (get_visible != null) { if (get_visible != visibility) buf.Append (' ').Append (get_visible); buf.Append (" get;"); } if (set_visible != null) { if (set_visible != visibility) buf.Append (' ').Append (set_visible); buf.Append (" set;"); } buf.Append (" }"); return buf [0] != ' ' ? buf.ToString () : buf.ToString (1, buf.Length-1); } protected override string GetFieldDeclaration (FieldDefinition field) { TypeDefinition declType = (TypeDefinition) field.DeclaringType; if (declType.IsEnum && field.Name == "value__") return null; // This member of enums aren't documented. StringBuilder buf = new StringBuilder (); AppendFieldVisibility (buf, field); if (buf.Length == 0) return null; if (declType.IsEnum) return field.Name; if (field.IsStatic && !field.IsLiteral) buf.Append (" static"); if (field.IsInitOnly) buf.Append (" readonly"); if (field.IsLiteral) buf.Append (" const"); buf.Append (' ').Append (GetTypeName (field.FieldType, new DynamicParserContext (field))).Append (' '); buf.Append (field.Name); AppendFieldValue (buf, field); buf.Append (';'); return buf.ToString (); } static StringBuilder AppendFieldVisibility (StringBuilder buf, FieldDefinition field) { if (field.IsPublic) return buf.Append ("public"); if (field.IsFamily || field.IsFamilyOrAssembly) return buf.Append ("protected"); return buf; } static StringBuilder AppendFieldValue (StringBuilder buf, FieldDefinition field) { // enums have a value__ field, which we ignore if (((TypeDefinition ) field.DeclaringType).IsEnum || field.DeclaringType.IsGenericType ()) return buf; if (field.HasConstant && field.IsLiteral) { object val = null; try { val = field.Constant; } catch { return buf; } if (val == null) buf.Append (" = ").Append ("null"); else if (val is Enum) buf.Append (" = ").Append (val.ToString ()); else if (val is IFormattable) { string value = ((IFormattable)val).ToString(); if (val is string) value = "\"" + value + "\""; buf.Append (" = ").Append (value); } } return buf; } protected override string GetEventDeclaration (EventDefinition e) { StringBuilder buf = new StringBuilder (); if (AppendVisibility (buf, e.AddMethod).Length == 0) { return null; } AppendModifiers (buf, e.AddMethod); buf.Append (" event "); buf.Append (GetTypeName (e.EventType, new DynamicParserContext (e.AddMethod.Parameters [0]))).Append (' '); buf.Append (e.Name).Append (';'); return buf.ToString (); } } class CSharpMemberFormatter : CSharpFullMemberFormatter { protected override StringBuilder AppendNamespace (StringBuilder buf, TypeReference type) { return buf; } } class DocTypeFullMemberFormatter : MemberFormatter { public static readonly MemberFormatter Default = new DocTypeFullMemberFormatter (); protected override char NestedTypeSeparator { get {return '+';} } } class DocTypeMemberFormatter : DocTypeFullMemberFormatter { protected override StringBuilder AppendNamespace (StringBuilder buf, TypeReference type) { return buf; } } class SlashDocMemberFormatter : MemberFormatter { protected override char[] GenericTypeContainer { get {return new char[]{'{', '}'};} } private bool AddTypeCount = true; private TypeReference genDeclType; private MethodReference genDeclMethod; protected override StringBuilder AppendTypeName (StringBuilder buf, TypeReference type, DynamicParserContext context) { if (type is GenericParameter) { int l = buf.Length; if (genDeclType != null) { IList genArgs = genDeclType.GenericParameters; for (int i = 0; i < genArgs.Count; ++i) { if (genArgs [i].Name == type.Name) { buf.Append ('`').Append (i); break; } } } if (genDeclMethod != null) { IList genArgs = null; if (genDeclMethod.IsGenericMethod ()) { genArgs = genDeclMethod.GenericParameters; for (int i = 0; i < genArgs.Count; ++i) { if (genArgs [i].Name == type.Name) { buf.Append ("``").Append (i); break; } } } } if (genDeclType == null && genDeclMethod == null) { // Probably from within an explicitly implemented interface member, // where CSC uses parameter names instead of indices (why?), e.g. // MyList`2.Mono#DocTest#Generic#IFoo{A}#Method``1(`0,``0) instead of // MyList`2.Mono#DocTest#Generic#IFoo{`0}#Method``1(`0,``0). buf.Append (type.Name); } if (buf.Length == l) { throw new Exception (string.Format ( "Unable to translate generic parameter {0}; genDeclType={1}, genDeclMethod={2}", type.Name, genDeclType, genDeclMethod)); } } else { base.AppendTypeName (buf, type, context); if (AddTypeCount) { int numArgs = type.GenericParameters.Count; if (type.DeclaringType != null) numArgs -= type.GenericParameters.Count; if (numArgs > 0) { buf.Append ('`').Append (numArgs); } } } return buf; } protected override StringBuilder AppendArrayModifiers (StringBuilder buf, ArrayType array) { buf.Append (ArrayDelimeters [0]); int rank = array.Rank; if (rank > 1) { buf.Append ("0:"); for (int i = 1; i < rank; ++i) { buf.Append (",0:"); } } return buf.Append (ArrayDelimeters [1]); } protected override StringBuilder AppendGenericType (StringBuilder buf, TypeReference type, DynamicParserContext context) { if (!AddTypeCount) base.AppendGenericType (buf, type, context); else AppendType (buf, type, context); return buf; } private StringBuilder AppendType (StringBuilder buf, TypeReference type, DynamicParserContext context) { List decls = DocUtils.GetDeclaringTypes (type); bool insertNested = false; int prevParamCount = 0; foreach (var decl in decls) { if (insertNested) buf.Append (NestedTypeSeparator); insertNested = true; base.AppendTypeName (buf, decl, context); int argCount = DocUtils.GetGenericArgumentCount (decl); int numArgs = argCount - prevParamCount; prevParamCount = argCount; if (numArgs > 0) buf.Append ('`').Append (numArgs); } return buf; } public override string GetDeclaration (MemberReference member) { TypeReference r = member as TypeReference; if (r != null) { return "T:" + GetTypeName (r); } return base.GetDeclaration (member); } protected override string GetConstructorName (MethodReference constructor) { return GetMethodDefinitionName (constructor, "#ctor"); } protected override string GetMethodName (MethodReference method) { string name = null; MethodDefinition methodDef = method as MethodDefinition; if (methodDef == null || !DocUtils.IsExplicitlyImplemented (methodDef)) name = method.Name; else { TypeReference iface; MethodReference ifaceMethod; DocUtils.GetInfoForExplicitlyImplementedMethod (methodDef, out iface, out ifaceMethod); AddTypeCount = false; name = GetTypeName (iface) + "." + ifaceMethod.Name; AddTypeCount = true; } return GetMethodDefinitionName (method, name); } private string GetMethodDefinitionName (MethodReference method, string name) { StringBuilder buf = new StringBuilder (); buf.Append (GetTypeName (method.DeclaringType)); buf.Append ('.'); buf.Append (name.Replace (".", "#")); if (method.IsGenericMethod ()) { IList genArgs = method.GenericParameters; if (genArgs.Count > 0) buf.Append ("``").Append (genArgs.Count); } IList parameters = method.Parameters; try { genDeclType = method.DeclaringType; genDeclMethod = method; AppendParameters (buf, method.DeclaringType.GenericParameters, parameters); } finally { genDeclType = null; genDeclMethod = null; } return buf.ToString (); } private StringBuilder AppendParameters (StringBuilder buf, IList genArgs, IList parameters) { if (parameters.Count == 0) return buf; buf.Append ('('); AppendParameter (buf, genArgs, parameters [0]); for (int i = 1; i < parameters.Count; ++i) { buf.Append (','); AppendParameter (buf, genArgs, parameters [i]); } return buf.Append (')'); } private StringBuilder AppendParameter (StringBuilder buf, IList genArgs, ParameterDefinition parameter) { AddTypeCount = false; buf.Append (GetTypeName (parameter.ParameterType)); AddTypeCount = true; return buf; } protected override string GetPropertyName (PropertyReference property) { string name = null; PropertyDefinition propertyDef = property as PropertyDefinition; MethodDefinition method = null; if (propertyDef != null) method = propertyDef.GetMethod ?? propertyDef.SetMethod; if (method != null && !DocUtils.IsExplicitlyImplemented (method)) name = property.Name; else { TypeReference iface; MethodReference ifaceMethod; DocUtils.GetInfoForExplicitlyImplementedMethod (method, out iface, out ifaceMethod); AddTypeCount = false; name = string.Join ("#", new string[]{ GetTypeName (iface).Replace (".", "#"), DocUtils.GetMember (property.Name) }); AddTypeCount = true; } StringBuilder buf = new StringBuilder (); buf.Append (GetName (property.DeclaringType)); buf.Append ('.'); buf.Append (name); IList parameters = property.Parameters; if (parameters.Count > 0) { genDeclType = property.DeclaringType; buf.Append ('('); IList genArgs = property.DeclaringType.GenericParameters; AppendParameter (buf, genArgs, parameters [0]); for (int i = 1; i < parameters.Count; ++i) { buf.Append (','); AppendParameter (buf, genArgs, parameters [i]); } buf.Append (')'); genDeclType = null; } return buf.ToString (); } protected override string GetFieldName (FieldReference field) { return string.Format ("{0}.{1}", GetName (field.DeclaringType), field.Name); } protected override string GetEventName (EventReference e) { return string.Format ("{0}.{1}", GetName (e.DeclaringType), e.Name); } protected override string GetTypeDeclaration (TypeDefinition type) { string name = GetName (type); if (type == null) return null; return "T:" + name; } protected override string GetConstructorDeclaration (MethodDefinition constructor) { string name = GetName (constructor); if (name == null) return null; return "M:" + name; } protected override string GetMethodDeclaration (MethodDefinition method) { string name = GetName (method); if (name == null) return null; if (method.Name == "op_Implicit" || method.Name == "op_Explicit") { genDeclType = method.DeclaringType; genDeclMethod = method; name += "~" + GetName (method.ReturnType); genDeclType = null; genDeclMethod = null; } return "M:" + name; } protected override string GetPropertyDeclaration (PropertyDefinition property) { string name = GetName (property); if (name == null) return null; return "P:" + name; } protected override string GetFieldDeclaration (FieldDefinition field) { string name = GetName (field); if (name == null) return null; return "F:" + name; } protected override string GetEventDeclaration (EventDefinition e) { string name = GetName (e); if (name == null) return null; return "E:" + name; } } class FileNameMemberFormatter : SlashDocMemberFormatter { protected override StringBuilder AppendNamespace (StringBuilder buf, TypeReference type) { return buf; } protected override char NestedTypeSeparator { get {return '+';} } } class ResolvedTypeInfo { TypeDefinition typeDef; public ResolvedTypeInfo (TypeReference value) { Reference = value; } public TypeReference Reference { get; private set; } public TypeDefinition Definition { get { if (typeDef == null) { typeDef = Reference.Resolve (); } return typeDef; } } } /// Formats attribute values. Should return true if it is able to format the value. class AttributeValueFormatter { public virtual bool TryFormatValue (object v, ResolvedTypeInfo type, out string returnvalue) { TypeReference valueType = type.Reference; if (v == null) { returnvalue = "null"; return true; } if (valueType.FullName == "System.Type") { var vTypeRef = v as TypeReference; if (vTypeRef != null) returnvalue = "typeof(" + NativeTypeManager.GetTranslatedName (vTypeRef) + ")"; // TODO: drop NS handling else returnvalue = "typeof(" + v.ToString () + ")"; return true; } if (valueType.FullName == "System.String") { returnvalue = "\"" + v.ToString () + "\""; return true; } if (valueType.FullName == "System.Char") { returnvalue = "'" + v.ToString () + "'"; return true; } if (v is Boolean) { returnvalue = (bool)v ? "true" : "false"; return true; } TypeDefinition valueDef = type.Definition; if (valueDef == null || !valueDef.IsEnum) { returnvalue = v.ToString (); return true; } string typename = MDocUpdater.GetDocTypeFullName (valueType); var values = MDocUpdater.GetEnumerationValues (valueDef); long c = MDocUpdater.ToInt64 (v); if (values.ContainsKey (c)) { returnvalue = typename + "." + values [c]; return true; } returnvalue = null; return false; } } /// The final value formatter in the pipeline ... if no other formatter formats the value, /// then this one will serve as the default implementation. class DefaultAttributeValueFormatter : AttributeValueFormatter { public override bool TryFormatValue (object v, ResolvedTypeInfo type, out string returnvalue) { returnvalue = "(" + MDocUpdater.GetDocTypeFullName (type.Reference) + ") " + v.ToString (); return true; } } /// Flags enum formatter that assumes powers of two values. /// As described here: https://msdn.microsoft.com/en-us/library/vstudio/ms229062(v=vs.100).aspx class StandardFlagsEnumFormatter : AttributeValueFormatter { public override bool TryFormatValue (object v, ResolvedTypeInfo type, out string returnvalue) { TypeReference valueType = type.Reference; TypeDefinition valueDef = type.Definition; if (valueDef.CustomAttributes.Any (ca => ca.AttributeType.FullName == "System.FlagsAttribute")) { string typename = MDocUpdater.GetDocTypeFullName (valueType); var values = MDocUpdater.GetEnumerationValues (valueDef); long c = MDocUpdater.ToInt64 (v); returnvalue = string.Join (" | ", (from i in values.Keys where (c & i) == i && i != 0 select typename + "." + values [i]) .DefaultIfEmpty (c.ToString ()).ToArray ()); return true; } returnvalue = null; return false; } } /// A custom formatter for the ObjCRuntime.Platform enumeration. class ApplePlatformEnumFormatter : AttributeValueFormatter { public override bool TryFormatValue (object v, ResolvedTypeInfo type, out string returnvalue) { TypeReference valueType = type.Reference; string typename = MDocUpdater.GetDocTypeFullName (valueType); TypeDefinition valueDef = type.Definition; if (typename.Contains ("ObjCRuntime.Platform") && valueDef.CustomAttributes.Any (ca => ca.AttributeType.FullName == "System.FlagsAttribute")) { var values = MDocUpdater.GetEnumerationValues (valueDef); long c = MDocUpdater.ToInt64 (v); returnvalue = Format (c, values, typename); return true; } returnvalue = null; return false; } string Format (long c, IDictionary values, string typename) { int iosarch, iosmajor, iosminor, iossubminor; int macarch, macmajor, macminor, macsubminor; GetEncodingiOS (c, out iosarch, out iosmajor, out iosminor, out iossubminor); GetEncodingMac ((ulong)c, out macarch, out macmajor, out macminor, out macsubminor); if (iosmajor == 0 & iosminor == 0 && iossubminor == 0) { return FormatValues ("Mac", macarch, macmajor, macminor, macsubminor); } if (macmajor == 0 & macminor == 0 && macsubminor == 0) { return FormatValues ("iOS", iosarch, iosmajor, iosminor, iossubminor); } return string.Format ("(Platform){0}", c); } string FormatValues (string plat, int arch, int major, int minor, int subminor) { string archstring = ""; switch (arch) { case 1: archstring = "32"; break; case 2: archstring = "64"; break; } return string.Format ("Platform.{4}_{0}_{1}{2} | Platform.{4}_Arch{3}", major, minor, subminor == 0 ? "" : "_" + subminor.ToString (), archstring, plat ); } void GetEncodingiOS (long entireLong, out int archindex, out int major, out int minor, out int subminor) { long lowerBits = entireLong & 0xffffffff; int lowerBitsAsInt = (int) lowerBits; GetEncoding (lowerBitsAsInt, out archindex, out major, out minor, out subminor); } void GetEncodingMac (ulong entireLong, out int archindex, out int major, out int minor, out int subminor) { ulong higherBits = entireLong & 0xffffffff00000000; int higherBitsAsInt = (int) ((higherBits) >> 32); GetEncoding (higherBitsAsInt, out archindex, out major, out minor, out subminor); } void GetEncoding (Int32 encodedBits, out int archindex, out int major, out int minor, out int subminor) { // format is AAJJNNSS archindex = (int)((encodedBits & 0xFF000000) >> 24); major = (int)((encodedBits & 0x00FF0000) >> 16); minor = (int)((encodedBits & 0x0000FF00) >> 8); subminor = (int)((encodedBits & 0x000000FF) >> 0); } } }