Skip to content
Merged
Show file tree
Hide file tree
Changes from 21 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
5 changes: 5 additions & 0 deletions .cspell.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,17 @@ words:
- fileassert
- frontmatter
- GHDL
- hrefs
- isysroot
- isystem
- libc
- libclang
- libclangsharp
- libext
- linkified
- Linkification
- Linkify
- linkify
- misattributed
- msbuild
- MSBuild
Expand Down
43 changes: 30 additions & 13 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 Expand Up @@ -97,8 +106,16 @@ ApiMark generates documentation automatically after every build.
# Generate API documentation from a .NET assembly
apimark dotnet --assembly MyProject.dll --xml-doc MyProject.xml --output docs/api

# Generate API documentation from C++ public headers
# Generate API documentation from C++ public headers (document all headers under include/)
apimark cpp --includes include/ --output docs/api

# Document only specific headers using --api-headers patterns (gitignore-style, ordered)
apimark cpp \
--includes include/ \
--api-headers "include/**" \
--api-headers "!include/detail/**" \
--api-headers "include/detail/public_api.h" \
--output docs/api
```

Run `apimark --help` for all options. Run `apimark dotnet --help` or `apimark cpp --help` for language-specific options.
Expand Down
19 changes: 10 additions & 9 deletions docs/design/api-mark-cpp.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,14 +79,16 @@ N/A — not a safety-classified software item.
## Data Flow

1. The caller (ApiMarkTool) constructs `CppGeneratorOptions` with
PublicIncludeRoots, IncludePatterns, ExcludePatterns, SystemIncludePaths,
AdditionalIncludePaths, Defines, CppStandard, AdditionalCompilerArguments,
PublicIncludeRoots, ApiHeaderPatterns, SystemIncludePaths,
Defines, CppStandard, AdditionalCompilerArguments,
Visibility, IncludeDeprecated, and LibraryName, then passes an
IMarkdownWriterFactory to Generate.
2. CppGenerator enumerates all header files under each PublicIncludeRoot,
applying IncludePatterns and ExcludePatterns, to produce the candidate file
set. Each matched header is parsed as an independent translation unit so that
headers are self-contained.
2. CppGenerator enumerates all header files under each PublicIncludeRoot.
When ApiHeaderPatterns is non-empty, patterns are applied with gitignore-style
last-match-wins semantics to produce the candidate file set; when empty, all
recognized header files under every root are included automatically. Each
matched header is parsed as an independent translation unit so that headers
are self-contained.
3. CppGenerator calls `ClangAstParser.Parse` with all candidate headers and the
configured options. `ClangAstParser` invokes `clang -ast-dump=json` and parses
the resulting JSON into `CppCompilationResult` containing `CppNamespaceDecl`
Expand All @@ -96,9 +98,8 @@ N/A — not a safety-classified software item.
physically located in the public headers, already filtered by ownership.
5. CppGenerator applies the IsOwnedDeclaration filter to each declaration:
only declarations whose source file normalizes to a path under a
PublicIncludeRoot, matches IncludePatterns, and does not match
ExcludePatterns are documented. System and third-party declarations are
used for type resolution only.
PublicIncludeRoot that was selected by ApiHeaderPatterns are documented.
System and third-party declarations are used for type resolution only.
6. For each owned declaration, CppGenerator derives the canonical #include path
as the source file path relative to its matching PublicIncludeRoot, expressed
with forward slashes.
Expand Down
98 changes: 66 additions & 32 deletions docs/design/api-mark-cpp/cpp-generator.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ library, emitted as an introductory paragraph in `api.md`. Omitted when empty.
the top-level heading in `api.md`.

**CppGeneratorOptions.PublicIncludeRoots**: `IReadOnlyList<string>` — one or
more directories that define the public include root(s) of the library. This
property serves a dual purpose:
more directories that define the compiler include roots. This property serves
two purposes:

1. *Parse environment*: each root is passed to the Clang parser as an `-I`
include directory so that `#include <mylib/types.h>` resolves correctly
Expand All @@ -38,22 +38,23 @@ property serves a dual purpose:
normalizing separators to forward slashes. For example, if the root is
`C:\project\include` and the declaration source is
`C:\project\include\mylib\renderer.h`, the canonical include path is
`mylib/renderer.h`, yielding `#include <mylib/renderer.h>` in the type
`mylib/renderer.h`, yielding `#include "mylib/renderer.h"` in the type
page output.

When multiple roots are configured and a file matches more than one, the
longest matching root path (most specific) wins.

