Skip to content

Commit

Permalink
CodeAnalysis module and docs recipe now supports extension methods, #342
Browse files Browse the repository at this point in the history
  • Loading branch information
daveaglick committed Dec 12, 2016
1 parent e0f0874 commit bba21b0
Show file tree
Hide file tree
Showing 9 changed files with 158 additions and 5 deletions.
8 changes: 8 additions & 0 deletions examples/CodeAnalysis/output/global/A/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,16 @@ <h1>A</h1>

<h2>Members</h2>

<li>~Object() - </li>
<li>ABar - &#xA; A class walks into a Bar...&#xA; </li>
<li>AFoo() - &#xA; This method does some stuff.&#xA; </li>
<li>Equals(object) - </li>
<li>Equals(object, object) - </li>
<li>GetHashCode() - </li>
<li>GetType() - </li>
<li>MemberwiseClone() - </li>
<li>ReferenceEquals(object, object) - </li>
<li>ToString() - </li>



Expand Down
8 changes: 8 additions & 0 deletions examples/CodeAnalysis/output/global/B/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,16 @@ <h1>B</h1>

<h2>Members</h2>

<li>~Object() - </li>
<li>BBar - &#xA; A property that returns a bool.&#xA; </li>
<li>BFoo() - &#xA; A method that returns an int.&#xA; </li>
<li>Equals(object) - </li>
<li>Equals(object, object) - </li>
<li>GetHashCode() - </li>
<li>GetType() - </li>
<li>MemberwiseClone() - </li>
<li>ReferenceEquals(object, object) - </li>
<li>ToString() - </li>



Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@ internal class AnalyzeSymbolVisitor : SymbolVisitor
private readonly ConcurrentDictionary<string, ConcurrentHashSet<INamespaceSymbol>> _namespaceDisplayNameToSymbols = new ConcurrentDictionary<string, ConcurrentHashSet<INamespaceSymbol>>();
private readonly ConcurrentDictionary<ISymbol, IDocument> _symbolToDocument = new ConcurrentDictionary<ISymbol, IDocument>();
private readonly ConcurrentDictionary<string, IDocument> _commentIdToDocument = new ConcurrentDictionary<string, IDocument>();
private readonly ConcurrentHashSet<IMethodSymbol> _extensionMethods = new ConcurrentHashSet<IMethodSymbol>();
private ImmutableArray<KeyValuePair<INamedTypeSymbol, IDocument>> _namedTypes; // This contains all of the NamedType symbols and documents obtained during the initial processing

private readonly Compilation _compilation;
private readonly IExecutionContext _context;
private readonly Func<ISymbol, bool> _symbolPredicate;
Expand Down Expand Up @@ -134,6 +136,7 @@ public override void VisitNamedType(INamedTypeSymbol symbol)
{ CodeAnalysisKeys.BaseTypes, DocumentsFor(GetBaseTypes(symbol)) },
{ CodeAnalysisKeys.AllInterfaces, DocumentsFor(symbol.AllInterfaces) },
{ CodeAnalysisKeys.Members, DocumentsFor(GetAccessibleMembersInThisAndBaseTypes(symbol, symbol).Where(MemberPredicate)) },
{ CodeAnalysisKeys.ExtensionMethods, _ => DocumentsFor(_extensionMethods.Where(x => x.ReduceExtensionMethod(symbol) != null)) },
{ CodeAnalysisKeys.Constructors,
DocumentsFor(symbol.Constructors.Where(x => !x.IsImplicitlyDeclared)) },
{ CodeAnalysisKeys.TypeParameters, DocumentsFor(symbol.TypeParameters) },
Expand Down Expand Up @@ -189,6 +192,12 @@ public override void VisitParameter(IParameterSymbol symbol)

