Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for <inheritdoc /> #972

Merged
merged 18 commits into from
Oct 29, 2022
Merged
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 @@ internal void WriteSection(
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 @@ 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)
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;
}
}
}
Loading