Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
2e81a4f
Show direct base classes and interfaces in type signatures
Malcolmnixon Jun 8, 2026
4ed528c
Update README badges to match project template
Malcolmnixon Jun 8, 2026
b2b7073
Remove architecture qualifiers from Platform Support table
Malcolmnixon Jun 8, 2026
f446d41
Make Features section iconic with emoji icons
Malcolmnixon Jun 8, 2026
0aaabda
Cache CppGenerator clang runs in IClassFixture to speed up tests
Malcolmnixon Jun 8, 2026
c54b265
Add intra-doc links and External Types section for C# and C++
Malcolmnixon Jun 8, 2026
e0a52ee
Fix GenericParameter types being incorrectly linked in intra-doc reso…
Malcolmnixon Jun 8, 2026
90d17f4
Fix PR review issues: qualifier preservation, CompareTo tie-breaker, …
Malcolmnixon Jun 8, 2026
e4733f4
Add --search-paths and glob/exclude pattern support to cpp subcommand
Malcolmnixon Jun 8, 2026
03fd978
fix: normalize all generated markdown pages to start with H1 headings
Malcolmnixon Jun 8, 2026
44eabe6
fix: add missing inheritance signature tests for reqstream coverage
Malcolmnixon Jun 8, 2026
bdcaa8e
fix: only check stripped base name for std:: prefix in CppTypeLinkRes…
Jun 8, 2026
6b3e70b
refactor: replace --search-paths/--include-patterns/--exclude-pattern…
Jun 9, 2026
231fbe6
fix: update requirements traceability and design docs after header se…
Jun 9, 2026
bd1b695
fix: replace antipattern/unconfigured with cspell-clean terms and upd…
Jun 9, 2026
58f31c2
refactor: replace synthesized default globs and fix header pattern ma…
Jun 9, 2026
e5ae30f
fix: address PR review r4454762697 (6 comments)
Jun 9, 2026
325e284
feat: render delegate types with source-level delegate syntax
Jun 9, 2026
c2f535c
Strip arity suffix from generic type names in page files, headings, a…
Jun 9, 2026
63cb05e
Fix PR review r4454901364: namespace-prefix checks, FlattenArity, del…
Jun 9, 2026
88a4d99
feat: add C++ using type alias support
Jun 9, 2026
0e08fe5
feat: add APIMARK_CLANG_PATH env var; doc deleted functions and type …
Jun 9, 2026
b2e23f4
fix: PR review r4455192693 — delegate fallback, nsPath dots, test names
Jun 9, 2026
d33cf79
fix: linkify return types in WriteFreeFunctionContent and WriteFuncti…
Jun 9, 2026
ab38133
feat: render @note as blockquote; show default parameter values in si…
Jun 9, 2026
8bc9a1b
Add bool/int/float default parameter tests and fix bool literal parsing
Jun 9, 2026
71f6ff5
Fix type alias linkification and simplification
Jun 9, 2026
1781e53
Add nested class and class-scoped type alias support
Jun 9, 2026
096c108
Fix nullable array type rendering and reqstream test name mismatch
Jun 9, 2026
cd5bd24
Update output structure table with nested type and class-scoped alias…
Jun 9, 2026
198b0e4
Fix documentation issues flagged in PR #15 review
Jun 9, 2026
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
4 changes: 4 additions & 0 deletions .cspell.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,16 @@ words:
- fileassert
- frontmatter
- GHDL
- hrefs
- isysroot
- isystem
- libc
- libclang
- libclangsharp
- libext
- Linkification
- Linkify
- linkify
- misattributed
- msbuild
- MSBuild
Expand Down
33 changes: 21 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
# ApiMark