public override void VisitMethod(IMethodSymbol symbol)
{
// If this is an extension method, record it
if (!_finished && symbol.IsExtensionMethod)
{
_extensionMethods.Add(symbol);
}

if (_finished || _symbolPredicate == null || _symbolPredicate(symbol))
{
AddMemberDocument(symbol, true, new MetadataItems
Expand Down
1 change: 1 addition & 0 deletions src/extensions/Wyam.CodeAnalysis/CodeAnalysisKeys.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ public static class CodeAnalysisKeys
public const string BaseTypes = nameof(BaseTypes); // IReadOnlyList<IDocument>
public const string AllInterfaces = nameof(AllInterfaces); // IReadOnlyList<IDocument>
public const string Members = nameof(Members); // IReadOnlyList<IDocument>
public const string ExtensionMethods = nameof(ExtensionMethods); // IReadOnlyList<IDocument>
public const string DerivedTypes = nameof(DerivedTypes); // IReadOnlyList<IDocument>
public const string ImplementingTypes = nameof(ImplementingTypes); // IReadOnlyList<IDocument>
public const string Constructors = nameof(Constructors); // IReadOnlyList<IDocument>
Expand Down
3 changes: 3 additions & 0 deletions src/extensions/Wyam.CodeAnalysis/Documentation.xml
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,9 @@
This is available for type symbols and contains a collection of the documents that represent all members of the type, including inherited ones. The collection
is empty if the type doesn't have any members.
</metadata>
<metadata name="ExtensionMethods" type="IReadOnlyList&lt;IDocument&gt;">
This is available for type symbols and contains a collection of the documents that represent all extension members applicable to the type.
</metadata>
<metadata name="DerivedTypes" type="IReadOnlyList&lt;IDocument&gt;">
This is available for type symbols and contains a collection of the documents that represent all types derived from the type. The collection
is empty if the type doesn't have any derived types.
Expand Down
17 changes: 12 additions & 5 deletions src/extensions/Wyam.Docs/Extensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Linq;
using System.Text;
using Microsoft.AspNetCore.Html;
using Wyam.CodeAnalysis;
using Wyam.Common.Documents;
using Wyam.Common.Execution;
using Wyam.Common.Meta;
Expand All @@ -15,9 +16,10 @@ namespace Wyam.Docs
/// </summary>
public static class Extensions
{
public static HtmlString Name(this IMetadata metadata)
public static HtmlString Name(this IMetadata metadata) => FormatName(metadata.String(CodeAnalysisKeys.DisplayName));

private static HtmlString FormatName(string name)
{
string name = metadata.String("DisplayName");
if (name == null)
{
return new HtmlString(string.Empty);
Expand Down Expand Up @@ -49,7 +51,11 @@ public static HtmlString Name(this IMetadata metadata)
return new HtmlString(replaced ? string.Join("<wbr>", segments) : name);
}

public static HtmlString GetTypeLink(this IExecutionContext context, IMetadata metadata)
public static HtmlString GetTypeLink(this IExecutionContext context, IMetadata metadata) => context.GetTypeLink(metadata, metadata.Name());

public static HtmlString GetTypeLink(this IExecutionContext context, IMetadata metadata, string name) => context.GetTypeLink(metadata, name == null ? null : FormatName(name));

public static HtmlString GetTypeLink(this IExecutionContext context, IMetadata metadata, HtmlString name)
{
if (metadata.String("Kind") == "TypeParameter")
{
Expand All @@ -62,10 +68,11 @@ public static HtmlString GetTypeLink(this IExecutionContext context, IMetadata m
}
}
return metadata.ContainsKey("WritePath")
? new HtmlString($"<a href=\"{context.GetLink(metadata.FilePath("WritePath"))}\">{metadata.Name()}</a>")
: metadata.Name();
? new HtmlString($"<a href=\"{context.GetLink(metadata.FilePath("WritePath"))}\">{name ?? metadata.Name()}</a>")
: (name ?? metadata.Name());
}


/// <summary>
/// Generates links to each heading on a page and returns a string containing all of the links.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,72 @@ enum Orange
CollectionAssert.AreEquivalent(new[] { string.Empty, "Foo", "Blue", "Green", "Red", "Yellow", "Orange" }, results.Select(x => x["Name"]));
}

[Test]
public void ReturnsExtensionMethods()
{
// Given
string code = @"
namespace Foo
{
public class Blue
{
}
public static class Green
{
public static void Ext(this Blue blue)
{
}
}
}
";
IDocument document = GetDocument(code);
IExecutionContext context = GetContext();
IModule module = new AnalyzeCSharp();

// When
List<IDocument> results = module.Execute(new[] { document }, context).ToList(); // Make sure to materialize the result list

// Then
CollectionAssert.AreEquivalent(new[] { "Ext" },
results.Single(x => x["Name"].Equals("Blue")).Get<IEnumerable<IDocument>>("ExtensionMethods").Select(x => x["Name"]));
}

[Test]
public void ReturnsExtensionMethodsForBaseClass()
{
// Given
string code = @"
namespace Foo
{
public class Red
{
}
public class Blue : Red
{
}
public static class Green
{
public static void Ext(this Red red)
{
}
}
}
";
IDocument document = GetDocument(code);
IExecutionContext context = GetContext();
IModule module = new AnalyzeCSharp();

// When
List<IDocument> results = module.Execute(new[] { document }, context).ToList(); // Make sure to materialize the result list

// Then
CollectionAssert.AreEquivalent(new[] { "Ext" },
results.Single(x => x["Name"].Equals("Blue")).Get<IEnumerable<IDocument>>("ExtensionMethods").Select(x => x["Name"]));
}

[Test]
public void MemberTypesReturnsNestedTypes()
{
Expand Down
1 change: 1 addition & 0 deletions themes/Docs/Samson/Shared/Kind/_NamedType.cshtml
Original file line number Diff line number Diff line change
Expand Up @@ -152,5 +152,6 @@
@Html.Partial("Section\\_Events")
@Html.Partial("Section\\_Properties")
@Html.Partial("Section\\_Methods")
@Html.Partial("Section\\_ExtensionMethods")
@Html.Partial("Section\\_Remarks")
@Html.Partial("Section\\_SeeAlso")
50 changes: 50 additions & 0 deletions themes/Docs/Samson/Shared/Section/_ExtensionMethods.cshtml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
@using Microsoft.AspNetCore.Html;
@using Microsoft.CodeAnalysis;
@{
ITypeSymbol modelSymbol = Model.Get<ITypeSymbol>("Symbol");
IList<IDocument> methods = Model.List<IDocument>("ExtensionMethods")
?.Where(x => x.Get<bool>("IsResult"))
.OrderBy(x => x["DisplayName"])
.ToList();
if(methods.Count > 0)
{
<text>
<h1 id="ExtensionMethods">Extension Methods</h1>
<div class="box">
<div class="box-body no-padding">
<table class="table table-striped table-hover three-cols">
<thead>
<tr>
<th>Name</th>
<th>Value</th>
<th>Summary</th>
</tr>
</thead>
@foreach(IDocument method in methods)
{
ISymbol reducedSymbol = method.Get<IMethodSymbol>("Symbol")?.ReduceExtensionMethod(modelSymbol);
string reducedName = reducedSymbol?.ToDisplayString(new SymbolDisplayFormat(
typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypes,
genericsOptions: SymbolDisplayGenericsOptions.IncludeTypeParameters,
parameterOptions: SymbolDisplayParameterOptions.IncludeType,
memberOptions: SymbolDisplayMemberOptions.IncludeParameters,
miscellaneousOptions: SymbolDisplayMiscellaneousOptions.UseSpecialTypes));
IDocument returnType = method.Get<IDocument>("ReturnType");
<tr>
<td>@Context.GetTypeLink(method, reducedName)</td>
<td>@(returnType == null ? new HtmlString(string.Empty) : Context.GetTypeLink(returnType))</td>
<td>
<div>@Html.Raw(method["Summary"])</div>
@{
IDocument containingType = method.Document(CodeAnalysisKeys.ContainingType);
<div><small><em>From @Context.GetTypeLink(containingType)</em></small></div>
}
</td>
</tr>
}
</table>
</div>
</div>
</text>
}
}

0 comments on commit bba21b0

Please sign in to comment.