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
(?=\\k\>)",
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)
{