PageRenderTime 71ms CodeModel.GetById 29ms RepoModel.GetById 1ms app.codeStats 1ms

/mcs/tools/mdoc/Mono.Documentation/monodocer.cs

https://bitbucket.org/danipen/mono
C# | 5008 lines | 4465 code | 460 blank | 83 comment | 1000 complexity | 30cc9fee6844ac8e85177f7b58d515c2 MD5 | raw file
Possible License(s): Unlicense, Apache-2.0, LGPL-2.0, MPL-2.0-no-copyleft-exception, CC-BY-SA-3.0, GPL-2.0

Large files files are truncated, but you can click here to view the full file

  1. // Updater program for syncing Mono's ECMA-style documentation files
  2. // with an assembly.
  3. // By Joshua Tauberer <tauberer@for.net>
  4. using System;
  5. using System.Collections;
  6. using System.Collections.Generic;
  7. using System.Collections.ObjectModel;
  8. using System.Diagnostics;
  9. using System.Globalization;
  10. using System.IO;
  11. using System.Linq;
  12. using System.Text;
  13. using System.Xml;
  14. using System.Xml.XPath;
  15. using Mono.Cecil;
  16. using Mono.Options;
  17. using MyXmlNodeList = System.Collections.Generic.List<System.Xml.XmlNode>;
  18. using StringList = System.Collections.Generic.List<string>;
  19. using StringToStringMap = System.Collections.Generic.Dictionary<string, string>;
  20. using StringToXmlNodeMap = System.Collections.Generic.Dictionary<string, System.Xml.XmlNode>;
  21. namespace Mono.Documentation {
  22. class MDocUpdater : MDocCommand
  23. {
  24. string srcPath;
  25. List<AssemblyDefinition> assemblies;
  26. readonly DefaultAssemblyResolver assemblyResolver = new DefaultAssemblyResolver();
  27. bool delete;
  28. bool show_exceptions;
  29. bool no_assembly_versions, ignore_missing_types;
  30. ExceptionLocations? exceptions;
  31. internal int additions = 0, deletions = 0;
  32. List<DocumentationImporter> importers = new List<DocumentationImporter> ();
  33. DocumentationEnumerator docEnum;
  34. string since;
  35. static readonly MemberFormatter docTypeFormatter = new DocTypeMemberFormatter ();
  36. static readonly MemberFormatter filenameFormatter = new FileNameMemberFormatter ();
  37. static MemberFormatter[] typeFormatters = new MemberFormatter[]{
  38. new CSharpMemberFormatter (),
  39. new ILMemberFormatter (),
  40. };
  41. static MemberFormatter[] memberFormatters = new MemberFormatter[]{
  42. new CSharpFullMemberFormatter (),
  43. new ILFullMemberFormatter (),
  44. };
  45. internal static readonly MemberFormatter slashdocFormatter = new SlashDocMemberFormatter ();
  46. MyXmlNodeList extensionMethods = new MyXmlNodeList ();
  47. HashSet<string> forwardedTypes = new HashSet<string> ();
  48. public override void Run (IEnumerable<string> args)
  49. {
  50. show_exceptions = DebugOutput;
  51. var types = new List<string> ();
  52. var p = new OptionSet () {
  53. { "delete",
  54. "Delete removed members from the XML files.",
  55. v => delete = v != null },
  56. { "exceptions:",
  57. "Document potential exceptions that members can generate. {SOURCES} " +
  58. "is a comma-separated list of:\n" +
  59. " asm Method calls in same assembly\n" +
  60. " depasm Method calls in dependent assemblies\n" +
  61. " all Record all possible exceptions\n" +
  62. " added Modifier; only create <exception/>s\n" +
  63. " for NEW types/members\n" +
  64. "If nothing is specified, then only exceptions from the member will " +
  65. "be listed.",
  66. v => exceptions = ParseExceptionLocations (v) },
  67. { "f=",
  68. "Specify a {FLAG} to alter behavior. See later -f* options for available flags.",
  69. v => {
  70. switch (v) {
  71. case "ignore-missing-types":
  72. ignore_missing_types = true;
  73. break;
  74. case "no-assembly-versions":
  75. no_assembly_versions = true;
  76. break;
  77. default:
  78. throw new Exception ("Unsupported flag `" + v + "'.");
  79. }
  80. } },
  81. { "fignore-missing-types",
  82. "Do not report an error if a --type=TYPE type\nwas not found.",
  83. v => ignore_missing_types = v != null },
  84. { "fno-assembly-versions",
  85. "Do not generate //AssemblyVersion elements.",
  86. v => no_assembly_versions = v != null },
  87. { "i|import=",
  88. "Import documentation from {FILE}.",
  89. v => AddImporter (v) },
  90. { "L|lib=",
  91. "Check for assembly references in {DIRECTORY}.",
  92. v => assemblyResolver.AddSearchDirectory (v) },
  93. { "library=",
  94. "Ignored for compatibility with update-ecma-xml.",
  95. v => {} },
  96. { "o|out=",
  97. "Root {DIRECTORY} to generate/update documentation.",
  98. v => srcPath = v },
  99. { "r=",
  100. "Search for dependent assemblies in the directory containing {ASSEMBLY}.\n" +
  101. "(Equivalent to '-L `dirname ASSEMBLY`'.)",
  102. v => assemblyResolver.AddSearchDirectory (Path.GetDirectoryName (v)) },
  103. { "since=",
  104. "Manually specify the assembly {VERSION} that new members were added in.",
  105. v => since = v },
  106. { "type=",
  107. "Only update documentation for {TYPE}.",
  108. v => types.Add (v) },
  109. };
  110. var assemblies = Parse (p, args, "update",
  111. "[OPTIONS]+ ASSEMBLIES",
  112. "Create or update documentation from ASSEMBLIES.");
  113. if (assemblies == null)
  114. return;
  115. if (assemblies.Count == 0)
  116. Error ("No assemblies specified.");
  117. foreach (var dir in assemblies
  118. .Where (a => a.Contains (Path.DirectorySeparatorChar))
  119. .Select (a => Path.GetDirectoryName (a)))
  120. assemblyResolver.AddSearchDirectory (dir);
  121. // PARSE BASIC OPTIONS AND LOAD THE ASSEMBLY TO DOCUMENT
  122. if (srcPath == null)
  123. throw new InvalidOperationException("The --out option is required.");
  124. this.assemblies = assemblies.Select (a => LoadAssembly (a)).ToList ();
  125. // Store types that have been forwarded to avoid duplicate generation
  126. GatherForwardedTypes ();
  127. docEnum = docEnum ?? new DocumentationEnumerator ();
  128. // PERFORM THE UPDATES
  129. if (types.Count > 0) {
  130. types.Sort ();
  131. DoUpdateTypes (srcPath, types, srcPath);
  132. }
  133. #if false
  134. else if (opts.@namespace != null)
  135. DoUpdateNS (opts.@namespace, Path.Combine (opts.path, opts.@namespace),
  136. Path.Combine (dest_dir, opts.@namespace));
  137. #endif
  138. else
  139. DoUpdateAssemblies (srcPath, srcPath);
  140. Console.WriteLine("Members Added: {0}, Members Deleted: {1}", additions, deletions);
  141. }
  142. void AddImporter (string path)
  143. {
  144. try {
  145. XmlReader r = new XmlTextReader (path);
  146. if (r.Read ()) {
  147. while (r.NodeType != XmlNodeType.Element) {
  148. if (!r.Read ())
  149. Error ("Unable to read XML file: {0}.", path);
  150. }
  151. if (r.LocalName == "doc") {
  152. importers.Add (new MsxdocDocumentationImporter (path));
  153. }
  154. else if (r.LocalName == "Libraries") {
  155. var ecmadocs = new XmlTextReader (path);
  156. docEnum = new EcmaDocumentationEnumerator (this, ecmadocs);
  157. importers.Add (new EcmaDocumentationImporter (ecmadocs));
  158. }
  159. else
  160. Error ("Unsupported XML format within {0}.", path);
  161. }
  162. r.Close ();
  163. } catch (Exception e) {
  164. Environment.ExitCode = 1;
  165. Error ("Could not load XML file: {0}.", e.Message);
  166. }
  167. }
  168. void GatherForwardedTypes ()
  169. {
  170. foreach (var asm in assemblies)
  171. foreach (var type in asm.MainModule.ExportedTypes.Where (t => t.IsForwarder).Select (t => t.FullName))
  172. forwardedTypes.Add (type);
  173. }
  174. static ExceptionLocations ParseExceptionLocations (string s)
  175. {
  176. ExceptionLocations loc = ExceptionLocations.Member;
  177. if (s == null)
  178. return loc;
  179. foreach (var type in s.Split (',')) {
  180. switch (type) {
  181. case "added": loc |= ExceptionLocations.AddedMembers; break;
  182. case "all": loc |= ExceptionLocations.Assembly | ExceptionLocations.DependentAssemblies; break;
  183. case "asm": loc |= ExceptionLocations.Assembly; break;
  184. case "depasm": loc |= ExceptionLocations.DependentAssemblies; break;
  185. default: throw new NotSupportedException ("Unsupported --exceptions value: " + type);
  186. }
  187. }
  188. return loc;
  189. }
  190. internal void Warning (string format, params object[] args)
  191. {
  192. Message (TraceLevel.Warning, "mdoc: " + format, args);
  193. }
  194. private AssemblyDefinition LoadAssembly (string name)
  195. {
  196. AssemblyDefinition assembly = null;
  197. try {
  198. assembly = AssemblyDefinition.ReadAssembly (name, new ReaderParameters { AssemblyResolver = assemblyResolver });
  199. } catch (System.IO.FileNotFoundException) { }
  200. if (assembly == null)
  201. throw new InvalidOperationException("Assembly " + name + " not found.");
  202. return assembly;
  203. }
  204. private static void WriteXml(XmlElement element, System.IO.TextWriter output) {
  205. OrderTypeAttributes (element);
  206. XmlTextWriter writer = new XmlTextWriter(output);
  207. writer.Formatting = Formatting.Indented;
  208. writer.Indentation = 2;
  209. writer.IndentChar = ' ';
  210. element.WriteTo(writer);
  211. output.WriteLine();
  212. }
  213. private static void WriteFile (string filename, FileMode mode, Action<TextWriter> action)
  214. {
  215. Action<string> creator = file => {
  216. using (var writer = OpenWrite (file, mode))
  217. action (writer);
  218. };
  219. MdocFile.UpdateFile (filename, creator);
  220. }
  221. private static void OrderTypeAttributes (XmlElement e)
  222. {
  223. foreach (XmlElement type in e.SelectNodes ("//Type")) {
  224. OrderTypeAttributes (type.Attributes);
  225. }
  226. }
  227. static readonly string[] TypeAttributeOrder = {
  228. "Name", "FullName", "FullNameSP", "Maintainer"
  229. };
  230. private static void OrderTypeAttributes (XmlAttributeCollection c)
  231. {
  232. XmlAttribute[] attrs = new XmlAttribute [TypeAttributeOrder.Length];
  233. for (int i = 0; i < c.Count; ++i) {
  234. XmlAttribute a = c [i];
  235. for (int j = 0; j < TypeAttributeOrder.Length; ++j) {
  236. if (a.Name == TypeAttributeOrder [j]) {
  237. attrs [j] = a;
  238. break;
  239. }
  240. }
  241. }
  242. for (int i = attrs.Length-1; i >= 0; --i) {
  243. XmlAttribute n = attrs [i];
  244. if (n == null)
  245. continue;
  246. XmlAttribute r = null;
  247. for (int j = i+1; j < attrs.Length; ++j) {
  248. if (attrs [j] != null) {
  249. r = attrs [j];
  250. break;
  251. }
  252. }
  253. if (r == null)
  254. continue;
  255. c.Remove (n);
  256. c.InsertBefore (n, r);
  257. }
  258. }
  259. private XmlDocument CreateIndexStub()
  260. {
  261. XmlDocument index = new XmlDocument();
  262. XmlElement index_root = index.CreateElement("Overview");
  263. index.AppendChild(index_root);
  264. if (assemblies.Count == 0)
  265. throw new Exception ("No assembly");
  266. XmlElement index_assemblies = index.CreateElement("Assemblies");
  267. index_root.AppendChild(index_assemblies);
  268. XmlElement index_remarks = index.CreateElement("Remarks");
  269. index_remarks.InnerText = "To be added.";
  270. index_root.AppendChild(index_remarks);
  271. XmlElement index_copyright = index.CreateElement("Copyright");
  272. index_copyright.InnerText = "To be added.";
  273. index_root.AppendChild(index_copyright);
  274. XmlElement index_types = index.CreateElement("Types");
  275. index_root.AppendChild(index_types);
  276. return index;
  277. }
  278. private static void WriteNamespaceStub(string ns, string outdir) {
  279. XmlDocument index = new XmlDocument();
  280. XmlElement index_root = index.CreateElement("Namespace");
  281. index.AppendChild(index_root);
  282. index_root.SetAttribute("Name", ns);
  283. XmlElement index_docs = index.CreateElement("Docs");
  284. index_root.AppendChild(index_docs);
  285. XmlElement index_summary = index.CreateElement("summary");
  286. index_summary.InnerText = "To be added.";
  287. index_docs.AppendChild(index_summary);
  288. XmlElement index_remarks = index.CreateElement("remarks");
  289. index_remarks.InnerText = "To be added.";
  290. index_docs.AppendChild(index_remarks);
  291. WriteFile (outdir + "/ns-" + ns + ".xml", FileMode.CreateNew,
  292. writer => WriteXml (index.DocumentElement, writer));
  293. }
  294. public void DoUpdateTypes (string basepath, List<string> typenames, string dest)
  295. {
  296. var index = CreateIndexForTypes (dest);
  297. var found = new HashSet<string> ();
  298. foreach (AssemblyDefinition assembly in assemblies) {
  299. foreach (TypeDefinition type in docEnum.GetDocumentationTypes (assembly, typenames)) {
  300. string relpath = DoUpdateType (type, basepath, dest);
  301. if (relpath == null)
  302. continue;
  303. found.Add (type.FullName);
  304. if (index == null)
  305. continue;
  306. index.Add (assembly);
  307. index.Add (type);
  308. }
  309. }
  310. if (index != null)
  311. index.Write ();
  312. if (ignore_missing_types)
  313. return;
  314. var notFound = from n in typenames where !found.Contains (n) select n;
  315. if (notFound.Any ())
  316. throw new InvalidOperationException("Type(s) not found: " + string.Join (", ", notFound.ToArray ()));
  317. }
  318. class IndexForTypes {
  319. MDocUpdater app;
  320. string indexFile;
  321. XmlDocument index;
  322. XmlElement index_types;
  323. XmlElement index_assemblies;
  324. public IndexForTypes (MDocUpdater app, string indexFile, XmlDocument index)
  325. {
  326. this.app = app;
  327. this.indexFile = indexFile;
  328. this.index = index;
  329. index_types = WriteElement (index.DocumentElement, "Types");
  330. index_assemblies = WriteElement (index.DocumentElement, "Assemblies");
  331. }
  332. public void Add (AssemblyDefinition assembly)
  333. {
  334. if (index_assemblies.SelectSingleNode ("Assembly[@Name='" + assembly.Name.Name + "']") != null)
  335. return;
  336. app.AddIndexAssembly (assembly, index_assemblies);
  337. }
  338. public void Add (TypeDefinition type)
  339. {
  340. app.AddIndexType (type, index_types);
  341. }
  342. public void Write ()
  343. {
  344. SortIndexEntries (index_types);
  345. WriteFile (indexFile, FileMode.Create,
  346. writer => WriteXml (index.DocumentElement, writer));
  347. }
  348. }
  349. IndexForTypes CreateIndexForTypes (string dest)
  350. {
  351. string indexFile = Path.Combine (dest, "index.xml");
  352. if (File.Exists (indexFile))
  353. return null;
  354. return new IndexForTypes (this, indexFile, CreateIndexStub ());
  355. }
  356. public string DoUpdateType (TypeDefinition type, string basepath, string dest)
  357. {
  358. if (type.Namespace == null)
  359. Warning ("warning: The type `{0}' is in the root namespace. This may cause problems with display within monodoc.",
  360. type.FullName);
  361. if (!IsPublic (type))
  362. return null;
  363. // Must get the A+B form of the type name.
  364. string typename = GetTypeFileName(type);
  365. string reltypefile = DocUtils.PathCombine (DocUtils.GetNamespace (type), typename + ".xml");
  366. string typefile = Path.Combine (basepath, reltypefile);
  367. System.IO.FileInfo file = new System.IO.FileInfo(typefile);
  368. string output = null;
  369. if (dest == null) {
  370. output = typefile;
  371. } else if (dest == "-") {
  372. output = null;
  373. } else {
  374. output = Path.Combine (dest, reltypefile);
  375. }
  376. if (file.Exists) {
  377. // Update
  378. XmlDocument basefile = new XmlDocument();
  379. try {
  380. basefile.Load(typefile);
  381. } catch (Exception e) {
  382. throw new InvalidOperationException("Error loading " + typefile + ": " + e.Message, e);
  383. }
  384. DoUpdateType2("Updating", basefile, type, output, false);
  385. } else {
  386. // Stub
  387. XmlElement td = StubType(type, output);
  388. if (td == null)
  389. return null;
  390. System.IO.DirectoryInfo dir = new System.IO.DirectoryInfo (DocUtils.PathCombine (dest, type.Namespace));
  391. if (!dir.Exists) {
  392. dir.Create();
  393. Console.WriteLine("Namespace Directory Created: " + type.Namespace);
  394. }
  395. }
  396. return reltypefile;
  397. }
  398. public void DoUpdateNS (string ns, string nspath, string outpath)
  399. {
  400. Dictionary<TypeDefinition, object> seenTypes = new Dictionary<TypeDefinition,object> ();
  401. AssemblyDefinition assembly = assemblies [0];
  402. foreach (System.IO.FileInfo file in new System.IO.DirectoryInfo(nspath).GetFiles("*.xml")) {
  403. XmlDocument basefile = new XmlDocument();
  404. string typefile = Path.Combine(nspath, file.Name);
  405. try {
  406. basefile.Load(typefile);
  407. } catch (Exception e) {
  408. throw new InvalidOperationException("Error loading " + typefile + ": " + e.Message, e);
  409. }
  410. string typename =
  411. GetTypeFileName (basefile.SelectSingleNode("Type/@FullName").InnerText);
  412. TypeDefinition type = assembly.GetType(typename);
  413. if (type == null) {
  414. Warning ("Type no longer in assembly: " + typename);
  415. continue;
  416. }
  417. seenTypes[type] = seenTypes;
  418. DoUpdateType2("Updating", basefile, type, Path.Combine(outpath, file.Name), false);
  419. }
  420. // Stub types not in the directory
  421. foreach (TypeDefinition type in docEnum.GetDocumentationTypes (assembly, null)) {
  422. if (type.Namespace != ns || seenTypes.ContainsKey(type))
  423. continue;
  424. XmlElement td = StubType(type, Path.Combine(outpath, GetTypeFileName(type) + ".xml"));
  425. if (td == null) continue;
  426. }
  427. }
  428. private static string GetTypeFileName (TypeReference type)
  429. {
  430. return filenameFormatter.GetName (type);
  431. }
  432. public static string GetTypeFileName (string typename)
  433. {
  434. StringBuilder filename = new StringBuilder (typename.Length);
  435. int numArgs = 0;
  436. int numLt = 0;
  437. bool copy = true;
  438. for (int i = 0; i < typename.Length; ++i) {
  439. char c = typename [i];
  440. switch (c) {
  441. case '<':
  442. copy = false;
  443. ++numLt;
  444. break;
  445. case '>':
  446. --numLt;
  447. if (numLt == 0) {
  448. filename.Append ('`').Append ((numArgs+1).ToString());
  449. numArgs = 0;
  450. copy = true;
  451. }
  452. break;
  453. case ',':
  454. if (numLt == 1)
  455. ++numArgs;
  456. break;
  457. default:
  458. if (copy)
  459. filename.Append (c);
  460. break;
  461. }
  462. }
  463. return filename.ToString ();
  464. }
  465. private void AddIndexAssembly (AssemblyDefinition assembly, XmlElement parent)
  466. {
  467. XmlElement index_assembly = parent.OwnerDocument.CreateElement("Assembly");
  468. index_assembly.SetAttribute ("Name", assembly.Name.Name);
  469. index_assembly.SetAttribute ("Version", assembly.Name.Version.ToString());
  470. AssemblyNameDefinition name = assembly.Name;
  471. if (name.HasPublicKey) {
  472. XmlElement pubkey = parent.OwnerDocument.CreateElement ("AssemblyPublicKey");
  473. var key = new StringBuilder (name.PublicKey.Length*3 + 2);
  474. key.Append ("[");
  475. foreach (byte b in name.PublicKey)
  476. key.AppendFormat ("{0,2:x2} ", b);
  477. key.Append ("]");
  478. pubkey.InnerText = key.ToString ();
  479. index_assembly.AppendChild (pubkey);
  480. }
  481. if (!string.IsNullOrEmpty (name.Culture)) {
  482. XmlElement culture = parent.OwnerDocument.CreateElement ("AssemblyCulture");
  483. culture.InnerText = name.Culture;
  484. index_assembly.AppendChild (culture);
  485. }
  486. MakeAttributes (index_assembly, GetCustomAttributes (assembly.CustomAttributes, ""));
  487. parent.AppendChild(index_assembly);
  488. }
  489. private void AddIndexType (TypeDefinition type, XmlElement index_types)
  490. {
  491. string typename = GetTypeFileName(type);
  492. // Add namespace and type nodes into the index file as needed
  493. string ns = DocUtils.GetNamespace (type);
  494. XmlElement nsnode = (XmlElement) index_types.SelectSingleNode ("Namespace[@Name='" + ns + "']");
  495. if (nsnode == null) {
  496. nsnode = index_types.OwnerDocument.CreateElement("Namespace");
  497. nsnode.SetAttribute ("Name", ns);
  498. index_types.AppendChild (nsnode);
  499. }
  500. string doc_typename = GetDocTypeName (type);
  501. XmlElement typenode = (XmlElement) nsnode.SelectSingleNode ("Type[@Name='" + typename + "']");
  502. if (typenode == null) {
  503. typenode = index_types.OwnerDocument.CreateElement ("Type");
  504. typenode.SetAttribute ("Name", typename);
  505. nsnode.AppendChild (typenode);
  506. }
  507. if (typename != doc_typename)
  508. typenode.SetAttribute("DisplayName", doc_typename);
  509. else
  510. typenode.RemoveAttribute("DisplayName");
  511. typenode.SetAttribute ("Kind", GetTypeKind (type));
  512. }
  513. private void DoUpdateAssemblies (string source, string dest)
  514. {
  515. string indexfile = dest + "/index.xml";
  516. XmlDocument index;
  517. if (System.IO.File.Exists(indexfile)) {
  518. index = new XmlDocument();
  519. index.Load(indexfile);
  520. // Format change
  521. ClearElement(index.DocumentElement, "Assembly");
  522. ClearElement(index.DocumentElement, "Attributes");
  523. } else {
  524. index = CreateIndexStub();
  525. }
  526. string defaultTitle = "Untitled";
  527. if (assemblies.Count == 1)
  528. defaultTitle = assemblies[0].Name.Name;
  529. WriteElementInitialText(index.DocumentElement, "Title", defaultTitle);
  530. XmlElement index_types = WriteElement(index.DocumentElement, "Types");
  531. XmlElement index_assemblies = WriteElement(index.DocumentElement, "Assemblies");
  532. index_assemblies.RemoveAll ();
  533. HashSet<string> goodfiles = new HashSet<string> (StringComparer.OrdinalIgnoreCase);
  534. foreach (AssemblyDefinition assm in assemblies) {
  535. AddIndexAssembly (assm, index_assemblies);
  536. DoUpdateAssembly (assm, index_types, source, dest, goodfiles);
  537. }
  538. SortIndexEntries (index_types);
  539. CleanupFiles (dest, goodfiles);
  540. CleanupIndexTypes (index_types, goodfiles);
  541. CleanupExtensions (index_types);
  542. WriteFile (indexfile, FileMode.Create,
  543. writer => WriteXml(index.DocumentElement, writer));
  544. }
  545. private static char[] InvalidFilenameChars = {'\\', '/', ':', '*', '?', '"', '<', '>', '|'};
  546. private void DoUpdateAssembly (AssemblyDefinition assembly, XmlElement index_types, string source, string dest, HashSet<string> goodfiles)
  547. {
  548. foreach (TypeDefinition type in docEnum.GetDocumentationTypes (assembly, null)) {
  549. string typename = GetTypeFileName(type);
  550. if (!IsPublic (type) || typename.IndexOfAny (InvalidFilenameChars) >= 0 || forwardedTypes.Contains (type.FullName))
  551. continue;
  552. string reltypepath = DoUpdateType (type, source, dest);
  553. if (reltypepath == null)
  554. continue;
  555. // Add namespace and type nodes into the index file as needed
  556. AddIndexType (type, index_types);
  557. // Ensure the namespace index file exists
  558. string onsdoc = DocUtils.PathCombine (dest, type.Namespace + ".xml");
  559. string nsdoc = DocUtils.PathCombine (dest, "ns-" + type.Namespace + ".xml");
  560. if (File.Exists (onsdoc)) {
  561. File.Move (onsdoc, nsdoc);
  562. }
  563. if (!File.Exists (nsdoc)) {
  564. Console.WriteLine("New Namespace File: " + type.Namespace);
  565. WriteNamespaceStub(type.Namespace, dest);
  566. }
  567. goodfiles.Add (reltypepath);
  568. }
  569. }
  570. private static void SortIndexEntries (XmlElement indexTypes)
  571. {
  572. XmlNodeList namespaces = indexTypes.SelectNodes ("Namespace");
  573. XmlNodeComparer c = new AttributeNameComparer ();
  574. SortXmlNodes (indexTypes, namespaces, c);
  575. for (int i = 0; i < namespaces.Count; ++i)
  576. SortXmlNodes (namespaces [i], namespaces [i].SelectNodes ("Type"), c);
  577. }
  578. private static void SortXmlNodes (XmlNode parent, XmlNodeList children, XmlNodeComparer comparer)
  579. {
  580. MyXmlNodeList l = new MyXmlNodeList (children.Count);
  581. for (int i = 0; i < children.Count; ++i)
  582. l.Add (children [i]);
  583. l.Sort (comparer);
  584. for (int i = l.Count - 1; i > 0; --i) {
  585. parent.InsertBefore (parent.RemoveChild ((XmlNode) l [i-1]), (XmlNode) l [i]);
  586. }
  587. }
  588. abstract class XmlNodeComparer : IComparer, IComparer<XmlNode>
  589. {
  590. public abstract int Compare (XmlNode x, XmlNode y);
  591. public int Compare (object x, object y)
  592. {
  593. return Compare ((XmlNode) x, (XmlNode) y);
  594. }
  595. }
  596. class AttributeNameComparer : XmlNodeComparer {
  597. string attribute;
  598. public AttributeNameComparer ()
  599. : this ("Name")
  600. {
  601. }
  602. public AttributeNameComparer (string attribute)
  603. {
  604. this.attribute = attribute;
  605. }
  606. public override int Compare (XmlNode x, XmlNode y)
  607. {
  608. return x.Attributes [attribute].Value.CompareTo (y.Attributes [attribute].Value);
  609. }
  610. }
  611. class VersionComparer : XmlNodeComparer {
  612. public override int Compare (XmlNode x, XmlNode y)
  613. {
  614. // Some of the existing docs use e.g. 1.0.x.x, which Version doesn't like.
  615. string a = GetVersion (x.InnerText);
  616. string b = GetVersion (y.InnerText);
  617. return new Version (a).CompareTo (new Version (b));
  618. }
  619. static string GetVersion (string v)
  620. {
  621. int n = v.IndexOf ("x");
  622. if (n < 0)
  623. return v;
  624. return v.Substring (0, n-1);
  625. }
  626. }
  627. private static string GetTypeKind (TypeDefinition type)
  628. {
  629. if (type.IsEnum)
  630. return "Enumeration";
  631. if (type.IsValueType)
  632. return "Structure";
  633. if (type.IsInterface)
  634. return "Interface";
  635. if (DocUtils.IsDelegate (type))
  636. return "Delegate";
  637. if (type.IsClass || type.FullName == "System.Enum") // FIXME
  638. return "Class";
  639. throw new ArgumentException ("Unknown kind for type: " + type.FullName);
  640. }
  641. private static bool IsPublic (TypeDefinition type)
  642. {
  643. TypeDefinition decl = type;
  644. while (decl != null) {
  645. if (!(decl.IsPublic || decl.IsNestedPublic ||
  646. decl.IsNestedFamily || decl.IsNestedFamily || decl.IsNestedFamilyOrAssembly)) {
  647. return false;
  648. }
  649. decl = (TypeDefinition) decl.DeclaringType;
  650. }
  651. return true;
  652. }
  653. private void CleanupFiles (string dest, HashSet<string> goodfiles)
  654. {
  655. // Look for files that no longer correspond to types
  656. foreach (System.IO.DirectoryInfo nsdir in new System.IO.DirectoryInfo(dest).GetDirectories("*")) {
  657. foreach (System.IO.FileInfo typefile in nsdir.GetFiles("*.xml")) {
  658. string relTypeFile = Path.Combine(nsdir.Name, typefile.Name);
  659. if (!goodfiles.Contains (relTypeFile)) {
  660. XmlDocument doc = new XmlDocument ();
  661. doc.Load (typefile.FullName);
  662. XmlElement e = doc.SelectSingleNode("/Type") as XmlElement;
  663. if (e != null && !no_assembly_versions && UpdateAssemblyVersions(e, GetAssemblyVersions(), false)) {
  664. using (TextWriter writer = OpenWrite (typefile.FullName, FileMode.Truncate))
  665. WriteXml(doc.DocumentElement, writer);
  666. goodfiles.Add (relTypeFile);
  667. continue;
  668. }
  669. string newname = typefile.FullName + ".remove";
  670. try { System.IO.File.Delete(newname); } catch (Exception) { }
  671. try { typefile.MoveTo(newname); } catch (Exception) { }
  672. Console.WriteLine("Class no longer present; file renamed: " + Path.Combine(nsdir.Name, typefile.Name));
  673. }
  674. }
  675. }
  676. }
  677. private static TextWriter OpenWrite (string path, FileMode mode)
  678. {
  679. var w = new StreamWriter (
  680. new FileStream (path, mode),
  681. new UTF8Encoding (false)
  682. );
  683. w.NewLine = "\n";
  684. return w;
  685. }
  686. private string[] GetAssemblyVersions ()
  687. {
  688. return (from a in assemblies select GetAssemblyVersion (a)).ToArray ();
  689. }
  690. private static void CleanupIndexTypes (XmlElement index_types, HashSet<string> goodfiles)
  691. {
  692. // Look for type nodes that no longer correspond to types
  693. MyXmlNodeList remove = new MyXmlNodeList ();
  694. foreach (XmlElement typenode in index_types.SelectNodes("Namespace/Type")) {
  695. string fulltypename = Path.Combine (((XmlElement)typenode.ParentNode).GetAttribute("Name"), typenode.GetAttribute("Name") + ".xml");
  696. if (!goodfiles.Contains (fulltypename)) {
  697. remove.Add (typenode);
  698. }
  699. }
  700. foreach (XmlNode n in remove)
  701. n.ParentNode.RemoveChild (n);
  702. }
  703. private void CleanupExtensions (XmlElement index_types)
  704. {
  705. XmlNode e = index_types.SelectSingleNode ("/Overview/ExtensionMethods");
  706. if (extensionMethods.Count == 0) {
  707. if (e == null)
  708. return;
  709. index_types.SelectSingleNode ("/Overview").RemoveChild (e);
  710. return;
  711. }
  712. if (e == null) {
  713. e = index_types.OwnerDocument.CreateElement ("ExtensionMethods");
  714. index_types.SelectSingleNode ("/Overview").AppendChild (e);
  715. }
  716. else
  717. e.RemoveAll ();
  718. extensionMethods.Sort (DefaultExtensionMethodComparer);
  719. foreach (XmlNode m in extensionMethods) {
  720. e.AppendChild (index_types.OwnerDocument.ImportNode (m, true));
  721. }
  722. }
  723. class ExtensionMethodComparer : XmlNodeComparer {
  724. public override int Compare (XmlNode x, XmlNode y)
  725. {
  726. XmlNode xLink = x.SelectSingleNode ("Member/Link");
  727. XmlNode yLink = y.SelectSingleNode ("Member/Link");
  728. int n = xLink.Attributes ["Type"].Value.CompareTo (
  729. yLink.Attributes ["Type"].Value);
  730. if (n != 0)
  731. return n;
  732. n = xLink.Attributes ["Member"].Value.CompareTo (
  733. yLink.Attributes ["Member"].Value);
  734. if (n == 0 && !object.ReferenceEquals (x, y))
  735. throw new InvalidOperationException ("Duplicate extension method found!");
  736. return n;
  737. }
  738. }
  739. static readonly XmlNodeComparer DefaultExtensionMethodComparer = new ExtensionMethodComparer ();
  740. public void DoUpdateType2 (string message, XmlDocument basefile, TypeDefinition type, string output, bool insertSince)
  741. {
  742. Console.WriteLine(message + ": " + type.FullName);
  743. StringToXmlNodeMap seenmembers = new StringToXmlNodeMap ();
  744. // Update type metadata
  745. UpdateType(basefile.DocumentElement, type);
  746. // Update existing members. Delete member nodes that no longer should be there,
  747. // and remember what members are already documented so we don't add them again.
  748. if (true) {
  749. MyXmlNodeList todelete = new MyXmlNodeList ();
  750. foreach (DocsNodeInfo info in docEnum.GetDocumentationMembers (basefile, type)) {
  751. XmlElement oldmember = info.Node;
  752. MemberReference oldmember2 = info.Member;
  753. string sig = oldmember2 != null ? memberFormatters [0].GetDeclaration (oldmember2) : null;
  754. // Interface implementations and overrides are deleted from the docs
  755. // unless the overrides option is given.
  756. if (oldmember2 != null && sig == null)
  757. oldmember2 = null;
  758. // Deleted (or signature changed)
  759. if (oldmember2 == null) {
  760. if (!no_assembly_versions && UpdateAssemblyVersions (oldmember, new string[]{ GetAssemblyVersion (type.Module.Assembly) }, false))
  761. continue;
  762. DeleteMember ("Member Removed", output, oldmember, todelete);
  763. continue;
  764. }
  765. // Duplicated
  766. if (seenmembers.ContainsKey (sig)) {
  767. if (object.ReferenceEquals (oldmember, seenmembers [sig])) {
  768. // ignore, already seen
  769. }
  770. else if (DefaultMemberComparer.Compare (oldmember, seenmembers [sig]) == 0)
  771. DeleteMember ("Duplicate Member Found", output, oldmember, todelete);
  772. else
  773. Warning ("TODO: found a duplicate member '{0}', but it's not identical to the prior member found!", sig);
  774. continue;
  775. }
  776. // Update signature information
  777. UpdateMember(info);
  778. seenmembers.Add (sig, oldmember);
  779. }
  780. foreach (XmlElement oldmember in todelete)
  781. oldmember.ParentNode.RemoveChild (oldmember);
  782. }
  783. if (!DocUtils.IsDelegate (type)) {
  784. XmlNode members = WriteElement (basefile.DocumentElement, "Members");
  785. foreach (MemberReference m in type.GetMembers()) {
  786. if (m is TypeDefinition) continue;
  787. string sig = memberFormatters [0].GetDeclaration (m);
  788. if (sig == null) continue;
  789. if (seenmembers.ContainsKey(sig)) continue;
  790. XmlElement mm = MakeMember(basefile, new DocsNodeInfo (null, m));
  791. if (mm == null) continue;
  792. members.AppendChild( mm );
  793. Console.WriteLine("Member Added: " + mm.SelectSingleNode("MemberSignature/@Value").InnerText);
  794. additions++;
  795. }
  796. }
  797. // Import code snippets from files
  798. foreach (XmlNode code in basefile.GetElementsByTagName("code")) {
  799. if (!(code is XmlElement)) continue;
  800. string file = ((XmlElement)code).GetAttribute("src");
  801. string lang = ((XmlElement)code).GetAttribute("lang");
  802. if (file != "") {
  803. string src = GetCodeSource (lang, Path.Combine (srcPath, file));
  804. if (src != null)
  805. code.InnerText = src;
  806. }
  807. }
  808. if (insertSince && since != null) {
  809. XmlNode docs = basefile.DocumentElement.SelectSingleNode("Docs");
  810. docs.AppendChild (CreateSinceNode (basefile));
  811. }
  812. do {
  813. XmlElement d = basefile.DocumentElement ["Docs"];
  814. XmlElement m = basefile.DocumentElement ["Members"];
  815. if (d != null && m != null)
  816. basefile.DocumentElement.InsertBefore (
  817. basefile.DocumentElement.RemoveChild (d), m);
  818. SortTypeMembers (m);
  819. } while (false);
  820. if (output == null)
  821. WriteXml(basefile.DocumentElement, Console.Out);
  822. else {
  823. FileInfo file = new FileInfo (output);
  824. if (!file.Directory.Exists) {
  825. Console.WriteLine("Namespace Directory Created: " + type.Namespace);
  826. file.Directory.Create ();
  827. }
  828. WriteFile (output, FileMode.Create,
  829. writer => WriteXml(basefile.DocumentElement, writer));
  830. }
  831. }
  832. private string GetCodeSource (string lang, string file)
  833. {
  834. int anchorStart;
  835. if (lang == "C#" && (anchorStart = file.IndexOf (".cs#")) >= 0) {
  836. // Grab the specified region
  837. string region = "#region " + file.Substring (anchorStart + 4);
  838. file = file.Substring (0, anchorStart + 3);
  839. try {
  840. using (StreamReader reader = new StreamReader (file)) {
  841. string line;
  842. StringBuilder src = new StringBuilder ();
  843. int indent = -1;
  844. while ((line = reader.ReadLine ()) != null) {
  845. if (line.Trim() == region) {
  846. indent = line.IndexOf (region);
  847. continue;
  848. }
  849. if (indent >= 0 && line.Trim().StartsWith ("#endregion")) {
  850. break;
  851. }
  852. if (indent >= 0)
  853. src.Append (
  854. (line.Length > 0 ? line.Substring (indent) : string.Empty) +
  855. "\n");
  856. }
  857. return src.ToString ();
  858. }
  859. } catch (Exception e) {
  860. Warning ("Could not load <code/> file '{0}' region '{1}': {2}",
  861. file, region, show_exceptions ? e.ToString () : e.Message);
  862. return null;
  863. }
  864. }
  865. try {
  866. using (StreamReader reader = new StreamReader (file))
  867. return reader.ReadToEnd ();
  868. } catch (Exception e) {
  869. Warning ("Could not load <code/> file '" + file + "': " + e.Message);
  870. }
  871. return null;
  872. }
  873. void DeleteMember (string reason, string output, XmlNode member, MyXmlNodeList todelete)
  874. {
  875. string format = output != null
  876. ? "{0}: File='{1}'; Signature='{4}'"
  877. : "{0}: XPath='/Type[@FullName=\"{2}\"]/Members/Member[@MemberName=\"{3}\"]'; Signature='{4}'";
  878. Warning (format,
  879. reason,
  880. output,
  881. member.OwnerDocument.DocumentElement.GetAttribute ("FullName"),
  882. member.Attributes ["MemberName"].Value,
  883. member.SelectSingleNode ("MemberSignature[@Language='C#']/@Value").Value);
  884. if (!delete && MemberDocsHaveUserContent (member)) {
  885. Warning ("Member deletions must be enabled with the --delete option.");
  886. } else {
  887. todelete.Add (member);
  888. deletions++;
  889. }
  890. }
  891. class MemberComparer : XmlNodeComparer {
  892. public override int Compare (XmlNode x, XmlNode y)
  893. {
  894. int r;
  895. string xMemberName = x.Attributes ["MemberName"].Value;
  896. string yMemberName = y.Attributes ["MemberName"].Value;
  897. // generic methods *end* with '>'
  898. // it's possible for explicitly implemented generic interfaces to
  899. // contain <...> without being a generic method
  900. if ((!xMemberName.EndsWith (">") || !yMemberName.EndsWith (">")) &&
  901. (r = xMemberName.CompareTo (yMemberName)) != 0)
  902. return r;
  903. int lt;
  904. if ((lt = xMemberName.IndexOf ("<")) >= 0)
  905. xMemberName = xMemberName.Substring (0, lt);
  906. if ((lt = yMemberName.IndexOf ("<")) >= 0)
  907. yMemberName = yMemberName.Substring (0, lt);
  908. if ((r = xMemberName.CompareTo (yMemberName)) != 0)
  909. return r;
  910. // if @MemberName matches, then it's either two different types of
  911. // members sharing the same name, e.g. field & property, or it's an
  912. // overloaded method.
  913. // for different type, sort based on MemberType value.
  914. r = x.SelectSingleNode ("MemberType").InnerText.CompareTo (
  915. y.SelectSingleNode ("MemberType").InnerText);
  916. if (r != 0)
  917. return r;
  918. // same type -- must be an overloaded method. Sort based on type
  919. // parameter count, then parameter count, then by the parameter
  920. // type names.
  921. XmlNodeList xTypeParams = x.SelectNodes ("TypeParameters/TypeParameter");
  922. XmlNodeList yTypeParams = y.SelectNodes ("TypeParameters/TypeParameter");
  923. if (xTypeParams.Count != yTypeParams.Count)
  924. return xTypeParams.Count <= yTypeParams.Count ? -1 : 1;
  925. for (int i = 0; i < xTypeParams.Count; ++i) {
  926. r = xTypeParams [i].Attributes ["Name"].Value.CompareTo (
  927. yTypeParams [i].Attributes ["Name"].Value);
  928. if (r != 0)
  929. return r;
  930. }
  931. XmlNodeList xParams = x.SelectNodes ("Parameters/Parameter");
  932. XmlNodeList yParams = y.SelectNodes ("Parameters/Parameter");
  933. if (xParams.Count != yParams.Count)
  934. return xParams.Count <= yParams.Count ? -1 : 1;
  935. for (int i = 0; i < xParams.Count; ++i) {
  936. r = xParams [i].Attributes ["Type"].Value.CompareTo (
  937. yParams [i].Attributes ["Type"].Value);
  938. if (r != 0)
  939. return r;
  940. }
  941. // all parameters match, but return value might not match if it was
  942. // changed between one version and another.
  943. XmlNode xReturn = x.SelectSingleNode ("ReturnValue/ReturnType");
  944. XmlNode yReturn = y.SelectSingleNode ("ReturnValue/ReturnType");
  945. if (xReturn != null && yReturn != null) {
  946. r = xReturn.InnerText.CompareTo (yReturn.InnerText);
  947. if (r != 0)
  948. return r;
  949. }
  950. return 0;
  951. }
  952. }
  953. static readonly MemberComparer DefaultMemberComparer = new MemberComparer ();
  954. private static void SortTypeMembers (XmlNode members)
  955. {
  956. if (members == null)
  957. return;
  958. SortXmlNodes (members, members.SelectNodes ("Member"), DefaultMemberComparer);
  959. }
  960. private static bool MemberDocsHaveUserContent (XmlNode e)
  961. {
  962. e = (XmlElement)e.SelectSingleNode("Docs");
  963. if (e == null) return false;
  964. foreach (XmlElement d in e.SelectNodes("*"))
  965. if (d.InnerText != "" && !d.InnerText.StartsWith("To be added"))
  966. return true;
  967. return false;
  968. }
  969. // UPDATE HELPER FUNCTIONS
  970. // CREATE A STUB DOCUMENTATION FILE
  971. public XmlElement StubType (TypeDefinition type, string output)
  972. {
  973. string typesig = typeFormatters [0].GetDeclaration (type);
  974. if (typesig == null) return null; // not publicly visible
  975. XmlDocument doc = new XmlDocument();
  976. XmlElement root = doc.CreateElement("Type");
  977. doc.AppendChild (root);
  978. DoUpdateType2 ("New Type", doc, type, output, true);
  979. return root;
  980. }
  981. private XmlElement CreateSinceNode (XmlDocument doc)
  982. {
  983. XmlElement s = doc.CreateElement ("since");
  984. s.SetAttribute ("version", since);
  985. return s;
  986. }
  987. // STUBBING/UPDATING FUNCTIONS
  988. public void UpdateType (XmlElement root, TypeDefinition type)
  989. {
  990. root.SetAttribute("Name", GetDocTypeName (type));
  991. root.SetAttribute("FullName", GetDocTypeFullName (type));
  992. foreach (MemberFormatter f in typeFormatters) {
  993. string element = "TypeSignature[@Language='" + f.Language + "']";
  994. WriteElementAttribute (root, element, "Language", f.Language);
  995. WriteElementAttribute (root, element, "Value", f.GetDeclaration (type));
  996. }
  997. XmlElement ass = WriteElement(root, "AssemblyInfo");
  998. WriteElementText(ass, "AssemblyName", type.Module.Assembly.Name.Name);
  999. if (!no_assembly_versions) {
  1000. UpdateAssemblyVersions (root, type, true);
  1001. }
  1002. else {
  1003. var versions = ass.SelectNodes ("AssemblyVersion").Cast<XmlNode> ().ToList ();
  1004. foreach (var version in versions)
  1005. ass.RemoveChild (version);
  1006. }
  1007. if (!string.IsNullOrEmpty (type.Module.Assembly.Name.Culture))
  1008. WriteElementText(ass, "AssemblyCulture", type.Module.Assembly.Name.Culture);
  1009. else
  1010. ClearElement(ass, "AssemblyCulture");
  1011. // Why-oh-why do we put assembly attributes in each type file?
  1012. // Neither monodoc nor monodocs2html use them, so I'm deleting them
  1013. // since they're outdated in current docs, and a waste of space.
  1014. //MakeAttributes(ass, type.Assembly, true);
  1015. XmlNode assattrs = ass.SelectSingleNode("Attributes");
  1016. if (assattrs != null)
  1017. ass.RemoveChild(assattrs);
  1018. NormalizeWhitespace(ass);
  1019. if (type.IsGenericType ()) {
  1020. MakeTypeParameters (root, type.GenericParameters);
  1021. } else {
  1022. ClearElement(root, "TypeParameters");
  1023. }
  1024. if (type.BaseType != null) {
  1025. XmlElement basenode = WriteElement(root, "Base");
  1026. string basetypename = GetDocTypeFullName (type.BaseType);
  1027. if (basetypename == "System.MulticastDelegate") basetypename = "System.Delegate";
  1028. WriteElementText(root, "Base/BaseTypeName", basetypename);
  1029. // Document how this type instantiates the generic parameters of its base type
  1030. TypeReference origBase = type.BaseType.GetElementType ();
  1031. if (origBase.IsGenericType ()) {
  1032. ClearElement(basenode, "BaseTypeArguments");
  1033. GenericInstanceType baseInst = type.BaseType as GenericInstanceType;
  1034. IList<TypeReference> baseGenArgs = baseInst == null ? null : baseInst.GenericArguments;
  1035. IList<GenericParameter> baseGenParams = origBase.GenericParameters;
  1036. if (baseGenArgs.Count != baseGenParams.Count)
  1037. throw new InvalidOperationException ("internal error: number of generic arguments doesn't match number of generic parameters.");
  1038. for (int i = 0; baseGenArgs != null && i < baseGenArgs.Count; i++) {
  1039. GenericParameter param = baseGenParams [i];
  1040. TypeReference value = baseGenArgs [i];
  1041. XmlElement bta = WriteElement(basenode, "BaseTypeArguments");
  1042. XmlElement arg = bta.OwnerDocument.CreateElement("BaseTypeArgument");
  1043. bta.AppendChild(arg);
  1044. arg.SetAttribute ("TypeParamName", param.Name);
  1045. arg.InnerText = GetDocTypeFullName (value);
  1046. }
  1047. }
  1048. } else {
  1049. ClearElement(root, "Base");
  1050. }
  1051. if (!DocUtils.IsDelegate (type) && !type.IsEnum) {
  1052. IEnumerable<TypeReference> userInterfaces = DocUtils.GetUserImplementedInterfaces (type);
  1053. List<string> interface_names = userInterfaces
  1054. .Select (iface => GetDocTypeFullName (iface))
  1055. .OrderBy (s => s)
  1056. .ToList ();
  1057. XmlElement interfaces = WriteElement(root, "Interfaces");
  1058. interfaces.RemoveAll();
  1059. foreach (string iname in interface_names) {
  1060. XmlElement iface = root.OwnerDocument.CreateElement("Interface");
  1061. interfaces.AppendChild(iface);
  1062. WriteElementText(iface, "InterfaceName", iname);
  1063. }
  1064. } else {
  1065. ClearElement(root, "Interfaces");
  1066. }
  1067. MakeAttributes (root, GetCustomAttributes (type));
  1068. if (DocUtils.IsDelegate (type)) {
  1069. MakeTypeParameters (root, type.GenericParameters);
  1070. MakeParameters(root, type.GetMethod("Invoke").Parameters);
  1071. MakeReturnValue(root, type.GetMethod("Invoke"));
  1072. }
  1073. DocsNodeInfo typeInfo = new DocsNodeInfo (WriteElement(root, "Docs"), type);
  1074. MakeDocNode (typeInfo);
  1075. if (!DocUtils.IsDelegate (type))
  1076. WriteElement (root, "Members");
  1077. OrderTypeNodes (root, root.ChildNodes);
  1078. NormalizeWhitespace(root);
  1079. }
  1080. static readonly string[] TypeNodeOrder = {
  1081. "TypeSignature",
  1082. "MemberOfLibrary",
  1083. "AssemblyInfo",
  1084. "ThreadingSafetyStatement",
  1085. "ThreadSafetyStatement",
  1086. "TypeParameters",
  1087. "Base",
  1088. "Interfaces",
  1089. "Attributes",
  1090. "Parameters",
  1091. "ReturnValue",
  1092. "Docs",
  1093. "Members",
  1094. "TypeExcluded",
  1095. };
  1096. static void OrderTypeNodes (XmlNode member, XmlNodeList children)
  1097. {
  1098. ReorderNodes (member, children, TypeNodeOrder);
  1099. }
  1100. internal static IEnumerable<T> Sort<T> (IEnumerable<T> list)
  1101. {
  1102. List<T> l = new List<T> (list);
  1103. l.Sort ();
  1104. return l;
  1105. }
  1106. private void UpdateMember (DocsNodeInfo info)
  1107. {
  1108. XmlElement me = (XmlElement) info.Node;
  1109. MemberReference mi = info.Member;
  1110. foreach (MemberFormatter f in memberFormatters) {
  1111. string element = "MemberSignature[@Language='" + f.Language + "']";
  1112. WriteElementAttribute (me, element, "Language", f.Language);
  1113. WriteElementAttribute (me, element, "Value", f.GetDeclaration (mi));
  1114. }
  1115. WriteElementText(me, "MemberType", GetMemberType(mi));
  1116. if (!no_assembly_versions) {
  1117. UpdateAssemblyVersions (me, mi, true);
  1118. }
  1119. else {
  1120. ClearElement (me, "AssemblyInfo");
  1121. }
  1122. MakeAttributes (me, GetCustomAttributes (mi));
  1123. MakeReturnValue(me, mi);
  1124. if (mi is MethodReference) {
  1125. MethodReference mb = (MethodReference) mi;
  1126. if (mb.IsGenericMethod ())
  1127. MakeTypeParameters (me, mb.GenericParameters);
  1128. }
  1129. MakeParameters(me, mi);
  1130. string fieldValue;
  1131. if (mi is FieldDefinition && GetFieldConstValue ((FieldDefinition)mi, out fieldValue))
  1132. WriteElementText(me, "MemberValue", fieldValue);
  1133. info.Node = WriteElement (me, "Docs");
  1134. MakeDocNode (info);
  1135. OrderMemberNodes (me, me.ChildNodes);
  1136. UpdateExtensionMethods (me, info);
  1137. }
  1138. static readonly string[] MemberNodeOrder = {
  1139. "MemberSignature",
  1140. "MemberType",
  1141. "AssemblyInfo",
  1142. "Attributes",
  1143. "ReturnValue",
  1144. "TypeParameters",
  1145. "Parameters",
  1146. "MemberValue",
  1147. "Docs",
  1148. "Excluded",
  1149. "ExcludedLibrary",
  1150. "Link",
  1151. };
  1152. static void OrderMemberNodes (XmlNode member, XmlNodeList children)
  1153. {
  1154. ReorderNodes (member, children, MemberNodeOrder);
  1155. }
  1156. static void ReorderNodes (XmlNode node, XmlNodeList children, string[] ordering)
  1157. {
  1158. MyXmlNodeList newChildren = new MyXmlNodeList (children.Count);
  1159. for (int i = 0; i < ordering.Length; ++i) {
  1160. for (int j = 0; j < children.Count; ++j) {
  1161. XmlNode c = children [j];
  1162. if (c.Name == ordering [i]) {
  1163. newChildren.Add (c);
  1164. }
  1165. }
  1166. }
  1167. if (newChildren.Count >= 0)
  1168. node.PrependChild ((XmlNode) newChildren [0]);
  1169. for (int i = 1; i < newChildren.Count; ++i) {
  1170. XmlNode prev = (XmlNode) newChildren [i-1];
  1171. XmlNode cur = (XmlNode) newChildren [i];
  1172. node.RemoveChild (cur);
  1173. node.InsertAfter (cur, prev);
  1174. }
  1175. }
  1176. IEnumerable<string> GetCustomAttributes (MemberReference mi)
  1177. {
  1178. IEnumerable<string> attrs = Enumerable.Empty<string>();
  1179. ICustomAttributeProvider p = mi as ICustomAttributeProvider;
  1180. if (p != null)
  1181. attrs = attrs.Concat (GetCustomAttributes (p.CustomAttributes, ""));
  1182. PropertyDefinition pd = mi as PropertyDefinition;
  1183. if (pd != null) {
  1184. if (pd.GetMethod != null)
  1185. attrs = attrs.Concat (GetCustomAttributes (pd.GetMethod.CustomAttributes, "get: "));
  1186. if (pd.SetMethod != null)
  1187. attrs = attrs.Concat (GetCustomAttributes (pd.SetMethod.CustomAttributes, "set: "));
  1188. }
  1189. EventDefinition ed = mi as EventDefinition;
  1190. if (ed != null) {
  1191. if (ed.AddMethod != null)
  1192. attrs = attrs.Concat (GetCustomAttributes (ed.AddMethod.CustomAttributes, "add: "));
  1193. if (ed.RemoveMethod != null)
  1194. attrs = attrs.Concat (GetCustomAttributes (ed.RemoveMethod.CustomAttributes, "remove: "));
  1195. }
  1196. return attrs;
  1197. }
  1198. IEnumerable<string> GetCustomAttributes (IList<CustomAttribute> attributes, string prefix)
  1199. {
  1200. foreach (CustomAttribute attribute in attributes.OrderBy (ca => ca.AttributeType.FullName)) {
  1201. TypeDefinition attrType = attribute.AttributeType as TypeDefinition;
  1202. if (attrType != null && !IsPublic (attrType))
  1203. continue;
  1204. if (slashdocFormatter.GetName (attribute.AttributeType) == null)
  1205. continue;
  1206. if (Array.IndexOf (IgnorableAttributes, attribute.AttributeType.FullName) >= 0)
  1207. continue;
  1208. StringList fields = new StringList ();
  1209. for (int i = 0; i < attribute.ConstructorArguments.Count; ++i) {
  1210. CustomAttributeArgument argument = attribute.ConstructorArguments [i];
  1211. fields.Add (MakeAttributesValueString (
  1212. argument.Value,
  1213. argument.Type));
  1214. }
  1215. var namedArgs =
  1216. (from namedArg in attribute.Fields
  1217. select new { Type=namedArg.Argument.Type, Name=namedArg.Name, Value=namedArg.Argument.Value })
  1218. .Concat (
  1219. (from namedArg in attribute.Properties
  1220. select new { Type=namedArg.Argument.Type, Name=namedArg.Name, Value=namedArg.Argument.Value }))
  1221. .OrderBy (v => v.Name);
  1222. foreach (var d in namedArgs)
  1223. fields.Add (string.Format ("{0}={1}", d.Name,
  1224. MakeAttributesValueString (d.Value, d.Type)));
  1225. string a2 = String.Join(", ", fields.ToArray ());
  1226. if (a2 != "") a2 = "(" + a2 + ")";
  1227. string name = attribute.GetDeclaringType();
  1228. if (name.EndsWith("Attribute")) name = name.Substring(0, name.Length-"Attribute".Length);
  1229. yield return prefix + name + a2;
  1230. }
  1231. }
  1232. static readonly string[] ValidExtensionMembers = {
  1233. "Docs",
  1234. "MemberSignature",
  1235. "MemberType",
  1236. "Parameters",
  1237. "ReturnValue",
  1238. "TypeParameters",
  1239. };
  1240. static readonly string[] ValidExtensionDocMembers = {
  1241. "param",
  1242. "summary",
  1243. "typeparam",
  1244. };
  1245. private void UpdateExtensionMethods (XmlElement e, DocsNodeInfo info)
  1246. {
  1247. MethodDefinition me = info.Member as MethodDefinition;
  1248. if (me == null)
  1249. return;
  1250. if (info.Parameters.Count < 1)
  1251. return;
  1252. if (!DocUtils.IsExtensionMethod (me))
  1253. return;
  1254. XmlNode em = e.OwnerDocument.CreateElement ("ExtensionMethod");
  1255. XmlNode member = e.CloneNode (true);
  1256. em.AppendChild (member);
  1257. RemoveExcept (member, ValidExtensionMembers);
  1258. RemoveExcept (member.SelectSingleNode ("Docs"), ValidExtensionDocMembers);
  1259. WriteElementText (member, "MemberType", "ExtensionMethod");
  1260. XmlElement link = member.OwnerDocument.CreateElement ("Link");
  1261. link.SetAttribute ("Type", slashdocFormatter.GetName (me.DeclaringType));
  1262. link.SetAttribute ("Member", slashdocFormatter.GetDeclaration (me));
  1263. member.AppendChild (link);
  1264. AddTargets (em, info);
  1265. extensionMethods.Add (em);
  1266. }
  1267. private static void RemoveExcept (XmlNode node, string[] except)
  1268. {
  1269. if (node == null)
  1270. return;
  1271. MyXmlNodeList remove = null;
  1272. foreach (XmlNode n in node.ChildNodes) {
  1273. if (Array.BinarySearch (except, n.Name) < 0) {
  1274. if (remove == null)
  1275. remove = new MyXmlNodeList ();
  1276. remove.Add (n);
  1277. }
  1278. }
  1279. if (remove != null)
  1280. foreach (XmlNode n in remove)
  1281. node.RemoveChild (n);
  1282. }
  1283. private static void AddTargets (XmlNode member, DocsNodeInfo info)
  1284. {
  1285. XmlElement targets = member.OwnerDocument.CreateElement ("Targets");
  1286. member.PrependChild (targets);
  1287. if (!(info.Parameters [0].ParameterType is GenericParameter)) {
  1288. AppendElementAttributeText (targets, "Target", "Type",
  1289. slashdocFormatter.GetDeclaration (info.Parameters [0].ParameterType));
  1290. }
  1291. else {
  1292. GenericParameter gp = (GenericParameter) info.Parameters [0].ParameterType;
  1293. IList<TypeReference> constraints = gp.Constraints;
  1294. if (constraints.Count == 0)
  1295. AppendElementAttributeText (targets, "Target", "Type", "System.Object");
  1296. else
  1297. foreach (TypeReference c in constraints)
  1298. AppendElementAttributeText(targets, "Target", "Type",
  1299. slashdocFormatter.GetDeclaration (c));
  1300. }
  1301. }
  1302. private static bool GetFieldConstValue (FieldDefinition field, out string value)
  1303. {
  1304. value = null;
  1305. TypeDefinition type = field.DeclaringType.Resolve ();
  1306. if (type != null && type.IsEnum) return false;
  1307. if (type != null && type.IsGenericType ()) return false;
  1308. if (!field.HasConstant)
  1309. return false;
  1310. if (field.IsLiteral) {
  1311. object val = field.Constant;
  1312. if (val == null) value = "null";
  1313. else if (val is Enum) value = val.ToString();
  1314. else if (val is IFormattable) {
  1315. value = ((IFormattable)val).ToString();
  1316. if (val is string)
  1317. value = "\"" + value + "\"";
  1318. }
  1319. if (value != null && value != "")
  1320. return true;
  1321. }
  1322. return false;
  1323. }
  1324. // XML HELPER FUNCTIONS
  1325. internal static XmlElement WriteElement(XmlNode parent, string element) {
  1326. XmlElement ret = (XmlElement)parent.SelectSingleNode(element);
  1327. if (ret == null) {
  1328. string[] path = element.Split('/');
  1329. foreach (string p in path) {
  1330. ret = (XmlElement)parent.SelectSingleNode(p);
  1331. if (ret == null) {
  1332. string ename = p;
  1333. if (ename.IndexOf('[') >= 0) // strip off XPath predicate
  1334. ename = ename.Substring(0, ename.IndexOf('['));
  1335. ret = parent.OwnerDocument.CreateElement(ename);
  1336. parent.AppendChild(ret);
  1337. parent = ret;
  1338. } else {
  1339. parent = ret;
  1340. }
  1341. }
  1342. }
  1343. return ret;
  1344. }
  1345. private static void WriteElementText(XmlNode parent, string element, string value) {
  1346. XmlElement node = WriteElement(parent, element);
  1347. node.InnerText = value;
  1348. }
  1349. static XmlElement AppendElementText (XmlNode parent, string element, string value)
  1350. {
  1351. XmlElement n = parent.OwnerDocument.CreateElement (element);
  1352. parent.AppendChild (n);
  1353. n.InnerText = value;
  1354. return n;
  1355. }
  1356. static XmlElement AppendElementAttributeText (XmlNode parent, string element, string attribute, string value)
  1357. {
  1358. XmlElement n = parent.OwnerDocument.CreateElement (element);
  1359. parent.AppendChild (n);
  1360. n.SetAttribute (attribute, value);
  1361. return n;
  1362. }
  1363. internal static XmlNode CopyNode (XmlNode source, XmlNode dest)
  1364. {
  1365. XmlNode copy = dest.OwnerDocument.ImportNode (source, true);
  1366. dest.AppendChild (copy);
  1367. return copy;
  1368. }
  1369. pr

Large files files are truncated, but you can click here to view the full file