**CppGeneratorOptions.IncludePatterns**: `IReadOnlyList<string>` — glob
patterns relative to each PublicIncludeRoot selecting which header files
contribute to the documented public API. Defaults to `["**/*"]` when empty,
which selects all files under all roots.
**CppGeneratorOptions.ApiHeaderPatterns**: `IList<string>` — ordered list of
glob and exclusion pattern strings that determine which header files contribute to the
documented public API. Gitignore-style semantics apply: patterns are evaluated
in order; the last matching pattern wins. Entries without a `!` prefix are
include patterns; entries with a `!` prefix are exclusion patterns (the `!`
is stripped before glob matching). When empty, all headers with recognized C++
extensions (`.h`, `.hpp`, `.hxx`, `.h++`) under all configured roots are
documented automatically without any pattern filtering.

**CppGeneratorOptions.ExcludePatterns**: `IReadOnlyList<string>` — glob
patterns relative to each PublicIncludeRoot for files to exclude from the
documented API. Common values: `"detail/**"`, `"*_impl.h"`, `"internal/**"`.
ExcludePatterns are evaluated after IncludePatterns; a file matching both is
excluded.
Example — all headers except `detail/`, with one re-included:
`["include/**", "!include/detail/**", "include/detail/public_api.h"]`

**CppGeneratorOptions.SystemIncludePaths**: `IReadOnlyList<string>` — toolchain
and SDK include directories passed to the Clang parser as system include paths
Expand All @@ -62,13 +63,6 @@ and SDK include directories passed to the Clang parser as system include paths
are used for type resolution only; declarations found within them are never
documented.

**CppGeneratorOptions.AdditionalIncludePaths**: `IReadOnlyList<string>` —
additional include directories for third-party headers included by public
headers but not part of the documented API (e.g. a vendored library's include
directory). Passed to the Clang parser as `-I` flags. Declarations from these
paths are never documented. Note: this option is currently not wired through the
CLI; it is available for direct API use only (v1 scope).

**CppGeneratorOptions.Defines**: `IReadOnlyList<string>` — preprocessor symbol
definitions passed to the Clang parser as `-D` flags, in the form `"NAME"` or
`"NAME=value"`. Export macros (e.g. `MYLIB_API`, `__declspec(dllexport)`
Expand Down Expand Up @@ -148,10 +142,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 All @@ -166,9 +160,9 @@ filter, and writes the full Markdown output tree.
name `"global"`.

Execution steps: enumerate candidate header files under each PublicIncludeRoot
applying IncludePatterns and ExcludePatterns; build Clang options from all
configured paths, defines, standard, and additional arguments; write a temporary
combined header that `#include`s all candidate headers, invoke
applying ApiHeaderPatterns with gitignore-style last-match-wins semantics; build
Clang options from all configured paths, defines, standard, and additional arguments;
write a temporary combined header that `#include`s all candidate headers, invoke
`clang -Xclang -ast-dump=json -fparse-all-comments -fsyntax-only` on it, parse the resulting
JSON AST; walk the AST and apply IsOwnedDeclaration to each declaration; apply
Visibility and IncludeDeprecated filters; write the library entrypoint; for
Expand All @@ -190,9 +184,8 @@ the documented public API.

- *Parameters*: declaration source file (absolute, normalized path).
- *Returns*: `(bool owned, string includeRoot, string relativePath)` — owned
is true when the file falls under a PublicIncludeRoot and matches
IncludePatterns without matching ExcludePatterns; includeRoot and
relativePath are set when owned is true.
is true when the file falls under a PublicIncludeRoot and is selected by
ApiHeaderPatterns; includeRoot and relativePath are set when owned is true.
- *Algorithm*:
1. Normalize the declaration source file path: resolve to absolute path,
normalize directory separators to the OS separator, resolve `..` and
Expand All @@ -203,9 +196,12 @@ the documented public API.
overlap).
4. Compute relative path: strip the root prefix and normalize separators to
forward slashes.
5. Test the relative path against IncludePatterns (must match at least one)
and ExcludePatterns (must not match any). Return owned=true only when both
conditions hold.
5. When ApiHeaderPatterns is non-empty, test whether the file is selected using
gitignore-style last-match-wins evaluation. Patterns are first evaluated
CWD-relative (when the file falls within the current working directory); when
the root is outside the CWD the file path relative to the root is used instead.
Return owned=true only when the final evaluated state is included. When
ApiHeaderPatterns is empty, all files under the matched root are owned.

**CppGenerator.WriteCombinedMemberPage** (private): Writes a single combined
Markdown page for a group of members whose base names collide on case-insensitive
Expand Down Expand Up @@ -261,6 +257,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
Loading
Loading