mirror of https://github.com/icsharpcode/ILSpy.git
				
				
			
			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
						
					
					
				
			
		
		
	
	
							265 lines
						
					
					
						
							8.0 KiB
						
					
					
				// Copyright (c) 2009-2013 AlphaSierraPapa for the SharpDevelop Team | 
						|
//  | 
						|
// Permission is hereby granted, free of charge, to any person obtaining a copy of this | 
						|
// software and associated documentation files (the "Software"), to deal in the Software | 
						|
// without restriction, including without limitation the rights to use, copy, modify, merge, | 
						|
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons | 
						|
// to whom the Software is furnished to do so, subject to the following conditions: | 
						|
//  | 
						|
// The above copyright notice and this permission notice shall be included in all copies or | 
						|
// substantial portions of the Software. | 
						|
//  | 
						|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, | 
						|
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR | 
						|
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE | 
						|
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR | 
						|
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER | 
						|
// DEALINGS IN THE SOFTWARE. | 
						|
 | 
						|
#nullable enable | 
						|
 | 
						|
using System; | 
						|
using System.Collections.Generic; | 
						|
using System.Linq; | 
						|
using System.Text; | 
						|
using System.Threading; | 
						|
using System.Xml.Linq; | 
						|
 | 
						|
using ICSharpCode.Decompiler.TypeSystem; | 
						|
using ICSharpCode.Decompiler.Util; | 
						|
 | 
						|
