Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
72f0473
feat: Add C# 14 and .NET 10 support
Khojasteh Dec 8, 2025
332ff6b
Add XML comment to extension methods for testing and update project s…
Khojasteh Dec 8, 2025
73f6811
Refactor extension stubs and improve code clarity
Khojasteh Dec 8, 2025
031492f
Add `asDeclared` parameter to `GetMethodMetadata`
Khojasteh Dec 8, 2025
f9dcf81
Add GenericMethodWithGenericParameter test case
Khojasteh Dec 8, 2025
b4c8b75
Enhance extension methods and update tests
Khojasteh Dec 8, 2025
2ec41b6
Refactor code reference logic; extend test coverage
Khojasteh Dec 8, 2025
a9beb13
Add ToString overrides for ExtensionMethodInfo/PropertyInfo
Khojasteh Dec 8, 2025
da434a1
Add asDeclared param to MethodInfo.GetMetadata extension
Khojasteh Dec 9, 2025
f57e7a3
Expand method/property code reference test coverage
Khojasteh Dec 9, 2025
38dee44
Clarify test guideline: use Acme.cs types in cases
Khojasteh Dec 9, 2025
93ed325
Add XML docs to all types and members in Acme namespace
Khojasteh Dec 9, 2025
3cf6d2f
Expand and refactor XML doc provider test coverage
Khojasteh Dec 9, 2025
721b376
Update XML docs and add NamespaceDoc for Acme namespace
Khojasteh Dec 9, 2025
23c627d
Improve generic type resolution and update related tests
Khojasteh Dec 9, 2025
86cacec
Refactor XML doc provider: add resolver/repository layer
Khojasteh Dec 9, 2025
fe74b1e
Refactor: unify extension member handling via extension blocks
Khojasteh Dec 14, 2025
bf27a74
Refactor extension block/member model for clarity
Khojasteh Dec 14, 2025
f51e198
Refactor extension block reflection to use MemberInfo model
Khojasteh Dec 15, 2025
b44fbfb
Rename generic type parameter from T to W in extensions
Khojasteh Dec 15, 2025
1753656
Improve parameter compatibility logic to support covariant return typ…
Khojasteh Dec 15, 2025
a329a95
Clarify IsSubstitutableBy semantics and expand test coverage
Khojasteh Dec 15, 2025
1972c82
Refactor type compatibility: replace IsSubstitutableBy
Khojasteh Dec 15, 2025
29347c3
Refactor Accepts to IsDeclarationOriginOf for clarity
Khojasteh Dec 15, 2025
00be95d
Improve generic type constraint and substitution logic
Khojasteh Dec 15, 2025
d0159ee
Refactor type substitution and constraint satisfaction logic
Khojasteh Dec 15, 2025
f6a98de
Refactor type substitution and constraint satisfaction logic
Khojasteh Dec 15, 2025
05a6df3
Add support for ExtensionBlockInfo in member adapter
Khojasteh Dec 16, 2025
555d6cb
Add sealed IsDirectDeclaration overrides to type adapters
Khojasteh Dec 16, 2025
7a37f01
Refactor generic type assignability logic for clarity
Khojasteh Dec 16, 2025
6b6a18b
Rename UnqualifiedName to SimpleName in generic type APIs
Khojasteh Dec 16, 2025
be07549
Refactor XML doc provider API; add extension block support
Khojasteh Dec 16, 2025
e5dc6da
Add support for extension blocks in documentation model
Khojasteh Dec 16, 2025
67dfdb8
Rename setup step for clarity in .github workflow
Khojasteh Dec 16, 2025
83b934c
Refactor extension block metadata methods to use IType instead of ICl…
Khojasteh Dec 16, 2025
808370a
Refactor assembly loading logic to improve error handling and clarity
Khojasteh Dec 16, 2025
16ec64c
Add test for FixedBuffer field XML doc retrieval
Khojasteh Dec 16, 2025
138a31b
Enhance FieldAdapter to handle FixedBuffer type and update tests for …
Khojasteh Dec 16, 2025
b4e88e3
Filter out compiler-generated types in GetExportedTypes method and up…
Khojasteh Dec 16, 2025
9a41b68
Add static modifier to eligible lambdas and delegates
Khojasteh Dec 16, 2025
c6f6d5f
Improve null safety in IsInitOnly property check
Khojasteh Dec 16, 2025
fa4fb77
Refactor variable names and improve type checks
Khojasteh Dec 16, 2025
b837aea
Update MetadataLoadContext to use floating patch version
Khojasteh Dec 17, 2025
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
1 change: 1 addition & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ XML doc transformation uses embedded XSLT resources:
- Uses **NUnit** with **Moq** for mocking
- Use `[TestCase(..., ExpectedResult=...)]` for parameterized tests.
- Name test methods: `MemberName_StateUnderTest_ExpectedBehavior`.
- When applicable, use types and members defined in `Acme.cs` for test cases.
- Prefer constraint-based assertions.
- Write only tests for public members and business logic (skip private methods and parameter validation).
- `tests/MockHelper.cs` provides common test utilities
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@ jobs:
steps:
- uses: actions/checkout@v4

