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

Features/cref resolving #8

Merged
merged 5 commits into from
May 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions Doki.sln
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Doki.Output.Json", "src\Dok
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Doki.Output.Json.Tests", "tests\Doki.Output.Json.Tests\Doki.Output.Json.Tests.csproj", "{6CCD9EE6-B3FC-485F-9155-553165141B20}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Doki.TestAssembly", "tests\assemblies\Doki.TestAssembly\Doki.TestAssembly.csproj", "{0293D689-DFDC-4A78-80D8-BFC11DB0A175}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Doki.Tests.Common", "tests\Doki.Tests.Common\Doki.Tests.Common.csproj", "{0FA0FF7A-6EDA-4CB1-8D81-2DFB1A4077CC}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -46,6 +50,8 @@ Global
{A89D22B2-2427-4863-A2D9-9E1BEFF37C61} = {568576F3-3D48-459E-B4D2-1790DAE80E7A}
{00BCCBC0-A719-489C-A746-559B4D055B56} = {568576F3-3D48-459E-B4D2-1790DAE80E7A}
{6CCD9EE6-B3FC-485F-9155-553165141B20} = {8C7B5305-B599-4F08-B28B-DD9F1715DD51}
{0293D689-DFDC-4A78-80D8-BFC11DB0A175} = {08041208-BE3D-4BE8-9AF7-806B73985275}
{0FA0FF7A-6EDA-4CB1-8D81-2DFB1A4077CC} = {8C7B5305-B599-4F08-B28B-DD9F1715DD51}
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{6F31B87A-2BD3-4FB4-8C08-7E059A338D4A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
Expand Down Expand Up @@ -96,5 +102,13 @@ Global
{6CCD9EE6-B3FC-485F-9155-553165141B20}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6CCD9EE6-B3FC-485F-9155-553165141B20}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6CCD9EE6-B3FC-485F-9155-553165141B20}.Release|Any CPU.Build.0 = Release|Any CPU
{0293D689-DFDC-4A78-80D8-BFC11DB0A175}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0293D689-DFDC-4A78-80D8-BFC11DB0A175}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0293D689-DFDC-4A78-80D8-BFC11DB0A175}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0293D689-DFDC-4A78-80D8-BFC11DB0A175}.Release|Any CPU.Build.0 = Release|Any CPU
{0FA0FF7A-6EDA-4CB1-8D81-2DFB1A4077CC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0FA0FF7A-6EDA-4CB1-8D81-2DFB1A4077CC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0FA0FF7A-6EDA-4CB1-8D81-2DFB1A4077CC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0FA0FF7A-6EDA-4CB1-8D81-2DFB1A4077CC}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal
Original file line number Diff line number Diff line change
Expand Up @@ -42,5 +42,6 @@ Inheritance: [System.Object](https://learn.microsoft.com/en-us/dotnet/api/System
|Constructor|A constructor in the documentation.|
|Field|A field in the documentation.|
|Property|A property in the documentation.|
|Method|A method in the documentation.|


Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ Implements: [System.IEquatable<Doki.MemberDocumentation>](https://learn.mi
|Namespace|Gets the namespace of the member.|
|Assembly|Gets the assembly of the member.|
|Summary|Gets the summary of the member.|
|IsDocumented|Gets a value indicating whether the type is documented.|
|ContentType||


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ Implements: [System.IEquatable<Doki.TypeDocumentationReference>](https://l
|EqualityContract||
|IsGeneric|Gets a value indicating whether the type is generic.|
|FullName|Gets the full name of the type.|
|IsDocumented|Gets a value indicating whether the type is documented.|
|IsMicrosoft|Gets a value indicating whether the type is from Microsoft.|
|BaseType|Gets the base type of the type.|
|GenericArguments|Gets the generic arguments of the type.|
Expand Down
5 changes: 5 additions & 0 deletions src/Doki.Abstractions/DocumentationContentType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -94,4 +94,9 @@ public enum DocumentationContentType
/// A property in the documentation.
/// </summary>
Property,

/// <summary>
/// A method in the documentation.
/// </summary>
Method,
}
5 changes: 5 additions & 0 deletions src/Doki.Abstractions/MemberDocumentation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,10 @@ public record MemberDocumentation : DocumentationObject
/// </summary>
public XmlDocumentation? Summary { get; set; }

/// <summary>
/// Gets a value indicating whether the type is documented.
/// </summary>
public bool IsDocumented { get; init; }

public new DocumentationContentType ContentType { get; init; }
}
5 changes: 0 additions & 5 deletions src/Doki.Abstractions/TypeDocumentationReference.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,6 @@ public record TypeDocumentationReference : MemberDocumentation
/// </summary>
public string FullName { get; init; } = null!;

/// <summary>
/// Gets a value indicating whether the type is documented.
/// </summary>
public bool IsDocumented { get; init; }

/// <summary>
/// Gets a value indicating whether the type is from Microsoft.
/// </summary>
Expand Down
54 changes: 41 additions & 13 deletions src/Doki.Output.Markdown/InternalExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,10 @@ public static Text BuildText(this MarkdownBuilder builder, DocumentationObject o
text.Append(builder.BuildLinkTo(typeDocumentationReference));
text.Space();
break;
case MemberDocumentation memberDocumentation:
text.Append(builder.BuildLinkTo(memberDocumentation));
text.Space();
break;
default:
throw new NotSupportedException(
$"Unsupported {nameof(DocumentationObject)} type: {content.GetType().Name}");
Expand All @@ -74,27 +78,51 @@ public static string BuildRelativePath(this MarkdownBuilder builder, Documentati

public static Element BuildLinkTo(this MarkdownBuilder builder, DocumentationObject to, string? text = null)
{
var indexFile = to.ContentType is DocumentationContentType.Root or DocumentationContentType.Assembly
or DocumentationContentType.Namespace;

var asText = false;
string? relativePath = null;
if (to is TypeDocumentationReference typeDocumentationReference)
string relativePath;
switch (to)
{
text ??= typeDocumentationReference.IsDocumented
? typeDocumentationReference.Name
: typeDocumentationReference.FullName;
case TypeDocumentationReference typeDocumentationReference:
{
text ??= typeDocumentationReference.IsDocumented
? typeDocumentationReference.Name
: typeDocumentationReference.FullName;

asText = typeDocumentationReference is { IsDocumented: false, IsMicrosoft: false };

if (typeDocumentationReference.IsMicrosoft)
{
relativePath =
$"https://learn.microsoft.com/en-us/dotnet/api/{typeDocumentationReference.FullName}";
}
else
{
relativePath = builder.BuildRelativePath(to) + ".md";
}

break;
}
case MemberDocumentation memberDocumentation:
{
text ??= memberDocumentation.Name;

asText = typeDocumentationReference is { IsDocumented: false, IsMicrosoft: false };
asText = !memberDocumentation.IsDocumented;

if (typeDocumentationReference.IsMicrosoft)
relativePath = builder.BuildRelativePath(memberDocumentation.Parent!) + ".md";
break;
}
default:
{
relativePath = $"https://learn.microsoft.com/en-us/dotnet/api/{typeDocumentationReference.FullName}";
var indexFile = to.ContentType is DocumentationContentType.Root or DocumentationContentType.Assembly
or DocumentationContentType.Namespace;

relativePath = indexFile
? builder.BuildRelativePath(to, "README.md")
: builder.BuildRelativePath(to) + ".md";
break;
}
}

relativePath ??= indexFile ? builder.BuildRelativePath(to, "README.md") : builder.BuildRelativePath(to) + ".md";

text ??= to.Id;
if (text == "root") text = "Packages";

Expand Down
125 changes: 117 additions & 8 deletions src/Doki/DocumentationGenerator.Content.cs
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ private IEnumerable<MemberDocumentation> BuildFieldDocumentation(Type type, Docu
Namespace = field.DeclaringType.Namespace,
Assembly = fieldAssembly.Name,
Parent = parent,
IsDocumented = true
};

if (summary != null)
Expand Down Expand Up @@ -173,6 +174,7 @@ private IEnumerable<MemberDocumentation> BuildConstructorDocumentation(Type type
Namespace = constructor.DeclaringType.Namespace,
Assembly = constructorAssembly.Name,
Parent = parent,
IsDocumented = true
};

if (summary != null)
Expand Down Expand Up @@ -214,6 +216,7 @@ private IEnumerable<MemberDocumentation> BuildPropertyDocumentation(Type type, D
Namespace = property.DeclaringType.Namespace,
Assembly = propertyAssembly.Name,
Parent = parent,
IsDocumented = true
};

if (summary != null)
Expand Down Expand Up @@ -253,10 +256,11 @@ private IEnumerable<MemberDocumentation> BuildMethodDocumentation(Type type, Doc
{
Id = methodId,
Name = method.GetSanitizedName(),
ContentType = DocumentationContentType.Property,
ContentType = DocumentationContentType.Method,
Namespace = method.DeclaringType.Namespace,
Assembly = methodAssembly.Name,
Parent = parent,
IsDocumented = true
};

if (summary != null)
Expand Down Expand Up @@ -337,13 +341,16 @@ private XmlDocumentation BuildXmlDocumentation(XPathNavigator navigator, Documen

private DocumentationObject BuildCRefDocumentation(string cref, DocumentationObject parent)
{
if (!cref.StartsWith("T:")) throw new ArgumentOutOfRangeException(nameof(cref), cref, "Unsupported cref.");
var parts = cref.Split(':');
if (parts.Length != 2) throw new ArgumentOutOfRangeException(nameof(cref), cref, "Invalid cref format.");

var typeName = cref[2..];
if (!typeName.Contains('.'))
var memberType = parts[0];
var memberName = parts[1];
var typeName = parts[1];
if (memberType != "T")
{
var @namespace = parent.TryGetParent<NamespaceDocumentation>(DocumentationContentType.Namespace);
if (@namespace != null) typeName = $"{@namespace.Id}.{typeName}";
memberName = memberName.Split('.').Last();
typeName = typeName[..typeName.LastIndexOf('.')];
}

var type = LookupType(typeName);
Expand All @@ -352,10 +359,112 @@ private DocumentationObject BuildCRefDocumentation(string cref, DocumentationObj
{
Id = "text",
Parent = parent,
Text = typeName
Text = memberName
};

return BuildTypeDocumentationReference(type, parent);
var typeDocumentationReference = BuildTypeDocumentationReference(type, parent);

switch (memberType)
{
case "T":
return typeDocumentationReference;
case "P":
{
var property = type.GetProperty(memberName, AllMembersBindingFlags);
if (property == null)
return new TextContent
{
Id = "text",
Parent = parent,
Text = memberName
};

return new MemberDocumentation
{
Id = property.GetXmlDocumentationId(),
Name = property.Name,
ContentType = DocumentationContentType.Property,
Namespace = type.Namespace,
Assembly = type.Assembly.GetName().Name,
Parent = typeDocumentationReference,
IsDocumented = PropertyFilter.Expression?.Invoke(property) ?? PropertyFilter.Default(property)
};
}
case "F":
{
var field = type.GetField(memberName, AllMembersBindingFlags);
if (field == null)
return new TextContent
{
Id = "text",
Parent = parent,
Text = memberName
};

return new MemberDocumentation
{
Id = field.GetXmlDocumentationId(),
Name = field.Name,
ContentType = DocumentationContentType.Field,
Namespace = type.Namespace,
Assembly = type.Assembly.GetName().Name,
Parent = typeDocumentationReference,
IsDocumented = FieldFilter.Expression?.Invoke(field) ?? FieldFilter.Default(field)
};
}
case "M":
{
if (memberName.StartsWith("#ctor"))
{
var constructors = type.GetConstructors(AllMembersBindingFlags);

var constructor = constructors.FirstOrDefault(c => c.GetXmlDocumentationId() == parts[1]);

if (constructor == null)
return new TextContent
{
Id = "text",
Parent = parent,
Text = memberName
};

return new MemberDocumentation
{
Id = constructor.GetXmlDocumentationId(),
Name = constructor.GetSanitizedName(),
ContentType = DocumentationContentType.Constructor,
Namespace = type.Namespace,
Assembly = type.Assembly.GetName().Name,
Parent = typeDocumentationReference,
IsDocumented = ConstructorFilter.Expression?.Invoke(constructor) ??
ConstructorFilter.Default(constructor)
};
}

var method = type.GetMethod(memberName, AllMembersBindingFlags);

if (method == null)
return new TextContent
{
Id = "text",
Parent = parent,
Text = memberName
};

return new MemberDocumentation
{
Id = method.GetXmlDocumentationId(),
Name = method.GetSanitizedName(),
ContentType = DocumentationContentType.Method,
Namespace = type.Namespace,
Assembly = type.Assembly.GetName().Name,
Parent = typeDocumentationReference,
IsDocumented = MethodFilter.Expression?.Invoke(method) ?? MethodFilter.Default(method)
};
}
default:
throw new ArgumentOutOfRangeException(nameof(cref), cref, "Unsupported cref member type.");
}
}

private TypeDocumentationReference BuildTypeDocumentationReference(Type type, DocumentationObject parent)
Expand Down
14 changes: 14 additions & 0 deletions tests/Doki.Tests.Common/Doki.Tests.Common.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.1" />
<PackageReference Include="xunit.abstractions" Version="2.0.3" />
</ItemGroup>

</Project>
29 changes: 29 additions & 0 deletions tests/Doki.Tests.Common/TestOutputLogger.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using Microsoft.Extensions.Logging;
using Xunit.Abstractions;

namespace Doki.Tests.Common;

public sealed class TestOutputLogger(ITestOutputHelper output) : ILogger
{
public bool HadError { get; private set; }

public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception,
Func<TState, Exception?, string> formatter)
{
if (logLevel == LogLevel.Error) HadError = true;

output.WriteLine(formatter(state, exception));

if (exception != null) output.WriteLine(exception.ToString());
}

public bool IsEnabled(LogLevel logLevel)
{
return true;
}

public IDisposable? BeginScope<TState>(TState state) where TState : notnull
{
return null;
}
}
2 changes: 2 additions & 0 deletions tests/Doki.Tests.Snapshots/Doki.Tests.Snapshots.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
<ProjectReference Include="..\..\src\Doki\Doki.csproj" />
<ProjectReference Include="..\assemblies\Doki.TestAssembly.ParentRootNamespace\Doki.TestAssembly.ParentRootNamespace.csproj" />
<ProjectReference Include="..\assemblies\Doki.TestAssembly.InheritanceChain\Doki.TestAssembly.InheritanceChain.csproj" />
<ProjectReference Include="..\assemblies\Doki.TestAssembly\Doki.TestAssembly.csproj" />
<ProjectReference Include="..\Doki.Tests.Common\Doki.Tests.Common.csproj" />
</ItemGroup>

</Project>
Loading
Loading