Skip to content

Commit

Permalink
Add support for <inheritdoc /> (#972)
Browse files Browse the repository at this point in the history
  • Loading branch information
josefpihrt committed Oct 29, 2022
1 parent 3d9775d commit 2a8f3da
Show file tree
Hide file tree
Showing 7 changed files with 205 additions and 32 deletions.
1 change: 1 addition & 0 deletions ChangeLog.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 `<inheritdoc />` when generating documentation (`generate-doc` command) ([#972](https://github.com/josefpihrt/roslynator/pull/972)).

### Changed

Expand Down
6 changes: 3 additions & 3 deletions src/CommandLine/Html/SymbolDefinitionHtmlWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down Expand Up @@ -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)
{
Expand All @@ -767,7 +767,7 @@ private void WriteDocumentationCommentToolTip(ISymbol symbol)

var hasExceptions = false;

using (IEnumerator<XElement> en = xmlDocumentation.Elements(WellKnownXmlTags.Exception).GetEnumerator())
using (IEnumerator<XElement> en = xmlDocumentation.GetElements(WellKnownXmlTags.Exception).GetEnumerator())
{
if (en.MoveNext())
{
Expand Down
6 changes: 3 additions & 3 deletions src/Documentation/DocumentationGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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:
{
Expand Down Expand Up @@ -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:
{
Expand Down
13 changes: 12 additions & 1 deletion src/Documentation/DocumentationModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public sealed class DocumentationModel

private readonly Dictionary<IAssemblySymbol, XmlDocumentation> _xmlDocumentations;

private ImmutableArray<string> _additionalXmlDocumentationPaths;
private readonly ImmutableArray<string> _additionalXmlDocumentationPaths;

private ImmutableArray<XmlDocumentation> _additionalXmlDocumentations;

Expand Down Expand Up @@ -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);
Expand Down
26 changes: 13 additions & 13 deletions src/Documentation/DocumentationWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -546,7 +546,7 @@ public virtual void WriteTypeParameters(ImmutableArray<ITypeParameterSymbol> 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)
{
Expand Down Expand Up @@ -585,7 +585,7 @@ public virtual void WriteParameters(ImmutableArray<IParameterSymbol> 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)
{
Expand Down Expand Up @@ -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;
Expand All @@ -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:
Expand All @@ -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;
}
}
Expand All @@ -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;
}
}
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -988,7 +988,7 @@ public virtual void WriteEnumFields(IEnumerable<IFieldSymbol> fields, INamedType
if (xmlDocumentation != null)
{
WriteStartTableCell();
xmlDocumentation?.Element(WellKnownXmlTags.Summary)?.WriteContentTo(this, inlineOnly: true);
xmlDocumentation?.GetElement(WellKnownXmlTags.Summary)?.WriteContentTo(this, inlineOnly: true);
WriteEndTableCell();
}

Expand Down Expand Up @@ -1098,7 +1098,7 @@ public virtual void WriteSeeAlso(ISymbol symbol, SymbolXmlDocumentation xmlDocum

IEnumerable<ISymbol> GetSymbols()
{
foreach (XElement element in xmlDocumentation.Elements(WellKnownXmlTags.SeeAlso))
foreach (XElement element in xmlDocumentation.GetElements(WellKnownXmlTags.SeeAlso))
{
string commentId = element.Attribute("cref")?.Value;

Expand Down Expand Up @@ -1147,7 +1147,7 @@ public virtual void WriteAppliesTo(ISymbol symbol, ImmutableArray<SourceReferenc
string elementName,
int headingLevelBase = 0)
{
XElement element = xmlDocumentation.Element(elementName);
XElement element = xmlDocumentation.GetElement(elementName);

if (element == null)
return;
Expand Down Expand Up @@ -1365,17 +1365,17 @@ static string CreateLocalLink(ISymbol symbol)

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)
Expand Down
161 changes: 161 additions & 0 deletions src/Documentation/InheritDocUtility.cs
Original file line number Diff line number Diff line change
@@ -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<ISymbol, XElement> 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<ISymbol, XElement> 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<IParameterSymbol> parameters1 = x.Parameters;
ImmutableArray<IParameterSymbol> 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<ISymbol, XElement> 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<ISymbol, XElement> 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<ISymbol, XElement> 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<ISymbol, XElement> 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<XElement> 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;
}
}
}

0 comments on commit 2a8f3da

Please sign in to comment.