- name: Setup .NET 8.0
uses: actions/setup-dotnet@v3
- name: Setup .NET SDK
uses: actions/setup-dotnet@v4
with:
dotnet-version: '8.0.x'
dotnet-version: '10.0.x'

- name: Restore Dependencies
run: dotnet restore
Expand Down
2 changes: 1 addition & 1 deletion src/DevOpsWikiStrategy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -305,7 +305,7 @@ public static string EncodeWikiPath(ReadOnlySpan<char> title)
/// <remarks>
/// This method encodes fragments to match Azure DevOps Wiki heading fragment rules:
/// <list type="bullet">
/// <item><description>Lowercases all characters.</description></item>
/// <item><description>All characters are converted to lowercase.</description></item>
/// <item><description>Spaces are replaced with hyphens.</description></item>
/// <item><description>Reserved or special characters are percent-encoded.</description></item>
/// <item><description>Forbidden characters such (#, /, \) are replaced with underscores.</description></item>
Expand Down
2 changes: 1 addition & 1 deletion src/DocFxStrategy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ public override bool TryResolveMemberAddress(IMember member, [NotNullWhen(true)]
if (member is not IType type)
{
type = member.DeclaringType!;
memberAnchor = member.CodeReference[2..].ReplaceMany(['.', '`', '#', ',', '(', ')', '~'], '_');
memberAnchor = member.CodeReference[2..].ReplaceChars(['.', '`', '#', ',', '(', ')', '~'], '_');
}

var typePath = type.Signature.Replace('`', '_');
Expand Down
10 changes: 3 additions & 7 deletions src/DocumentationComposer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -269,11 +269,7 @@ protected virtual void GenerateDocumentationForInterface(IDocumentWriterFactory
}

if (granularity.HasFlag(PageGranularity.Member))
{
GenerateDocumentationForProperties(writerFactory, interfaceType.Properties);
GenerateDocumentationForMethods(writerFactory, interfaceType.Methods);
GenerateDocumentationForEvents(writerFactory, interfaceType.Events);
}
GenerateDocumentationForCompositeTypeMembers(writerFactory, interfaceType);
}