[![Build](https://github.com/DemaConsulting/ApiMark/actions/workflows/build_on_push.yaml/badge.svg)](https://github.com/DemaConsulting/ApiMark/actions/workflows/build_on_push.yaml)

<!-- IMPORTANT: All links in this file must be absolute URLs.
This file is distributed in packages and relative links will not resolve. -->

[![GitHub forks](https://img.shields.io/github/forks/demaconsulting/ApiMark?style=plastic)](https://github.com/demaconsulting/ApiMark/network/members)
[![GitHub stars](https://img.shields.io/github/stars/demaconsulting/ApiMark?style=plastic)](https://github.com/demaconsulting/ApiMark/stargazers)
[![GitHub contributors](https://img.shields.io/github/contributors/demaconsulting/ApiMark?style=plastic)](https://github.com/demaconsulting/ApiMark/graphs/contributors)
[![License](https://img.shields.io/github/license/demaconsulting/ApiMark?style=plastic)](https://github.com/demaconsulting/ApiMark/blob/main/LICENSE)
[![Build](https://img.shields.io/github/actions/workflow/status/demaconsulting/ApiMark/build_on_push.yaml)](https://github.com/demaconsulting/ApiMark/actions/workflows/build_on_push.yaml)
[![Quality Gate](https://sonarcloud.io/api/project_badges/measure?project=demaconsulting_ApiMark&metric=alert_status)](https://sonarcloud.io/dashboard?id=demaconsulting_ApiMark)
[![Security](https://sonarcloud.io/api/project_badges/measure?project=demaconsulting_ApiMark&metric=security_rating)](https://sonarcloud.io/dashboard?id=demaconsulting_ApiMark)
[![NuGet](https://img.shields.io/nuget/v/DemaConsulting.ApiMark.MSBuild?style=plastic)](https://www.nuget.org/packages/DemaConsulting.ApiMark.MSBuild)

## Overview

ApiMark generates compact, AI-friendly API reference documentation in Markdown
Expand All @@ -15,21 +22,23 @@ type page — consuming only as much context as the task requires.

## Features

- Generates compact Markdown API reference from source code and doc comments
- Gradual disclosure output: root index → namespace summary → type page → member detail
- C#/.NET support via Mono.Cecil and XML documentation comments
- C++ support via `clang -ast-dump=json` and Doxygen-style doc comments
- MSBuild task integration for `.csproj` and `.vcxproj`-based builds
- `dotnet tool` CLI (`apimark`) covering all supported languages
- Designed for AI consumption — minimal noise, explicit navigation links between levels
- 📄 **Compact Markdown Output** - AI-friendly API reference from source code
- 🔍 **Gradual Disclosure** - Index → namespace → type → member detail
- 🔷 **C#/.NET Support** - Mono.Cecil + XML documentation comments
- ➕ **C++ Support** - `clang -ast-dump=json` + Doxygen-style comments
- 🔧 **MSBuild Integration** - Auto-documents `.csproj` and `.vcxproj` builds
- 🖥️ **CLI Tool** - `apimark` dotnet tool covering all languages
- 🤖 **AI-Optimized** - Minimal noise, explicit navigation links
- 🌐 **Multi-Platform** - Windows, Linux, and macOS on .NET 8, 9, and 10
- ✅ **Self-Validation** - Built-in qualification tests for regulated environments

## Platform Support

| Platform | .NET | C++ |
| --- | --- | --- |
| Windows x64 | ✅ | ✅ |
| Linux x64 | ✅ | ✅ |
| macOS (Apple Silicon) | ✅ | ✅ |
| Windows | ✅ | ✅ |
| Linux | ✅ | ✅ |
| macOS | ✅ | ✅ |

## Prerequisites

Expand Down
46 changes: 42 additions & 4 deletions docs/design/api-mark-cpp/cpp-generator.md
Original file line number Diff line number Diff line change
Expand Up @@ -148,10 +148,10 @@ filter, and writes the full Markdown output tree.
the canonical `#include <path>` at the top, followed by the class
declaration, inheritance information, template parameters (for primary
templates), and grouped sub-tables with links to all member detail pages.
When the class is marked `final`, a `class ClassName final` declaration
line (including base class names when inheritance is present) is appended
to the signature block so consumers can see the constraint without opening
the header.
When the class is marked `final` or has direct base classes, a class
declaration line (e.g. `class FinalClass final`, `class Circle : public Shape`)
is appended to the signature block so consumers can see the constraint and
inheritance chain without opening the header.
- `factory.CreateMarkdown($"{qualifiedNamespace}/{typeName}", memberName)` —
dedicated page for every visible non-operator member. All non-operator members
always receive their own page, making navigation fully deterministic.
Expand Down Expand Up @@ -261,6 +261,44 @@ identified by the declaring class name.
function → `className`; non-constructor function → `fn.Name`; field → `field.Name`;
any other type → `className`.

**CppTypeLinkResolver** (internal): Resolves C++ type strings to Markdown link text
for use in table cells.

- *Constructor*: Accepts `IReadOnlyDictionary<string, string> knownTypes` — maps
fully-qualified C++ type names (e.g. `"fixtures::SampleClass"`) to documentation
page keys (e.g. `"fixtures/SampleClass"`). Built in `CppGenerator.Generate` from
the `namespaceDecls` dictionary.
- **Linkify** method: resolves a simplified C++ type string to a Markdown link or plain text.
- *Parameters*: `string cppTypeString`, `string currentFolder`, `ISet<CppExternalTypeInfo>
externalTypes` accumulator.
- *Returns*: a Markdown link `[Name](relative/path.md)` when the stripped base type
is in `knownTypes`; the original string unchanged otherwise; non-std external types
with a namespace are tracked in `externalTypes`.
- *Algorithm*: strip qualifiers (`const`, `volatile`, `*`, `&`, `&&`, trailing-const,
and template arguments) to isolate the base type name; reject primitives and `std::`
types immediately; look up the base type in `knownTypes` by exact qualified match,
then by short-name fallback; compute a relative path and return the link; if not
found and the type has a non-std namespace, track as external.
- **StripQualifiers** (internal static): removes C++ cv and reference qualifiers and
template arguments from a type string, returning the bare base type name.

**CppExternalTypeInfo** (internal record): Represents a non-standard external C++
type reference collected during table cell generation.

- *Properties*: `TypeString` (short type name without namespace), `Namespace`
(the C++ namespace using `::` separators).
- *Ordering*: implements `IComparable<CppExternalTypeInfo>` by `TypeString` so
`SortedSet<CppExternalTypeInfo>` produces alphabetically ordered tables.

**CppGenerator.WriteExternalTypesSection** (private static): Emits the
`## External Types` section at the bottom of a page when at least one external
type was referenced in table cells.

- *Parameters*: `IMarkdownWriter writer`, `SortedSet<CppExternalTypeInfo>
externalTypes`.
- *Algorithm*: Returns immediately when the set is empty; otherwise writes an
H2 heading `"External Types"` and a two-column table (`Type`, `Namespace`).

### Error Handling

CppGenerator throws `DirectoryNotFoundException` when a path in
Expand Down
53 changes: 53 additions & 0 deletions docs/design/api-mark-dot-net/dot-net-generator.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,24 @@ between members of different kinds or differently-cased method names) emit a
single combined page via `WriteCombinedMemberPage`; emit grouped sub-tables with
links; dispose the AssemblyDefinition.

**DotNetGenerator.BuildTypeSignature** (private): Builds a human-readable C#
declaration signature for a type definition, including direct base class and
interface names when present.

- *Parameters*: `TypeDefinition type` — the type to represent;
`string contextNamespace` — the namespace used to simplify base type and
interface names.
- *Returns*: `string` — a declaration of the form `public class Name`,
`public interface Name<T>`, or `public class Name : BaseClass, IInterface`
when direct inheritance is present.
- *Algorithm*: Determines the keyword (`class`, `interface`, `enum`, or `struct`)
from the type's flags; computes the `sealed` or `static` modifier for classes;
strips generic arity from the type name and appends generic parameter names when
present; collects the direct base class (skipping `System.Object`,
`System.ValueType`, `System.Enum`, and `System.MulticastDelegate`) and all
directly declared interfaces using TypeNameSimplifier to produce idiomatic C#
names; appends `: BaseClass, IInterface` when the collected list is non-empty.

**DotNetGenerator.WriteCombinedMemberPage** (private): Writes a single combined
Markdown page for a group of members whose sanitized file names collide on
case-insensitive filesystems.
Expand Down Expand Up @@ -120,6 +138,41 @@ a short human-readable kind string used in combined page H4 headings.
`MethodDefinition` with name `.ctor` → `"Constructor"`; `MethodDefinition` →
`"Method"`; all other types → `"Member"`.

**TypeLinkResolver** (internal): Resolves Mono.Cecil `TypeReference` instances
to Markdown link text for use in table cells.

- *Constructor*: Accepts `IReadOnlyList<string> rootNamespaces` — forwarded to
`DotNetGenerator.GetNamespaceFolderPath` when computing target page paths.
- **Linkify** method: resolves a `TypeReference` to a Markdown link string.
- *Parameters*: `TypeReference typeRef`, `string currentFolder` (path of the
containing file), `string contextNamespace`, `ISet<ExternalTypeInfo>
externalTypes` accumulator, optional `bool isNullableAnnotated`.
- *Returns*: a Markdown link when the type is intra-assembly; the original
simplified name otherwise; external non-System types are tracked in
`externalTypes`.
- *Rules*: `Nullable<T>` → `T?` via recursion; array types → `elementText[]`;
generic instance types linkify the container when intra-assembly; primitives
and `System.*` types render as plain text; non-System external types are
added to the accumulator.
- Intra-assembly detection: `TypeReference.Scope is ModuleDefinition`.

**ExternalTypeInfo** (internal record): Represents a non-standard external type
reference collected during table cell generation.

- *Properties*: `SimplifiedName` (display form, may include escaped generic
angle brackets), `Namespace` (the type's .NET namespace).
- *Ordering*: implements `IComparable<ExternalTypeInfo>` by `SimplifiedName` so
`SortedSet<ExternalTypeInfo>` produces alphabetically ordered tables.

**DotNetGenerator.WriteExternalTypesSection** (private static): Emits the
`## External Types` section at the bottom of a page when at least one external
type was referenced in table cells.

- *Parameters*: `IMarkdownWriter writer`, `SortedSet<ExternalTypeInfo>
externalTypes`.
- *Algorithm*: Returns immediately when the set is empty; otherwise writes an
H2 heading `"External Types"` and a two-column table (`Type`, `Namespace`).

### Error Handling

DotNetGenerator throws `FileNotFoundException` explicitly when XmlDocPath does not
Expand Down
32 changes: 32 additions & 0 deletions docs/reqstream/api-mark-cpp/cpp-generator.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -135,3 +135,35 @@ sections:
- CppGenerator_Generate_ClassWithOperators_OperatorsPageContainsOperatorEntry
- CppGenerator_Generate_ClassWithOperators_TypePageLinksToOperatorsPage
- CppGenerator_Generate_NamespaceFreeOperator_CreatesNamespaceOperatorsPage
- id: ApiMarkCpp-CppGenerator-ShowDirectInheritanceInTypeSignature
title: >-
CppGenerator shall include direct base class names in the class declaration
line of the signature block for classes that have direct base classes or are
marked final, so the inheritance chain is visible without opening the header.
justification: |
The direct base class list tells AI agents the contract a type satisfies and
the class it extends without requiring them to navigate to the header file.
Combining this with the final specifier in a single declaration line keeps
the signature block minimal and immediately scannable.
tests:
- CppGenerator_Generate_InheritanceClass_EmitsBaseClassInSignature
- CppGenerator_Generate_FinalClass_EmitsFinalKeywordInSignature
- id: ApiMarkCpp-CppGenerator-EmitIntraDocLinksInTableCells
title: CppGenerator shall emit Markdown links in type and parameter type table cells when the referenced type is documented in the same library.
justification: |
AI agents and human readers navigating API documentation benefit from
clickable links that jump directly to the referenced type page rather
than requiring a separate search. Links are only emitted in table cells,
not inside fenced code blocks, because Markdown links do not render
inside fences.
tests:
- CppGenerator_Generate_IntraLibraryReturnType_EmitsMarkdownLinkInReturnsCell
- id: ApiMarkCpp-CppGenerator-EmitExternalTypesSection
title: CppGenerator shall emit an "External Types" section at the bottom of each generated page that references at least one non-std external type.
justification: |
Readers using an external library type documented on a page need to know
which namespace or header to include. Collecting all external type
references per page and listing them alphabetically provides this context
without cluttering individual type cells with fully-qualified names.
tests:
- CppTypeLinkResolver_Linkify_UnknownNamespacedType_TracksExternalType
31 changes: 31 additions & 0 deletions docs/reqstream/api-mark-dot-net/dot-net-generator.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -85,3 +85,34 @@ sections:
opening it first.
tests:
- DotNetGenerator_Generate_ApiMd_ListsAllNamespacesWithTypeCount
- id: ApiMarkDotNet-DotNetGenerator-ShowDirectInheritanceInTypeSignature
title: >-
DotNetGenerator shall include the direct base class and directly declared
interfaces in the type signature, omitting well-known implicit bases such as
System.Object, System.ValueType, System.Enum, and System.MulticastDelegate.
justification: |
The direct inheritance list tells AI agents the contract a type satisfies and
the class it extends without requiring them to navigate to the source file.
Suppressing well-known implicit bases avoids noise that adds no information.
tests:
- DotNetGenerator_Generate_SampleImplementation_TypeSignatureShowsInterface
- DotNetGenerator_Generate_EnumTypeSignature_HasNoBaseClass
- id: ApiMarkDotNet-DotNetGenerator-EmitIntraDocLinksInTableCells
title: DotNetGenerator shall emit Markdown links in type and parameter type table cells when the referenced type is documented in the same assembly.
justification: |
AI agents and human readers navigating API documentation benefit from
clickable links that jump directly to the referenced type page rather
than requiring a separate search. Links are only emitted in table cells,
not inside fenced code blocks, because Markdown links do not render
inside fences.
tests:
- DotNetGenerator_Generate_IntraAssemblyReturnType_EmitsMarkdownLinkInReturnsCell
- id: ApiMarkDotNet-DotNetGenerator-EmitExternalTypesSection
title: DotNetGenerator shall emit an "External Types" section at the bottom of each generated page that references at least one non-System external type.
justification: |
Readers using an external library type documented on a page need to know
which package or namespace to import. Collecting all external type
references per page and listing them alphabetically provides this context
without cluttering individual type cells with fully-qualified names.
tests:
- DotNetGenerator_Generate_ExternalNonSystemParameterType_EmitsExternalTypesSection
30 changes: 28 additions & 2 deletions src/ApiMark.Cpp/CppAst/ClangAstParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -718,6 +718,28 @@ private void ParseClass(
var isFinal = false;
var location = GetCurrentSourceLocation(node);

// Clang 18+ surfaces base class information in a top-level "bases" array on the
// CXXRecordDecl node rather than as CXXBaseSpecifier child nodes inside "inner".
// Earlier versions emit CXXBaseSpecifier nodes in "inner" (handled below).
// Both paths are retained so the parser works across clang versions.
var hasTopLevelBases = false;
if (node.TryGetProperty("bases", out var bases))
{
foreach (var baseEntry in bases.EnumerateArray())
{
if (baseEntry.TryGetProperty("type", out var bt) &&
bt.TryGetProperty("qualType", out var qtEl))
{
var baseName = qtEl.GetString();
if (!string.IsNullOrEmpty(baseName))
{
baseTypes.Add(new CppBaseType(baseName));
hasTopLevelBases = true;
}
}
}
}

if (node.TryGetProperty("inner", out var inner))
{
foreach (var child in inner.EnumerateArray())
Expand Down Expand Up @@ -760,8 +782,12 @@ private void ParseClass(
}

case "CXXBaseSpecifier":
// Capture the base-class name from type.qualType
if (child.TryGetProperty("type", out var bt) &&
// Fallback for clang versions older than 18 that emit base specifiers
// as CXXBaseSpecifier child nodes in "inner" rather than in a top-level
// "bases" array. Skipped when base types were already collected from the
// top-level "bases" array to avoid duplicates across clang versions.
if (!hasTopLevelBases &&
child.TryGetProperty("type", out var bt) &&
bt.TryGetProperty("qualType", out var qtEl))
{
var baseName = qtEl.GetString();
Expand Down
Loading
Loading