namespace ICSharpCode.Decompiler.Documentation | 
						|
{ | 
						|
	/// <summary> | 
						|
	/// Represents an element in the XML documentation. | 
						|
	/// Any occurrences of "<inheritdoc/>" are replaced with the inherited documentation. | 
						|
	/// </summary> | 
						|
	public class XmlDocumentationElement | 
						|
	{ | 
						|
		readonly XElement? element; | 
						|
		readonly IEntity? declaringEntity; | 
						|
		readonly Func<string, IEntity?>? crefResolver; | 
						|
		volatile string? textContent; | 
						|
 | 
						|
		/// <summary> | 
						|
		/// Inheritance level; used to prevent cyclic doc inheritance. | 
						|
		/// </summary> | 
						|
		int nestingLevel; | 
						|
 | 
						|
		/// <summary> | 
						|
		/// Creates a new documentation element. | 
						|
		/// </summary> | 
						|
		public XmlDocumentationElement(XElement element, IEntity? declaringEntity, Func<string, IEntity?>? crefResolver) | 
						|
		{ | 
						|
			if (element == null) | 
						|
				throw new ArgumentNullException(nameof(element)); | 
						|
			this.element = element; | 
						|
			this.declaringEntity = declaringEntity; | 
						|
			this.crefResolver = crefResolver; | 
						|
		} | 
						|
 | 
						|
		/// <summary> | 
						|
		/// Creates a new documentation element. | 
						|
		/// </summary> | 
						|
		public XmlDocumentationElement(string text, IEntity? declaringEntity) | 
						|
		{ | 
						|
			if (text == null) | 
						|
				throw new ArgumentNullException(nameof(text)); | 
						|
			this.declaringEntity = declaringEntity; | 
						|
			this.textContent = text; | 
						|
		} | 
						|
 | 
						|
		/// <summary> | 
						|
		/// Gets the entity on which this documentation was originally declared. | 
						|
		/// May return null. | 
						|
		/// </summary> | 
						|
		public IEntity? DeclaringEntity { | 
						|
			get { return declaringEntity; } | 
						|
		} | 
						|
 | 
						|
		IEntity? referencedEntity; | 
						|
		volatile bool referencedEntityInitialized; | 
						|
 | 
						|
		/// <summary> | 
						|
		/// Gets the entity referenced by the 'cref' attribute. | 
						|
		/// May return null. | 
						|
		/// </summary> | 
						|
		public IEntity? ReferencedEntity { | 
						|
			get { | 
						|
				if (!referencedEntityInitialized) | 
						|
				{ | 
						|
					string? cref = GetAttribute("cref"); | 
						|
					try | 
						|
					{ | 
						|
						if (!string.IsNullOrEmpty(cref) && crefResolver != null) | 
						|
							referencedEntity = crefResolver(cref!); | 
						|
					} | 
						|
					catch | 
						|
					{ | 
						|
						referencedEntity = null; | 
						|
					} | 
						|
					referencedEntityInitialized = true; | 
						|
				} | 
						|
				return referencedEntity; | 
						|
			} | 
						|
		} | 
						|
 | 
						|
		/// <summary> | 
						|
		/// Gets the element name. | 
						|
		/// </summary> | 
						|
		public string Name { | 
						|
			get { | 
						|
				return element != null ? element.Name.LocalName : string.Empty; | 
						|
			} | 
						|
		} | 
						|
 | 
						|
		/// <summary> | 
						|
		/// Gets the attribute value. | 
						|
		/// </summary> | 
						|
		public string? GetAttribute(string? name) | 
						|
		{ | 
						|
			return name == null ? null : element?.Attribute(name)?.Value; | 
						|
		} | 
						|
 | 
						|
		/// <summary> | 
						|
		/// Gets whether this is a pure text node. | 
						|
		/// </summary> | 
						|
		public bool IsTextNode { | 
						|
			get { return element == null; } | 
						|
		} | 
						|
 | 
						|
		/// <summary> | 
						|
		/// Gets the text content. | 
						|
		/// </summary> | 
						|
		public string TextContent { | 
						|
			get { | 
						|
				if (textContent == null) | 
						|
				{ | 
						|
					StringBuilder b = new StringBuilder(); | 
						|
					foreach (var child in this.Children) | 
						|
						b.Append(child.TextContent); | 
						|
					textContent = b.ToString(); | 
						|
				} | 
						|
				return textContent; | 
						|
			} | 
						|
		} | 
						|
 | 
						|
		IList<XmlDocumentationElement>? children; | 
						|
 | 
						|
		/// <summary> | 
						|
		/// Gets the child elements. | 
						|
		/// </summary> | 
						|
		public IList<XmlDocumentationElement> Children { | 
						|
			get { | 
						|
				if (element == null) | 
						|
					return EmptyList<XmlDocumentationElement>.Instance; | 
						|
				return LazyInitializer.EnsureInitialized( | 
						|
					ref this.children, | 
						|
					() => CreateElements(element.Nodes(), declaringEntity, crefResolver, nestingLevel))!; | 
						|
			} | 
						|
		} | 
						|
 | 
						|
		static readonly string[] doNotInheritIfAlreadyPresent = { | 
						|
			"example", "exclude", "filterpriority", "preliminary", "summary", | 
						|
			"remarks", "returns", "threadsafety", "value" | 
						|
		}; | 
						|
 | 
						|
		static List<XmlDocumentationElement> CreateElements(IEnumerable<XObject?> childObjects, IEntity? declaringEntity, Func<string, IEntity?>? crefResolver, int nestingLevel) | 
						|
		{ | 
						|
			List<XmlDocumentationElement> list = new List<XmlDocumentationElement>(); | 
						|
			foreach (var child in childObjects) | 
						|
			{ | 
						|
				var childText = child as XText; | 
						|
				var childTag = child as XCData; | 
						|
				var childElement = child as XElement; | 
						|
				if (childText != null) | 
						|
				{ | 
						|
					list.Add(new XmlDocumentationElement(childText.Value, declaringEntity)); | 
						|
				} | 
						|
				else if (childTag != null) | 
						|
				{ | 
						|
					list.Add(new XmlDocumentationElement(childTag.Value, declaringEntity)); | 
						|
				} | 
						|
				else if (childElement != null) | 
						|
				{ | 
						|
					if (nestingLevel < 5 && childElement.Name == "inheritdoc") | 
						|
					{ | 
						|
						string? cref = childElement.Attribute("cref")?.Value; | 
						|
						IEntity? inheritedFrom = null; | 
						|
						string? inheritedDocumentation = null; | 
						|
						if (cref != null && crefResolver != null) | 
						|
						{ | 
						|
							inheritedFrom = crefResolver(cref); | 
						|
							if (inheritedFrom != null) | 
						|
								inheritedDocumentation = "<doc>" + inheritedFrom.GetDocumentation() + "</doc>"; | 
						|
						} | 
						|
						else | 
						|
						{ | 
						|
							foreach (IMember baseMember in InheritanceHelper.GetBaseMembers((IMember?)declaringEntity, includeImplementedInterfaces: true)) | 
						|
							{ | 
						|
								inheritedDocumentation = baseMember.GetDocumentation(); | 
						|
								if (inheritedDocumentation != null) | 
						|
								{ | 
						|
									inheritedFrom = baseMember; | 
						|
									inheritedDocumentation = "<doc>" + inheritedDocumentation + "</doc>"; | 
						|
									break; | 
						|
								} | 
						|
							} | 
						|
						} | 
						|
 | 
						|
						if (inheritedDocumentation != null) | 
						|
						{ | 
						|
							var doc = XDocument.Parse(inheritedDocumentation).Element("doc"); | 
						|
 | 
						|
							// XPath filter not yet implemented | 
						|
							if (doc != null && childElement.Parent?.Parent == null && childElement.Attribute("select")?.Value == null) | 
						|
							{ | 
						|
								// Inheriting documentation at the root level | 
						|
								List<string> doNotInherit = new List<string>(); | 
						|
								doNotInherit.Add("overloads"); | 
						|
								doNotInherit.AddRange(childObjects.OfType<XElement>().Select(e => e.Name.LocalName).Intersect( | 
						|
									doNotInheritIfAlreadyPresent)); | 
						|
 | 
						|
								var inheritedChildren = doc.Nodes().Where( | 
						|
									inheritedObject => { | 
						|
										XElement? inheritedElement = inheritedObject as XElement; | 
						|
										return !(inheritedElement != null && doNotInherit.Contains(inheritedElement.Name.LocalName)); | 
						|
									}); | 
						|
 | 
						|
								list.AddRange(CreateElements(inheritedChildren, inheritedFrom, crefResolver, nestingLevel + 1)); | 
						|
							} | 
						|
						} | 
						|
					} | 
						|
					else | 
						|
					{ | 
						|
						list.Add(new XmlDocumentationElement(childElement, declaringEntity, crefResolver) { nestingLevel = nestingLevel }); | 
						|
					} | 
						|
				} | 
						|
			} | 
						|
			if (list.Count > 0 && list[0].IsTextNode) | 
						|
			{ | 
						|
				if (string.IsNullOrWhiteSpace(list[0].textContent)) | 
						|
					list.RemoveAt(0); | 
						|
				else | 
						|
					list[0].textContent = list[0].textContent!.TrimStart(); | 
						|
			} | 
						|
			if (list.Count > 0 && list[list.Count - 1].IsTextNode) | 
						|
			{ | 
						|
				if (string.IsNullOrWhiteSpace(list[list.Count - 1].textContent)) | 
						|
					list.RemoveAt(list.Count - 1); | 
						|
				else | 
						|
					list[list.Count - 1].textContent = list[list.Count - 1].textContent!.TrimEnd(); | 
						|
			} | 
						|
			return list; | 
						|
		} | 
						|
 | 
						|
		/// <inheritdoc/> | 
						|
		public override string ToString() | 
						|
		{ | 
						|
			if (element != null) | 
						|
				return "<" + element.Name + ">"; | 
						|
			else | 
						|
				return this.TextContent; | 
						|
		} | 
						|
	} | 
						|
}
 | 
						|
 |