/// <summary>
Expand Down Expand Up @@ -485,12 +481,12 @@ protected virtual void GenerateDocumentationForCompositeTypeMembers(IDocumentWri
if (compositeType is null)
throw new ArgumentNullException(nameof(compositeType));

GenerateDocumentationForConstructors(writerFactory, compositeType.Constructors);
GenerateDocumentationForFields(writerFactory, compositeType.Fields);
GenerateDocumentationForConstructors(writerFactory, compositeType.Constructors);
GenerateDocumentationForProperties(writerFactory, compositeType.Properties.Concat(compositeType.ExplicitInterfaceMembers.OfType<PropertyModel>()));
GenerateDocumentationForMethods(writerFactory, compositeType.Methods.Concat(compositeType.ExplicitInterfaceMembers.OfType<MethodModel>()));
GenerateDocumentationForEvents(writerFactory, compositeType.Events.Concat(compositeType.ExplicitInterfaceMembers.OfType<EventModel>()));
GenerateDocumentationForOperators(writerFactory, compositeType.Operators);
GenerateDocumentationForOperators(writerFactory, compositeType.Operators.Concat(compositeType.ExplicitInterfaceMembers.OfType<OperatorModel>()));
}
}
}
2 changes: 1 addition & 1 deletion src/DocumentationContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,7 @@ protected void Dispose(bool disposing)
{
0 => TypeCollection.Empty,
1 => Assemblies.First().ExportedTypes,
_ => new TypeCollection(Assemblies.SelectMany(asm => asm.ExportedTypes).OrderBy(static type => type.Name, StringComparer.Ordinal))
_ => new TypeCollection(Assemblies.SelectMany(static asm => asm.ExportedTypes).OrderBy(static type => type.Name, StringComparer.Ordinal))
};
}
}
8 changes: 4 additions & 4 deletions src/DocumentationContextBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@ public virtual DocumentationContextBuilder<TContext> AddTopic(string topicId, st
/// <returns>The current builder instance for method chaining.</returns>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="topic"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentException">Thrown when <paramref name="topic"/> is a subtopic.</exception>
/// <exception cref="ArgumentException">Thrown when a topic with the same ID already exists.</exception>"
/// <exception cref="ArgumentException">Thrown when a topic with the same ID already exists.</exception>
public virtual DocumentationContextBuilder<TContext> AddTopic(ITopic topic)
{
if (topic is null)
Expand Down Expand Up @@ -309,15 +309,15 @@ protected virtual MetadataUniverse CreateMetadataUniverse()
/// </remarks>
protected virtual IXmlDocProvider CreateXmlDocProvider()
{
var xmlProvider = new XmlDocProvider
var repository = new XmlDocRepository
{
ErrorHandler = XmlDocErrorHandler
};

foreach (var xmlDocPath in XmlDocPaths)
xmlProvider.ImportFile(xmlDocPath);
repository.ImportFile(xmlDocPath);

return xmlProvider;
return new XmlDocProvider(repository);
}

/// <summary>
Expand Down
3 changes: 2 additions & 1 deletion src/DocumentationContextExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ namespace Kampute.DocToolkit
using Kampute.DocToolkit.Formatters;
using Kampute.DocToolkit.Languages;
using Kampute.DocToolkit.Metadata;
using Kampute.DocToolkit.Metadata.Capabilities;
using Kampute.DocToolkit.Models;
using Kampute.DocToolkit.XmlDoc;
using System;
Expand Down Expand Up @@ -182,7 +183,7 @@ public static NameQualifier DetermineNameQualifier(this IDocumentationContext co
if (member is IType)
return NameQualifier.DeclaringType;

if (member is IMethod { IsExtension: true } or IOperator)
if (member is IWithExtensionBehavior { IsExtension: true } or IOperator)
return NameQualifier.None;

return context.AddressProvider.ActiveScope.Model is TypeModel typeModel
Expand Down
5 changes: 5 additions & 0 deletions src/DocumentationModelType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -74,5 +74,10 @@ public enum DocumentationModelType
/// The model represents an operator member or overloads.
/// </summary>
Operator,

/// <summary>
/// The model represents an extension block.
/// </summary>
ExtensionBlock,
}
}
2 changes: 1 addition & 1 deletion src/DotNetApiStrategy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ public override bool TryResolveMemberAddress(IMember member, [NotNullWhen(true)]

var memberAnchor = string.Empty;
if (member is IWithOverloads { HasOverloads: true })
memberAnchor = signature.ReplaceMany(['.', '_'], '-').Replace("--", "-");
memberAnchor = signature.ReplaceChars(['.', '_'], '-').Replace("--", "-");

address = CreateAddressFromPath(GetApiPath(memberPath), memberAnchor);
return true;
Expand Down
8 changes: 4 additions & 4 deletions src/IO/Minifiers/HtmlMinifier.cs
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ public HtmlMinifier(TextWriter writer, bool leaveOpen = false)
/// </value>
public HashSet<string> PreformattedTags { get; } = new(StringComparer.OrdinalIgnoreCase)
{
"pre", "textarea", "script", "style"
"PRE", "TEXTAREA", "SCRIPT", "STYLE"
};

/// <summary>
Expand All @@ -109,9 +109,9 @@ public HtmlMinifier(TextWriter writer, bool leaveOpen = false)
/// </value>
public HashSet<string> InlineTags { get; } = new(StringComparer.OrdinalIgnoreCase)
{
"a", "abbr", "b", "bdi", "bdo", "br", "button", "cite", "code", "data", "dfn", "del", "em", "i",
"img", "input", "ins", "kbd", "label", "mark", "object", "output", "q", "ruby", "rt", "rp", "s",
"samp", "select", "small", "span", "strong", "sub", "sup", "time", "tt", "u", "var", "wbr"
"A", "ABBR", "B", "BDI", "BDO", "BR", "BUTTON", "CITE", "CODE", "DATA", "DFN", "DEL", "EM", "I",
"IMG", "INPUT", "INS", "KBD", "LABEL", "MARK", "OBJECT", "OUTPUT", "Q", "RUBY", "RT", "RP", "S",
"SAMP", "SELECT", "SMALL", "SPAN", "STRONG", "SUB", "SUP", "TIME", "TT", "U", "VAR", "WBR"
};

/// <summary>
Expand Down
2 changes: 1 addition & 1 deletion src/IO/Writers/MarkupWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,7 @@ public virtual void WriteDocLink(MemberModel member)
/// <param name="ns">The namespace for which the link is written.</param>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="ns"/> is <see langword="null"/>.</exception>
/// <remarks>
/// This method writes a hyperlink to the documentation for the specified namespace. If the namespace has has a valid documentation
/// This method writes a hyperlink to the documentation for the specified namespace. If the namespace has a valid documentation
/// URL, a hyperlink is written using the namespace's name. If the namespace does not have documentation URL, the namespace's name
/// is written as plain text.
/// </remarks>
Expand Down
4 changes: 2 additions & 2 deletions src/Kampute.DocToolkit.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<TargetFramework>netstandard2.1</TargetFramework>
<Title>Kampute.DocDotLib</Title>
<Description>Provides extensible pipeline for generating .NET API documentation by transforming assembly metadata and XML documentation into structured models, with automatic cross-reference resolution, support for multiple output formats (HTML, Markdown), and integration of conceptual topics.</Description>
<Version>1.1.1</Version>
<Version>2.0.0</Version>
<Company>Kampute</Company>
<Authors>Kambiz Khojasteh</Authors>
<Copyright>Copyright (c) 2025 Kampute</Copyright>
Expand Down Expand Up @@ -48,7 +48,7 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="System.Reflection.MetadataLoadContext" Version="9.0.10" />
<PackageReference Include="System.Reflection.MetadataLoadContext" Version="10.0.*" />
</ItemGroup>

<PropertyGroup Condition="Exists('../SigningKey.snk')">
Expand Down
15 changes: 15 additions & 0 deletions src/Languages/CSharp.Data.cs
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ public partial class CSharp
["descending"] = "descending",
["dynamic"] = "../builtin-types/reference-types",
["equals"] = "equals",
["extension"] = "extension",
["field"] = "field",
["file"] = "file",
["from"] = "from-clause",
Expand Down Expand Up @@ -203,6 +204,20 @@ public partial class CSharp
["Decrement"] = "--",
["Implicit"] = "implicit",
["Explicit"] = "explicit",
["UnsignedRightShift"] = ">>>",
["AdditionAssignment"] = "+=",
["SubtractionAssignment"] = "-=",
["MultiplyAssignment"] = "*=",
["DivisionAssignment"] = "/=",
["ModulusAssignment"] = "%=",
["AndAssignment"] = "&=",
["OrAssignment"] = "|=",
["ExclOrAssignment"] = "^=",
["LeftShiftAssignment"] = "<<=",
["RightShiftAssignment"] = ">>=",
["UnsignedRightShiftAssignment"] = ">>>=",
["IncrementAssignment"] = "++",
["DecrementAssignment"] = "--",
};

/// <summary>
Expand Down
Loading