From 2a8f3daa81c57cd76ead3e8cc696f1a9c1d1e9b3 Mon Sep 17 00:00:00 2001 From: Josef Pihrt Date: Sat, 29 Oct 2022 13:55:59 +0200 Subject: [PATCH] Add support for (#972) --- ChangeLog.md | 1 + .../Html/SymbolDefinitionHtmlWriter.cs | 6 +- src/Documentation/DocumentationGenerator.cs | 6 +- src/Documentation/DocumentationModel.cs | 13 +- src/Documentation/DocumentationWriter.cs | 26 +-- src/Documentation/InheritDocUtility.cs | 161 ++++++++++++++++++ src/Documentation/SymbolXmlDocumentation.cs | 24 +-- 7 files changed, 205 insertions(+), 32 deletions(-) create mode 100644 src/Documentation/InheritDocUtility.cs diff --git a/ChangeLog.md b/ChangeLog.md index 64eb67c169..1eb4cd1d0b 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `roslynator generate-doc --host docusaurus` - [CLI] Generate reference documentation that can be published with Sphinx ([#961](https://github.com/josefpihrt/roslynator/pull/961)). - `roslynator generate-doc --host sphinx` +- [CLI] Basic support for `` when generating documentation (`generate-doc` command) ([#972](https://github.com/josefpihrt/roslynator/pull/972)). ### Changed diff --git a/src/CommandLine/Html/SymbolDefinitionHtmlWriter.cs b/src/CommandLine/Html/SymbolDefinitionHtmlWriter.cs index 5233f0fad3..f07bb58011 100644 --- a/src/CommandLine/Html/SymbolDefinitionHtmlWriter.cs +++ b/src/CommandLine/Html/SymbolDefinitionHtmlWriter.cs @@ -502,7 +502,7 @@ protected override void WriteParameterName(ISymbol symbol, SymbolDisplayPart par { XElement element = DocumentationProvider? .GetXmlDocumentation(symbol)? - .Element(WellKnownXmlTags.Param, "name", part.ToString()); + .GetElement(WellKnownXmlTags.Param, "name", part.ToString()); if (element != null) { @@ -757,7 +757,7 @@ private void WriteDocumentationCommentToolTip(ISymbol symbol) if (xmlDocumentation == null) return; - XElement summaryElement = xmlDocumentation.Element(WellKnownXmlTags.Summary); + XElement summaryElement = xmlDocumentation.GetElement(WellKnownXmlTags.Summary); if (summaryElement != null) { @@ -767,7 +767,7 @@ private void WriteDocumentationCommentToolTip(ISymbol symbol) var hasExceptions = false; - using (IEnumerator en = xmlDocumentation.Elements(WellKnownXmlTags.Exception).GetEnumerator()) + using (IEnumerator en = xmlDocumentation.GetElements(WellKnownXmlTags.Exception).GetEnumerator()) { if (en.MoveNext()) { diff --git a/src/Documentation/DocumentationGenerator.cs b/src/Documentation/DocumentationGenerator.cs index b3987c0362..90ec85e29e 100644 --- a/src/Documentation/DocumentationGenerator.cs +++ b/src/Documentation/DocumentationGenerator.cs @@ -361,7 +361,7 @@ private DocumentationGeneratorResult GenerateNamespace(INamespaceSymbol namespac } case NamespaceDocumentationParts.Summary: { - xmlDocumentation?.Element(WellKnownXmlTags.Summary)?.WriteContentTo(writer); + xmlDocumentation?.GetElement(WellKnownXmlTags.Summary)?.WriteContentTo(writer); break; } case NamespaceDocumentationParts.Examples: @@ -511,7 +511,7 @@ bool HasContent(NamespaceDocumentationParts part) } case NamespaceDocumentationParts.SeeAlso: { - return xmlDocumentation?.Elements(WellKnownXmlTags.SeeAlso).Any() == true; + return xmlDocumentation?.GetElements(WellKnownXmlTags.SeeAlso).Any() == true; } default: { @@ -903,7 +903,7 @@ bool HasContent(TypeDocumentationParts part) } case TypeDocumentationParts.SeeAlso: { - return xmlDocumentation?.Elements(WellKnownXmlTags.SeeAlso).Any() == true; + return xmlDocumentation?.GetElements(WellKnownXmlTags.SeeAlso).Any() == true; } default: { diff --git a/src/Documentation/DocumentationModel.cs b/src/Documentation/DocumentationModel.cs index b7ac22141c..ea403b64fc 100644 --- a/src/Documentation/DocumentationModel.cs +++ b/src/Documentation/DocumentationModel.cs @@ -22,7 +22,7 @@ public sealed class DocumentationModel private readonly Dictionary _xmlDocumentations; - private ImmutableArray _additionalXmlDocumentationPaths; + private readonly ImmutableArray _additionalXmlDocumentationPaths; private ImmutableArray _additionalXmlDocumentations; @@ -316,6 +316,17 @@ public SymbolXmlDocumentation GetXmlDocumentation(ISymbol symbol, string preferr { var element = XElement.Parse(xml, LoadOptions.PreserveWhitespace); + if (InheritDocUtility.ContainsInheritDoc(element)) + { + XElement inheritedElement = InheritDocUtility.FindInheritedDocumentation(symbol, s => GetXmlDocumentation(s, preferredCultureName)?.Element); + + if (inheritedElement is not null) + { + element.RemoveNodes(); + element.Add(inheritedElement.Elements()); + } + } + xmlDocumentation = new SymbolXmlDocumentation(symbol, element); _symbolData[symbol] = data.WithXmlDocumentation(xmlDocumentation); diff --git a/src/Documentation/DocumentationWriter.cs b/src/Documentation/DocumentationWriter.cs index 8573705b31..6dae6c4a72 100644 --- a/src/Documentation/DocumentationWriter.cs +++ b/src/Documentation/DocumentationWriter.cs @@ -546,7 +546,7 @@ public virtual void WriteTypeParameters(ImmutableArray typ { WriteBold(en.Current.Name); - XElement element = GetXmlDocumentation(en.Current.ContainingSymbol)?.Element(WellKnownXmlTags.TypeParam, "name", en.Current.Name); + XElement element = GetXmlDocumentation(en.Current.ContainingSymbol)?.GetElement(WellKnownXmlTags.TypeParam, "name", en.Current.Name); if (element?.Nodes().Any() == true) { @@ -585,7 +585,7 @@ public virtual void WriteParameters(ImmutableArray parameters) WriteSpace(); WriteTypeLink(en.Current.Type, includeContainingNamespace: Options.IncludeContainingNamespace(IncludeContainingNamespaceFilter.Parameter)); - XElement element = GetXmlDocumentation(en.Current.ContainingSymbol)?.Element(WellKnownXmlTags.Param, "name", en.Current.Name); + XElement element = GetXmlDocumentation(en.Current.ContainingSymbol)?.GetElement(WellKnownXmlTags.Param, "name", en.Current.Name); if (element?.Nodes().Any() == true) { @@ -627,7 +627,7 @@ public virtual void WriteReturnType(ISymbol symbol, SymbolXmlDocumentation xmlDo WriteReturnType(returnType, Resources.ReturnValueTitle); - xmlDocumentation?.Element(WellKnownXmlTags.Returns)?.WriteContentTo(this); + xmlDocumentation?.GetElement(WellKnownXmlTags.Returns)?.WriteContentTo(this); } break; @@ -650,7 +650,7 @@ public virtual void WriteReturnType(ISymbol symbol, SymbolXmlDocumentation xmlDo { WriteReturnType(methodSymbol.ReturnType, Resources.ReturnsTitle); - xmlDocumentation?.Element(WellKnownXmlTags.Returns)?.WriteContentTo(this); + xmlDocumentation?.GetElement(WellKnownXmlTags.Returns)?.WriteContentTo(this); break; } default: @@ -662,7 +662,7 @@ public virtual void WriteReturnType(ISymbol symbol, SymbolXmlDocumentation xmlDo WriteReturnType(returnType, Resources.ReturnsTitle); - xmlDocumentation?.Element(WellKnownXmlTags.Returns)?.WriteContentTo(this); + xmlDocumentation?.GetElement(WellKnownXmlTags.Returns)?.WriteContentTo(this); break; } } @@ -677,7 +677,7 @@ public virtual void WriteReturnType(ISymbol symbol, SymbolXmlDocumentation xmlDo string elementName = (propertySymbol.IsIndexer) ? WellKnownXmlTags.Returns : WellKnownXmlTags.Value; - xmlDocumentation?.Element(elementName)?.WriteContentTo(this); + xmlDocumentation?.GetElement(elementName)?.WriteContentTo(this); break; } } @@ -894,7 +894,7 @@ public virtual void WriteExceptions(ISymbol symbol, SymbolXmlDocumentation xmlDo IEnumerable<(XElement element, INamedTypeSymbol exceptionSymbol)> GetExceptions() { - foreach (XElement element in xmlDocumentation.Elements(WellKnownXmlTags.Exception)) + foreach (XElement element in xmlDocumentation.GetElements(WellKnownXmlTags.Exception)) { string commentId = element.Attribute("cref")?.Value; @@ -988,7 +988,7 @@ public virtual void WriteEnumFields(IEnumerable fields, INamedType if (xmlDocumentation != null) { WriteStartTableCell(); - xmlDocumentation?.Element(WellKnownXmlTags.Summary)?.WriteContentTo(this, inlineOnly: true); + xmlDocumentation?.GetElement(WellKnownXmlTags.Summary)?.WriteContentTo(this, inlineOnly: true); WriteEndTableCell(); } @@ -1098,7 +1098,7 @@ public virtual void WriteSeeAlso(ISymbol symbol, SymbolXmlDocumentation xmlDocum IEnumerable GetSymbols() { - foreach (XElement element in xmlDocumentation.Elements(WellKnownXmlTags.SeeAlso)) + foreach (XElement element in xmlDocumentation.GetElements(WellKnownXmlTags.SeeAlso)) { string commentId = element.Attribute("cref")?.Value; @@ -1147,7 +1147,7 @@ internal void WriteSection( string elementName, int headingLevelBase = 0) { - XElement element = xmlDocumentation.Element(elementName); + XElement element = xmlDocumentation.GetElement(elementName); if (element == null) return; @@ -1365,17 +1365,17 @@ internal void WriteTable( if (symbol.Kind == SymbolKind.Parameter) { - GetXmlDocumentation(symbol.ContainingSymbol)?.Element(WellKnownXmlTags.Param, "name", symbol.Name)?.WriteContentTo(this); + GetXmlDocumentation(symbol.ContainingSymbol)?.GetElement(WellKnownXmlTags.Param, "name", symbol.Name)?.WriteContentTo(this); } else if (symbol.Kind == SymbolKind.TypeParameter) { - GetXmlDocumentation(symbol.ContainingSymbol)?.Element(WellKnownXmlTags.TypeParam, "name", symbol.Name)?.WriteContentTo(this); + GetXmlDocumentation(symbol.ContainingSymbol)?.GetElement(WellKnownXmlTags.TypeParam, "name", symbol.Name)?.WriteContentTo(this); } else { ISymbol symbol2 = (isInherited) ? symbol.OriginalDefinition : symbol; - GetXmlDocumentation(symbol2)?.Element(WellKnownXmlTags.Summary)?.WriteContentTo(this, inlineOnly: true); + GetXmlDocumentation(symbol2)?.GetElement(WellKnownXmlTags.Summary)?.WriteContentTo(this, inlineOnly: true); } if (isInherited) diff --git a/src/Documentation/InheritDocUtility.cs b/src/Documentation/InheritDocUtility.cs new file mode 100644 index 0000000000..0773539ece --- /dev/null +++ b/src/Documentation/InheritDocUtility.cs @@ -0,0 +1,161 @@ +// Copyright (c) Josef Pihrt and Contributors. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Xml.Linq; +using Microsoft.CodeAnalysis; + +namespace Roslynator.Documentation +{ + internal static class InheritDocUtility + { + public static XElement FindInheritedDocumentation(ISymbol symbol, Func getDocumentation) + { + if (symbol.IsKind(SymbolKind.Method, SymbolKind.Property, SymbolKind.Event)) + { + if (symbol is IMethodSymbol methodSymbol + && methodSymbol.MethodKind == MethodKind.Constructor) + { + return FindInheritedDocumentationFromBaseConstructor(methodSymbol, getDocumentation); + } + + return FindInheritedDocumentationFromBaseMember(symbol, getDocumentation) + ?? FindInheritedDocumentationFromImplementedInterfaceMember(symbol, getDocumentation); + } + else if (symbol is INamedTypeSymbol namedTypeSymbol) + { + return FindInheritedDocumentationFromBaseType(namedTypeSymbol, getDocumentation) + ?? FindInheritedDocumentationFromImplementedInterface(namedTypeSymbol, getDocumentation); + } + + return null; + } + + private static XElement FindInheritedDocumentationFromBaseConstructor(IMethodSymbol symbol, Func getDocumentation) + { + foreach (INamedTypeSymbol baseType in symbol.ContainingType.BaseTypes()) + { + foreach (IMethodSymbol baseConstructor in baseType.Constructors) + { + if (ParametersEqual(symbol, baseConstructor)) + { + XElement element = getDocumentation(baseConstructor); + + return (ContainsInheritDoc(element)) + ? FindInheritedDocumentationFromBaseConstructor(baseConstructor, getDocumentation) + : element; + } + } + } + + return null; + + static bool ParametersEqual(IMethodSymbol x, IMethodSymbol y) + { + ImmutableArray parameters1 = x.Parameters; + ImmutableArray parameters2 = y.Parameters; + + if (parameters1.Length != parameters2.Length) + return false; + + for (int i = 0; i < parameters1.Length; i++) + { + if (!SymbolEqualityComparer.Default.Equals(parameters1[i].Type, parameters2[i].Type)) + return false; + } + + return true; + } + } + + private static XElement FindInheritedDocumentationFromBaseMember(ISymbol symbol, Func getDocumentation) + { + ISymbol s = symbol; + + while ((s = s.OverriddenSymbol()) is not null) + { + XElement element = getDocumentation(s); + + if (!ContainsInheritDoc(element)) + return element; + } + + return null; + } + + private static XElement FindInheritedDocumentationFromImplementedInterfaceMember(ISymbol symbol, Func getDocumentation) + { + INamedTypeSymbol containingType = symbol.ContainingType; + + if (containingType != null) + { + foreach (INamedTypeSymbol interfaceSymbol in containingType.Interfaces) + { + foreach (ISymbol memberSymbol in interfaceSymbol.GetMembers(symbol.Name)) + { + if (SymbolEqualityComparer.Default.Equals(symbol, containingType.FindImplementationForInterfaceMember(memberSymbol))) + { + XElement element = getDocumentation(memberSymbol); + + return (ContainsInheritDoc(element)) + ? FindInheritedDocumentationFromImplementedInterfaceMember(memberSymbol, getDocumentation) + : element; + } + } + } + } + + return null; + } + + private static XElement FindInheritedDocumentationFromBaseType(INamedTypeSymbol namedTypeSymbol, Func getDocumentation) + { + foreach (INamedTypeSymbol baseType in namedTypeSymbol.BaseTypes()) + { + XElement element = getDocumentation(baseType); + + if (!ContainsInheritDoc(element)) + return element; + } + + return null; + } + + private static XElement FindInheritedDocumentationFromImplementedInterface(INamedTypeSymbol namedTypeSymbol, Func getDocumentation) + { + foreach (INamedTypeSymbol interfaceSymbol in namedTypeSymbol.Interfaces) + { + XElement element = getDocumentation(interfaceSymbol); + + return (ContainsInheritDoc(element)) + ? FindInheritedDocumentationFromImplementedInterface(interfaceSymbol, getDocumentation) + : element; + } + + return null; + } + + public static bool ContainsInheritDoc(XElement element) + { + if (element is not null) + { + using (IEnumerator en = element.Elements().GetEnumerator()) + { + if (en.MoveNext()) + { + XElement e = en.Current; + + if (!en.MoveNext() + && string.Equals(e.Name.LocalName, "inheritdoc", StringComparison.OrdinalIgnoreCase)) + { + return true; + } + } + } + } + + return false; + } + } +} diff --git a/src/Documentation/SymbolXmlDocumentation.cs b/src/Documentation/SymbolXmlDocumentation.cs index 21d50575ec..42f030ca02 100644 --- a/src/Documentation/SymbolXmlDocumentation.cs +++ b/src/Documentation/SymbolXmlDocumentation.cs @@ -21,27 +21,27 @@ public class SymbolXmlDocumentation (?=\\>)", RegexOptions.IgnorePatternWhitespace); - private readonly XElement _element; - internal static SymbolXmlDocumentation Default { get; } = new(null, null); public SymbolXmlDocumentation(ISymbol symbol, XElement element) { Symbol = symbol; - _element = element; + Element = element; } public ISymbol Symbol { get; } + public XElement Element { get; } + [DebuggerBrowsable(DebuggerBrowsableState.Never)] private string DebuggerDisplay { - get { return $"{_element}"; } + get { return $"{Element}"; } } - public XElement Element(string name) + public XElement GetElement(string name) { - foreach (XElement element in _element.Elements()) + foreach (XElement element in Element.Elements()) { if (string.Equals(element.Name.LocalName, name, StringComparison.OrdinalIgnoreCase)) return element; @@ -50,9 +50,9 @@ public XElement Element(string name) return default; } - internal XElement Element(string name, string attributeName, string attributeValue) + internal XElement GetElement(string name, string attributeName, string attributeValue) { - foreach (XElement element in _element.Elements()) + foreach (XElement element in Element.Elements()) { if (string.Equals(element.Name.LocalName, name, StringComparison.OrdinalIgnoreCase) && element.Attribute(attributeName)?.Value == attributeValue) @@ -64,9 +64,9 @@ internal XElement Element(string name, string attributeName, string attributeVal return default; } - public IEnumerable Elements(string name) + public IEnumerable GetElements(string name) { - foreach (XElement element in _element.Elements()) + foreach (XElement element in Element.Elements()) { if (string.Equals(element.Name.LocalName, name, StringComparison.OrdinalIgnoreCase)) yield return element; @@ -75,12 +75,12 @@ public IEnumerable Elements(string name) public bool HasElement(string name) { - return Element(name) != null; + return GetElement(name) != null; } public IEnumerable GetElementsAsText(bool skipEmptyElement = false, bool makeSingleLine = false) { - foreach (XElement element in _element.Elements()) + foreach (XElement element in Element.Elements()) { switch (element.Name.LocalName) {