From f6106ea39cb414162618973941d40bf2954e7514 Mon Sep 17 00:00:00 2001 From: dvolper Date: Sat, 22 Jun 2024 17:59:12 +0200 Subject: [PATCH] cleanup & tests --- Doki.sln | 7 ++ .../DocumentationObjectExtensions.cs | 20 --- .../DocumentationObjectExtensions.cs | 112 ++--------------- .../DocumentationRootExtensions.cs | 118 ++++++++++++++++++ .../Doki.Output.Markdown.csproj | 1 + src/Doki.Output.Markdown/MarkdownOutput.cs | 6 +- tests/Doki.CommandLine.Tests/NuGetTests.cs | 2 +- .../DocumentationRootExtensionTests.cs | 40 ++++++ .../Doki.Extensions.Tests.csproj | 32 +++++ tests/Doki.Extensions.Tests/GlobalUsings.cs | 1 + .../Doki.Output.Json.Tests/JsonOutputTests.cs | 44 +++---- .../DocumentationRootCapture.cs | 14 +++ .../Doki.Tests.Common.csproj | 4 + 13 files changed, 250 insertions(+), 151 deletions(-) delete mode 100644 src/Doki.Abstractions/DocumentationObjectExtensions.cs create mode 100644 src/Doki.Extensions/DocumentationRootExtensions.cs create mode 100644 tests/Doki.Extensions.Tests/DocumentationRootExtensionTests.cs create mode 100644 tests/Doki.Extensions.Tests/Doki.Extensions.Tests.csproj create mode 100644 tests/Doki.Extensions.Tests/GlobalUsings.cs create mode 100644 tests/Doki.Tests.Common/DocumentationRootCapture.cs diff --git a/Doki.sln b/Doki.sln index 3aa7d32..a7ae356 100644 --- a/Doki.sln +++ b/Doki.sln @@ -38,6 +38,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Doki.Extensions", "src\Doki EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Doki.CommandLine.Tests", "tests\Doki.CommandLine.Tests\Doki.CommandLine.Tests.csproj", "{67B12AF1-2696-411F-ADFD-B5C32A43BA71}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Doki.Extensions.Tests", "tests\Doki.Extensions.Tests\Doki.Extensions.Tests.csproj", "{863614BC-38CF-4CF5-BE4B-B8EC01AE4FB6}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -57,6 +59,7 @@ Global {0293D689-DFDC-4A78-80D8-BFC11DB0A175} = {08041208-BE3D-4BE8-9AF7-806B73985275} {0FA0FF7A-6EDA-4CB1-8D81-2DFB1A4077CC} = {8C7B5305-B599-4F08-B28B-DD9F1715DD51} {67B12AF1-2696-411F-ADFD-B5C32A43BA71} = {8C7B5305-B599-4F08-B28B-DD9F1715DD51} + {863614BC-38CF-4CF5-BE4B-B8EC01AE4FB6} = {8C7B5305-B599-4F08-B28B-DD9F1715DD51} EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {6F31B87A-2BD3-4FB4-8C08-7E059A338D4A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU @@ -123,5 +126,9 @@ Global {67B12AF1-2696-411F-ADFD-B5C32A43BA71}.Debug|Any CPU.Build.0 = Debug|Any CPU {67B12AF1-2696-411F-ADFD-B5C32A43BA71}.Release|Any CPU.ActiveCfg = Release|Any CPU {67B12AF1-2696-411F-ADFD-B5C32A43BA71}.Release|Any CPU.Build.0 = Release|Any CPU + {863614BC-38CF-4CF5-BE4B-B8EC01AE4FB6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {863614BC-38CF-4CF5-BE4B-B8EC01AE4FB6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {863614BC-38CF-4CF5-BE4B-B8EC01AE4FB6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {863614BC-38CF-4CF5-BE4B-B8EC01AE4FB6}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal diff --git a/src/Doki.Abstractions/DocumentationObjectExtensions.cs b/src/Doki.Abstractions/DocumentationObjectExtensions.cs deleted file mode 100644 index 41b875e..0000000 --- a/src/Doki.Abstractions/DocumentationObjectExtensions.cs +++ /dev/null @@ -1,20 +0,0 @@ -namespace Doki; - -public static class DocumentationObjectExtensions -{ - public static T? TryGetParent(this DocumentationObject obj, DocumentationContentType? expectedContent = null) - where T : DocumentationObject - { - ArgumentNullException.ThrowIfNull(obj); - - var parent = obj.Parent; - while (parent != null) - { - if (parent is T t && (expectedContent == null || t.ContentType == expectedContent)) return t; - - parent = parent.Parent; - } - - return null; - } -} \ No newline at end of file diff --git a/src/Doki.Extensions/DocumentationObjectExtensions.cs b/src/Doki.Extensions/DocumentationObjectExtensions.cs index 251108f..5bb4618 100644 --- a/src/Doki.Extensions/DocumentationObjectExtensions.cs +++ b/src/Doki.Extensions/DocumentationObjectExtensions.cs @@ -2,117 +2,19 @@ public static class DocumentationObjectExtensions { - public static DocumentationObject? TryGetParent(this DocumentationRoot root, DocumentationObject child) + public static T? TryGetByParents(this DocumentationObject obj, DocumentationContentType? expectedContent = null) + where T : DocumentationObject { - ArgumentNullException.ThrowIfNull(root); - ArgumentNullException.ThrowIfNull(child); + ArgumentNullException.ThrowIfNull(obj); - return child.Parent ?? SearchParent(root, child); - } - - private static DocumentationObject? SearchParent(DocumentationObject from, DocumentationObject to) - { - if (from == to) return from; - - switch (from) + var parent = obj.Parent; + while (parent != null) { - case DocumentationRoot root: return SearchParentIn(root.Assemblies, to); - case AssemblyDocumentation assemblyDocumentation: - return SearchParentIn(assemblyDocumentation.Namespaces, to); - case NamespaceDocumentation namespaceDocumentation: return SearchParentIn(namespaceDocumentation.Types, to); - case TypeDocumentation typeDocumentation: - { - var constructor = SearchParentIn(typeDocumentation.Constructors, to); - if (constructor != null) return constructor; - - var method = SearchParentIn(typeDocumentation.Methods, to); - if (method != null) return method; - - var property = SearchParentIn(typeDocumentation.Properties, to); - if (property != null) return property; - - var field = SearchParentIn(typeDocumentation.Fields, to); - if (field != null) return field; - - var interfaceDocumentation = SearchParentIn(typeDocumentation.Interfaces, to); - if (interfaceDocumentation != null) return interfaceDocumentation; - - var derivedType = SearchParentIn(typeDocumentation.DerivedTypes, to); - if (derivedType != null) return derivedType; - - var result = SearchParentInTypeDocumentationReference(typeDocumentation, to); - if (result != null) return result; - - break; - } - case GenericTypeArgumentDocumentation genericTypeArgumentDocumentation: - { - if (genericTypeArgumentDocumentation.Description != null) - { - var description = SearchParent(genericTypeArgumentDocumentation.Description, to); - if (description != null) return description; - } - - var result = SearchParentInTypeDocumentationReference(genericTypeArgumentDocumentation, to); - if (result != null) return result; - - break; - } - case TypeDocumentationReference typeDocumentationReference: - { - var result = SearchParentInTypeDocumentationReference(typeDocumentationReference, to); - if (result != null) return result; - - break; - } - case MemberDocumentation memberDocumentation: - { - var result = SearchParentInMemberDocumentation(memberDocumentation, to); - if (result != null) return result; - - break; - } - case XmlDocumentation xmlDocumentation: - { - var result = SearchParentIn(xmlDocumentation.Contents, to); - if (result != null) return result; + if (parent is T t && (expectedContent == null || t.ContentType == expectedContent)) return t; - break; - } + parent = parent.Parent; } return null; } - - private static DocumentationObject? SearchParentInTypeDocumentationReference( - TypeDocumentationReference typeDocumentationReference, DocumentationObject to) - { - if (typeDocumentationReference.BaseType != null) - { - var baseType = SearchParent(typeDocumentationReference.BaseType, to); - if (baseType != null) return baseType; - } - - var genericArgument = SearchParentIn(typeDocumentationReference.GenericArguments, to); - return genericArgument ?? SearchParentInMemberDocumentation(typeDocumentationReference, to); - } - - private static DocumentationObject? SearchParentInMemberDocumentation(MemberDocumentation memberDocumentation, - DocumentationObject to) - { - var summary = SearchParentIn(memberDocumentation.Summaries, to); - if (summary != null) return summary; - - var remarks = SearchParentIn(memberDocumentation.Remarks, to); - if (remarks != null) return remarks; - - var example = SearchParentIn(memberDocumentation.Examples, to); - return example; - } - - private static DocumentationObject? SearchParentIn(IEnumerable children, - DocumentationObject to) - { - return children.Select(child => SearchParent(child, to)).OfType().FirstOrDefault(); - } } \ No newline at end of file diff --git a/src/Doki.Extensions/DocumentationRootExtensions.cs b/src/Doki.Extensions/DocumentationRootExtensions.cs new file mode 100644 index 0000000..aca8208 --- /dev/null +++ b/src/Doki.Extensions/DocumentationRootExtensions.cs @@ -0,0 +1,118 @@ +namespace Doki.Extensions; + +public static class DocumentationRootExtensions +{ + public static DocumentationObject? TryGetParent(this DocumentationRoot root, DocumentationObject child) + { + ArgumentNullException.ThrowIfNull(root); + ArgumentNullException.ThrowIfNull(child); + + return child.Parent ?? SearchParent(root, child); + } + + private static DocumentationObject? SearchParent(DocumentationObject from, DocumentationObject to) + { + if (from == to) return from; + + switch (from) + { + case DocumentationRoot root: return SearchParentIn(root.Assemblies, to); + case AssemblyDocumentation assemblyDocumentation: + return SearchParentIn(assemblyDocumentation.Namespaces, to); + case NamespaceDocumentation namespaceDocumentation: return SearchParentIn(namespaceDocumentation.Types, to); + case TypeDocumentation typeDocumentation: + { + var constructor = SearchParentIn(typeDocumentation.Constructors, to); + if (constructor != null) return constructor; + + var method = SearchParentIn(typeDocumentation.Methods, to); + if (method != null) return method; + + var property = SearchParentIn(typeDocumentation.Properties, to); + if (property != null) return property; + + var field = SearchParentIn(typeDocumentation.Fields, to); + if (field != null) return field; + + var interfaceDocumentation = SearchParentIn(typeDocumentation.Interfaces, to); + if (interfaceDocumentation != null) return interfaceDocumentation; + + var derivedType = SearchParentIn(typeDocumentation.DerivedTypes, to); + if (derivedType != null) return derivedType; + + var result = SearchParentInTypeDocumentationReference(typeDocumentation, to); + if (result != null) return result; + + break; + } + case GenericTypeArgumentDocumentation genericTypeArgumentDocumentation: + { + if (genericTypeArgumentDocumentation.Description != null) + { + var description = SearchParent(genericTypeArgumentDocumentation.Description, to); + if (description != null) return description; + } + + var result = SearchParentInTypeDocumentationReference(genericTypeArgumentDocumentation, to); + if (result != null) return result; + + break; + } + case TypeDocumentationReference typeDocumentationReference: + { + var result = SearchParentInTypeDocumentationReference(typeDocumentationReference, to); + if (result != null) return result; + + break; + } + case MemberDocumentation memberDocumentation: + { + var result = SearchParentInMemberDocumentation(memberDocumentation, to); + if (result != null) return result; + + break; + } + case XmlDocumentation xmlDocumentation: + { + var result = SearchParentIn(xmlDocumentation.Contents, to); + if (result != null) return result; + + break; + } + } + + return null; + } + + private static DocumentationObject? SearchParentInTypeDocumentationReference( + TypeDocumentationReference typeDocumentationReference, DocumentationObject to) + { + if (typeDocumentationReference.BaseType != null) + { + var baseType = SearchParent(typeDocumentationReference.BaseType, to); + if (baseType != null) return baseType; + } + + var genericArgument = SearchParentIn(typeDocumentationReference.GenericArguments, to); + return genericArgument ?? SearchParentInMemberDocumentation(typeDocumentationReference, to); + } + + private static DocumentationObject? SearchParentInMemberDocumentation(MemberDocumentation memberDocumentation, + DocumentationObject to) + { + var summary = SearchParentIn(memberDocumentation.Summaries, to); + if (summary != null) return summary; + + var remarks = SearchParentIn(memberDocumentation.Remarks, to); + if (remarks != null) return remarks; + + var example = SearchParentIn(memberDocumentation.Examples, to); + return example; + } + + private static DocumentationObject? SearchParentIn(IEnumerable children, + DocumentationObject to) + { + return children.Select(child => SearchParent(child, to)).OfType().FirstOrDefault(); + } +} \ No newline at end of file diff --git a/src/Doki.Output.Markdown/Doki.Output.Markdown.csproj b/src/Doki.Output.Markdown/Doki.Output.Markdown.csproj index e837710..63b7ae0 100644 --- a/src/Doki.Output.Markdown/Doki.Output.Markdown.csproj +++ b/src/Doki.Output.Markdown/Doki.Output.Markdown.csproj @@ -19,6 +19,7 @@ + diff --git a/src/Doki.Output.Markdown/MarkdownOutput.cs b/src/Doki.Output.Markdown/MarkdownOutput.cs index c743091..db2a49d 100644 --- a/src/Doki.Output.Markdown/MarkdownOutput.cs +++ b/src/Doki.Output.Markdown/MarkdownOutput.cs @@ -1,4 +1,5 @@ using System.Text.RegularExpressions; +using Doki.Extensions; using Doki.Output.Markdown.Elements; namespace Doki.Output.Markdown; @@ -90,14 +91,13 @@ public async Task WriteAsync(TypeDocumentation typeDocumentation, CancellationTo .Add(new Heading(typeDocumentation.Name, 1).Append($" {Enum.GetName(typeDocumentation.ContentType)}")) .Add(new Heading(nameof(TypeDocumentation.Definition), 2)); - var namespaceDocumentation = - typeDocumentation.TryGetParent(DocumentationContentType.Namespace); + var namespaceDocumentation = typeDocumentation.TryGetByParents(); if (namespaceDocumentation != null) { markdown.Add(new Text("Namespace: ").Append(markdown.BuildLinkTo(namespaceDocumentation))); } - var assemblyAssemblyDocumentation = typeDocumentation.TryGetParent(); + var assemblyAssemblyDocumentation = typeDocumentation.TryGetByParents(); if (assemblyAssemblyDocumentation != null) { markdown.Add(new Text("Assembly: ").Append(markdown.BuildLinkTo(assemblyAssemblyDocumentation, diff --git a/tests/Doki.CommandLine.Tests/NuGetTests.cs b/tests/Doki.CommandLine.Tests/NuGetTests.cs index f8b452c..10ce4b7 100644 --- a/tests/Doki.CommandLine.Tests/NuGetTests.cs +++ b/tests/Doki.CommandLine.Tests/NuGetTests.cs @@ -7,7 +7,7 @@ namespace Doki.CommandLine.Tests; public class NuGetTests(ITestOutputHelper testOutputHelper) { [Fact] - public async Task NuGetLoader_TestAsync() + public async Task Test_NuGetLoader() { const string packageId = "Doki.Output.Json"; var tmpDirectory = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); diff --git a/tests/Doki.Extensions.Tests/DocumentationRootExtensionTests.cs b/tests/Doki.Extensions.Tests/DocumentationRootExtensionTests.cs new file mode 100644 index 0000000..a1e8395 --- /dev/null +++ b/tests/Doki.Extensions.Tests/DocumentationRootExtensionTests.cs @@ -0,0 +1,40 @@ +using System.Xml.XPath; +using Doki.TestAssembly.InheritanceChain; +using Doki.Tests.Common; +using Microsoft.Extensions.Logging.Abstractions; + +namespace Doki.Extensions.Tests; + +public class DocumentationRootExtensionTests +{ + [Fact] + public async Task Test_TryGetParent_ParentPropertyIsNotNull() + { + var testOutput = new DocumentationRootCapture(); + + var documentationGenerator = new DocumentationGenerator(); + + var emptyDocumentation = new XPathDocument(new StringReader("""""")); + + documentationGenerator.AddOutput(testOutput); + + documentationGenerator.AddAssembly(typeof(SimpleClass).Assembly, emptyDocumentation); + + await documentationGenerator.GenerateAsync(NullLogger.Instance); + + Assert.NotNull(testOutput.Root); + + var assemblyDocumentation = testOutput.Root.Assemblies.Single(); + + var namespaceDocumentation = assemblyDocumentation.Namespaces.Single(); + + var simpleClassDocumentation = namespaceDocumentation.Types.First(); + + Assert.NotNull(simpleClassDocumentation.Parent); + + var parent = testOutput.Root.TryGetParent(simpleClassDocumentation); + + Assert.NotNull(parent); + Assert.Equal(namespaceDocumentation, parent); + } +} \ No newline at end of file diff --git a/tests/Doki.Extensions.Tests/Doki.Extensions.Tests.csproj b/tests/Doki.Extensions.Tests/Doki.Extensions.Tests.csproj new file mode 100644 index 0000000..35c0651 --- /dev/null +++ b/tests/Doki.Extensions.Tests/Doki.Extensions.Tests.csproj @@ -0,0 +1,32 @@ + + + + net8.0 + enable + enable + + false + true + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + + + diff --git a/tests/Doki.Extensions.Tests/GlobalUsings.cs b/tests/Doki.Extensions.Tests/GlobalUsings.cs new file mode 100644 index 0000000..8c927eb --- /dev/null +++ b/tests/Doki.Extensions.Tests/GlobalUsings.cs @@ -0,0 +1 @@ +global using Xunit; \ No newline at end of file diff --git a/tests/Doki.Output.Json.Tests/JsonOutputTests.cs b/tests/Doki.Output.Json.Tests/JsonOutputTests.cs index ced6297..9c60559 100644 --- a/tests/Doki.Output.Json.Tests/JsonOutputTests.cs +++ b/tests/Doki.Output.Json.Tests/JsonOutputTests.cs @@ -12,31 +12,31 @@ public async Task Test_Deserialization() { var outputDirectory = new DirectoryInfo(Path.Combine(Path.GetTempPath(), "doki")); - var emptyDocumentation = new XPathDocument(new StringReader(""" - - - - Doki.TestAssembly.InheritanceChain.Abstractions - - - - - This is an abstract class. See for more information. - - - This is an example of how to use the class. - - public class ExampleClass : AbstractClass {} - - - - - - """)); + var xmlDocumentation = new XPathDocument(new StringReader(""" + + + + Doki.TestAssembly.InheritanceChain.Abstractions + + + + + This is an abstract class. See for more information. + + + This is an example of how to use the class. + + public class ExampleClass : AbstractClass {} + + + + + + """)); var generator = new DocumentationGenerator(); - generator.AddAssembly(typeof(AbstractClass).Assembly, emptyDocumentation); + generator.AddAssembly(typeof(AbstractClass).Assembly, xmlDocumentation); generator.AddOutput(new JsonOutput(new OutputOptions { diff --git a/tests/Doki.Tests.Common/DocumentationRootCapture.cs b/tests/Doki.Tests.Common/DocumentationRootCapture.cs new file mode 100644 index 0000000..0b9d104 --- /dev/null +++ b/tests/Doki.Tests.Common/DocumentationRootCapture.cs @@ -0,0 +1,14 @@ +using Doki.Output; + +namespace Doki.Tests.Common; + +public sealed class DocumentationRootCapture : IOutput +{ + public DocumentationRoot? Root { get; private set; } + + public Task WriteAsync(DocumentationRoot root, CancellationToken cancellationToken = default) + { + Root = root; + return Task.CompletedTask; + } +} \ No newline at end of file diff --git a/tests/Doki.Tests.Common/Doki.Tests.Common.csproj b/tests/Doki.Tests.Common/Doki.Tests.Common.csproj index c367fb7..6b5c604 100644 --- a/tests/Doki.Tests.Common/Doki.Tests.Common.csproj +++ b/tests/Doki.Tests.Common/Doki.Tests.Common.csproj @@ -11,4 +11,8 @@ + + + +