You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

265 lines
8.0 KiB

  1. // Copyright (c) 2009-2013 AlphaSierraPapa for the SharpDevelop Team
  2. //
  3. // Permission is hereby granted, free of charge, to any person obtaining a copy of this
  4. // software and associated documentation files (the "Software"), to deal in the Software
  5. // without restriction, including without limitation the rights to use, copy, modify, merge,
  6. // publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
  7. // to whom the Software is furnished to do so, subject to the following conditions:
  8. //
  9. // The above copyright notice and this permission notice shall be included in all copies or
  10. // substantial portions of the Software.
  11. //
  12. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
  13. // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
  14. // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
  15. // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
  16. // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
  17. // DEALINGS IN THE SOFTWARE.
  18. #nullable enable
  19. using System;
  20. using System.Collections.Generic;
  21. using System.Linq;
  22. using System.Text;
  23. using System.Threading;
  24. using System.Xml.Linq;
  25. using ICSharpCode.Decompiler.TypeSystem;
  26. using ICSharpCode.Decompiler.Util;
  27. namespace ICSharpCode.Decompiler.Documentation
  28. {
  29. /// <summary>
  30. /// Represents an element in the XML documentation.
  31. /// Any occurrences of "&lt;inheritdoc/>" are replaced with the inherited documentation.
  32. /// </summary>
  33. public class XmlDocumentationElement
  34. {
  35. readonly XElement? element;
  36. readonly IEntity? declaringEntity;
  37. readonly Func<string, IEntity?>? crefResolver;
  38. volatile string? textContent;
  39. /// <summary>
  40. /// Inheritance level; used to prevent cyclic doc inheritance.
  41. /// </summary>
  42. int nestingLevel;
  43. /// <summary>
  44. /// Creates a new documentation element.
  45. /// </summary>
  46. public XmlDocumentationElement(XElement element, IEntity? declaringEntity, Func<string, IEntity?>? crefResolver)
  47. {
  48. if (element == null)
  49. throw new ArgumentNullException(nameof(element));
  50. this.element = element;
  51. this.declaringEntity = declaringEntity;
  52. this.crefResolver = crefResolver;
  53. }
  54. /// <summary>
  55. /// Creates a new documentation element.
  56. /// </summary>
  57. public XmlDocumentationElement(string text, IEntity? declaringEntity)
  58. {
  59. if (text == null)
  60. throw new ArgumentNullException(nameof(text));
  61. this.declaringEntity = declaringEntity;
  62. this.textContent = text;
  63. }
  64. /// <summary>
  65. /// Gets the entity on which this documentation was originally declared.
  66. /// May return null.
  67. /// </summary>
  68. public IEntity? DeclaringEntity {
  69. get { return declaringEntity; }
  70. }
  71. IEntity? referencedEntity;
  72. volatile bool referencedEntityInitialized;
  73. /// <summary>
  74. /// Gets the entity referenced by the 'cref' attribute.
  75. /// May return null.
  76. /// </summary>
  77. public IEntity? ReferencedEntity {
  78. get {
  79. if (!referencedEntityInitialized)
  80. {
  81. string? cref = GetAttribute("cref");
  82. try
  83. {
  84. if (!string.IsNullOrEmpty(cref) && crefResolver != null)
  85. referencedEntity = crefResolver(cref!);
  86. }
  87. catch
  88. {
  89. referencedEntity = null;
  90. }
  91. referencedEntityInitialized = true;
  92. }
  93. return referencedEntity;
  94. }
  95. }
  96. /// <summary>
  97. /// Gets the element name.
  98. /// </summary>
  99. public string Name {
  100. get {
  101. return element != null ? element.Name.LocalName : string.Empty;
  102. }
  103. }
  104. /// <summary>
  105. /// Gets the attribute value.
  106. /// </summary>
  107. public string? GetAttribute(string? name)
  108. {
  109. return name == null ? null : element?.Attribute(name)?.Value;
  110. }
  111. /// <summary>
  112. /// Gets whether this is a pure text node.
  113. /// </summary>
  114. public bool IsTextNode {
  115. get { return element == null; }
  116. }
  117. /// <summary>
  118. /// Gets the text content.
  119. /// </summary>
  120. public string TextContent {
  121. get {
  122. if (textContent == null)
  123. {
  124. StringBuilder b = new StringBuilder();
  125. foreach (var child in this.Children)
  126. b.Append(child.TextContent);
  127. textContent = b.ToString();
  128. }
  129. return textContent;
  130. }
  131. }
  132. IList<XmlDocumentationElement>? children;
  133. /// <summary>
  134. /// Gets the child elements.
  135. /// </summary>
  136. public IList<XmlDocumentationElement> Children {
  137. get {
  138. if (element == null)
  139. return EmptyList<XmlDocumentationElement>.Instance;
  140. return LazyInitializer.EnsureInitialized(
  141. ref this.children,
  142. () => CreateElements(element.Nodes(), declaringEntity, crefResolver, nestingLevel))!;
  143. }
  144. }
  145. static readonly string[] doNotInheritIfAlreadyPresent = {
  146. "example", "exclude", "filterpriority", "preliminary", "summary",
  147. "remarks", "returns", "threadsafety", "value"
  148. };
  149. static List<XmlDocumentationElement> CreateElements(IEnumerable<XObject?> childObjects, IEntity? declaringEntity, Func<string, IEntity?>? crefResolver, int nestingLevel)
  150. {
  151. List<XmlDocumentationElement> list = new List<XmlDocumentationElement>();
  152. foreach (var child in childObjects)
  153. {
  154. var childText = child as XText;
  155. var childTag = child as XCData;
  156. var childElement = child as XElement;
  157. if (childText != null)
  158. {
  159. list.Add(new XmlDocumentationElement(childText.Value, declaringEntity));
  160. }
  161. else if (childTag != null)
  162. {
  163. list.Add(new XmlDocumentationElement(childTag.Value, declaringEntity));
  164. }
  165. else if (childElement != null)
  166. {
  167. if (nestingLevel < 5 && childElement.Name == "inheritdoc")
  168. {
  169. string? cref = childElement.Attribute("cref")?.Value;
  170. IEntity? inheritedFrom = null;
  171. string? inheritedDocumentation = null;
  172. if (cref != null && crefResolver != null)
  173. {
  174. inheritedFrom = crefResolver(cref);
  175. if (inheritedFrom != null)
  176. inheritedDocumentation = "<doc>" + inheritedFrom.GetDocumentation() + "</doc>";
  177. }
  178. else
  179. {
  180. foreach (IMember baseMember in InheritanceHelper.GetBaseMembers((IMember?)declaringEntity, includeImplementedInterfaces: true))
  181. {
  182. inheritedDocumentation = baseMember.GetDocumentation();
  183. if (inheritedDocumentation != null)
  184. {
  185. inheritedFrom = baseMember;
  186. inheritedDocumentation = "<doc>" + inheritedDocumentation + "</doc>";
  187. break;
  188. }
  189. }
  190. }
  191. if (inheritedDocumentation != null)
  192. {
  193. var doc = XDocument.Parse(inheritedDocumentation).Element("doc");
  194. // XPath filter not yet implemented
  195. if (doc != null && childElement.Parent?.Parent == null && childElement.Attribute("select")?.Value == null)
  196. {
  197. // Inheriting documentation at the root level
  198. List<string> doNotInherit = new List<string>();
  199. doNotInherit.Add("overloads");
  200. doNotInherit.AddRange(childObjects.OfType<XElement>().Select(e => e.Name.LocalName).Intersect(
  201. doNotInheritIfAlreadyPresent));
  202. var inheritedChildren = doc.Nodes().Where(
  203. inheritedObject => {
  204. XElement? inheritedElement = inheritedObject as XElement;
  205. return !(inheritedElement != null && doNotInherit.Contains(inheritedElement.Name.LocalName));
  206. });
  207. list.AddRange(CreateElements(inheritedChildren, inheritedFrom, crefResolver, nestingLevel + 1));
  208. }
  209. }
  210. }
  211. else
  212. {
  213. list.Add(new XmlDocumentationElement(childElement, declaringEntity, crefResolver) { nestingLevel = nestingLevel });
  214. }
  215. }
  216. }
  217. if (list.Count > 0 && list[0].IsTextNode)
  218. {
  219. if (string.IsNullOrWhiteSpace(list[0].textContent))
  220. list.RemoveAt(0);
  221. else
  222. list[0].textContent = list[0].textContent!.TrimStart();
  223. }
  224. if (list.Count > 0 && list[list.Count - 1].IsTextNode)
  225. {
  226. if (string.IsNullOrWhiteSpace(list[list.Count - 1].textContent))
  227. list.RemoveAt(list.Count - 1);
  228. else
  229. list[list.Count - 1].textContent = list[list.Count - 1].textContent!.TrimEnd();
  230. }
  231. return list;
  232. }
  233. /// <inheritdoc/>
  234. public override string ToString()
  235. {
  236. if (element != null)
  237. return "<" + element.Name + ">";
  238. else
  239. return this.TextContent;
  240. }
  241. }
  242. }